一、前情提要
在前一篇中,我们已经完成了一个旋转的彩色立方体的制作,对GL来说算是一个简单的helloworld。同时我们简单接触了一下状态机这个概念,我们用【glEnable()】和【glBegin() & glEnd()】来控制了GL的状态实现渲染。
但这是不够的!我相信来看这篇文章的你们都是想做一个3D小游戏的吧,那么我们就需要什么?添加交互!!!
二、添加摄像机交互
首先让我们设计一下我们的交互,我们希望先实现摄像机的移动,就用游戏的标配wasd作为输入键,用空格和Ctrl作为上升和下降的输入键。
计划好了,让我们先为摄像机的三轴定义一个变量
cam_pos_x = 0
cam_pos_y = 0
cam_pos_z = 0
然后就是处理移动逻辑了,这里我们使用pygame的事件处理来实现,所以说pygame真是天赐之物
for event in pg.event.get():
if event.type == pg.QUIT:
pg.quit()
quit()
keys = pg.key.get_pressed()
if keys[pg.K_w]:
cam_pos_z += 0.1
if keys[pg.K_s]:
cam_pos_z -= 0.1
if keys[pg.K_a]:
cam_pos_x += 0.1
if keys[pg.K_w]:
cam_pos_x -= 0.1
if keys[pg.K_w]:
cam_pos_y -= 0.1
if keys[pg.K_w]:
cam_pos_y += 0.1
OK,这样移动的逻辑就设定好了,速度设置为了0.1,接下来让我们在循环中为移动赋值
for event in pg.event.get():
if event.type == pg.QUIT:
pg.quit()
quit()
keys = pg.key.get_pressed()
if keys[pg.K_w]:
cam_pos_z += 0.1
if keys[pg.K_s]:
cam_pos_z -= 0.1
if keys[pg.K_a]:
cam_pos_x += 0.1
if keys[pg.K_d]:
cam_pos_x -= 0.1
if keys[pg.K_SPACE]:
cam_pos_y -= 0.1
if keys[pg.K_LCTRL]:
cam_pos_y += 0.1
glLoadIdentity()
glTranslatef(cam_pos_x, cam_pos_y, cam_pos_z)
GL的位移旋转是由矩阵操作的,我们调用【glTranslatef()】函数来创建一个4x4的齐次矩阵,并将其右乘到当前的矩阵堆栈,同时在每次循环中我们需要调用【glLoadIdentity()】函数来将矩阵重置为单位矩阵,不然坐标的变化会一直累积直到变成闪电侠
话不多说,让我们运行代码看看效果如何
。。。这什么玩意儿。。。
别急,我们还需要设置一下投影矩阵,让我们回到main()函数的开头加上这段代码
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
gluPerspective(70, (display[0] / display[1]), 0.1, 50.0)
glMatrixMode(GL_MODELVIEW)
记得去掉初始化时我们添加上的位移,然后将摄像机的z值设置为-5!然后让我们再运行一下看看
"The World!!"
这个立方体就像被时停了的承太郎一样只能狠狠的看着我们,但是我们可以在这个空间中自由移动,让我们来修复这个BUG。
这里的关键问题是:GL的矩阵操作是逆序应用的,当前的矩阵实际变换顺序是【旋转→平移→透视投影】,这会导致旋转只发生在相机坐标系中,所以我们不能用之前的世界坐标系的方式来操作,让我们修改一下代码
import pygame as pg
from pygame.locals import *
from OpenGL.GL import *
from OpenGL.GLU import *
# Vertex
cubeVertices = ((1, 1, 1), (1, 1, -1), (1, -1, -1), (1, -1, 1),
(-1, 1, 1), (-1, -1, -1), (-1, -1, 1), (-1, 1, -1))
cubeQuads = ((0, 4, 6, 3), (2, 3, 6, 5), (1, 2, 5, 7),
(1, 7, 4, 0), (7, 5, 6, 4), (2, 1, 0, 3))
cubeColors = ((1, 0, 0), (0, 1, 0), (0, 0, 1), (1, 1, 0), (1, 0, 1), (0, 1, 1))
def draw_cube():
glBegin(GL_QUADS)
for i, quad in enumerate(cubeQuads):
glColor3fv(cubeColors[i])
for vertex in quad:
glVertex3fv(cubeVertices[vertex])
glEnd()
def main():
pg.init()
display = (960, 540)
pg.display.set_mode(display, DOUBLEBUF | OPENGL | RESIZABLE)
glEnable(GL_CULL_FACE)
glEnable(GL_DEPTH_TEST)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
gluPerspective(70, (display[0] / display[1]), 0.1, 50.0)
glMatrixMode(GL_MODELVIEW)
cam_pos_x = 0
cam_pos_y = 0
cam_pos_z = -5
rotation_y = 0
rotate_speed = 1
move_speed = 0.1
while True:
for event in pg.event.get():
if event.type == pg.QUIT:
pg.quit()
quit()
keys = pg.key.get_pressed()
if keys[pg.K_w]:
cam_pos_z += move_speed
if keys[pg.K_s]:
cam_pos_z -= move_speed
if keys[pg.K_a]:
cam_pos_x += move_speed
if keys[pg.K_d]:
cam_pos_x -= move_speed
if keys[pg.K_SPACE]:
cam_pos_y -= move_speed
if keys[pg.K_LCTRL]:
cam_pos_y += move_speed
rotation_y += rotate_speed
glLoadIdentity()
glTranslatef(cam_pos_x, cam_pos_y, cam_pos_z)
glRotatef(rotation_y, 0, 1, 0)
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
draw_cube()
pg.display.flip()
pg.time.wait(10)
if __name__ == "__main__":
main()
这里我顺便给移动速度定义为了一个变量,这样代码更优雅的同时调整起来也方便;同时我们定义了一个y轴旋转角和旋转速度的变量,在每次循环中为旋转角增加一个单位速度;最后我也给绘制立方体封装为了一个函数。
我真的很不喜欢在循环中有一大段一大段的东西,让我在后续加功能的时候一个脑袋两个大,所以封装函数对我来说真的很有用,感觉延长了至少一年的寿命,话扯远了,让我们运行一下看看效果
完美,下一篇让我们为摄像机添加上视角变化的功能,就可以用鼠标来控制视角了