OpenGL 中的太阳系,相机位置

Solar System in OpenGL, Camera position

我想用 OpenGL 制作简单的太阳系,有四个摄像头。

我要的很简单,在地球的一侧安个摄像头就可以了。

在后续代码中,我通过 glGetFloatv(GL_MODELVIEW_MATRIX)(第 116 行)

得到 MODELVIEW_MATRIX

所以我想 { MODELVIEW_MATRIX multiple [[0],[0],[0],[1]] matrix } 在世界坐标系中得到行星的原点。

但效果不佳,所以我需要一些帮助。

from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *

import math

import numpy as np

WINDOW_WIDTH = 600
WINDOW_HEIGHT = 600
WINDOW_POSITION_X = 0
WINDOW_POSITION_Y = 0

earthRevolveAngle = 180
earthRotateAngle = 0
satelliteRevolveAngle = 180
satellitePlaneAngle = 0
plutoRevolveAngle = 180
plutoRotateAngle = 0

plutoCamera = np.array([0, 0, 0])
earthPosition = np.array([0, 0, 0])


class Camera :

    def __init__(self):  #constructor
        self.loc = np.array([0.0, 50.0,  0.0])
        self.tar = np.array([0.0, 0.0, 0.0])
        self.up  = np.array([1.0, 0.0,  0.0])
        self.right = np.array([1.0, 0.0, 0.0])
        self.dir = np.array([0.0, 0.0, -1.0])
        self.asp = 1.0
        self.fov = 70
        self.near= 0.1
        self.far = 500.0


    def setCameraLoc(self, loc):
        self.loc = loc
        self.tar = self.loc + self.dir

    def setCamera(self, loc, tar, up):
        self.loc, self.tar, self.up = loc, tar, up

        self.dir = self.tar - self.loc
        l = np.linalg.norm(self.dir)
        if l > 0.0 :
            self.dir = self.dir / l

        l = np.linalg.norm(self.up)
        if l > 0.0 :
            self.up = self.up / l

        self.right = np.cross(self.dir, self.up)


    def setLens(self, fov, asp, near, far):
        self.fov, self.asp, self.near, self.far = fov, asp, near, far

    def applyCamera(self):
        gluLookAt(self.loc[0], self.loc[1], self.loc[2],
                  self.tar[0], self.tar[1], self.tar[2],
                  self.up [0], self.up [1], self.up [2])

    def applyLens(self):
        gluPerspective(self.fov, self.asp, self.near, self.far)

    def moveForward(self, step=1.0):
        self.tar += self.dir*step
        self.loc += self.dir*step

    def zoomIn(self, step=1.0):
        self.loc += self.dir*step

    def zoomOut(self, step=1.0):
        self.loc -= self.dir*step



def drawPlanet(semiMajor, semiMinor, revolveAngle, rotateAngle, shape, slope,           axisTilt) :

    global plutoCamera, earthPosition

    a = semiMajor
    b = semiMinor

    #Orbit's slope
    glRotatef(slope, 1, 0, 0)


    #Start draw orbit
    glBegin(GL_LINE_STRIP)
    for i in range(0, 361):
        theta = 2.0 * 3.141592 * i / 360.0
        x = a*math.cos(theta)
        z = b*math.sin(theta)
        glVertex3f(x, 0, z)
    glEnd()
    #End draw orbit

    theta = 2.0 * 3.141592 * (revolveAngle%360) / 360.0
    x = a * math.cos(theta)
    z = b * math.sin(theta)


    glRotatef(revolveAngle, 0, 1, 0) 
    glTranslatef( math.sqrt( x**2 + z**2 ) , 0, 0) 


    glRotatef(rotateAngle, 0, 1, 0)
    glRotatef(axisTilt, 0, 0, 1)

    t = glGetFloatv(GL_MODELVIEW_MATRIX)

    if(shape == "satellite"):
        glScalef(0.4,0.4,0.4)
        glutSolidTetrahedron()
        glScalef(2.5,2.5,2.5)

    elif(shape == "earth"):
        glutWireCube(1)
        earthPosition = t * np.matrix( [[0],[0],[0],[1]] )

    elif(shape == "pluto"):
        glScalef(0.4,0.4,0.4)
        glutWireOctahedron()
        glScalef(2.5,2.5,2.5)


def drawScene() :
    global earthRevolveAngle, earthRotateAngle, satelliteAngle, satelliteRevolveAngle, satellitePlaneAngle, plutoRevolveAngle, plutoRotateAngle


    # draw solar
    glColor3f(1,0,0)
    glutWireSphere(1.0, 20, 20)

    glPushMatrix()


    # draw earth
    glColor3f(0,0.5,1.0)
    earthRevolveAngle+=0.05 # earth's revolution
    earthRotateAngle+=0.2
    drawPlanet(5, 5, earthRevolveAngle, earthRotateAngle, "earth",0,15)


    # draw satellite

    glColor3f(0.7,0.7,0.7)
    satelliteRevolveAngle+=1.5
    satellitePlaneAngle += 0.25
    glRotatef(satellitePlaneAngle, 1, 0, 0)
    drawPlanet(1, 1, satelliteRevolveAngle, 1, "satellite",0,0)


    # draw pluto
    glPopMatrix() # comeback to solar central coordinate
    glPushMatrix()

    glColor3f(0.9,0.7,0.26)
    plutoRevolveAngle+=0.0125 # pluto's revolution
    plutoRotateAngle+=0.1 # pluto's rotation
    drawPlanet(10, 8, plutoRevolveAngle,plutoRotateAngle, "pluto",0,0)
    glPopMatrix()

Cam = Camera()

def disp() :

    global plutoCamera, earthPosition, Cam

    # reset buffer
    glClear(GL_COLOR_BUFFER_BIT)

    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()

    # Camera view setting
    Cam.setLens(30,1.0,0.1,1000)
    Cam.applyLens()


    glMatrixMode(GL_MODELVIEW)
    glLoadIdentity()



    # first quadrant
    glViewport(int(WINDOW_POSITION_X+WINDOW_WIDTH/2), int(WINDOW_POSITION_Y + WINDOW_HEIGHT/2), int(WINDOW_WIDTH/2), int(WINDOW_HEIGHT/2)) 
    glPushMatrix()
    Cam.setCamera( np.array([0,0,1]), np.array([0,0,100]), np.array([0,1,0]))
    Cam.applyCamera()

    drawScene()
    glPopMatrix()



    # second quadrant
    glViewport(int(WINDOW_POSITION_X), int(WINDOW_POSITION_Y + WINDOW_HEIGHT/2), int(WINDOW_WIDTH/2), int(WINDOW_HEIGHT/2) ) 
    glPushMatrix()
    Cam.setCamera( np.array([30,30,30]), np.array([0,0,0]), np.array([0,1,0]))
    Cam.applyCamera()

    drawScene()
    glPopMatrix()



    # third quadrant
    glViewport(WINDOW_POSITION_X, WINDOW_POSITION_Y, int(WINDOW_WIDTH/2) , int(WINDOW_HEIGHT/2) )
    glPushMatrix()
    Cam.setCamera( plutoCamera, np.array([0,0,0]), np.array([0,1,0]))
    Cam.applyCamera()
    drawScene()
    glPopMatrix()


    # fourth quadrant
    glViewport(int(WINDOW_POSITION_X+WINDOW_WIDTH/2), WINDOW_POSITION_Y, int(WINDOW_WIDTH/2), int(WINDOW_HEIGHT/2) ) 
    glPushMatrix()
    Cam.setCamera( earthPosition, np.array([0,0,0]) , np.array([0,1,0]))
    Cam.applyCamera()
    drawScene()
    glPopMatrix()

    glFlush()


def main():
    # windowing
    glutInit(sys.argv)
    glutInitDisplayMode(GLUT_SINGLE|GLUT_RGB)
    glutInitWindowSize(WINDOW_WIDTH,WINDOW_HEIGHT)
    glutInitWindowPosition(WINDOW_POSITION_X,WINDOW_POSITION_Y)
    glutCreateWindow(b"Simple Solar_201624489_ParkChangHae")

    glClearColor(0, 0.0, 0.0, 0)

    # register callbacks
    glutDisplayFunc(disp)
    glutIdleFunc(disp)

    # enter main infinite-loop
    glutMainLoop()

if __name__=="__main__":
    main()

* 运算符没有按照您的预期执行,它是数组乘法,而不是矩阵乘法。它将执行元素的分量乘法。参见 how does multiplication differ for NumPy Matrix vs Array classes? and Numerical operations on arrays

使用numpy.dot or numpy.matmul通过矩阵变换向量。
一个4分量向量的变换结果(Homogeneous coordinates) by 4*4 matrix, is still a 4 component vector. In general you would have to do a perspective divide after the transformation. But the model view matrix is an Orthogonal matrix,所以使用结果的前3个分量就足够了,因为第4个分量总是1:

pos = np.array( [0,0,0,1] )
pos = np.dot( pos, t )
earthPosition = pos[0:3]

但是注意,坐标(0,0,0,1)的视图space位置是模型视图矩阵的平移部分(第4行):

earthPosition = t[3][0:3]

遗憾的是这不是你想要做的,因为你想知道地球的世界位置,而不是视图位置。 由于 glGetFloatv(GL_MODELVIEW_MATRIX) returns 模型视图矩阵,转换计算视图位置,而不是世界位置。 您必须通过模型矩阵进行转换,而不是模型视图矩阵。由于您无法将模型矩阵与模型视图矩阵分开,因此这并不容易。 你能得到的是视图矩阵。使用视图矩阵和模型视图矩阵,您可以获得世界位置。 模型矩阵的变换与模型视图矩阵和逆视图矩阵的变换相同:

p_world = inverse(view_matrix) * model_view_matrix * p_model

我建议在 lookAt 设置 Cam class 后立即获取视图矩阵并计算逆模型视图矩阵。逆矩阵可以通过numpy.linalg.inv:

来计算
def applyCamera(self):
    gluLookAt(self.loc[0], self.loc[1], self.loc[2],
              self.tar[0], self.tar[1], self.tar[2],
              self.up [0], self.up [1], self.up [2])
    self.viewmat = glGetFloatv(GL_MODELVIEW_MATRIX)
    self.inv_viewmat = np.linalg.inv(self.viewmat)

最后世界位置是模型视图矩阵第4行通过逆视图矩阵的简单变换:

global plutoCamera, earthPosition, Cam

.....

model_view = glGetFloatv(GL_MODELVIEW_MATRIX)

if(shape == "satellite"):
    glScalef(0.4,0.4,0.4)
    glutSolidTetrahedron()
    glScalef(2.5,2.5,2.5)

elif(shape == "earth"):
    glutWireCube(1)

    pos = np.dot( model_view[3], Cam.inv_viewmat )
    earthPosition = pos[0:3]

elif(shape == "pluto"):
    glScalef(0.4,0.4,0.4)
    glutWireOctahedron()
    glScalef(2.5,2.5,2.5)

    pos = np.dot( model_view[3], Cam.inv_viewmat )
    plutoCamera = pos[0:3]

预览: