<五>初探opengl,编写我们的镜头
现在我们尝试编写一个镜头类,有了这个类,我们能上下左右前后移动,感觉在玩fps游戏,很不错,下面开始看看怎么写。
初次接触镜头类是我在魔兽地图编辑中,当时创建一个镜头的步骤就是放到某个位置,调节角度,分别有3个角度可以调节,一个是类似高度一样的东西,一个是环绕着某个点的旋转角度,还有就是镜头的旋转。opengl镜头其实跟这个是差不多的。
1.首先我们需要定一个摄像机位置 ,也就是把摄像机放到什么位置去
2.然后我们要定一个目标位置,这个决定我们摄像机观察的方向
3.然后就是一个上向量,这个一般为(0,1,0)
从这几个变量,我们可以获得摄像机的右轴,我们用摄像机方向叉乘上向量,就能得到一条垂直于摄像机方向与上向量构成的一个平面的法向量。有了这条轴,我们可以左右平移摄像机。还能得到计算机的上轴 ,就方向和右轴叉乘下就可以得到了,用来上下移动。
接下来我们可以使用函数lookAt来建构这个观察模型
glm::mat4 view;
view = glm::lookAt(vec3Pos, vec3Target, vec3Up); //提供摄像机位置,摄像机目标,上向量,他会根据上面的方法得出其他东西,构建矩阵
这样作为视图转换中的观察矩阵穿进去就好。貌似忘了记录这个:
转换过程如上,模型变换->视图变换->投射变换->裁剪。
首先我们写一段代码,创建一个立方体
float vertices[] = { 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // 右上 0.5f, -0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // 右下 -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // 左下 -0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, // 左上 0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // 右上 0.5f, -0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // 右下 -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // 左下 -0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f // 左上 }; unsigned int indices[] = { 3, 2, 1, 0, 1, 3, 7, 6, 5, 4, 5, 7, 0, 1, 4, 1, 4, 5, 0, 3, 4, 3, 4, 7, 2, 3, 6, 3, 6, 7, 1, 2, 6, 1, 5, 6, }; glGenVertexArrays(1, &vao); glBindVertexArray(vao); glGenBuffers(1, &vbo); glBindBuffer(GL_ARRAY_BUFFER, vbo); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); glGenBuffers(1, &ebo); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); //链接顶点属性 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0); glEnableVertexAttribArray(0); //颜色链接 glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float))); glEnableVertexAttribArray(1); glEnable(GL_DEPTH_TEST);
我们定义了立方体的8个顶点和具体面的索引,其他按照之前做法来就可以了,最后是开启深度测试,不然面都画到前面来,没有了层次就不像立方体了。
绘图代码:
glClearColor(0, 0, 0, 0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, 0);
现在应该立方体画出来了,这样还是看不出他是立方体,我们需要转换一下观察的位置。
glm::mat4 model = glm::mat4(1.0f); model = glm::rotate(model, glm::radians(0.0f), glm::vec3(0.5f, 1.0f, 0.0f)); //模型矩阵,可以实现模型基本的操作,例如缩放,位移等 model = glm::translate(model, cubePositions[i]); glm::mat4 view = glm::mat4(1.0f); view = glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f)); //观察矩阵,用来调整观察的视野 glm::mat4 projection = glm::mat4(1.0f); //投影矩阵,分为正射投影和透视投影。2d一般正射,3d一般透视投影,这里使用了透视投影,营造3d效果 projection = glm::perspective(glm::radians(fov), (float)800 / 600, 0.1f, 100.0f); //透视投影矩阵生成,fov为镜头的开口角度, //创建位置,目标,上向量 glm::vec3 cameraPos = glm::vec3(0.0f, 0.0f, 3.0f); glm::vec3 target = glm::vec3(0, 0, 0); glm::vec3 cameraDir = glm::normalize(cameraPos - target); glm::vec3 up = glm::vec3(0, 1, 0); glm::vec3 cameraRight = glm::normalize(glm::cross(up, cameraDir)); glm::vec3 cameraUp = glm::cross(cameraDir, cameraRight); float radius = 10.0f; float camX = sin(glfwGetTime())*radius; float camZ = cos(glfwGetTime())*radius; //view = glm::lookAt(glm::vec3(camX, 5, camZ), glm::vec3(0, 0, 0), glm::vec3(0, 1, 0)); //view = glm::lookAt(cameraPos, target, up); view = glm::lookAt(pos, pos + front, up); //创建观察矩阵,传入了位置,目标,和上向量,这里的目标恒定为摄像机前面的一点,由front控制 //给着色器传入几个矩阵,供给他们计算 unsigned int transformLoc = glGetUniformLocation(shader->ID, "model"); glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(model)); unsigned int transformLoc2 = glGetUniformLocation(shader->ID, "view"); glUniformMatrix4fv(transformLoc2, 1, GL_FALSE, glm::value_ptr(view)); unsigned int transformLoc3 = glGetUniformLocation(shader->ID, "projection"); glUniformMatrix4fv(transformLoc3, 1, GL_FALSE, glm::value_ptr(projection));
glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, 0);
因为懒惰,我们没有写这些进阶过来的过程,具体可以看一下opengl教程 坐标系统
几个矩阵分别控制的东西我们已经知道了,接下来就是顶点着色器的编写。
#version 330 core layout(location = 0) in vec3 aPos; layout(location = 1) in vec3 aColor; out vec4 vertexColor; uniform mat4 model; uniform mat4 view; uniform mat4 projection; void main() { gl_Position = projection * view*model* vec4(aPos, 1.0); vertexColor = vec4(aColor, 1); }
可以看到,我们从代码里已经传递进来的3个矩阵,通过一定的顺序来相乘来进行转换
我们能看到一个类似的效果,立方体的观察,随着你的调整会从不同角度的观察。
此时,我们要加入点更加游戏的元素,就是使用键盘鼠标来操作镜头移动。
我们在主循环里加入一个键盘操作处理函数的调用,然后编写我们的方法
while (!glfwWindowShouldClose(window)) { handleInput(window); draw(); glfwSwapBuffers(window); glfwPollEvents(); }
void Camera::handleInput(GLFWwindow* window) { float speed = 0.005f; if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) pos += speed * front; if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) pos -= speed * front; if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) pos -= glm::normalize(glm::cross(front, up)) * speed; if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) pos += glm::normalize(glm::cross(front, up)) * speed; if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) glfwSetWindowShouldClose(window, GL_TRUE); }
pos是我们摄像机的位置,front是表示一个方向,让我们镜头保持看向某一个方向,当我们前后移动,我们和front处理,当我们需要左右移动,我们求出右轴后左右变换即可。
然后我们需要修改我们lookAt参数,使其适配移动
view = glm::lookAt(cameraPos, cameraPos + cameraFront, cameraUp); //cameraPos+cameraFront让镜头目标一致处理front方向上
现在,应该可以进行前后左右的移动了。此刻,我们还要加上鼠标的操作,就完美了。
首先我们得了解一个叫欧拉角的东西,这个是可以表示3d空间中任何旋转的3个值,分别是俯视角(pitch),偏航角(yaw)和滚转角(roll)
用这三个角,我们就能控制我们的镜头各种移动了,我们先看看着3个角怎么实现操作
俯视角pitch是镜头方向和xz平面构成的夹角,假设我们距离原点为1,那y的高度就是sin pitch,x和z是cos pitch。
偏航角yaw是方向偏离x轴的角度,可见z为sin yaw, x为是cos yaw
所以根据上面所看,当我们知道pitch和yaw角度后,即可计算出一个方向向量
direction.x = cos(glm::radians(pitch)) * cos(glm::radians(yaw)); // 译注:direction代表摄像机的前轴(Front),这个前轴是和本文第一幅图片的第二个摄像机的方向向量是相反的 direction.y = sin(glm::radians(pitch)); direction.z = cos(glm::radians(pitch)) * sin(glm::radians(yaw));
这几个角度,我们可以通过鼠标的操作来获取。
首先我们做一些设置
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
glfwSetCursorPosCallback(window, mouse_callback);
设置glfwSetInputMode,鼠标就会锁定在窗口里,鼠标也看不到了,这时候关闭窗口就很麻烦,我在键盘操作里加个了关闭热键。然后添加鼠标操作的回调
void mouse_callback(GLFWwindow* window, double xpos, double ypos) { if(firstMouse) //为上次鼠标点设置默认值 { lastX = xpos; lastY = ypos; firstMouse = false; } float xoffset = xpos - lastX; //x方向的偏移 float yoffset = lastY - ypos; //y方向偏移,因为是鼠标向下移动,鼠标位置越大 lastX = xpos; lastY = ypos; float sensitivity = 0.05; //灵敏度 xoffset *= sensitivity; yoffset *= sensitivity; yaw += xoffset; pitch += yoffset; if(pitch > 89.0f) //这里俯视角不能高于89度,否则视角会发生逆转 pitch = 89.0f; if(pitch < -89.0f) pitch = -89.0f; glm::vec3 front; front.x = cos(glm::radians(yaw)) * cos(glm::radians(pitch)); front.y = sin(glm::radians(pitch)); front.z = sin(glm::radians(yaw)) * cos(glm::radians(pitch)); cameraFront = glm::normalize(front); }
这里就能计算出摄像机新的朝向角度,配合上wads的移动,我们就能实现看起来像3d游戏里的自由移动了。
那么入门级别的教程笔记就写到这里了。