如何使用 OpenGL 在 3D space 中进行垂直旋转?
how to get vertical rotation in 3D space with OpenGL?
我有一个在 OpenGL 中创建的立方体场,我按预期四处走动,并且旋转 "camera" 的一部分一直在工作,直到我尝试向上或向下看。
我有一段代码可以正常工作:
if pressed[pygame.K_UP] or pressed[pygame.K_DOWN]:
rotx = cos(rot/radian)
rotz = sin(rot/radian)
if pressed[pygame.K_UP]:
glRotatef(speed / 2, -rotx, 0, rotz)
if pressed[pygame.K_DOWN]:
glRotatef(speed / 2, rotx, 0, -rotz)
但它只在 rot 为 0 时有效。所以当我第一次 运行 程序时,如果我只左右移动,我可以向上和向下看,而不是向左或向右看,或者向前移动和向后。
verticies = (
(1, -1, -1),
(1, 1, -1),
(-1, 1, -1),
(-1, -1, -1),
(1, -1, 1),
(1, 1, 1),
(-1, -1, 1),
(-1, 1, 1)
)
edges = (
(0,1),
(0,3),
(0,4),
(2,1),
(2,3),
(2,7),
(6,3),
(6,4),
(6,7),
(5,1),
(5,4),
(5,7)
)
def Cube(tX, tY, tZ):
glBegin(GL_LINES)
for edge in edges:
for vertex in edge:
glVertex3f(verticies[vertex][0] + tX, verticies[vertex][1] + tY, verticies[vertex][2] + tZ)
glEnd()
def main():
pygame.init()
screenSize = (1500, 800)
pygame.display.set_mode(screenSize, DOUBLEBUF|OPENGL)
gluPerspective(45, (screenSize[0]/screenSize[1]), 0.1, 50.0)
rot = 0
speed = 3
radian = 57.2958
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
pressed = pygame.key.get_pressed()
#==# Rotation with arrow keys #==#
if pressed[pygame.K_LEFT]:
glRotatef(speed / 2, 0, -1, 0)
rot += 1
if pressed[pygame.K_RIGHT]:
glRotatef(speed / 2, 0, 1, 0)
rot -= 1
if pressed[pygame.K_UP] or pressed[pygame.K_DOWN]:
rotx = cos(rot/radian)
rotz = sin(rot/radian)
if pressed[pygame.K_UP]:
glRotatef(speed / 2, -rotx, 0, rotz)
if pressed[pygame.K_DOWN]:
glRotatef(speed / 2, rotx, 0, -rotz)
#==# Walking with WASD #==#
if pressed[pygame.K_w]:
glTranslate(sin(rot/radian) / speed, 0, cos(rot/radian) / speed)
if pressed[pygame.K_s]:
glTranslate(-sin(rot/radian) / speed, 0, -cos(rot/radian) / speed)
if pressed[pygame.K_a]:
glTranslate(sin((rot + 90)/radian) / speed, 0, cos((rot + 90)/radian) / speed)
if pressed[pygame.K_d]:
glTranslate(-sin((rot + 90)/radian) / speed, 0, -cos((rot + 90)/radian) / speed)
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
for i in range(8):
for j in range(8):
Cube(-i*2.5, -4, -j*2.5)
pygame.display.flip()
pygame.time.wait(10)
main()
我认为这可以作为 FPS 游戏中的运动和相机,但事实并非如此。
都是顺序的问题。 OpenGL 是一个状态引擎。每个操作改变一个状态。当您执行 glTranslatef
or glRotatef
之类的操作时,矩阵堆栈上的当前矩阵将被更改。
在 OpenGL 中有不同的矩阵,例如模型视图矩阵和投影矩阵。您要做的第一件事是分离投影矩阵和模型视图矩阵。这可以通过设置矩阵模式来完成(参见 glMatrixMode
):
glMatrixMode(GL_PROJECTION)
gluPerspective(45, (screenSize[0]/screenSize[1]), 0.1, 50.0)
glMatrixMode(GL_MODELVIEW)
# [...]
当矩阵运算应用于矩阵堆栈时,当前矩阵乘以运算定义的新(附加)矩阵。这意味着 glRotatef
后跟 glTranslatef
死亡:
current_matrix = current_matrix * rotation_matrix * translation_matrix
问题是,如果您想在第一人称视图中应用新的平移或旋转,则必须在当前视图(当前模型视图矩阵)上应用新的变换。这意味着操作必须以相反的顺序执行,而不是矩阵操作:
current_matrix = rotation_matrix * translation_matrix * current_matrix
您已尝试通过考虑当前的观察方向来补偿这一点,这是您通过三角函数计算的。但还有替代解决方案:
- 通过
glGetFloatv(GL_MODELVIEW_MATRIX, ...)
读取当前模型视图矩阵
- 通过单位矩阵初始化当前矩阵
glLoadIdentity
- 评估输入并进行新的矩阵运算 (
glTranslatef
/ glRotatef
)
- 将读取的模型vie矩阵与当前matirx相乘
glMultMatrix
current_mv_mat = (GLfloat * 16)()
glGetFloatv(GL_MODELVIEW_MATRIX, current_mv_mat)
glLoadIdentity()
# [...] glTranslatef, glRotatef
glMultMatrixf(mv)
更多技巧是必须在当前视图上执行的上下旋转,但遗憾的是它不应该在当前矩阵中声明,因为移动和左右旋转不应该依赖关于上下视图:
current_matrix = rotation_matrix * translation_matrix * current_matrix
mdel_view_matrix = roate_updown * current_matrix
幸运的是,当前矩阵在堆栈上管理,可以由 glPushMatrix
/ glPopMatrix
压入和弹出。 up ad down rotation不得不总结,最后应用到view上:
glPushMatrix()
current_mv_mat = (GLfloat * 16)()
glGetFloatv(GL_MODELVIEW_MATRIX, current_mv_mat)
glLoadIdentity()
glRotatef(sum_rot_updown, 1, 0, 0)
glMultMatrixf(mv)
# [...] draw all the objects of the scene
glPopMatrix()
查看示例,其中我将建议应用于您的原始代码:
def main():
pygame.init()
screenSize = (1500, 800)
pygame.display.set_mode(screenSize, DOUBLEBUF|OPENGL)
glMatrixMode(GL_PROJECTION)
gluPerspective(45, (screenSize[0]/screenSize[1]), 0.1, 50.0)
glMatrixMode(GL_MODELVIEW)
rot = 0
speed = 3
radian = 57.2958
sum_rot_updown = 0
current_mv_mat = (GLfloat * 16)()
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
pressed = pygame.key.get_pressed()
glGetFloatv(GL_MODELVIEW_MATRIX, current_mv_mat)
glLoadIdentity()
#==# Rotation left and right with arrow keys #==#
if pressed[pygame.K_LEFT]:
glRotatef(speed / 2, 0, -1, 0)
rot += 1
if pressed[pygame.K_RIGHT]:
glRotatef(speed / 2, 0, 1, 0)
rot -= 1
#==# Walking with WASD #==#
if pressed[pygame.K_w]:
glTranslate(0, 0, 1/speed)
if pressed[pygame.K_s]:
glTranslate(0, 0, -1/speed)
if pressed[pygame.K_a]:
glTranslate(1/speed, 0, 0)
if pressed[pygame.K_d]:
glTranslate(-1/speed, 0, 0)
glMultMatrixf(current_mv_mat)
#==# Rotation up and down with arrow keys #==#
if pressed[pygame.K_UP]:
sum_rot_updown -= speed / 2
if pressed[pygame.K_DOWN]:
sum_rot_updown += speed / 2
glPushMatrix()
glGetFloatv(GL_MODELVIEW_MATRIX, current_mv_mat)
glLoadIdentity()
glRotatef(sum_rot_updown, 1, 0, 0)
glMultMatrixf(current_mv_mat)
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
for i in range(8):
for j in range(8):
Cube(-i*2.5, -4, -j*2.5)
glPopMatrix()
pygame.display.flip()
pygame.time.wait(10)
main()
我有一个在 OpenGL 中创建的立方体场,我按预期四处走动,并且旋转 "camera" 的一部分一直在工作,直到我尝试向上或向下看。
我有一段代码可以正常工作:
if pressed[pygame.K_UP] or pressed[pygame.K_DOWN]:
rotx = cos(rot/radian)
rotz = sin(rot/radian)
if pressed[pygame.K_UP]:
glRotatef(speed / 2, -rotx, 0, rotz)
if pressed[pygame.K_DOWN]:
glRotatef(speed / 2, rotx, 0, -rotz)
但它只在 rot 为 0 时有效。所以当我第一次 运行 程序时,如果我只左右移动,我可以向上和向下看,而不是向左或向右看,或者向前移动和向后。
verticies = (
(1, -1, -1),
(1, 1, -1),
(-1, 1, -1),
(-1, -1, -1),
(1, -1, 1),
(1, 1, 1),
(-1, -1, 1),
(-1, 1, 1)
)
edges = (
(0,1),
(0,3),
(0,4),
(2,1),
(2,3),
(2,7),
(6,3),
(6,4),
(6,7),
(5,1),
(5,4),
(5,7)
)
def Cube(tX, tY, tZ):
glBegin(GL_LINES)
for edge in edges:
for vertex in edge:
glVertex3f(verticies[vertex][0] + tX, verticies[vertex][1] + tY, verticies[vertex][2] + tZ)
glEnd()
def main():
pygame.init()
screenSize = (1500, 800)
pygame.display.set_mode(screenSize, DOUBLEBUF|OPENGL)
gluPerspective(45, (screenSize[0]/screenSize[1]), 0.1, 50.0)
rot = 0
speed = 3
radian = 57.2958
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
pressed = pygame.key.get_pressed()
#==# Rotation with arrow keys #==#
if pressed[pygame.K_LEFT]:
glRotatef(speed / 2, 0, -1, 0)
rot += 1
if pressed[pygame.K_RIGHT]:
glRotatef(speed / 2, 0, 1, 0)
rot -= 1
if pressed[pygame.K_UP] or pressed[pygame.K_DOWN]:
rotx = cos(rot/radian)
rotz = sin(rot/radian)
if pressed[pygame.K_UP]:
glRotatef(speed / 2, -rotx, 0, rotz)
if pressed[pygame.K_DOWN]:
glRotatef(speed / 2, rotx, 0, -rotz)
#==# Walking with WASD #==#
if pressed[pygame.K_w]:
glTranslate(sin(rot/radian) / speed, 0, cos(rot/radian) / speed)
if pressed[pygame.K_s]:
glTranslate(-sin(rot/radian) / speed, 0, -cos(rot/radian) / speed)
if pressed[pygame.K_a]:
glTranslate(sin((rot + 90)/radian) / speed, 0, cos((rot + 90)/radian) / speed)
if pressed[pygame.K_d]:
glTranslate(-sin((rot + 90)/radian) / speed, 0, -cos((rot + 90)/radian) / speed)
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
for i in range(8):
for j in range(8):
Cube(-i*2.5, -4, -j*2.5)
pygame.display.flip()
pygame.time.wait(10)
main()
我认为这可以作为 FPS 游戏中的运动和相机,但事实并非如此。
都是顺序的问题。 OpenGL 是一个状态引擎。每个操作改变一个状态。当您执行 glTranslatef
or glRotatef
之类的操作时,矩阵堆栈上的当前矩阵将被更改。
在 OpenGL 中有不同的矩阵,例如模型视图矩阵和投影矩阵。您要做的第一件事是分离投影矩阵和模型视图矩阵。这可以通过设置矩阵模式来完成(参见 glMatrixMode
):
glMatrixMode(GL_PROJECTION)
gluPerspective(45, (screenSize[0]/screenSize[1]), 0.1, 50.0)
glMatrixMode(GL_MODELVIEW)
# [...]
当矩阵运算应用于矩阵堆栈时,当前矩阵乘以运算定义的新(附加)矩阵。这意味着 glRotatef
后跟 glTranslatef
死亡:
current_matrix = current_matrix * rotation_matrix * translation_matrix
问题是,如果您想在第一人称视图中应用新的平移或旋转,则必须在当前视图(当前模型视图矩阵)上应用新的变换。这意味着操作必须以相反的顺序执行,而不是矩阵操作:
current_matrix = rotation_matrix * translation_matrix * current_matrix
您已尝试通过考虑当前的观察方向来补偿这一点,这是您通过三角函数计算的。但还有替代解决方案:
- 通过
glGetFloatv(GL_MODELVIEW_MATRIX, ...)
读取当前模型视图矩阵
- 通过单位矩阵初始化当前矩阵
glLoadIdentity
- 评估输入并进行新的矩阵运算 (
glTranslatef
/glRotatef
) - 将读取的模型vie矩阵与当前matirx相乘
glMultMatrix
current_mv_mat = (GLfloat * 16)()
glGetFloatv(GL_MODELVIEW_MATRIX, current_mv_mat)
glLoadIdentity()
# [...] glTranslatef, glRotatef
glMultMatrixf(mv)
更多技巧是必须在当前视图上执行的上下旋转,但遗憾的是它不应该在当前矩阵中声明,因为移动和左右旋转不应该依赖关于上下视图:
current_matrix = rotation_matrix * translation_matrix * current_matrix
mdel_view_matrix = roate_updown * current_matrix
幸运的是,当前矩阵在堆栈上管理,可以由 glPushMatrix
/ glPopMatrix
压入和弹出。 up ad down rotation不得不总结,最后应用到view上:
glPushMatrix()
current_mv_mat = (GLfloat * 16)()
glGetFloatv(GL_MODELVIEW_MATRIX, current_mv_mat)
glLoadIdentity()
glRotatef(sum_rot_updown, 1, 0, 0)
glMultMatrixf(mv)
# [...] draw all the objects of the scene
glPopMatrix()
查看示例,其中我将建议应用于您的原始代码:
def main():
pygame.init()
screenSize = (1500, 800)
pygame.display.set_mode(screenSize, DOUBLEBUF|OPENGL)
glMatrixMode(GL_PROJECTION)
gluPerspective(45, (screenSize[0]/screenSize[1]), 0.1, 50.0)
glMatrixMode(GL_MODELVIEW)
rot = 0
speed = 3
radian = 57.2958
sum_rot_updown = 0
current_mv_mat = (GLfloat * 16)()
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
pressed = pygame.key.get_pressed()
glGetFloatv(GL_MODELVIEW_MATRIX, current_mv_mat)
glLoadIdentity()
#==# Rotation left and right with arrow keys #==#
if pressed[pygame.K_LEFT]:
glRotatef(speed / 2, 0, -1, 0)
rot += 1
if pressed[pygame.K_RIGHT]:
glRotatef(speed / 2, 0, 1, 0)
rot -= 1
#==# Walking with WASD #==#
if pressed[pygame.K_w]:
glTranslate(0, 0, 1/speed)
if pressed[pygame.K_s]:
glTranslate(0, 0, -1/speed)
if pressed[pygame.K_a]:
glTranslate(1/speed, 0, 0)
if pressed[pygame.K_d]:
glTranslate(-1/speed, 0, 0)
glMultMatrixf(current_mv_mat)
#==# Rotation up and down with arrow keys #==#
if pressed[pygame.K_UP]:
sum_rot_updown -= speed / 2
if pressed[pygame.K_DOWN]:
sum_rot_updown += speed / 2
glPushMatrix()
glGetFloatv(GL_MODELVIEW_MATRIX, current_mv_mat)
glLoadIdentity()
glRotatef(sum_rot_updown, 1, 0, 0)
glMultMatrixf(current_mv_mat)
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
for i in range(8):
for j in range(8):
Cube(-i*2.5, -4, -j*2.5)
glPopMatrix()
pygame.display.flip()
pygame.time.wait(10)
main()