关于投影矩阵的问题以及为什么它显示的图片有时会变形
Problems about projection matrix and why the picture it shows somtimes deform
我目前正在研究 3d 渲染,我正在尝试在没有任何附加库的情况下完成它(pygame、math、sys 除外)。我做了很多研究,但仍然不太了解数学(我正在使用 Methods on wikipedia). It did output the right coordinate, but it somtimes has an severely deform. Here is what the result look like。我不太了解矩阵,所以我很难调试。我真的可以使用一些帮助来了解它为什么变形或只是矩阵如何工作,非常感谢!
这是我的代码:
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
class三角形是为了让代码更容易阅读
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])
class矩阵是为了让矩阵的加减乘乘更简单
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)
在屏幕上绘制点。
您的矩阵乘法定义不正确。我已经按照呈现的样式重写了它,尽管我会评论说有更多的 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)
你的代码没问题。但是pygame坐标系(0, 0)的原点在window的左上角。您需要将几何图形从左上角平移到 window:
的中心
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
# [...]
另见 and Does PyGame do 3d?。
我目前正在研究 3d 渲染,我正在尝试在没有任何附加库的情况下完成它(pygame、math、sys 除外)。我做了很多研究,但仍然不太了解数学(我正在使用 Methods on wikipedia). It did output the right coordinate, but it somtimes has an severely deform. Here is what the result look like。我不太了解矩阵,所以我很难调试。我真的可以使用一些帮助来了解它为什么变形或只是矩阵如何工作,非常感谢!
这是我的代码:
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
class三角形是为了让代码更容易阅读
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])
class矩阵是为了让矩阵的加减乘乘更简单
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)
在屏幕上绘制点。
您的矩阵乘法定义不正确。我已经按照呈现的样式重写了它,尽管我会评论说有更多的 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)
你的代码没问题。但是pygame坐标系(0, 0)的原点在window的左上角。您需要将几何图形从左上角平移到 window:
的中心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
# [...]
另见