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]
预览:
我想用 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]
预览: