diff --git a/docs/01 Getting started/09 Camera.md b/docs/01 Getting started/09 Camera.md index 0586ddb..019ea36 100644 --- a/docs/01 Getting started/09 Camera.md +++ b/docs/01 Getting started/09 Camera.md @@ -12,7 +12,7 @@ ## 摄像机/观察空间 -当我们讨论摄像机/观察空间(Camera/View Space)的时候,是在讨论以摄像机的视角作为场景原点时场景中的所有顶点坐标:观察矩阵把所有的世界坐标变换为相对于摄像机位置与方向的观察坐标。要定义一个摄像机,我们需要它在世界空间中的位置、观察的方向、一个指向它右测的向量以及一个指向它上方的向量。细心的读者可能已经注意到我们实际上创建了一个三个单位轴相互垂直的、以摄像机的位置为原点的坐标系。 +当我们讨论摄像机/观察空间(Camera/View Space)的时候,是在讨论以摄像机的视角作为场景原点时场景中所有的顶点坐标:观察矩阵把所有的世界坐标变换为相对于摄像机位置与方向的观察坐标。要定义一个摄像机,我们需要它在世界空间中的位置、观察的方向、一个指向它右测的向量以及一个指向它上方的向量。细心的读者可能已经注意到我们实际上创建了一个三个单位轴相互垂直的、以摄像机的位置为原点的坐标系。 ![](../img/01/09/camera_axes.png) @@ -30,7 +30,7 @@ glm::vec3 cameraPos = glm::vec3(0.0f, 0.0f, 3.0f); ### 2. 摄像机方向 -下一个需要的向量是摄像机的方向,比如它指向哪个方向。现在我们让摄像机指向场景原点:(0, 0, 0)。还记得两个矢量相减,我们能得到这两个矢量的差吗?用场景原点向量减去摄像机位置向量的结果就是摄像机的指向向量。由于我们知道摄像机指向z轴负方向,但我们希望方向向量(Direction Vector)指向摄像机的z轴正方向。如果我们交换相减的顺序,我们就会获得一个指向摄像机正z轴方向的向量: +下一个需要的向量是摄像机的方向,这里指的是摄像机指向哪个方向。现在我们让摄像机指向场景原点:(0, 0, 0)。还记得如果将两个矢量相减,我们就能得到这两个矢量的差吗?用场景原点向量减去摄像机位置向量的结果就是摄像机的指向向量。由于我们知道摄像机指向z轴负方向,但我们希望方向向量(Direction Vector)指向摄像机的z轴正方向。如果我们交换相减的顺序,我们就会获得一个指向摄像机正z轴方向的向量: ```c++ glm::vec3 cameraTarget = glm::vec3(0.0f, 0.0f, 0.0f); @@ -39,7 +39,7 @@ glm::vec3 cameraDirection = glm::normalize(cameraPos - cameraTarget); !!! Attention - **方向向量**(Direction Vector)并不是最好的名字,因为它正好指向从它到目标向量的相反方向(译注:注意看前面的那个图,所说的“方向向量”是指向z轴正方向的,而不是摄像机所注视的那个方向)。 + **方向**向量(Direction Vector)并不是最好的名字,因为它实际上指向从它到目标向量的相反方向(译注:注意看前面的那个图,蓝色的方向向量大概指向z轴的正方向,与摄像机实际指向的方向是正好相反的)。 ### 3. 右轴 @@ -79,18 +79,18 @@ view = glm::lookAt(glm::vec3(0.0f, 0.0f, 3.0f), glm::vec3(0.0f, 1.0f, 0.0f)); ``` -glm::LookAt函数需要一个位置、目标和上向量。它会创建一个和上一节一样的观察矩阵。 +glm::LookAt函数需要一个位置、目标和上向量。它会创建一个和在上一节使用的一样的观察矩阵。 在讨论用户输入之前,我们先来做些有意思的事,把我们的摄像机在场景中旋转。我们会将摄像机的注视点保持在(0, 0, 0)。 我们需要用到一点三角学的知识来在每一帧创建一个x和z坐标,它会代表圆上的一点,我们将会使用它作为摄像机的位置。通过重新计算x和y坐标,我们会遍历圆上的所有点,这样摄像机就会绕着场景旋转了。我们预先定义这个圆的半径radius,在每次渲染迭代中使用GLFW的glfwGetTime函数重新创建观察矩阵,来扩大这个圆。 ```c++ -GLfloat radius = 10.0f; -GLfloat camX = sin(glfwGetTime()) * radius; -GLfloat camZ = cos(glfwGetTime()) * radius; +float radius = 10.0f; +float camX = sin(glfwGetTime()) * radius; +float camZ = cos(glfwGetTime()) * radius; glm::mat4 view; -view = glm::lookAt(glm::vec3(camX, 0.0, camZ), glm::vec3(0.0, 0.0, 0.0), glm::vec3(0.0, 1.0, 0.0)); +view = glm::lookAt(glm::vec3(camX, 0.0, camZ), glm::vec3(0.0, 0.0, 0.0), glm::vec3(0.0, 1.0, 0.0)); ``` 如果你运行代码,应该会得到下面的结果: @@ -98,7 +98,7 @@ view = glm::lookAt(glm::vec3(camX, 0.0, camZ), glm::vec3(0.0, 0.0, 0.0), glm::ve -通过这一小段代码,摄像机现在会随着时间流逝围绕场景转动了。自己试试改变半径和位置/方向参数,看看**LookAt**矩阵是如何工作的。同时,如果你需要的话,这里有[源码](http://learnopengl.com/code_viewer.php?code=getting-started/camera_circle)、[顶点](http://learnopengl.com/code_viewer.php?code=getting-started/coordinate_systems&type=vertex)和[片段](http://learnopengl.com/code_viewer.php?code=getting-started/coordinate_systems&type=fragment)着色器。 +通过这一小段代码,摄像机现在会随着时间流逝围绕场景转动了。自己试试改变半径和位置/方向参数,看看**LookAt**矩阵是如何工作的。同时,如果你在哪卡住的话,这里有[源码](https://learnopengl.com/code_viewer_gh.php?code=src/1.getting_started/7.1.camera_circle/camera_circle.cpp)。 # 自由移动 @@ -118,115 +118,59 @@ view = glm::lookAt(cameraPos, cameraPos + cameraFront, cameraUp); 我们首先将摄像机位置设置为之前定义的cameraPos。方向是当前的位置加上我们刚刚定义的方向向量。这样能保证无论我们怎么移动,摄像机都会注视着目标方向。让我们摆弄一下这些向量,在按下某些按钮时更新cameraPos向量。 -我们已经为GLFW的键盘输入定义了一个key_callback函数,我们来新添加几个需要检查的按键命令: +我们已经为GLFW的键盘输入定义过一个processInput函数了,我们来新添加几个需要检查的按键命令: ```c++ -void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode) +void processInput(GLFWwindow *window) { ... - GLfloat cameraSpeed = 0.05f; - if(key == GLFW_KEY_W) + float cameraSpeed = 0.05f; // adjust accordingly + if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) cameraPos += cameraSpeed * cameraFront; - if(key == GLFW_KEY_S) + if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) cameraPos -= cameraSpeed * cameraFront; - if(key == GLFW_KEY_A) + if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) cameraPos -= glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed; - if(key == GLFW_KEY_D) - cameraPos += glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed; + if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) + cameraPos += glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed; } ``` -当我们按下**WASD**键的任意一个,摄像机的位置都会相应更新。如果我们希望向前或向后移动,我们就把位置向量加上或减去方向向量。如果我们希望向左右移动,我们使用叉乘来创建一个**右向量**(Right Vector),并沿着它相应移动就可以了。这样就创建了使用摄像机时熟悉的扫射(Strafe)效果。 +当我们按下**WASD**键的任意一个,摄像机的位置都会相应更新。如果我们希望向前或向后移动,我们就把位置向量加上或减去方向向量。如果我们希望向左右移动,我们使用叉乘来创建一个**右向量**(Right Vector),并沿着它相应移动就可以了。这样就创建了使用摄像机时熟悉的横移(Strafe)效果。 !!! important - 注意,我们对**右向量**进行了标准化。如果我们没对这个向量进行标准化,最后的叉乘结果会根据cameraFront变量返回大小不同的向量。如果我们不对向量进行标准化,我们就得根据摄像机的朝向不同加速或减速移动了,但假如进行了标准化移动就是匀速的。 + 注意,我们对**右向量**进行了标准化。如果我们没对这个向量进行标准化,最后的叉乘结果会根据cameraFront变量返回大小不同的向量。如果我们不对向量进行标准化,我们就得根据摄像机的朝向不同加速或减速移动了,但如果进行了标准化移动就是匀速的。 -如果你用这段代码更新key_callback函数,你就可以在场景中自由的前后左右移动了。 - - - -在摆弄这个基础的摄像机系统之后你可能会注意到这个摄像机系统不能同时朝两个方向移动(对角线移动),而且当你按下一个按键时,它会先顿一下才开始移动。这是因为大多数事件输入系统一次只能处理一个键盘输入,它们的函数只有当我们激活了一个按键时才被调用。虽然这对大多数GUI系统都没什么问题,它对摄像机来说并不合理。我们可以用一些小技巧解决这个问题。 - -这个技巧是在回调函数中只跟踪哪个按键被按下/释放。在游戏循环中我们读取这些值,检查哪个按键是**激活的**,然后做出相应反应。我们只储存哪个按键被按下/释放的状态信息,并在游戏循环中对状态做出反应。首先,我们来创建一个boolean数组代表被按下/释放的按键: - -```c++ -bool keys[1024]; -``` - -然后我们需要在key_callback函数中设置被按下/释放的按键为`true`或`false`: - -```c++ -if(action == GLFW_PRESS) - keys[key] = true; -else if(action == GLFW_RELEASE) - keys[key] = false; -``` - -并且我们创建一个新的叫做do_movement的函数,在这个函数中,我们将根据正在被按下的按键更新摄像机的值。: - -```c++ -void do_movement() -{ - // 摄像机控制 - GLfloat cameraSpeed = 0.01f; - if(keys[GLFW_KEY_W]) - cameraPos += cameraSpeed * cameraFront; - if(keys[GLFW_KEY_S]) - cameraPos -= cameraSpeed * cameraFront; - if(keys[GLFW_KEY_A]) - cameraPos -= glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed; - if(keys[GLFW_KEY_D]) - cameraPos += glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed; -} -``` - -之前的代码现在被移动到do_movement函数中。由于所有GLFW的按键枚举值本质上都是整数,我们可以把它们当数组索引使用。 - -最后,我们需要在游戏循环中添加新函数的调用: - -```c++ -while(!glfwWindowShouldClose(window)) -{ - // 检测并调用事件 - glfwPollEvents(); - do_movement(); - - // 渲染 - ... -} -``` - -至此,你应该可以同时向多个方向移动了,并且当你按下按钮时也会立刻移动了。如遇困难,可以查看下[源代码](http://learnopengl.com/code_viewer.php?code=getting-started/camera_keyboard)。 +现在你就应该能够移动摄像机了,虽然移动速度和系统有关,你可能会需要调整一下cameraSpeed。 ## 移动速度 -目前我们的移动速度是个常量。理论上没什么问题,但是实际情况下根据处理器的能力不同,有些人可能会比其他人每秒绘制更多帧,也就是以更高的频率调用do_movement函数。结果就是,根据配置的不同,有些人可能移动很快,而有些人会移动很慢。当你发布你的程序的时候,你必须确保它在所有硬件上移动速度都一样。 +目前我们的移动速度是个常量。理论上没什么问题,但是实际情况下根据处理器的能力不同,有些人可能会比其他人每秒绘制更多帧,也就是以更高的频率调用processInput函数。结果就是,根据配置的不同,有些人可能移动很快,而有些人会移动很慢。当你发布你的程序的时候,你必须确保它在所有硬件上移动速度都一样。 -图形程序和游戏通常会跟踪一个时间差(Deltatime)变量,它储存了渲染上一帧所用的时间。我们把所有速度都去乘以deltaTime值。结果就是,如果我们的deltaTime很大,就意味着上一帧的渲染花费了更多时间,所以这一帧的速度需要变得更高,来平衡渲染所花去的时间。使用这种方法时,无论你的电脑快还是慢,摄像机的速度都会相应平衡,这样每个用户的体验就都一样了。 +图形程序和游戏通常会跟踪一个时间差(Deltatime)变量,它储存了渲染上一帧所用的时间。我们把所有速度都去乘以deltaTime值。结果就是,如果我们的deltaTime很大,就意味着上一帧的渲染花费了更多时间,所以这一帧的速度需要变得更高来平衡渲染所花去的时间。使用这种方法时,无论你的电脑快还是慢,摄像机的速度都会相应平衡,这样每个用户的体验就都一样了。 我们跟踪两个全局变量来计算出deltaTime值: ```c++ -GLfloat deltaTime = 0.0f; // 当前帧与上一帧的时间差 -GLfloat lastFrame = 0.0f; // 上一帧的时间 +float deltaTime = 0.0f; // 当前帧与上一帧的时间差 +float lastFrame = 0.0f; // 上一帧的时间 ``` 在每一帧中我们计算出新的deltaTime以备后用。 ```c++ -GLfloat currentFrame = glfwGetTime(); +float currentFrame = glfwGetTime(); deltaTime = currentFrame - lastFrame; -lastFrame = currentFrame; +lastFrame = currentFrame; ``` 现在我们有了deltaTime,在计算速度的时候可以将其考虑进去了: ```c++ -void do_movement() +void processInput(GLFWwindow *window) { - GLfloat cameraSpeed = 5.0f * deltaTime; + float cameraSpeed = 2.5f * deltaTime; ... } ``` @@ -237,7 +181,7 @@ void do_movement() -现在我们有了一个在任何系统上移动速度都一样的摄像机。同样,如果你卡住了,查看一下[源码](http://learnopengl.com/code_viewer.php?code=getting-started/camera_keyboard_dt)。我们可以看到任何移动都会影响返回的deltaTime值。 +现在我们有了一个在任何系统上移动速度都一样的摄像机。同样,如果你卡住了,查看一下[源码](https://learnopengl.com/code_viewer_gh.php?code=src/1.getting_started/7.2.camera_keyboard_dt/camera_keyboard_dt.cpp)。我们可以看到任何移动都会影响返回的deltaTime值。 # 视角移动 @@ -323,18 +267,18 @@ glfwSetCursorPosCallback(window, mouse_callback); 第一步是计算鼠标自上一帧的偏移量。我们必须先在程序中储存上一帧的鼠标位置,我们把它的初始值设置为屏幕的中心(屏幕的尺寸是800x600): ```c++ -GLfloat lastX = 400, lastY = 300; +float lastX = 400, lastY = 300; ``` 然后在鼠标的回调函数中我们计算当前帧和上一帧鼠标位置的偏移量: ```c++ -GLfloat xoffset = xpos - lastX; -GLfloat yoffset = lastY - ypos; // 注意这里是相反的,因为y坐标是从底部往顶部依次增大的 +float xoffset = xpos - lastX; +float yoffset = lastY - ypos; // 注意这里是相反的,因为y坐标是从底部往顶部依次增大的 lastX = xpos; lastY = ypos; -GLfloat sensitivity = 0.05f; +float sensitivity = 0.05f; xoffset *= sensitivity; yoffset *= sensitivity; ``` @@ -345,7 +289,7 @@ yoffset *= sensitivity; ```c++ yaw += xoffset; -pitch += yoffset; +pitch += yoffset; ``` 第三步,我们需要给摄像机添加一些限制,这样摄像机就不会发生奇怪的移动了(这样也会避免一些奇怪的问题)。对于俯仰角,要让用户不能看向高于89度的地方(在90度时视角会发生逆转,所以我们把89度作为极限),同样也不允许小于-89度。这样能够保证用户只能看到天空或脚下,但是不能超越这个限制。我们可以在值超过限制的时候将其改为极限值来实现: @@ -376,9 +320,9 @@ cameraFront = glm::normalize(front); ```c++ if(firstMouse) // 这个bool变量初始时是设定为true的 { - lastX = xpos; - lastY = ypos; - firstMouse = false; + lastX = xpos; + lastY = ypos; + firstMouse = false; } ``` @@ -394,12 +338,12 @@ void mouse_callback(GLFWwindow* window, double xpos, double ypos) firstMouse = false; } - GLfloat xoffset = xpos - lastX; - GLfloat yoffset = lastY - ypos; + float xoffset = xpos - lastX; + float yoffset = lastY - ypos; lastX = xpos; lastY = ypos; - GLfloat sensitivity = 0.05; + float sensitivity = 0.05; xoffset *= sensitivity; yoffset *= sensitivity; @@ -419,7 +363,7 @@ void mouse_callback(GLFWwindow* window, double xpos, double ypos) } ``` -现在我们就可以自由地在3D场景中移动了!如果你遇到困难,可以来看一下[源代码](http://www.learnopengl.com/code_viewer.php?code=getting-started/camera_mouse)。 +现在我们就可以自由地在3D场景中移动了! ## 缩放 @@ -442,7 +386,7 @@ void scroll_callback(GLFWwindow* window, double xoffset, double yoffset) 我们现在在每一帧都必须把透视投影矩阵上传到GPU,但现在使用fov变量作为它的视野: ```c++ -projection = glm::perspective(fov, (GLfloat)WIDTH/(GLfloat)HEIGHT, 0.1f, 100.0f); +projection = glm::perspective(glm::radians(fov), 800.0f / 600.0f, 0.1f, 100.0f); ``` 最后不要忘记注册鼠标滚轮的回调函数: @@ -456,7 +400,7 @@ glfwSetScrollCallback(window, scroll_callback); -你可以去自由地实验,如果遇到困难,可以对比[源代码](http://learnopengl.com/code_viewer.php?code=getting-started/camera_zoom)。 +你可以去自由地实验,如果遇到困难,可以对比[源代码](https://learnopengl.com/code_viewer_gh.php?code=src/1.getting_started/7.3.camera_mouse_zoom/camera_mouse_zoom.cpp)。 !!! Important @@ -466,19 +410,16 @@ glfwSetScrollCallback(window, scroll_callback); 接下来的教程中,我们将会一直使用一个摄像机来浏览场景,从各个角度观察结果。然而,由于一个摄像机会占用每篇教程很大的篇幅,我们将会从细节抽象出来,创建我们自己的摄像机对象,它会完成大多数的工作,而且还会提供一些附加的功能。与着色器教程不同,我们不会带你一步一步创建摄像机类,我们只会提供你一份(有完整注释的)代码,如果你想知道它的内部构造的话可以自己去阅读。 -和着色器对象一样,我们把摄像机类写在一个单独的头文件中。你可以在[这里](http://learnopengl.com/code_viewer.php?type=header&code=camera)找到它,你现在应该能够理解所有的代码了。我们建议您至少看一看这个类,看看如何创建一个自己的摄像机类。 +和着色器对象一样,我们把摄像机类写在一个单独的头文件中。你可以在[这里](https://learnopengl.com/code_viewer_gh.php?code=includes/learnopengl/camera.h)找到它,你现在应该能够理解所有的代码了。我们建议您至少看一看这个类,看看如何创建一个自己的摄像机类。 !!! Attention 我们介绍的摄像机系统是一个FPS风格的摄像机,它能够满足大多数情况需要,而且与欧拉角兼容,但是在创建不同的摄像机系统,比如飞行模拟摄像机,时就要当心。每个摄像机系统都有自己的优点和不足,所以确保对它们进行了详细研究。比如,这个FPS摄像机不允许俯仰角大于90度,而且我们使用了一个固定的上向量(0, 1, 0),这在需要考虑滚转角的时候就不能用了。 -使用新摄像机对象,更新后版本的源码可以在[这里](http://learnopengl.com/code_viewer.php?code=getting-started/camera_with_class)找到。 - -(译注:总而言之这个摄像机实现并不十分完美,你可以看看最终的源码。建议先看[这篇文章](https://github.com/cybercser/OpenGL_3_3_Tutorial_Translation/blob/master/Tutorial%2017%20Rotations.md),对旋转有更深的理解后,你就能做出更好的摄像机类,不过本文有些内容比如如何防止按键停顿和GLFW鼠标事件实现摄像机的注意事项比较重要,其它的就要做一定的取舍了) - +使用新摄像机对象,更新后版本的源码可以在[这里](https://learnopengl.com/code_viewer_gh.php?code=src/1.getting_started/7.4.camera_class/camera_class.cpp)找到。 ## 练习 -- 看看你是否能够修改摄像机类,使得其能够变成一个**真正的**FPS摄像机(也就是说不能够随意飞行);你只能够呆在xz平面上:[参考解答](http://www.learnopengl.com/code_viewer.php?code=getting-started/camera-exercise1) +- 看看你是否能够修改摄像机类,使得其能够变成一个**真正的**FPS摄像机(也就是说不能够随意飞行);你只能够呆在xz平面上:[参考解答](https://learnopengl.com/code_viewer.php?code=getting-started/camera-exercise1) -- 试着创建你自己的LookAt函数,其中你需要手动创建一个我们在一开始讨论的观察矩阵。用你的函数实现来替换GLM的LookAt函数,看看它是否还能一样地工作:[参考解答](http://www.learnopengl.com/code_viewer.php?code=getting-started/camera-exercise2) +- 试着创建你自己的LookAt函数,其中你需要手动创建一个我们在一开始讨论的观察矩阵。用你的函数实现来替换GLM的LookAt函数,看看它是否还能一样地工作:[参考解答](https://learnopengl.com/code_viewer.php?code=getting-started/camera-exercise2) diff --git a/docs/legacy.md b/docs/legacy.md index c70f743..0f7964d 100644 --- a/docs/legacy.md +++ b/docs/legacy.md @@ -252,4 +252,107 @@ glBindVertexArray(0); ![](img/01/06/textures_combined2.png) -如果你看到了一个开心的箱子,你就做对了。你可以对比一下[源代码](http://learnopengl.com/code_viewer.php?code=getting-started/textures_combined),以及[顶点着](http://learnopengl.com/code_viewer.php?type=vertex&code=getting-started/texture)和[片段](http://learnopengl.com/code_viewer.php?type=fragment&code=getting-started/texture)着色器。 \ No newline at end of file +如果你看到了一个开心的箱子,你就做对了。你可以对比一下[源代码](http://learnopengl.com/code_viewer.php?code=getting-started/textures_combined),以及[顶点着](http://learnopengl.com/code_viewer.php?type=vertex&code=getting-started/texture)和[片段](http://learnopengl.com/code_viewer.php?type=fragment&code=getting-started/texture)着色器。 + +## 01-09 摄像机 + + +### 自由移动 + +让摄像机绕着场景转的确很有趣,但是让我们自己移动摄像机会更有趣!首先我们必须设置一个摄像机系统,所以在我们的程序前面定义一些摄像机变量很有用: + +```c++ +glm::vec3 cameraPos = glm::vec3(0.0f, 0.0f, 3.0f); +glm::vec3 cameraFront = glm::vec3(0.0f, 0.0f, -1.0f); +glm::vec3 cameraUp = glm::vec3(0.0f, 1.0f, 0.0f); +``` + +`LookAt`函数现在成了: + +```c++ +view = glm::lookAt(cameraPos, cameraPos + cameraFront, cameraUp); +``` + +我们首先将摄像机位置设置为之前定义的cameraPos。方向是当前的位置加上我们刚刚定义的方向向量。这样能保证无论我们怎么移动,摄像机都会注视着目标方向。让我们摆弄一下这些向量,在按下某些按钮时更新cameraPos向量。 + +我们已经为GLFW的键盘输入定义了一个key_callback函数,我们来新添加几个需要检查的按键命令: + +```c++ +void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode) +{ + ... + GLfloat cameraSpeed = 0.05f; + if(key == GLFW_KEY_W) + cameraPos += cameraSpeed * cameraFront; + if(key == GLFW_KEY_S) + cameraPos -= cameraSpeed * cameraFront; + if(key == GLFW_KEY_A) + cameraPos -= glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed; + if(key == GLFW_KEY_D) + cameraPos += glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed; +} +``` + +当我们按下**WASD**键的任意一个,摄像机的位置都会相应更新。如果我们希望向前或向后移动,我们就把位置向量加上或减去方向向量。如果我们希望向左右移动,我们使用叉乘来创建一个**右向量**(Right Vector),并沿着它相应移动就可以了。这样就创建了使用摄像机时熟悉的扫射(Strafe)效果。 + +!!! important + + 注意,我们对**右向量**进行了标准化。如果我们没对这个向量进行标准化,最后的叉乘结果会根据cameraFront变量返回大小不同的向量。如果我们不对向量进行标准化,我们就得根据摄像机的朝向不同加速或减速移动了,但假如进行了标准化移动就是匀速的。 + +如果你用这段代码更新key_callback函数,你就可以在场景中自由的前后左右移动了。 + + + +在摆弄这个基础的摄像机系统之后你可能会注意到这个摄像机系统不能同时朝两个方向移动(对角线移动),而且当你按下一个按键时,它会先顿一下才开始移动。这是因为大多数事件输入系统一次只能处理一个键盘输入,它们的函数只有当我们激活了一个按键时才被调用。虽然这对大多数GUI系统都没什么问题,它对摄像机来说并不合理。我们可以用一些小技巧解决这个问题。 + +这个技巧是在回调函数中只跟踪哪个按键被按下/释放。在游戏循环中我们读取这些值,检查哪个按键是**激活的**,然后做出相应反应。我们只储存哪个按键被按下/释放的状态信息,并在游戏循环中对状态做出反应。首先,我们来创建一个boolean数组代表被按下/释放的按键: + +```c++ +bool keys[1024]; +``` + +然后我们需要在key_callback函数中设置被按下/释放的按键为`true`或`false`: + +```c++ +if(action == GLFW_PRESS) + keys[key] = true; +else if(action == GLFW_RELEASE) + keys[key] = false; +``` + +并且我们创建一个新的叫做do_movement的函数,在这个函数中,我们将根据正在被按下的按键更新摄像机的值。: + +```c++ +void do_movement() +{ + // 摄像机控制 + GLfloat cameraSpeed = 0.01f; + if(keys[GLFW_KEY_W]) + cameraPos += cameraSpeed * cameraFront; + if(keys[GLFW_KEY_S]) + cameraPos -= cameraSpeed * cameraFront; + if(keys[GLFW_KEY_A]) + cameraPos -= glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed; + if(keys[GLFW_KEY_D]) + cameraPos += glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed; +} +``` + +之前的代码现在被移动到do_movement函数中。由于所有GLFW的按键枚举值本质上都是整数,我们可以把它们当数组索引使用。 + +最后,我们需要在游戏循环中添加新函数的调用: + +```c++ +while(!glfwWindowShouldClose(window)) +{ + // 检测并调用事件 + glfwPollEvents(); + do_movement(); + + // 渲染 + ... +} +``` + +至此,你应该可以同时向多个方向移动了,并且当你按下按钮时也会立刻移动了。如遇困难,可以查看下[源代码](http://learnopengl.com/code_viewer.php?code=getting-started/camera_keyboard)。 \ No newline at end of file diff --git a/glossary.md b/glossary.md index 6b3c16b..1ef433a 100644 --- a/glossary.md +++ b/glossary.md @@ -166,7 +166,7 @@ - Camera Space:摄像机空间 - Gram-Schmidt Process:格拉姆—施密特正交化 - LookAt Matrix:LookAt矩阵 -- Strafe:(?)扫射 +- Strafe:横移 - Deltatime:时间差 - Euler Angles:欧拉角 - Pitch:俯仰角