如何改进 pygame 中的 3d space 模拟?

How to improve my simulation of 3d space in pygame?

至于我之前的问题,我正在尝试在 pygame 中模拟 3d space。到目前为止,我想出了一个非常简单的想法,即使用第三个坐标作为 'compress' 的分母(很确定这里有一些我不知道的术语)屏幕中心周围最远的点并减少他们的尺寸。

有人可以对这个想法提出简单的改进吗?我觉得我可以调整用于投影的分母(参见代码)以创建更准确的模拟方式。

如果你 运行 下面的代码,你将有一个不错的模拟(假设)一艘 space 飞船经过一些星星(按 w 或 s)。如果它们走得太远,它们就会消失,然后会创建一个新的。但是,如果我应用旋转(a 或 d),很明显模拟效果不佳,因为我并没有真正将 3d 点投影到 2d 屏幕上。

import pygame
import random
import numpy as np

pygame.init()
run=True

#screensize
screensize = (width,height)=(600,600)
center=(int(width/2),int(height/2))
screen = pygame.display.set_mode(screensize)

#delta mov
ds=0.1
do=0.0001

#Stars
points=[]
for i in range(1000):
    n1 = random.randrange(-5000,5000)
    n2 = random.randrange(-5000,5000)
    n3 = random.randrange(-30,30)
    points.append([n1,n2,n3])

while run:
    pygame.time.delay(20)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run=False

    ################## keys
    keys=pygame.key.get_pressed()

    if keys[pygame.K_w]:
        for p in points:
            p[2]-=ds
    if keys[pygame.K_s]:
        for p in points:
            p[2]+=ds

    if keys[pygame.K_a] or keys[pygame.K_d]:
        if keys[pygame.K_a]:
            for p in points:
                p[0]=np.cos(-do)*p[0]-np.sin(-do)*p[2]
                p[2]=np.sin(-do)*p[0]+np.cos(-do)*p[2]
        else:
            for p in points:
                p[0]=np.cos(do)*p[0]-np.sin(do)*p[2]
                p[2]=np.sin(do)*p[0]+np.cos(do)*p[2]


    ###############################projection###################

    for p in points:
        #this is to create new stars
        if p[2]<=-30 or p[2]>=30:
            p[0] = random.randrange(-5000,5000)
            p[1] = random.randrange(-5000,5000)
            p[2] =30
        else:
            #this is to ignore stars which are behind the ship
            if p[2]<=0:
                pass
            else:
                try:
                    #THIS IS THE PROJECTION I USE, I TAKE THE RADIUS BECAUSE I GUESS I'LL NEED IT... BUT I DON'T USE IT XD
                    r = ((p[0]**2+p[1]**2+p[2]**2)**(1/2))
                    pygame.draw.circle(screen,(255,255,0),(int(p[0]/p[2]+center[0]),int(p[1]/p[2]+center[1])),int(10/p[2]))
                #this is to prevent division by cero and alike
                except Exception as e:
                    pass

    pygame.display.update()
    screen.fill((0,0,0))


pygame.quit()

一般的透视是通过Homogeneous coordinates实现的。你的方法很接近。

我建议操作Cartesian coordinates,其中3个维度具有相同的比例。
绘制点时模拟 Perspective projection
这意味着您必须根据点(例如 w = p[2] * 30 / 5000)的深度(z 坐标)计算 Homogeneous coordinatesw 分量,并执行 "perspective divide" xyz 组件由 w 组件创建,然后再绘制点。例如:

#delta mov
ds=10
do=0.01

#Stars
points=[]
for i in range(1000):
    n1 = random.randrange(-5000,5000)
    n2 = random.randrange(-5000,5000)
    n3 = random.randrange(-5000,5000)
    points.append([n1,n2,n3])

while run:
    pygame.time.delay(20)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run=False

    ################## keys
    keys=pygame.key.get_pressed()

    if keys[pygame.K_w]:
        for p in points:
            p[2]-=ds
    if keys[pygame.K_s]:
        for p in points:
            p[2]+=ds

    if keys[pygame.K_a] or keys[pygame.K_d]:
        if keys[pygame.K_a]:
            for p in points:
                p[0], p[2] = np.cos(-do)*p[0]-np.sin(-do)*p[2], np.sin(-do)*p[0]+np.cos(-do)*p[2]
        else:
            for p in points:
                p[0], p[2] = np.cos(do)*p[0]-np.sin(do)*p[2], np.sin(do)*p[0]+np.cos(do)*p[2]

    ###############################projection###################

    screen.fill((0,0,0))
    for p in points:
        #this is to create new stars
        if p[2]<=-5000 or p[2]>=5000:
            p[0], p[1], p[2] = random.randrange(-5000,5000), random.randrange(-5000,5000), 5000
        else:
            #this is to ignore stars which are behind the ship
            if p[2]<=0:
                pass
            else:
                w = p[2] * 30 / 5000
                pygame.draw.circle(screen,(255,255,0),(int(p[0]/w+center[0]),int(p[1]/w+center[1])),int(10/w))

    pygame.display.update()

此外,旋转不正确。当你这样做时

p[0]=np.cos(-do)*p[0]-np.sin(-do)*p[2]
p[2]=np.sin(-do)*p[0]+np.cos(-do)*p[2]

第一行改变了p[0],但第二行应该使用原来的值
执行 "tuple" 作业以解决问题:

p[0], p[2] = np.cos(-do)*p[0]-np.sin(-do)*p[2], np.sin(-do)*p[0]+np.cos(-do)*p[2]