一、目的
1、键盘控制摄像机自由移动;
2、鼠标控制摄像机旋转;
3、鼠标滚轮放大缩小视野。
二、程序运行结果
三、自由移动
view = lookAt(cameraPos, cameraPos + cameraFront, cameraUp)
cameraPos为摄像机位置
cameraPos + cameraFront为物体位置
当我们按下WASD键,摄像机的位置都会相应更新。如果我们希望向前或向后移动,我们就把位置向量加上或减去方向向量。如果我们希望向旁边移动,我们做一个叉乘来创建一个右向量,沿着它移动就可以了。这样就创建了类似使用摄像机横向、前后移动的效果。
注意,我们对右向量进行了标准化。如果我们没对这个向量进行标准化,最后的叉乘结果会根据cameraFront变量的大小返回不同的大小。如果我们不对向量进行标准化,我们就得根据摄像机的方位加速或减速移动了,但假如进行了标准化移动就是匀速的。
一个技巧是只在回调函数中跟踪哪个键被按下/释放。在游戏循环中我们读取这些值,检查那个按键被激活了,然后做出相应反应。我们只储存哪个键被按下/释放的状态信息,在游戏循环中对状态做出反应,我们来创建一个布尔数组代表按下/释放的键:
四、视角移动
欧拉角(Euler Angle)是表示3D空间中可以表示任何旋转的三个值,由莱昂哈德·欧拉在18世纪提出。有三种欧拉角:俯仰角(Pitch)、偏航角(Yaw)和滚转角(Roll),
俯仰角是描述我们如何往上和往下看的角(X轴,Pitch)。
偏航角表示我们往左和往右看的大小(Y轴,Yaw)。
滚转角代表我们如何翻滚摄像机(Z轴,Roll)。
用一个给定的俯仰角和偏航角,我们可以把它们转换为一个代表新的方向向量的3D向量。
front.x = cos(glm::radians(pitch)) * cos(glm::radians(yaw))
// 注意我们先把角度转为弧度,direction代表摄像机的前轴
五、鼠标输入
偏航角和俯仰角是从鼠标移动获得的,鼠标水平移动影响偏航角,鼠标垂直移动影响俯仰角。它的思想是储存上一帧鼠标的位置,在当前帧中我们当前计算鼠标位置和上一帧的位置相差多少。如果差别越大那么俯仰角或偏航角就改变越大。
在处理FPS风格的摄像机鼠标输入的时候,我们必须在获取最终的方向向量之前做下面这几步:
1、计算鼠标和上一帧的偏移量。
2、把偏移量添加到摄像机和俯仰角和偏航角中。
3、对偏航角和俯仰角进行最大和最小值的限制。
4、计算方向向量。
六、缩放
我们还要往摄像机系统里加点东西,实现一个缩放接口。前面教程中我们说视野(Field of View或fov)定义了我们可以看到场景中多大的范围。当视野变小时可视区域就会减小,产生放大了的感觉。我们用鼠标滚轮来放大。和鼠标移动、键盘输入一样我们需要一个鼠标滚轮的回调函数。
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
yoffset值代表我们滚动的大小。当scroll_callback函数调用后,我们改变全局aspect变量的内容。因为45.0f是默认的fov,我们将会把缩放级别限制在1.0f到45.0f。
我们现在在每一帧都必须把透视投影矩阵上传到GPU,但这一次使aspect变量作为它的fov:
projection = glm::perspective(aspect, (GLfloat)WIDTH/(GLfloat)HEIGHT, 0.1f, 100.0f);
七、源代码
"""glfw_cube06.pyAuthor: dalong10Description: Draw 4 Cube, learning OPENGL """import glutils #Common OpenGL utilities,see glutils.pyimport sys, random, mathimport OpenGLfrom OpenGL.GL import *from OpenGL.GL.shaders import *import numpy import numpy as npimport glfwstrVS = """#version 330 corelayout(location = 0) in vec3 position;layout (location = 1) in vec2 inTexcoord;out vec2 outTexcoord;uniform mat4 uMVMatrix;uniform mat4 uPMatrix;uniform float a;uniform float b;uniform float c;uniform float scale;uniform float theta;void main(){mat4 rot1=mat4(vec4(1.0, 0.0,0.0,0),vec4(0.0, 1.0,0.0,0),vec4(0.0,0.0,1.0,0.0),vec4(a,b,c,1.0));mat4 rot2=mat4(vec4(scale, 0.0,0.0,0.0),vec4(0.0, scale,0.0,0.0),vec4(0.0,0.0,scale,0.0),vec4(0.0,0.0,0.0,1.0));mat4 rot3=mat4( vec4(0.5+0.5*cos(theta), 0.5-0.5*cos(theta), -0.707106781*sin(theta), 0),vec4(0.5-0.5*cos(theta),0.5+0.5*cos(theta), 0.707106781*sin(theta),0),vec4(0.707106781*sin(theta), -0.707106781*sin(theta),cos(theta), 0.0),vec4(0.0, 0.0,0.0, 1.0));gl_Position=uPMatrix * uMVMatrix * rot2 *rot1 *rot3 * vec4(position.x, position.y, position.z, 1.0);outTexcoord = inTexcoord;}"""strFS = """#version 330 coreout vec4 FragColor;in vec2 outTexcoord;uniform sampler2D texture1;void main(){FragColor = texture(texture1, outTexcoord);}"""class FirstCube:def __init__(self, side):self.side = side# load shadersself.program = glutils.loadShaders(strVS, strFS)glUseProgram(self.program)# attributesself.vertIndex = glGetAttribLocation(self.program, b"position")self.texIndex = glGetAttribLocation(self.program, b"inTexcoord")s = side/2.0cube_vertices = [-s, -s, -s, s, -s, -s,s, s, -s,s, s, -s,-s, s, -s,-s, -s, -s,-s, -s, s, s, -s, s,s, s, s,s, s, s,-s, s, s,-s, -s, s,-s, s, s, -s, s, -s,-s, -s, -s,-s, -s, -s,-s, -s, s,-s, s, s,s, s, s, s, s, -s,s, -s, -s,s, -s, -s,s, -s, s,s, s, s,-s, -s, -s, s, -s, -s,s, -s, s,s, -s, s,-s, -s, s,-s, -s, -s,-s, s, -s, s, s,-s,s, s, s,s, s, s,-s, s, s,-s, s,-s]# texture coordst=1.0quadT = [0,0, t,0, t,t, t,t, 0,t, 0,0, 0,0, t,0, t,t, t,t, 0,t, 0,0, t,0, t,t, 0,t, 0,t, 0,0, t,0, t,0, t,t, 0,t, 0,t, 0,0, t,0, 0,t, t,t, t,0, t,0, 0,0, 0,t, 0,t, t,t, t,0, t,0, 0,0, 0,t]# set up vertex array object (VAO)self.vao = glGenVertexArrays(1)glBindVertexArray(self.vao)# set up VBOsvertexData = numpy.array(cube_vertices, numpy.float32)self.vertexBuffer = glGenBuffers(1)glBindBuffer(GL_ARRAY_BUFFER, self.vertexBuffer)glBufferData(GL_ARRAY_BUFFER, 4*len(vertexData), vertexData, GL_STATIC_DRAW)tcData = numpy.array(quadT, numpy.float32)self.tcBuffer = glGenBuffers(1)glBindBuffer(GL_ARRAY_BUFFER, self.tcBuffer)glBufferData(GL_ARRAY_BUFFER, 4*len(tcData), tcData,GL_STATIC_DRAW)# enable arraysglEnableVertexAttribArray(self.vertIndex)glEnableVertexAttribArray(self.texIndex)# Position attributeglBindBuffer(GL_ARRAY_BUFFER, self.vertexBuffer)glVertexAttribPointer(self.vertIndex, 3, GL_FLOAT, GL_FALSE, 0,None)# TexCoord attributeglBindBuffer(GL_ARRAY_BUFFER, self.tcBuffer) glVertexAttribPointer(self.texIndex, 2, GL_FLOAT, GL_FALSE, 0,None)# unbind VAOglBindVertexArray(0)glBindBuffer(GL_ARRAY_BUFFER, 0) def render(self,pMatrix,mvMatrix,texid,a,b,c,scale,r): self.texid = texid# enable textureglActiveTexture(GL_TEXTURE0)glBindTexture(GL_TEXTURE_2D, self.texid)# use shader# set proj matrixglUniformMatrix4fv(glGetUniformLocation(self.program, 'uPMatrix'), 1, GL_FALSE, pMatrix) # set modelview matrixglUniformMatrix4fv(glGetUniformLocation(self.program, 'uMVMatrix'), 1, GL_FALSE, mvMatrix)glUseProgram(self.program)glUniform1f(glGetUniformLocation(self.program, "a"), a)glUniform1f(glGetUniformLocation(self.program, "b"), b)glUniform1f(glGetUniformLocation(self.program, "c"), c)glUniform1f(glGetUniformLocation(self.program, "scale"), scale)theta = r*PI/180.0glUniform1f(glGetUniformLocation(self.program, "theta"), theta)# bind VAOglBindVertexArray(self.vao)glEnable(GL_DEPTH_TEST)# drawglDrawArrays(GL_TRIANGLES, 0, 36)# unbind VAOglBindVertexArray(0)#Is called whenever a key is pressed/released via GLFWdef on_key(window, key, scancode, action, mods):if key == glfw.KEY_ESCAPE and action == glfw.PRESS:glfw.set_window_should_close(window,1)elif key >0 and key<1024 :if action == glfw.PRESS:keys[key]=Trueelif (action == glfw.RELEASE):keys[key]=False def do_movement():global cameraPosglobal cameraFrontglobal cameraUpglobal deltaTime#Camera controlscameraSpeed = 5.0 * deltaTimeif (keys[glfw.KEY_W]):cameraPos += cameraSpeed * cameraFrontprint(cameraPos)if (keys[glfw.KEY_S]):cameraPos -= cameraSpeed * cameraFrontif (keys[glfw.KEY_A]):# normalize up vectornorm = numpy.linalg.norm(cameraFront)cameraFront /= normnorm = np.linalg.norm(cameraUp)cameraUp /= norm# Side = forward x up side = np.cross(cameraFront, cameraUp)cameraPos -= cameraSpeed * sideif (keys[glfw.KEY_D]):# normalize up vectornorm = numpy.linalg.norm(cameraFront)cameraFront /= normnorm = np.linalg.norm(cameraUp)cameraUp /= norm# Side = forward x up side = np.cross(cameraFront, cameraUp)cameraPos += cameraSpeed * sidedef mouse_callback(window, xpos, ypos):global cameraFrontglobal firstMouseglobal lastXglobal lastYglobal yawglobal pitch#print('mouse button: ', window, xpos, ypos)if (firstMouse==True):lastX = xposlastY = yposfirstMouse = Falsexoffset = xpos - lastXyoffset = lastY - yposlastX = xposlastY = ypos#camera.ProcessMouseMovement(xoffset, yoffset)sensitivity = 0.05xoffset *= sensitivityyoffset *= sensitivityyaw += xoffsetpitch += yoffsetif(pitch > 89.0):pitch = 89.0if(pitch < -89.0):pitch = -89.0cameraFront=[math.cos(math.radians(yaw)) * math.cos(math.radians(pitch)),math.sin(math.radians(pitch)),math.sin(math.radians(yaw))* math.cos(math.radians(pitch)) ] norm = numpy.linalg.norm(cameraFront)cameraFront /= normdef scroll_callback(window, xoffset, yoffset):global aspectif(aspect >= 1.0 and aspect <= 45.0):aspect -= yoffsetif(aspect <= 1.0):aspect=1.0if(aspect >= 45.0):aspect=45.0print('aspect=',aspect)if __name__ == '__main__':import sysimport glfwimport OpenGL.GL as glglobal cameraPosglobal cameraFrontglobal cameraUpglobal deltaTimeglobal firstMouseglobal lastXglobal lastYglobal yawglobal pitchglobal aspectkeys=numpy.zeros(1024)deltaTime = 0.0lastFrame = 0.0 # Time of last framefirstMouse = TruelastX= 400lastY = 300yaw = -90.0pitch = 0.0aspect=45.0camera = glutils.Camera([0.0, 0.0, 5.0],[0.0, 0.0, 0.0],[0.0, 1.0, 0.0])cameraPos =numpy.array([0,0,3.0], numpy.float32) cameraFront=numpy.array([0,0,-1.0], numpy.float32) cameraUp =numpy.array([0,1.0,0], numpy.float32) # Initialize the libraryif not glfw.init():sys.exit()# Create a windowed mode window and its OpenGL contextwindow = glfw.create_window(800, 600, "draw Cube ", None, None)if not window:glfw.terminate()sys.exit()# Make the window's context currentglfw.make_context_current(window)# Install a key handlerglfw.set_key_callback(window, on_key)# set window mouse callbacksglfw.set_cursor_pos_callback(window, mouse_callback)glfw.set_scroll_callback(window, scroll_callback)PI = 3.14159265358979323846264texid = glutils.loadTexture("container2.png")# Loop until the user closes the windowa=0firstCube0 = FirstCube(1.0)while not glfw.window_should_close(window):currentFrame = glfw.get_time()deltaTime = currentFrame - lastFrame lastFrame = currentFrameglfw.poll_events()do_movement()# Render herewidth, height = glfw.get_framebuffer_size(window)ratio = width / float(height)gl.glViewport(0, 0, width, height)gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)gl.glMatrixMode(gl.GL_PROJECTION)gl.glLoadIdentity()gl.glOrtho(-ratio, ratio, -1, 1, 1, -1)gl.glMatrixMode(gl.GL_MODELVIEW)gl.glLoadIdentity()gl.glClearColor(0.0,0.0,4.0,0.0)i=a camera.eye=[5*math.sin(glfw.get_time()), 0 , 5* math.cos(glfw.get_time()) ]# modelview matrixmvMatrix = glutils.lookAt(cameraPos, cameraPos+cameraFront, cameraUp)pMatrix = glutils.perspective(aspect, ratio, 0.1, 100.0)glBindTexture(GL_TEXTURE_2D, texid) firstCube0.render(pMatrix, mvMatrix,texid,0.0,1,0,0.4,i)firstCube0.render(pMatrix, mvMatrix,texid,1.0,0,0.4,0.5,i+30)firstCube0.render(pMatrix, mvMatrix,texid,0.0,-1,-0.5,0.3,i+60)firstCube0.render(pMatrix, mvMatrix,texid,-1.0,0,0.2,0.2,i+120)# Swap front and back buffersglfw.swap_buffers(window) # Poll for and process eventsglfw.poll_events()glfw.terminate()
八、参考文献
1、learnopengl教程 https://learnopengl-cn.readthedocs.io/zh/latest/01%20Getting%20started/09%20Camera/
2、大龙10简书 /p/4382b25ad797