如何解决在 pygame 中为 3D 渲染器添加简单的透视图
我在 pyGame 中创建了一个 3D 渲染器,但是我现在想添加透视图。我已经尝试了一段时间,但似乎无法弄清楚。
我读过最简单的透视形式是将 x 和 y 坐标乘以 z 坐标的倒数,以便 x 和 y 取决于 z 值。这意味着 x 和 y 距离应该随着 z 坐标的增加而减小,而 x 和 y 应该随着 z 的减小而增加。我设法让它稍微起作用,但是它似乎会积累,所以当我左右旋转盒子时,盒子的背面变得非常小,并且似乎积累了负比例,而不是在设置时保持恒定大小z 距离。
这是我的代码:
线框.py:
class Wireframe:
def __init__(self):
self.nodes = np.zeros((0,4))
self.edges = []
def addNodes(self,node_array):
ones_column = np.ones((len(node_array),1))
ones_added = np.hstack((node_array,ones_column))
self.nodes = np.vstack((self.nodes,ones_added))
def addEdges(self,edgeList):
self.edges += edgeList
def outputNodes(self):
print("\n --- Nodes ---")
for i,(x,y,z,_) in enumerate(self.nodes):
print(" %d: (%.2f,%.2f,%.2f)" % (i,node.x,node.y,node.z))
def outputEdges(self):
print("\n --- Edges ---")
for i,(node1,node2) in enumerate(self.edges):
print(" %d: %d -> %d" % (i,node1,node2))
def translate(self,axis,d):
if axis in ['x','y','z']:
for node in self.nodes:
setattr(node,getattr(node,axis) + d)
def scale(self,centre_x,centre_y,scale):
for node in self.nodes:
node.x = centre_x + scale * (node.x - centre_x)
node.y = centre_y + scale * (node.y - centre_y)
node.z *= scale
def findCentre(self):
num_nodes = len(self.nodes)
meanX = sum([node.x for node in self.nodes]) / num_nodes
meanY = sum([node.y for node in self.nodes]) / num_nodes
meanZ = sum([node.z for node in self.nodes]) / num_nodes
return (meanX,meanY,meanZ)
def rotateZ(self,centre,radians):
cx,cy,cz = centre
for node in self.nodes:
x = node.x - cx
y = node.y - cy
d = math.hypot(y,x)
theta = math.atan2(y,x) + radians
node.x = cx + d * math.cos(theta)
node.y = cy + d * math.sin(theta)
def rotateX(self,cz = centre
for node in self.nodes:
y = node.y - cy
z = node.z - cz
d = math.hypot(y,z)
theta = math.atan2(y,z) + radians
node.z = cz + d * math.cos(theta)
node.y = cy + d * math.sin(theta)
def rotateY(self,cz = centre
for node in self.nodes:
x = node.x - cx
z = node.z - cz
d = math.hypot(x,z)
theta = math.atan2(x,z) + radians
node.z = cz + d * math.cos(theta)
node.x = cx + d * math.sin(theta)
def transform(self,matrix):
self.nodes = np.dot(self.nodes,matrix)
def transform_for_perspective(self):
for node in self.nodes:
print(node[0],node[1],node[2])
if node[2] != 0:
node[0] = node[0]*(1/(1-(node[2]*0.00005)))
node[1] = node[1]*(1/(1-(node[2]*0.00005)))
node[2] = node[2]*1
def translationMatrix(self,dx=0,dy=0,dz=0):
return np.array([[1,0],[0,1,[dx,dy,dz,1]])
def scaleMatrix(self,sx=0,sy=0,sz=0):
return np.array([[sx,sy,sz,1]])
def rotateXMatrix(self,radians):
c = np.cos(radians)
s = np.sin(radians)
return np.array([[1,c,-s,s,1]])
def rotateYMatrix(self,radians):
c = np.cos(radians)
s = np.sin(radians)
return np.array([[c,[-s,1]])
def rotateZMatrix(self,[s,1]])
def movCamera(self,tilt,pan):
return np.array([[1,200],[pan,0]])
projectionViewer.py
from wireframe import *
import pygame
import numpy as np
class ProjectionViewer:
''' displays 3D Objects on a Pygame Screen '''
def __init__(self,width,height):
self.width = width
self.height = height
self.screen = pygame.display.set_mode((width,height))
pygame.display.set_caption('Wireframe display')
self.background = (10,10,50)
self.wireframes = {}
self.displayNodes = True
self.displayEdges = True
self.nodeColour = (255,255,255)
self.edgeColour = (200,200,200)
self.nodeRadius = 4
def run(self):
key_to_function = {
pygame.K_LEFT: (lambda x: x.translateall([-10,0])),pygame.K_RIGHT:(lambda x: x.translateall([ 10,pygame.K_DOWN: (lambda x: x.translateall([0,pygame.K_UP: (lambda x: x.translateall([0,-10,pygame.K_a: (lambda x: x.rotate_about_Center('Y',-0.08)),pygame.K_d: (lambda x: x.rotate_about_Center('Y',0.08)),pygame.K_w: (lambda x: x.rotate_about_Center('X',pygame.K_s: (lambda x: x.rotate_about_Center('X',pygame.K_EQUALS: (lambda x: x.scale_centre([1.25,1.25,1.25])),pygame.K_MINUS: (lambda x: x.scale_centre([0.8,0.8,0.8])),pygame.K_q: (lambda x: x.rotateall('X',0.1)),pygame.K_z: (lambda x: x.rotateall('Z',pygame.K_x: (lambda x: x.rotateall('Z',-0.1)),pygame.K_p: (lambda x: x.perspectiveMode()),pygame.K_t: (lambda x: x.translate_Camera())
}
running = True
flag = False
while running:
keys = pygame.key.get_pressed()
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if keys[pygame.K_LEFT]:
key_to_function[pygame.K_LEFT](self)
if keys[pygame.K_RIGHT]:
key_to_function[pygame.K_RIGHT](self)
if keys[pygame.K_DOWN]:
key_to_function[pygame.K_DOWN](self)
if keys[pygame.K_UP]:
key_to_function[pygame.K_UP](self)
if keys[pygame.K_EQUALS]:
key_to_function[pygame.K_EQUALS](self)
if keys[pygame.K_MINUS]:
key_to_function[pygame.K_MINUS](self)
if keys[pygame.K_LEFT]:
key_to_function[pygame.K_LEFT](self)
if keys[pygame.K_q]:
key_to_function[pygame.K_q](self)
if keys[pygame.K_w]:
key_to_function[pygame.K_w](self)
if keys[pygame.K_a]:
key_to_function[pygame.K_a](self)
if keys[pygame.K_s]:
key_to_function[pygame.K_s](self)
if keys[pygame.K_z]:
key_to_function[pygame.K_z](self)
if keys[pygame.K_x]:
key_to_function[pygame.K_x](self)
if keys[pygame.K_p]:
key_to_function[pygame.K_p](self)
if keys[pygame.K_t]:
key_to_function[pygame.K_t](self)
if keys[pygame.K_d]:
key_to_function[pygame.K_d](self)
self.display()
pygame.display.flip()
def addWireframe(self,name,wireframe):
self.wireframes[name] = wireframe
#translate to center
wf = Wireframe()
matrix = wf.translationMatrix(-self.width/2,-self.height/2,0)
for wireframe in self.wireframes.values():
wireframe.transform(matrix)
wf = Wireframe()
matrix = wf.translationMatrix(self.width,self.height,0)
for wireframe in self.wireframes.values():
wireframe.transform(matrix)
def display(self):
self.screen.fill(self.background)
for wireframe in self.wireframes.values():
if self.displayEdges:
for n1,n2 in wireframe.edges:
pygame.draw.aaline(self.screen,self.edgeColour,wireframe.nodes[n1][:2],wireframe.nodes[n2][:2],1)
wireframe.transform_for_perspective()
if self.displayNodes:
for node in wireframe.nodes:
pygame.draw.circle(self.screen,self.nodeColour,(int(node[0]),int(node[1])),self.nodeRadius,0)
def translateall(self,vector):
''' Translate all wireframes along a given axis by d units '''
wf = Wireframe()
matrix = wf.translationMatrix(*vector)
for wireframe in self.wireframes.values():
wireframe.transform(matrix)
def scaleAll(self,vector):
wf = Wireframe()
matrix = wf.scaleMatrix(*vector)
for wireframe in self.wireframes.values():
wireframe.transform(matrix)
def rotateall(self,theta):
wf = Wireframe()
if axis == 'X':
matrix = wf.rotateXMatrix(theta)
elif axis == 'Y':
matrix = wf.rotateYMatrix(theta)
elif axis == 'Z':
matrix = wf.rotateZMatrix(theta)
for wireframe in self.wireframes.values():
wireframe.transform(matrix)
#wireframe.transform_for_perspective()
def moveCameraX(self,x,y):
wf = Wireframe()
matrix = wf.movCamera(x,y)
print("test")
for wireframe in self.wireframes.values():
wireframe.transform(matrix)
def moveCameraZ(self,y):
wf = Wireframe()
matrix = wf.testMat((0,val))
for wireframe in self.wireframes.values():
wireframe.transform(matrix)
def perspectiveMode(self):
#First translate the centre of screen to 0,0
wf = Wireframe()
matrix = wf.translationMatrix(-self.width/2,0)
for wireframe in self.wireframes.values():
wireframe.transform(matrix)
#perform the perspectivecorrection
wf = Wireframe()
matrix = wf.translationMatrix(-self.width/2,0)
for wireframe in self.wireframes.values():
matrix = wf.perspectiveCorrection(1.2)
wireframe.transform(matrix)
wf = Wireframe()
matrix = wf.translationMatrix(self.width/2,self.height/2,0)
for wireframe in self.wireframes.values():
wireframe.transform(matrix)
def rotate_about_Center(self,Axis,theta):
#First translate Centre of screen to 0,0)
for wireframe in self.wireframes.values():
wireframe.transform(matrix)
#Do Rotation
wf = Wireframe()
if Axis == 'X':
matrix = wf.rotateXMatrix(theta)
elif Axis == 'Y':
matrix = wf.rotateYMatrix(theta)
elif Axis == 'Z':
matrix = wf.rotateZMatrix(theta)
for wireframe in self.wireframes.values():
wireframe.transform(matrix)
#Translate back to centre of screen
wf = Wireframe()
matrix = wf.translationMatrix(self.width/2,0)
for wireframe in self.wireframes.values():
wireframe.transform(matrix)
#Do perspective if needed
def scale_centre(self,vector):
#Transform center of screen to origin
wf = Wireframe()
matrix = wf.translationMatrix(-self.width/2,0)
for wireframe in self.wireframes.values():
wireframe.transform(matrix)
#Scale the origin by vector
wf = Wireframe()
matrix = wf.scaleMatrix(*vector)
for wireframe in self.wireframes.values():
wireframe.transform(matrix)
wf = Wireframe()
matrix = wf.translationMatrix(self.width/2,0)
for wireframe in self.wireframes.values():
wireframe.transform(matrix)
def add_perspective(self):
for wireframe in self.wireframes.values():
for node in wireframe.nodes:
if node[2] != 0:
print("Point ----------")
print("x node",node[0])
print("y node",node[1])
print("z node",node[2])
node[0] = node[0] + (10/node[2])
node[1] = node[1] + (10/node[2])
main.py
from projectionViewer import ProjectionViewer
import wireframe
import numpy as np
cube = wireframe.Wireframe()
cube_nodes = [(x,z) for x in (-100,100) for y in (-100,100) for z in (-100,100)]
print(cube_nodes)
cube.addNodes(np.array(cube_nodes))
cube.addEdges([(n,n + 4) for n in range(0,4)])
cube.addEdges([(n,n + 1) for n in range(0,8,2)])
cube.addEdges([(n,n + 2) for n in (0,4,5)])
pv = ProjectionViewer(1200,1000)
pv.addWireframe('cube',cube)
pv.run()
进行乘法运算的代码在线框文件和 transform_for_perspective() 函数中。
def transform_for_perspective(self):
for node in self.nodes:
print(node[0],node[2])
if node[2] != 0:
node[0] = node[0]*(1/(1-(node[2]*0.00005)))
node[1] = node[1]*(1/(1-(node[2]*0.00005)))
node[2] = node[2]*1
如果有人能告诉我我哪里出错了,并解释我需要按什么顺序调用透视矩阵,即旋转然后透视或透视然后旋转。
另外,因为 Pygame 从左上角的 (0,0) 开始,这意味着如果我想绕屏幕中心旋转,我必须平移屏幕中心,执行旋转矩阵,然后平移它回到中心。这对透视意味着什么?我是否必须将屏幕中心平移到左上角,然后执行透视矩阵,然后再将其平移回来?
任何帮助将不胜感激。
解决方法
您在 transform_for_perspective
中应用的转换只能应用一次。但是,您似乎在每一帧上都调用它,并且因为它将输出存储在同一个变量 (self.nodes
) 中,所以它被多次应用。
考虑将该转换的输出保存在新字段(例如 self.perspective_nodes
)中。
此外,转换对我不起作用,我尝试做一些变化并想出了这个:
class Wireframe:
def __init__(self):
self.nodes = np.zeros((0,4))
self.perspective_nodes = None
self.edges = []
....
def transform_for_perspective(self,center):
self.perspective_nodes = self.nodes.copy()
for i in range(len(self.nodes)):
node = self.nodes[i]
p_node = self.perspective_nodes[i]
print(node[0],node[1],node[2])
if node[2] != 0:
p_node[0] = center[0] + (node[0]-center[0])*250/(200-(node[2]))
p_node[1] = center[1] + (node[1]-center[1])*250/(200-(node[2]))
p_node[2] = node[2] * 1
您还需要在projectionViewer中修改显示:
def display(self):
self.screen.fill(self.background)
for wireframe in self.wireframes.values():
wireframe.transform_for_perspective((self.width/2,self.height/2))
if self.displayNodes:
for node in wireframe.perspective_nodes:
pygame.draw.circle(self.screen,self.nodeColour,(int(
node[0]),int(node[1])),self.nodeRadius,0)
if self.displayEdges:
for n1,n2 in wireframe.edges:
pygame.draw.aaline(
self.screen,self.edgeColour,wireframe.perspective_nodes[n1][:2],wireframe.perspective_nodes[n2][:2],1)
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。