PyOpenGL将视图坐标转换为对象坐标,用于ArcBall导航。

4
我正在按照这个教程学习3D中的弧球导航。

https://en.wikibooks.org/wiki/OpenGL_Programming/Modern_OpenGL_Tutorial_Arcball

我成功完成了所有的步骤,导航也正常工作,但是我似乎无法理解教程中的最后一步。
一个额外的技巧是将旋转轴从相机坐标系转换为物体坐标系。当相机和物体放置在不同位置时,这非常有用。例如,如果你将物体绕Y轴旋转90度(向右转头),然后用鼠标进行垂直移动,你会在相机的X轴上进行旋转,但对于物体来说,它应该变成绕Z轴的旋转(平面滚转)。通过将轴转换为物体坐标系,旋转将遵循用户在相机坐标系下的操作(所见即所得)。要从相机坐标系转换为物体坐标系,我们需要取MV矩阵的逆(从MVP矩阵三元组中)。
问题是,在第一步中旋转模型时,旋转轴也会发生变化,并且与我的“相机视图”不对齐。当然,我希望我的旋转轴始终与我的相机视图保持对齐。
请问有人可以给我建议如何解决这个问题吗?在教程中有一些代码,但对于它实际上在做什么并没有太多解释,而且我只会Python。
谢谢, Jacob
我的代码:
import pygame
from pygame.locals import *
from OpenGL.GL import *
from OpenGL.GLU import *
import math
import os
import numpy as np

size = 30
speed = 500
amplitude_amplificator = 80


color_table = ((1,0,0),
            (0,1,0),
            (0,0,1),
            (1,1,0),
            (1,0,1),
            (0,1,1),
            (1,0.5,0),
            (0.5,1,0),
            (0.5,1,0.5),
            (0,0.5,0)
            )


locations = ((0,-975, 0),
             (0, 975, 0),
             (-1273,-975, 0),
             (-1273, 975, 0),
             (-2482, -975, 0),
             (-2482, 975, 0),
             (-3737, -975, 0),
             (-3737, 975, 0)
             )

lines = ((0,2),
         (2, 4),
         (4, 6),
         (1, 3),
         (3, 5),
         (5, 7),
         (0, 1),
         (2, 3),
         (4, 5),
         (6, 7),

         )

amplitudes = ((3.38829249165602, 2.38305866657961, 2.52151563664636),
              (5.08487438107113, 2.36432294667884, 3.0843991148654),
              (3.44312569856563, 1.23112415468012, 1.29869765112226),
              (4.0421066637935, 1.40655294535107, 1.36083778879317),
              (3.78074337117764, 0.648255908566916, 0.752239154016233),
              (5.08887133464996, 0.607037324785205, 0.543523234321567),
              (4.49095206021647, 0.432732677308301, 2.18289872563964),
              (5.14707697114171, 0.335119576625248, 2.15666871777855)
              )

phases =   ((-146.873017352057,0,-95.316526141321),
             (-149.008372080797, 5.24886681104675, 78.3075732082314),
             (-148.241584335287, 5.54327579087787, -118.279685417256),
             (-151.844141596427, 6.48705235395368, -113.246406750217),
             (-148.14233553496, 27.9523171503408, 65.8254568277543),
             (-157.058723259828, 38.8760924034639, 85.2339573112435),
             (-153.417593784393, -120.329988461629, 16.0421535833842),
             (-156.779107376825, 83.2350395893582, 10.7592173681729)
             )

# DRAW CUBE
def Cube(po,si,co):

    POS = (
    (po[0]+si, po[1]-si, po[2]-si),
    (po[0]+si, po[1]+si, po[2]-si),
    (po[0]-si, po[1]+si, po[2]-si),
    (po[0]-si, po[1]-si, po[2]-si),
    (po[0]+si, po[1]-si, po[2]+si),
    (po[0]+si, po[1]+si, po[2]+si),
    (po[0]-si, po[1]-si, po[2]+si),
    (po[0]-si, po[1]+si, po[2]+si)
    )

    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)
    )

    glBegin(GL_LINES)
    for edge in edges:
        for vertex in edge:
            glColor3f(co[0],co[1],co[2])
            glVertex3fv(POS[vertex])
    glEnd()

#DRAW ORIGINAL SHAPE IN LINES
def Line_orig(po):

    glBegin(GL_LINES)
    for edge in po:
        for vertex in edge:
            glVertex3fv(locations[vertex])
    glEnd()

#Hemisphere mapping

def map_hemisphere(x,y):
    z = math.sqrt(abs(1-math.pow(x,2)-math.pow(y,2)))
    return z

# Calculate angle of two spatial vectors

