【问题标题】:Please help me in perspective projection [duplicate]请帮助我进行透视投影[重复]
【发布时间】:2021-10-22 02:19:15
【问题描述】:

我想将 3d 点投影到 2d 平面(屏幕)上。

这是我的代码以及将 3d 坐标转换为 2d 坐标的类。

将3d坐标转换为2d坐标的类:

class dtod(pygame.sprite.Sprite):
def __init__(self, point, scale):
    super().__init__()
    self.point = numpy.array(point)
    width, height = pygame.display.get_surface().get_size()
    self.angle = 0
    self.point[0] = (self.point[0] * 1) / self.point[2]
    self.point[1] = (self.point[1] * 1) / self.point[2]
    self.projection = numpy.array(
        [[math.cos(self.angle), math.sin(self.angle), 0.], [math.sin(self.angle), math.cos(self.angle), 0.]])
    self.point = numpy.dot(self.projection, self.point)
    self.point = ((self.point[0] * scale) + width / 2, height / 2 - (self.point[1] * scale))

我画东西的主要代码:

import pygame
import numpy as np
from coord import dtod
import math

pygame.init()
screen = pygame.display.set_mode((0, 0))
run = True
width, height = screen.get_size()
colors = {"white": (255, 255, 255), "red": (255, 0, 0), "green": (150, 253, 55), "blue": (0, 
227, 227), "orange": (255, 127, 39), "grey": (64, 64, 64), "yellow": (255, 240, 0)}
tx, ty, tz = 0., 0., 0.
fps = pygame.time.Clock()

def cos(x):
    return math.cos(x)


def sin(x):
    return math.sin(x)


rx = np.array([[1., 0., 0.], [0., cos(tx), -sin(tx)], [0., sin(tx), cos(tx)]])
ry = np.array([[cos(ty), 0., sin(ty)], [0., 1., 0.], [-sin(ty), 0., cos(ty)]])
rz = np.array([[cos(tz), -sin(ty), 0.], [sin(ty), cos(ty), 0.], [0., 0., 1.]])
scale = 200
p1 = np.array([0.5, 0.5, 1.])
p2 = np.array([-0.5, 0.5, 1.])
p3 = np.array([-0.5, -0.5, 1.])
p4 = np.array([0.5, -0.5, 1.])
p5 = np.array([0.5, 0.5, -2.])
p6 = np.array([-0.5, 0.5, -2.])
p7 = np.array([-0.5, -0.5, -2.])
p8 = np.array([0.5, -0.5, -2.])

def transform(tx, ty, tz):
    global rx, ry, rz, p1, p2, p3, p4, p5, p6, p7, p8
    rx = np.array([[1., 0., 0.], [0., cos(tx), -sin(tx)], [0., sin(tx), cos(tx)]])
    ry = np.array([[cos(ty), 0., sin(ty)], [0., 1., 0.], [-sin(ty), 0., cos(ty)]])
    rz = np.array([[cos(tz), -sin(ty), 0.], [sin(ty), cos(ty), 0.], [0., 0., 1.]])
    p1 = np.dot(rx, p1)
    p1 = np.dot(ry, p1)
    p1 = np.dot(rz, p1)
    p2 = np.dot(rx, p2)
    p2 = np.dot(ry, p2)
    p2 = np.dot(rz, p2)
    p3 = np.dot(rx, p3)
    p3 = np.dot(ry, p3)
    p3 = np.dot(rz, p3)
    p4 = np.dot(rx, p4)
    p4 = np.dot(ry, p4)
    p4 = np.dot(rz, p4)
    p5 = np.dot(rx, p5)
    p5 = np.dot(ry, p5)
    p5 = np.dot(rz, p5)
    p6 = np.dot(rx, p6)
    p6 = np.dot(ry, p6)
    p6 = np.dot(rz, p6)
    p7 = np.dot(rx, p7)
    p7 = np.dot(ry, p7)
    p7 = np.dot(rz, p7)
    p8 = np.dot(rx, p8)
    p8 = np.dot(ry, p8)
    p8 = np.dot(rz, p8)
