1

我目前正在研究 3d 渲染,我正在尝试在不添加任何库的情况下完成它(pygame、math、sys 除外)。我做了很多研究,但仍然不能完全理解数学(我在 wikipedia 上使用方法)。它确实输出了正确的坐标,但有时会严重变形。这是结果的样子。我不太了解矩阵,所以我很难调试。我真的可以使用一些帮助来了解它为什么会变形或只是矩阵如何工作,非常感谢!
这是我的代码:

import pygame
import math
import sys

width = 600
height = 480

class Triangle:
    def __init__(self, verts):
        for i in range(len(verts)):
            for r in range(3):
                verts[i][r] = int(float(verts[i][r]))
        self.verts = verts

Triangle 类是为了让代码更容易阅读

def getobj(filename):
    verts = []
    ind = []
    triangles = []
    data = None
    with open(filename, 'r') as f:
        data = f.readline()
        while data:
            data = data.rstrip("\n").split(" ")
            if data[0] == 'v':
                verts.append([-1 * int(float(data[1])), -1 * int(float(data[2])), -1 * int(float(data[3]))])
            elif data[0] == 'f':
                v = []
                t = []
                n = []
                for i in range(3):
                    l = data[i + 1].split('/')
                    v.append(int(l[0]))
                    t.append(int(l[1]))
                    n.append(int(l[2]))
                ind.append([v, t, n])
            data = f.readline()
    for points in ind:
        v = []
        for i in points[0]:
            v.append(verts[i - 1])
        triangles.append(Triangle(v))
    return triangles

从 obj 文件中获取顶点和线

class Matrix:
    def __init__(self, matrix):
        self.matrix = matrix
        self.height = len(matrix)
        self.width = len(matrix[0])

    def __add__(self, other):
        result = []
        for h in range(self.height):
            row = []
            for w in range(self.width):
                row.append(self.matrix[h][w] + other.matrix[h][w])
            result.append(row)
        return Matrix(result)

    def __sub__(self, other):
        result = []
        for h in range(self.height):
            row = []
            for w in range(self.width):
                row.append(self.matrix[h][w] - other.matrix[h][w])
            result.append(row)
        return Matrix(result)
    
    def __mul__(self, other):
        result = []
        for h in range(self.height):
            SUM = 0
            for w in range(self.width):
                SUM += self.matrix[h][w] * other.matrix[0][w]
            result.append(SUM)
        return Matrix([result])

Matrix类是为了让矩阵的加减乘法更简单

class Cam:
    def __init__(self):
        self.pos = Matrix([[0, 0, 0]])
        self.rot = [0, 0, 0]
    
    def getcoord(self, coord):
        m = Matrix([coord])
        data = m - self.pos
        F = Matrix([
            [math.cos(self.rot[2]), math.sin(self.rot[2]), 0],
            [-1 * math.sin(self.rot[2]), math.cos(self.rot[2]), 0],
            [0, 0, 1]
        ])
        S = Matrix([
            [math.cos(self.rot[1]), 0, -1 * math.sin(self.rot[1])],
            [0, 1, 0],
            [math.sin(self.rot[1]), 0, math.cos(self.rot[1])]
        ])
        T = Matrix([
            [1, 0, 0],
            [0, math.cos(self.rot[0]), math.sin(self.rot[0])],
            [0, -1 * math.sin(self.rot[0]), math.cos(self.rot[0])]
        ])
        data = F * data
        data = S * data
        data = T * data
        x = (width / 2) * data.matrix[0][0] / data.matrix[0][2]
        y = (height / 2) * data.matrix[0][1] / data.matrix[0][2]
        return [x, y]

    def events(self, event):
        if event.type == pygame.MOUSEMOTION:
            x, y = event.rel
            x /= 300
            y /= 300
            self.rot[0] -= y
            self.rot[1] += x
    
    def update(self, dt, key):
        s = dt * 2
        if key[pygame.K_LSHIFT]: self.pos.matrix[0][1] -= s
        if key[pygame.K_SPACE]: self.pos.matrix[0][1] += s

        x, y = s*math.sin(self.rot[1]), s*math.cos(self.rot[1])

        if key[pygame.K_w]: self.pos.matrix[0][0] -= x; self.pos.matrix[0][2] -= y
        if key[pygame.K_s]: self.pos.matrix[0][0] += x; self.pos.matrix[0][2] += y
        if key[pygame.K_a]: self.pos.matrix[0][0] += y; self.pos.matrix[0][2] -= x
        if key[pygame.K_d]: self.pos.matrix[0][0] -= y; self.pos.matrix[0][2] += x

