一、目的
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.py
Author: dalong10
Description: Draw 4 Cube, learning OPENGL
"""
import glutils #Common OpenGL utilities,see glutils.py
import sys, random, math
import OpenGL
from OpenGL.GL import *
from OpenGL.GL.shaders import *
import numpy
import numpy as np
import glfw
strVS = """
#version 330 core
layout(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 core
out vec4 FragColor;
in vec2 outTexcoord;
uniform sampler2D texture1;
void main(){
FragColor = texture(texture1, outTexcoord);
}
"""
class FirstCube:
def __init__(self, side):
self.side = side
# load shaders
self.program = glutils.loadShaders(strVS, strFS)
glUseProgram(self.program)
# attributes
self.vertIndex = glGetAttribLocation(self.program, b"position")
self.texIndex = glGetAttribLocation(self.program, b"inTexcoord")
s = side/2.0
cube_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 coords
t=1.0
quadT = [
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 VBOs
vertexData = 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 arrays
glEnableVertexAttribArray(self.vertIndex)
glEnableVertexAttribArray(self.texIndex)
# Position attribute
glBindBuffer(GL_ARRAY_BUFFER, self.vertexBuffer)
glVertexAttribPointer(self.vertIndex, 3, GL_FLOAT, GL_FALSE, 0,None)
# TexCoord attribute
glBindBuffer(GL_ARRAY_BUFFER, self.tcBuffer)
glVertexAttribPointer(self.texIndex, 2, GL_FLOAT, GL_FALSE, 0,None)
# unbind VAO
glBindVertexArray(0)
glBindBuffer(GL_ARRAY_BUFFER, 0)
def render(self,pMatrix,mvMatrix,texid,a,b,c,scale,r):
self.texid = texid
# enable texture
glActiveTexture(GL_TEXTURE0)
glBindTexture(GL_TEXTURE_2D, self.texid)
# use shader
# set proj matrix
glUniformMatrix4fv(glGetUniformLocation(self.program, 'uPMatrix'),
1, GL_FALSE, pMatrix)
# set modelview matrix
glUniformMatrix4fv(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.0
glUniform1f(glGetUniformLocation(self.program, "theta"), theta)
# bind VAO
glBindVertexArray(self.vao)
glEnable(GL_DEPTH_TEST)
# draw
glDrawArrays(GL_TRIANGLES, 0, 36)
# unbind VAO
glBindVertexArray(0)
#Is called whenever a key is pressed/released via GLFW
def 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]=True
elif (action == glfw.RELEASE):
keys[key]=False
def do_movement():
global cameraPos
global cameraFront
global cameraUp
global deltaTime
#Camera controls
cameraSpeed = 5.0 * deltaTime
if (keys[glfw.KEY_W]):
cameraPos += cameraSpeed * cameraFront
print(cameraPos)
if (keys[glfw.KEY_S]):
cameraPos -= cameraSpeed * cameraFront
if (keys[glfw.KEY_A]):
# normalize up vector
norm = numpy.linalg.norm(cameraFront)
cameraFront /= norm
norm = np.linalg.norm(cameraUp)
cameraUp /= norm
# Side = forward x up
side = np.cross(cameraFront, cameraUp)
cameraPos -= cameraSpeed * side
if (keys[glfw.KEY_D]):
# normalize up vector
norm = numpy.linalg.norm(cameraFront)
cameraFront /= norm
norm = np.linalg.norm(cameraUp)
cameraUp /= norm
# Side = forward x up
side = np.cross(cameraFront, cameraUp)
cameraPos += cameraSpeed * side
def mouse_callback(window, xpos, ypos):
global cameraFront
global firstMouse
global lastX
global lastY
global yaw
global pitch
#print('mouse button: ', window, xpos, ypos)
if (firstMouse==True):
lastX = xpos
lastY = ypos
firstMouse = False
xoffset = xpos - lastX
yoffset = lastY - ypos
lastX = xpos
lastY = ypos
#camera.ProcessMouseMovement(xoffset, yoffset)
sensitivity = 0.05
xoffset *= sensitivity
yoffset *= sensitivity
yaw += xoffset
pitch += yoffset
if(pitch > 89.0):
pitch = 89.0
if(pitch < -89.0):
pitch = -89.0
cameraFront=[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 /= norm
def scroll_callback(window, xoffset, yoffset):
global aspect
if(aspect >= 1.0 and aspect <= 45.0):
aspect -= yoffset
if(aspect <= 1.0):
aspect=1.0
if(aspect >= 45.0):
aspect=45.0
print('aspect=',aspect)
if __name__ == '__main__':
import sys
import glfw
import OpenGL.GL as gl
global cameraPos
global cameraFront
global cameraUp
global deltaTime
global firstMouse
global lastX
global lastY
global yaw
global pitch
global aspect
keys=numpy.zeros(1024)
deltaTime = 0.0
lastFrame = 0.0 # Time of last frame
firstMouse = True
lastX= 400
lastY = 300
yaw = -90.0
pitch = 0.0
aspect=45.0
camera = 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 library
if not glfw.init():
sys.exit()
# Create a windowed mode window and its OpenGL context
window = glfw.create_window(800, 600, "draw Cube ", None, None)
if not window:
glfw.terminate()
sys.exit()
# Make the window's context current
glfw.make_context_current(window)
# Install a key handler
glfw.set_key_callback(window, on_key)
# set window mouse callbacks
glfw.set_cursor_pos_callback(window, mouse_callback)
glfw.set_scroll_callback(window, scroll_callback)
PI = 3.14159265358979323846264
texid = glutils.loadTexture("container2.png")
# Loop until the user closes the window
a=0
firstCube0 = FirstCube(1.0)
while not glfw.window_should_close(window):
currentFrame = glfw.get_time()
deltaTime = currentFrame - lastFrame
lastFrame = currentFrame
glfw.poll_events()
do_movement()
# Render here
width, 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 matrix
mvMatrix = 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 buffers
glfw.swap_buffers(window)
# Poll for and process events
glfw.poll_events()
glfw.terminate()
八、参考文献
1、learnopengl教程https://learnopengl-cn.readthedocs.io/zh/latest/01%20Getting%20started/09%20Camera/
2、大龙10简书https://www.jianshu.com/p/4382b25ad797