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),这在需要考虑滚转角的时候就不能用了。
0.2鼠标控制方向,键盘控制距离,滚轮控制视角
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度
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
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 }
4.4还要注意一般不用翻滚角,且俯仰角的范围有限制,在正负89度之间,另外还有第一次捕捉鼠标位置时的问题
4.5鼠标滚轮的缩放移动
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
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投影矩阵的视角参数
5.完整的一个代码
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
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 }