def angle_calculation(a,b):

    r = math.degrees(math.acos((np.dot(a, b))/(np.linalg.norm(a)*np.linalg.norm(b))))

    return r


def main():

    mouse_pressed = 0
    pygame.init()
    display = (1200,800)
    pygame.display.set_mode(display, DOUBLEBUF|OPENGL)


    gluPerspective(45, (display[0]/display[1]), 0.1, 30000.0)

    glTranslatef(0,0.0,-10000)

    #glRotatef(90, 1, 0, 0)


    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                quit()


        glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)

        time = pygame.time.get_ticks()/1000

        norm_mouse_pos = (2*pygame.mouse.get_pos()[0]/display[0]-1,2*pygame.mouse.get_pos()[1]/display[1]-1,map_hemisphere(2*pygame.mouse.get_pos()[0]/display[0]-1,2*pygame.mouse.get_pos()[1]/display[1]-1))


        if pygame.mouse.get_pressed()[0]==1:

            if mouse_pressed == 0:

                mouse_pressed = 1

                clear = lambda: os.system('cls')
                clear()

                p1 = (norm_mouse_pos[0],norm_mouse_pos[1],map_hemisphere(norm_mouse_pos[0],norm_mouse_pos[1]))
                print(p1)

            else:

                p2 = (norm_mouse_pos[0],norm_mouse_pos[1],map_hemisphere(norm_mouse_pos[0],norm_mouse_pos[1]))
                cist = np.cross(p1, p2)
                print(angle_calculation(p1,p2))
                glRotatef( angle_calculation(p1,p2) , -cist[0] , cist[1] , cist[2] )

        else:

            mouse_pressed = 0






        # Translation of the model via keyboard handling

        keys=pygame.key.get_pressed()

        if keys[K_w]:
            glTranslatef(0, 100, 0)

        if keys[K_s]:
            glTranslatef(0, -100, 0)

        if keys[K_a]:
            glTranslatef(-100, 0, 0)

        if keys[K_d]:
            glTranslatef(100, 0, 0)

        # Drawing the Cubes at Nodes Loactions    

        for item, el in enumerate(locations):
            Cube((el[0] + amplitudes[item][0]*math.sin(time + phases[item][0]*(3.1415927/180))*amplitude_amplificator,
                  el[1] + amplitudes[item][1]*math.sin(time + phases[item][1]*(3.1415927/180))*amplitude_amplificator,
                  el[2] + amplitudes[item][2]*math.sin(time + phases[item][2]*(3.1415927/180))*amplitude_amplificator
                  ), size, color_table[item])

        # Drawing the Original Shapes (Specified nodes in Lines Tuple)

        Line_orig(lines)

        # Drawing the Deformed Shape

        glBegin(GL_LINES)
        for edge in lines:
            for vertex in edge:
                glVertex3fv((locations[vertex][0] + amplitudes[vertex][0]*math.sin(time + phases[vertex][0]*(3.1415927/180))*amplitude_amplificator,
                             locations[vertex][1] + amplitudes[vertex][1]*math.sin(time + phases[vertex][1]*(3.1415927/180))*amplitude_amplificator ,
                             locations[vertex][2] + amplitudes[vertex][2]*math.sin(time + phases[vertex][2]*(3.1415927/180))*amplitude_amplificator,
                             ))
        glEnd()

       # OpenGL Management



        pygame.display.flip()
        pygame.time.wait(10)

main()
1个回答

5
问题在于当我在第一步旋转模型时,旋转轴也会发生变化,并且它们与我的“摄像机视图”不对齐。当然,我希望我的旋转轴始终与我的摄像机视图保持对齐。
在渲染中,场景中的每个网格通常由模型矩阵、视图矩阵和投影矩阵进行变换。
- 投影矩阵: 投影矩阵描述了从场景的3D点到视口的2D点的映射。
- 视图矩阵: 视图矩阵描述了查看场景的方向和位置。视图矩阵将从世界空间转换到视图(眼睛)空间。
- 模型矩阵: 模型矩阵定义了场景中网格的位置、方向和相对大小。模型矩阵将网格的顶点位置从网格本身的坐标系变换到世界空间中。
如果您想围绕视图空间中的一个轴旋转场景,则必须执行以下操作:
  • 通过之前进行的所有旋转和平移操作来转换模型。

  • 应用新的旋转操作。

  • 应用视图平移。

  • 应用投影矩阵。


由于OpenGL固定功能管线具有矩阵堆栈,因此这些操作必须按相反的顺序执行。

例如,请参阅glMultMatrix的文档:

glMultMatrix将当前矩阵与使用m指定的矩阵相乘,并用乘积替换当前矩阵。