while run:
    screen.fill((0, 0, 0))
    fps.tick(60)
    transform(tx, ty, tz)
    pygame.draw.circle(screen, colors["white"], dtod(p1, scale).point, 5)
    pygame.draw.circle(screen, colors["white"], dtod(p2, scale).point, 5)
    pygame.draw.circle(screen, colors["white"], dtod(p3, scale).point, 5)
    pygame.draw.circle(screen, colors["white"], dtod(p4, scale).point, 5)
    pygame.draw.circle(screen, colors["white"], dtod(p5, scale).point, 5)
    pygame.draw.circle(screen, colors["white"], dtod(p6, scale).point, 5)
    pygame.draw.circle(screen, colors["white"], dtod(p7, scale).point, 5)
    pygame.draw.circle(screen, colors["white"], dtod(p8, scale).point, 5)
    pygame.draw.line(screen, colors["white"], dtod(p1, scale).point, dtod(p2, scale).point)
    pygame.draw.line(screen, colors["white"], dtod(p2, scale).point, dtod(p3, scale).point)
    pygame.draw.line(screen, colors["white"], dtod(p3, scale).point, dtod(p4, scale).point)
    pygame.draw.line(screen, colors["white"], dtod(p4, scale).point, dtod(p1, scale).point)
    pygame.draw.line(screen, colors["white"], dtod(p5, scale).point, dtod(p6, scale).point)
    pygame.draw.line(screen, colors["white"], dtod(p6, scale).point, dtod(p7, scale).point)
    pygame.draw.line(screen, colors["white"], dtod(p7, scale).point, dtod(p8, scale).point)
    pygame.draw.line(screen, colors["white"], dtod(p8, scale).point, dtod(p5, scale).point)
    pygame.draw.line(screen, colors["white"], dtod(p1, scale).point, dtod(p5, scale).point)
    pygame.draw.line(screen, colors["white"], dtod(p6, scale).point, dtod(p2, scale).point)
    pygame.draw.line(screen, colors["white"], dtod(p7, scale).point, dtod(p3, scale).point)
    pygame.draw.line(screen, colors["white"], dtod(p8, scale).point, dtod(p4, scale).point)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_r:
                run = False

    pygame.display.update()
    pygame.quit()

我使用的逻辑:

我正在使用正交投影技术,为了获得感知的感觉,我在实际转换 3d->2d 之前将 x 和 y 坐标除以深度(z 分量)。

提前谢谢你

【问题讨论】:

  • 您是如何生成这些图像的?以这种方式旋转立方体我该怎么做?
  • @Rabbid76,左边是我尝试将正交转换为透视时的实验结果。正确的是刚刚从网上下载的,所以我可以在问题中说出我想要实现的目标。
  • @Rabbid76 我想研究 3d 投影,如何在 2d 中渲染 3d 图形。有没有像可汗学院或 3b1b YouTube 频道这样的好资源。我是那个领域的绝对初学者。但是对线性代数和二维图有很好的理解。此外,我通过编程创建了很多动画,所以请推荐一个高级教学的好地方。

标签: pygame 3d projection


【解决方案1】:

我终于做到了,天哪,我太高兴了。

使用下面的代码,效果很好。

转换 3d 坐标的类 -> 2d 坐标:

class dtod(pygame.sprite.Sprite):
def __init__(self, point, scale):
    super().__init__()
    self.point = numpy.array(point)
    width, height = pygame.display.get_surface().get_size()
    self.angle = 2 * math.pi / 3
    self.length = 5
    self.projection = numpy.array(
        [[(self.length / math.tan(self.angle)) / (self.point[2] - (self.length / math.tan(self.angle))), 0., 0.],
         [0., (self.length / math.tan(self.angle)) / (self.point[2] - (self.length / math.tan(self.angle))), 0.]])
    self.point = numpy.dot(self.projection, self.point)
    self.point = ((self.point[0] * scale) + width / 2, height / 2 - (self.point[1] * scale))

我绘制立方体的主要代码:

import pygame
import numpy as np
from coord import dtod
import math

pygame.init()
screen = pygame.display.set_mode((0, 0))
run = True
width, height = screen.get_size()
colors = {"white": (255, 255, 255), "red": (255, 0, 0), "green": (150,253,55),"blue": (0, 227, 227),
      "orange": (255, 127, 39),
      "grey": (64, 64, 64), "yellow": (255, 240, 0)}
tx, ty, tz = 0., 0., 0.
fps = pygame.time.Clock()
x, y, z = 0, 0, 0


def cos(x):
    return math.cos(x)


def sin(x):
    return math.sin(x)


