在pygame中向3D渲染器添加简单透视图

2024-10-02 20:40:56 发布

您现在位置:Python中文网/ 问答频道 /正文

我已经在pyGame中创建了一个3D渲染器,但是现在我想添加透视图。我已经试了一段时间了,似乎想不出来

我已经读过,透视图最简单的形式是将x和y坐标乘以z坐标的倒数,这样x和y取决于z值。这意味着x和y距离应随z坐标的增加而减小,x和y应随z坐标的减小而增大。我已经设法让它稍微起作用,但是它似乎在累积,所以当我左右旋转盒子时,盒子的背面变得非常小,似乎在累积负刻度,而不是在设定的z距离保持恒定大小

这是我的密码:

wireframe.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, axis, 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, centre, radians):
        cx, cy, 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, centre, radians):
        cx, cy, 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,0],
                         [0,1,0,0],
                         [0,0,1,0],
                         [dx,dy,dz,1]])

    def scaleMatrix(self, sx=0, sy=0, sz=0):

        return np.array([[sx, 0, 0, 0], 
                         [0, sy, 0, 0],
                         [0, 0, sz, 0],
                         [0, 0, 0, 1]])

    def rotateXMatrix(self, radians):

        c = np.cos(radians)
        s = np.sin(radians)

        return np.array([[1,0,0,0],
                         [0,c,-s,0],
                         [0,s,c,0],
                         [0,0,0,1]])

    def rotateYMatrix(self, radians):

        c = np.cos(radians)
        s = np.sin(radians)

        return np.array([[c,0,s,0],
                         [0,1,0,0],
                         [-s,0,c,0],
                         [0,0,0,1]])

    def rotateZMatrix(self, radians):

        c = np.cos(radians)
        s = np.sin(radians)

        return np.array([[c,-s, 0, 0],
                         [s,c,0,0],
                         [0,0,1,0],
                         [0,0,0,1]])

    def movCamera(self, tilt, pan):

        return np.array([[1,0,0,200],
                         [0,1,0,0],
                         [pan,tilt,1,0],
                         [0,0,0,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, 0])),
        pygame.K_RIGHT:(lambda x: x.translateAll([ 10, 0, 0])),
        pygame.K_DOWN: (lambda x: x.translateAll([0,  10, 0])),
        pygame.K_UP:   (lambda x: x.translateAll([0, -10, 0])),

        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', -0.08)),
        pygame.K_s: (lambda x: x.rotate_about_Center('X', 0.08)),


        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', 0.1)),
        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, axis, 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,x,y):

        wf = Wireframe()

        matrix = wf.testMat((0,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,-self.height/2,0)

        for wireframe in self.wireframes.values():
            wireframe.transform(matrix)

        #perform the perspectivecorrection

        wf = Wireframe()
        matrix = wf.translationMatrix(-self.width/2,-self.height/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

        wf = Wireframe()
        matrix = wf.translationMatrix(-self.width/2,-self.height/2,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,self.height/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,-self.height/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,self.height/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, y, 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, 1, 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[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

如果有人能告诉我哪里出了问题,并解释我需要按什么顺序调用透视矩阵,即旋转,然后透视,或者透视,然后旋转

此外,因为Pygame从左上角的(0,0)开始,这意味着如果我想围绕屏幕中心旋转,我必须平移屏幕中心,执行旋转矩阵,然后将其平移回中心。这对透视图意味着什么?我是否必须将屏幕中心平移到左上角,然后执行透视矩阵,然后再将其平移回来

任何帮助都将不胜感激


Tags: toinselfnodeforifdefnp
1条回答
网友
1楼 · 发布于 2024-10-02 20:40:56

transform_for_perspective中应用的转换只能应用一次。但是,您似乎在每一帧上都调用它,并且当它将输出存储在同一个变量(self.nodes)中时,会多次应用它。 考虑将转换的输出保存在新字段中(如{{CD3}})。

此外,转换对我不起作用,我尝试了一些变化,并得出以下结论:

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)

相关问题 更多 >