在OpenGL中,每个矩阵模式(请参见glMatrixMode)都有一个矩阵堆栈。矩阵模式包括GL_MODELVIEWGL_PROJECTIONGL_TEXTURE

首先,您必须在单独的投影矩阵堆栈上设置投影矩阵:

glMatrixMode( GL_PROJECTION );
gluPerspective(45, (display[0]/display[1]), 0.1, 30000.0)

接下来创建一个模型矩阵

a = (GLfloat * 16)()
modelMat = glGetFloatv(GL_MODELVIEW_MATRIX, a)

在主循环中初始化模型视图矩阵:
glMatrixMode( GL_MODELVIEW );    
glLoadIdentity()

计算新的旋转和平移:

axis = (p2[0]- p1[0], p2[1]- p1[1])
glRotatef( angle_calculation(p1,p2), axis[1], axis[0], 0 )

将模型矩阵乘以先前的模型矩阵,并存储组合后的模型矩阵:
glMultMatrixf( modelMat )
modelMat = glGetFloatv(GL_MODELVIEW_MATRIX, a)

设置视图并应用新的模型矩阵:

glLoadIdentity()
glTranslatef(0,0.0,-10000)
glMultMatrixf( modelMat )


最终的代码可能看起来像这样:

.....

glMatrixMode( GL_PROJECTION );
gluPerspective(45, (display[0]/display[1]), 0.1, 30000.0)

a = (GLfloat * 16)()
modelMat = glGetFloatv(GL_MODELVIEW_MATRIX, a)

while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            quit()

    glMatrixMode( GL_MODELVIEW );    
    glLoadIdentity()

    norm_mouse_pos = (2*pygame.mouse.get_pos()[0]/display[0]-1,2*pygame.mouse.get_pos()[1]/display[1]-1,map_hemisphere(2*pygame.mouse.get_pos()[0]/display[0]-1,2*pygame.mouse.get_pos()[1]/display[1]-1))
    if pygame.mouse.get_pressed()[0]==1:
        if mouse_pressed == 0:
            mouse_pressed = 1
            clear = lambda: os.system('cls')
            clear()
            p1 = (norm_mouse_pos[0],norm_mouse_pos[1],map_hemisphere(norm_mouse_pos[0],norm_mouse_pos[1]))
        else:
            p2 = (norm_mouse_pos[0],norm_mouse_pos[1],map_hemisphere(norm_mouse_pos[0],norm_mouse_pos[1]))
            cist = np.cross(p1, p2)
            axis = (p2[0]- p1[0], p2[1]- p1[1])
            glRotatef( angle_calculation(p1,p2) , axis[1] , axis[0] , 0 )
    else:
        mouse_pressed = 0

    # Translation of the model via keyboard handling
    keys=pygame.key.get_pressed()
    if keys[K_w]:
        glTranslatef(0, 100, 0)
    if keys[K_s]:
        glTranslatef(0, -100, 0)
    if keys[K_a]:
        glTranslatef(-100, 0, 0)
    if keys[K_d]:
        glTranslatef(100, 0, 0)

    glMultMatrixf( modelMat )
    modelMat = glGetFloatv(GL_MODELVIEW_MATRIX, a)

    glLoadIdentity()
    glTranslatef(0,0.0,-10000)
    glMultMatrixf( modelMat )

    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
    .....


"ValueError: math domain error"有时会出现,这是因为只有当值在[-1, 1]范围内时,才定义了该值的反余弦。将该值夹紧到此范围内(min(1,max(cos_a,-1))):

def angle_calculation(a,b):
    cos_a = np.dot(a, b) / (np.linalg.norm(a)*np.linalg.norm(b))
    r = math.degrees(math.acos( min(1,max(cos_a,-1)) ))
    return r

多好的回答!非常感谢! - J.J.
我改了代码,现在它像魔法一样运行得很好。不过我需要花点时间去研究一下我刚刚做了什么。再次感谢! - J.J.
程序仍然存在问题,偶尔会因为浮点精度和反余弦函数导致数学错误而崩溃。 - J.J.
@J.J. 数学错误可以通过范围夹紧来修复。请参见答案的最新部分。 - Rabbid76
@Rabbid76 这个 a = (GLfloat * 16)() 是什么意思,为什么要乘以 16。我在我的代码中尝试了一下,当代码调用 glMultMatrixf(modelMat) 时,会得到 ValueError: 'Expected 64 byte array, got 12 byte array'. - Bramanta
@Bramanta GLfloat * 16 创建了一个包含16个浮点数的数组。这是Python基础知识。请参阅ctypes - Arrays - Rabbid76

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接