控制移动和旋转的位置,以及使用投影矩阵的位置。

cam = Cam()

pygame.init()
screen = pygame.display.set_mode((width, height))
clock = pygame.time.Clock()

pygame.event.get()
pygame.mouse.get_rel()
pygame.mouse.set_visible(0)
pygame.event.set_grab(1)

初始化

# tris = getobj("untitled.obj")
tris = [
    Triangle([[1, -1, 1], [-1, -1, -1], [-1, -1, 1]]),
    Triangle([[-1, -1, -1], [1, 1, -1], [-1, 1, -1]]),
    Triangle([[1, -1, -1], [1, 1, 1], [1, 1, -1]]),
    Triangle([[-1, 1, 1], [1, 1, -1], [1, 1, 1]]),
    Triangle([[-1, -1, 1], [-1, 1, -1], [-1, 1, 1]]),
    Triangle([[1, -1, 1], [-1, 1, 1], [1, 1, 1]]),
    Triangle([[1, -1, 1], [1, -1, -1], [-1, -1, -1]]),
    Triangle([[-1, -1, -1], [1, -1, -1], [1, 1, -1]]),
    Triangle([[1, -1, -1], [1, -1, 1], [1, 1, 1]]),
    Triangle([[-1, 1, 1], [-1, 1, -1], [1, 1, -1]]),
    Triangle([[-1, -1, 1], [-1, -1, -1], [-1, 1, -1]]),
    Triangle([[1, -1, 1], [-1, -1, 1], [-1, 1, 1]])
]

我使用的 obj 文件中的数据。


while True:
    dt = clock.tick()/1000
    screen.fill((255, 255, 255))
    for event in pygame.event.get():
        if event.type == pygame.QUIT: pygame.quit(); sys.exit()
        cam.events(event)
    for tri in tris:
        coord1 = cam.getcoord(tri.verts[0])
        coord2 = cam.getcoord(tri.verts[1])
        coord3 = cam.getcoord(tri.verts[2])
        pygame.draw.line(screen, (0, 0, 0), (int(coord1[0]), int(coord1[1])), (int(coord2[0]), int(coord2[1])))
        pygame.draw.line(screen, (0, 0, 0), (int(coord1[0]), int(coord1[1])), (int(coord3[0]), int(coord3[1])))
        pygame.draw.line(screen, (0, 0, 0), (int(coord2[0]), int(coord2[1])), (int(coord3[0]), int(coord3[1])))
    pygame.display.flip()
    
    key = pygame.key.get_pressed()
    cam.update(dt, key)

在屏幕上绘制点。

4

2 回答 2

1

你的代码很好。但是,pygame 坐标系的原点 (0, 0) 是在窗口的左上角。您需要将几何图形从左上角平移到窗口的中心:

x = (width / 2) * data.matrix[0][0] / data.matrix[0][2]
y = (height / 2) * data.matrix[0][1] / data.matrix[0][2]

x = (width / 2) + (width / 2) * data.matrix[0][0] / data.matrix[0][2]
y = (height / 2)  + (height / 2) * data.matrix[0][1] / data.matrix[0][2]

我建议改变相机的起始位置:

class Cam:
    def __init__(self):
        self.pos = Matrix([[0, 0, -5]])
        self.rot = [0, 0, 0]

我创建了动画:

angle_y = 0
while True:
    angle_y += 0.01
    cam.rot[1] = angle_y
    cam.pos = Matrix([[-math.sin(angle_y)*6, 0, -math.cos(angle_y)*6]])

    dt = clock.tick(100)/1000
    # [...]

另请参阅Pygame 绕轴旋转立方体PyGame 做 3d 吗?.

于 2021-11-27T09:26:28.603 回答
0

您的矩阵乘法定义不正确。我已经以呈现的样式重写了它,尽管我会评论说有更多的pythonic方法可以做到这一点:

def __mul__(self, other):
    if self.width != other.height:
        raise ValueError("incompatible matrices")

    result = []
    for h in range(self.height):
        row = []
        for w in range(other.width):
            SUM = 0
            for i in range(self.width):
                SUM += self.matrix[h][i] * other.matrix[i][w]
            row.append(SUM)
        result.append(row)
    return Matrix(result)
于 2021-11-27T04:52:50.127 回答