openGL學習----相機
0.參考:https://learnopengl-cn.github.io/01%20Getting%20started/09%20Camera/
0.0其實相機就是搞清楚cameraPos,cameraFornt,cameraUp的關係和用法,以及跟三個尤拉角的關係,以及如何跟滑鼠、鍵盤的wasd鍵聯絡起來(也就是視角移動跟距離移動)實現跟使用者的互動,然後生成LookAt矩陣就OK了,重點是理解跟4中的注意點
0.1我們介紹的攝像機系統是一個FPS風格的攝像機,它能夠滿足大多數情況需要,而且與尤拉角相容,但是在建立不同的攝像機系統,比如飛行模擬攝像機,時就要當心。每個攝像機系統都有自己的優點和不足,所以確保對它們進行了詳細研究。比如,這個FPS攝像機不允許俯仰角大於90度,而且我們使用了一個固定的上向量(0, 1, 0),這在需要考慮滾轉角的時候就不能用了。
1.當我們討論攝像機/觀察空間(Camera/View Space)的時候,是在討論以攝像機的視角作為場景原點時場景中所有的頂點座標(之前的原點是世界座標系的原點):觀察矩陣把所有的世界座標變換為相對於攝像機位置與方向的觀察座標。要定義一個攝像機,我們需要它在世界空間中的位置、觀察的方向、一個指向它右測的向量以及一個指向它上方的向量。細心的讀者可能已經注意到我們實際上建立了一個三個單位軸相互垂直的、以攝像機的位置為原點的座標系。
2.具體相機座標系原理可參看資料。。。
3.這裡只講一下大致的觀察矩陣的初始化api
相機座標系轉換就是生成一個觀察矩陣,也就是著名的LookAt矩陣
3.1幸運的是,GLM已經提供了這些支援。我們要做的只是定義一個攝像機位置,一個目標位置和一個表示世界空間中的上向量的向量(我們計算右向量使用的那個上向量)。接著GLM就會建立一個LookAt矩陣,我們可以把它當作我們的觀察矩陣:
glm::mat4 view; view = glm::lookAt(glm::vec3(0.0f, 0.0f, 3.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f));
glm::LookAt函式需要一個位置、目標和上向量。
其實比較經典和完美的初始化方法是下面(上面只是簡單情況下的寫法):
view = glm::lookAt(cameraPos, cameraPos + cameraFront, cameraUp);
第一個引數是相機位置,是個點,是世界座標系,因為還沒有生成觀察矩陣作用到座標上,怎麼可能是相機座標系呢
第二個引數是看向的目標位置,也是個點,也是世界座標系。例子中是相機前面的點,也就是說相機一直是朝著它前面的目標點,也就是z軸負方向看的。它只是一個點,並不是一個向量,僅僅是表明在第一個引數的情況下,相機朝向哪個點看的(他與第一個引數一起才構成了相機的朝向向量,也就是朝向哪個方向看的)。
一般情況下這個點是由相機的點跟方向向量相加而得。因為已知他的方向向量(詳見下面程式碼)
第三個引數是一個向量表示相機的上向量,指向y軸,可以通過這個向量跟前兩個引數,兩次向量叉乘出三個互相正交的向量
4.有了觀察矩陣也就是都轉換到相機座標系了。這時候可以有一些互動設計了,比如滑鼠控制朝向方向,鍵盤wasd控制移動,但是有些注意點(以下為相機移動的重點,要重點理解)
4.0主要講解滑鼠控制的視角移動跟鍵盤wasd控制的距離移動,以及滾輪控制的縮放移動
4.1鍵盤的wasd鍵用來控制前後左右的移動,其原理就是控制相機的位置,也就是更改cameraPos的值,那具體向哪個方向移動呢?對啦,就是向相機的方向向量(cameraFront)所指的方向移動就好啦,w就是他的方向,s就是反向,a就是他和相機的cameraUp向量叉乘出來的方向,d就是a的反向。但是要注意相乘的向量必須是單位向量,不然移動距離不對
4.2關於移動速度:
目前我們的移動速度是個常量。理論上沒什麼問題,但是實際情況下根據處理器的能力不同,有些人可能會比其他人每秒繪製更多幀,也就是以更高的頻率呼叫processInput函式。結果就是,根據配置的不同,有些人可能移動很快,而有些人會移動很慢。當你釋出你的程式的時候,你必須確保它在所有硬體上移動速度都一樣。
圖形程式和遊戲通常會跟蹤一個時間差(Deltatime)變數,它儲存了渲染上一幀所用的時間。我們把所有速度都去乘以deltaTime值。結果就是,如果我們的deltaTime很大,就意味著上一幀的渲染花費了更多時間,所以這一幀的速度需要變得更高來平衡渲染所花去的時間。使用這種方法時,無論你的電腦快還是慢,攝像機的速度都會相應平衡,這樣每個使用者的體驗就都一樣了。
4.3關於視角移動:
一共有3種尤拉角:俯仰角(Pitch)、偏航角(Yaw)和滾轉角(Roll),下面的圖片展示了它們的含義:
尤拉角與相機方向向量的關係:
direction.x = cos(glm::radians(pitch)) * cos(glm::radians(yaw)); direction.y = sin(glm::radians(pitch)); direction.z = cos(glm::radians(pitch)) * sin(glm::radians(yaw));
具體座標系再具體分析,滑鼠動一下,尤拉角響應加或者減一些,相機向量cameraFornt變一下,但是十分注意三個尤拉角的初始值,並不一定全是0,有可能是-90度
1 void mouse_callback(GLFWwindow* window, double xpos, double ypos) 2 { 3 if (firstMouse) 4 { 5 lastX = xpos; 6 lastY = ypos; 7 firstMouse = false; 8 } 9 10 float xoffset = xpos - lastX; 11 float yoffset = lastY - ypos; // reversed since y-coordinates go from bottom to top 12 lastX = xpos; 13 lastY = ypos; 14 15 float sensitivity = 0.1f; // change this value to your liking 16 xoffset *= sensitivity; 17 yoffset *= sensitivity; 18 19 yaw += xoffset; 20 pitch += yoffset; 21 22 // make sure that when pitch is out of bounds, screen doesn't get flipped 23 if (pitch > 89.0f) 24 pitch = 89.0f; 25 if (pitch < -89.0f) 26 pitch = -89.0f; 27 28 glm::vec3 front; 29 front.x = cos(glm::radians(yaw)) * cos(glm::radians(pitch)); 30 front.y = sin(glm::radians(pitch)); 31 front.z = sin(glm::radians(yaw)) * cos(glm::radians(pitch)); 32 cameraFront = glm::normalize(front); 33 }View Code
4.4還要注意一般不用翻滾角,且俯仰角的範圍有限制,在正負89度之間,另外還有第一次捕捉滑鼠位置時的問題
4.5滑鼠滾輪的縮放移動
1 void scroll_callback(GLFWwindow* window, double xoffset, double yoffset) 2 { 3 if(fov >= 1.0f && fov <= 45.0f) 4 fov -= yoffset; 5 if(fov <= 1.0f) 6 fov = 1.0f; 7 if(fov >= 45.0f) 8 fov = 45.0f; 9 } 10 //fov是init投影矩陣的視角引數View Code
5.完整的一個程式碼
1 #include <glad/glad.h> 2 #include <GLFW/glfw3.h> 3 #include <stb_image.h> 4 5 #include <glm/glm.hpp> 6 #include <glm/gtc/matrix_transform.hpp> 7 #include <glm/gtc/type_ptr.hpp> 8 9 #include <learnopengl/shader_m.h> 10 11 #include <iostream> 12 13 void framebuffer_size_callback(GLFWwindow* window, int width, int height); 14 void mouse_callback(GLFWwindow* window, double xpos, double ypos); 15 void scroll_callback(GLFWwindow* window, double xoffset, double yoffset); 16 void processInput(GLFWwindow *window); 17 18 // settings 19 const unsigned int SCR_WIDTH = 800; 20 const unsigned int SCR_HEIGHT = 600; 21 22 // camera 23 glm::vec3 cameraPos = glm::vec3(0.0f, 0.0f, 3.0f); 24 glm::vec3 cameraFront = glm::vec3(0.0f, 0.0f, -1.0f); 25 glm::vec3 cameraUp = glm::vec3(0.0f, 1.0f, 0.0f); 26 27 bool firstMouse = true; 28 float yaw = -90.0f; // yaw is initialized to -90.0 degrees since a yaw of 0.0 results in a direction vector pointing to the right so we initially rotate a bit to the left. 29 float pitch = 0.0f; 30 float lastX = 800.0f / 2.0; 31 float lastY = 600.0f / 2.0; 32 float fov = 45.0f; 33 34 // timing 35 float deltaTime = 0.0f; // time between current frame and last frame 36 float lastFrame = 0.0f; 37 38 int main() 39 { 40 // glfw: initialize and configure 41 // ------------------------------ 42 glfwInit(); 43 glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); 44 glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); 45 glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); 46 47 #ifdef __APPLE__ 48 glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // uncomment this statement to fix compilation on OS X 49 #endif 50 51 // glfw window creation 52 // -------------------- 53 GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL); 54 if (window == NULL) 55 { 56 std::cout << "Failed to create GLFW window" << std::endl; 57 glfwTerminate(); 58 return -1; 59 } 60 glfwMakeContextCurrent(window); 61 glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); 62 glfwSetCursorPosCallback(window, mouse_callback); 63 glfwSetScrollCallback(window, scroll_callback); 64 65 // tell GLFW to capture our mouse 66 glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); 67 68 // glad: load all OpenGL function pointers 69 // --------------------------------------- 70 if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) 71 { 72 std::cout << "Failed to initialize GLAD" << std::endl; 73 return -1; 74 } 75 76 // configure global opengl state 77 // ----------------------------- 78 glEnable(GL_DEPTH_TEST); 79 80 // build and compile our shader zprogram 81 // ------------------------------------ 82 Shader ourShader("6.2.coordinate_systems.vs", "6.2.coordinate_systems.fs"); 83 84 // set up vertex data (and buffer(s)) and configure vertex attributes 85 // ------------------------------------------------------------------ 86 float vertices[] = { 87 -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 88 0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 89 0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 90 0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 91 -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 92 -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 93 94 -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 95 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 96 0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 97 0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 98 -0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 99 -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 100 101 -0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 102 -0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 103 -0.5f, -0.5f, -0.5f, 0.0f, 1.0f, 104 -0.5f, -0.5f, -0.5f, 0.0f, 1.0f, 105 -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 106 -0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 107 108 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 109 0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 110 0.5f, -0.5f, -0.5f, 0.0f, 1.0f, 111 0.5f, -0.5f, -0.5f, 0.0f, 1.0f, 112 0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 113 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 114 115 -0.5f, -0.5f, -0.5f, 0.0f, 1.0f, 116 0.5f, -0.5f, -0.5f, 1.0f, 1.0f, 117 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 118 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 119 -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 120 -0.5f, -0.5f, -0.5f, 0.0f, 1.0f, 121 122 -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 123 0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 124 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 125 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 126 -0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 127 -0.5f, 0.5f, -0.5f, 0.0f, 1.0f 128 }; 129 // world space positions of our cubes 130 glm::vec3 cubePositions[] = { 131 glm::vec3(0.0f, 0.0f, 0.0f), 132 glm::vec3(2.0f, 5.0f, -15.0f), 133 glm::vec3(-1.5f, -2.2f, -2.5f), 134 glm::vec3(-3.8f, -2.0f, -12.3f), 135 glm::vec3(2.4f, -0.4f, -3.5f), 136 glm::vec3(-1.7f, 3.0f, -7.5f), 137 glm::vec3(1.3f, -2.0f, -2.5f), 138 glm::vec3(1.5f, 2.0f, -2.5f), 139 glm::vec3(1.5f, 0.2f, -1.5f), 140 glm::vec3(-1.3f, 1.0f, -1.5f) 141 }; 142 unsigned int VBO, VAO; 143 glGenVertexArrays(1, &VAO); 144 glGenBuffers(1, &VBO); 145 146 glBindVertexArray(VAO); 147 148 glBindBuffer(GL_ARRAY_BUFFER, VBO); 149 glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); 150 151 // position attribute 152 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0); 153 glEnableVertexAttribArray(0); 154 // texture coord attribute 155 glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float))); 156 glEnableVertexAttribArray(1); 157 158 159 // load and create a texture 160 // ------------------------- 161 unsigned int texture1, texture2; 162 // texture 1 163 // --------- 164 glGenTextures(1, &texture1); 165 glBindTexture(GL_TEXTURE_2D, texture1); 166 // set the texture wrapping parameters 167 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); 168 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); 169 // set texture filtering parameters 170 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 171 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 172 // load image, create texture and generate mipmaps 173 int width, height, nrChannels; 174 stbi_set_flip_vertically_on_load(true); // tell stb_image.h to flip loaded texture's on the y-axis. 175 unsigned char *data = stbi_load("resources/textures/container.jpg", &width, &height, &nrChannels, 0); 176 if (data) 177 { 178 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data); 179 glGenerateMipmap(GL_TEXTURE_2D); 180 } 181 else 182 { 183 std::cout << "Failed to load texture" << std::endl; 184 } 185 stbi_image_free(data); 186 // texture 2 187 // --------- 188 glGenTextures(1, &texture2); 189 glBindTexture(GL_TEXTURE_2D, texture2); 190 // set the texture wrapping parameters 191 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); 192 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); 193 // set texture filtering parameters 194 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 195 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 196 // load image, create texture and generate mipmaps 197 data = stbi_load("resources/textures/awesomeface.png", &width, &height, &nrChannels, 0); 198 if (data) 199 { 200 // note that the awesomeface.png has transparency and thus an alpha channel, so make sure to tell OpenGL the data type is of GL_RGBA 201 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); 202 glGenerateMipmap(GL_TEXTURE_2D); 203 } 204 else 205 { 206 std::cout << "Failed to load texture" << std::endl; 207 } 208 stbi_image_free(data); 209 210 // tell opengl for each sampler to which texture unit it belongs to (only has to be done once) 211 // ------------------------------------------------------------------------------------------- 212 ourShader.use(); 213 ourShader.setInt("texture1", 0); 214 ourShader.setInt("texture2", 1); 215 216 217 // render loop 218 // ----------- 219 while (!glfwWindowShouldClose(window)) 220 { 221 // per-frame time logic 222 // -------------------- 223 float currentFrame = glfwGetTime(); 224 deltaTime = currentFrame - lastFrame; 225 lastFrame = currentFrame; 226 227 // input 228 // ----- 229 processInput(window); 230 231 // render 232 // ------ 233 glClearColor(0.2f, 0.3f, 0.3f, 1.0f); 234 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 235 236 // bind textures on corresponding texture units 237 glActiveTexture(GL_TEXTURE0); 238 glBindTexture(GL_TEXTURE_2D, texture1); 239 glActiveTexture(GL_TEXTURE1); 240 glBindTexture(GL_TEXTURE_2D, texture2); 241 242 // activate shader 243 ourShader.use(); 244 245 // pass projection matrix to shader (note that in this case it could change every frame) 246 glm::mat4 projection = glm::perspective(glm::radians(fov), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f); 247 ourShader.setMat4("projection", projection); 248 249 // camera/view transformation 250 glm::mat4 view = glm::lookAt(cameraPos, cameraPos + cameraFront, cameraUp); 251 ourShader.setMat4("view", view); 252 253 // render boxes 254 glBindVertexArray(VAO); 255 for (unsigned int i = 0; i < 10; i++) 256 { 257 // calculate the model matrix for each object and pass it to shader before drawing 258 glm::mat4 model; 259 model = glm::translate(model, cubePositions[i]); 260 float angle = 20.0f * i; 261 model = glm::rotate(model, glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f)); 262 ourShader.setMat4("model", model); 263 264 glDrawArrays(GL_TRIANGLES, 0, 36); 265 } 266 267 // glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.) 268 // ------------------------------------------------------------------------------- 269 glfwSwapBuffers(window); 270 glfwPollEvents(); 271 } 272 273 // optional: de-allocate all resources once they've outlived their purpose: 274 // ------------------------------------------------------------------------ 275 glDeleteVertexArrays(1, &VAO); 276 glDeleteBuffers(1, &VBO); 277 278 // glfw: terminate, clearing all previously allocated GLFW resources. 279 // ------------------------------------------------------------------ 280 glfwTerminate(); 281 return 0; 282 } 283 284 // process all input: query GLFW whether relevant keys are pressed/released this frame and react accordingly 285 // --------------------------------------------------------------------------------------------------------- 286 void processInput(GLFWwindow *window) 287 { 288 if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) 289 glfwSetWindowShouldClose(window, true); 290 291 float cameraSpeed = 2.5 * deltaTime; 292 if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) 293 cameraPos += cameraSpeed * cameraFront; 294 if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) 295 cameraPos -= cameraSpeed * cameraFront; 296 if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) 297 cameraPos -= glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed; 298 if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) 299 cameraPos += glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed; 300 } 301 302 // glfw: whenever the window size changed (by OS or user resize) this callback function executes 303 // --------------------------------------------------------------------------------------------- 304 void framebuffer_size_callback(GLFWwindow* window, int width, int height) 305 { 306 // make sure the viewport matches the new window dimensions; note that width and 307 // height will be significantly larger than specified on retina displays. 308 glViewport(0, 0, width, height); 309 } 310 311 // glfw: whenever the mouse moves, this callback is called 312 // ------------------------------------------------------- 313 void mouse_callback(GLFWwindow* window, double xpos, double ypos) 314 { 315 if (firstMouse) 316 { 317 lastX = xpos; 318 lastY = ypos; 319 firstMouse = false; 320 } 321 322 float xoffset = xpos - lastX; 323 float yoffset = lastY - ypos; // reversed since y-coordinates go from bottom to top 324 lastX = xpos; 325 lastY = ypos; 326 327 float sensitivity = 0.1f; // change this value to your liking 328 xoffset *= sensitivity; 329 yoffset *= sensitivity; 330 331 yaw += xoffset; 332 pitch += yoffset; 333 334 // make sure that when pitch is out of bounds, screen doesn't get flipped 335 if (pitch > 89.0f) 336 pitch = 89.0f; 337 if (pitch < -89.0f) 338 pitch = -89.0f; 339 340 glm::vec3 front; 341 front.x = cos(glm::radians(yaw)) * cos(glm::radians(pitch)); 342 front.y = sin(glm::radians(pitch)); 343 front.z = sin(glm::radians(yaw)) * cos(glm::radians(pitch)); 344 cameraFront = glm::normalize(front); 345 } 346 347 // glfw: whenever the mouse scroll wheel scrolls, this callback is called 348 // ---------------------------------------------------------------------- 349 void scroll_callback(GLFWwindow* window, double xoffset, double yoffset) 350 { 351 if (fov >= 1.0f && fov <= 45.0f) 352 fov -= yoffset; 353 if (fov <= 1.0f) 354 fov = 1.0f; 355 if (fov >= 45.0f) 356 fov = 45.0f; 357 }View Code