rotation_matrices = [np.array([[1., 0., 0.], [0., cos(tx), -sin(tx)], [0., 
sin(tx), cos(tx)]]), np.array([[cos(ty), 0., sin(ty)], [0., 1., 0.], [-sin(ty), 
0., cos(ty)]]), np.array([[cos(tz), -sin(ty), 0.], [sin(ty), cos(ty), 0.], [0., 
0., 1.]])]
scale = 200
points = [np.array([0.5, 0.5, 0.5]), np.array([-0.5, 0.5, 0.5]), np.array([-0.5, 
-0.5, 0.5]),
          np.array([0.5, -0.5, 0.5]), np.array([0.5, 0.5, -0.5]), 
np.array([-0.5, 0.5, -0.5]),
          np.array([-0.5, -0.5, -0.5]), np.array([0.5, -0.5, -0.5])]


def transform(tx, ty, tz):
    global points, rotation_matrices
    rotation_matrices = [np.array([[1., 0., 0.], [0., cos(tx), -sin(tx)], [0., 
sin(tx), cos(tx)]]),
                         np.array([[cos(ty), 0., sin(ty)], [0., 1., 0.], [- 
sin(ty), 0., cos(ty)]]),
                         np.array([[cos(tz), -sin(tz), 0.], [sin(tz), cos(tz), 
0.], [0., 0., 1.]])]
    for i in range(8):
        for j in range(3):
            points[i] = np.dot(rotation_matrices[j], points[i])


while run:
    screen.fill((0, 0, 0))
    fps.tick(60)
    transform(tx, ty, tz)
    for i in range(8):
        pygame.draw.circle(screen, colors["white"], dtod(points[i], 
    scale).point, 5)
    pygame.draw.line(screen, colors["white"], dtod(points[0], scale).point, 
    dtod(points[1], scale).point)
    pygame.draw.line(screen, colors["white"], dtod(points[1], scale).point, 
    dtod(points[2], scale).point)
    pygame.draw.line(screen, colors["white"], dtod(points[2], scale).point, 
    dtod(points[3], scale).point)
    pygame.draw.line(screen, colors["white"], dtod(points[3], scale).point, 
    dtod(points[0], scale).point)
    pygame.draw.line(screen, colors["white"], dtod(points[4], scale).point, 
    dtod(points[5], scale).point)
    pygame.draw.line(screen, colors["white"], dtod(points[5], scale).point, 
    dtod(points[6], scale).point)
    pygame.draw.line(screen, colors["white"], dtod(points[6], scale).point, 
    dtod(points[7], scale).point)
    pygame.draw.line(screen, colors["white"], dtod(points[7], scale).point, 
    dtod(points[4], scale).point)
    pygame.draw.line(screen, colors["white"], dtod(points[0], scale).point, 
    dtod(points[4], scale).point)
    pygame.draw.line(screen, colors["white"], dtod(points[5], scale).point, 
    dtod(points[1], scale).point)
    pygame.draw.line(screen, colors["white"], dtod(points[6], scale).point, 
    dtod(points[2], scale).point)
    pygame.draw.line(screen, colors["white"], dtod(points[7], scale).point, 
    dtod(points[3], scale).point)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_r:
                run = 0
            if event.key == pygame.K_x:
                x = 1
            if event.key == pygame.K_y:
                y = 1
            if event.key == pygame.K_z:
                z = 1
            if event.key == pygame.K_UP:
                if x == 1:
                    tx += math.pi / 180
                if y == 1:
                    ty += math.pi / 180
                if z == 1:
                    tz += math.pi / 180
            if event.key == pygame.K_DOWN:
                if x == 1:
                    tx -= math.pi / 180
                if y == 1:
                    ty -= math.pi / 180
                if z == 1:
                    tz -= math.pi / 180
        if event.type == pygame.KEYUP:
            if event.key == pygame.K_x:
                x = 0
                tx = 0
            if event.key == pygame.K_y:
                y = 0
                ty = 0
            if event.key == pygame.K_z:
                z = 0
                tz = 0
    pygame.display.update()
    pygame.quit()

问题是我使用了视差方法,但使用了错误的方法。我没有考虑相机的位置。请注意,现在我从 self.point[0] 中减去和乘以一些值,即 x 分量和 y 分量相同。该值是与相机的距离,可以很容易地用相似三角形证明。

【讨论】: