[CG] TinyRenderer 学习记录(1):线段绘制

先前代码改动

setPixel 增加了边界检测。(之后这部分可能会再修改,因为目前是直接在屏幕坐标系上操作,还没有透视除法、NDC 之类的东西)

def setPixel(self, x, y, color):
x = int(x)
y = int(y)
if x >= 0 and x < self.__imgWidth and y >= 0 and y < self.__imgHeight:
self.__img[y][x] = color

基于线段的参数方程

def line1(self, x0, y0, x1, y1, color): # based on math equations
# P(t) = S + tV
S = np.array([x0, y0]) # start point
V = np.array([x1 - x0, y1 - y0]) # direction
for t in Utils.fRange(0.0, 1.0, 0.01):
P = S + t * V
self.setPixel(P[0], P[1], color)

其中 Utils.fRange 是我在 Utils.py 写的一个自定义 generator,行为类似于 range,但前者支持浮点数:

# Utils.py
def fRange(start: float, stop: float, step: float): # iterate over [start, stop]
while start <= stop:
yield start
start += step

优点:

  • 不需要判断输入的两点之间的相对位置关系。
  • 不用处理斜率为 0 或者不存在的情况。

缺点:

  • 效率不高。

DDA

def line2(self, x0, y0, x1, y1, color): # line1 improved / DDA
x0 = int(x0)
y0 = int(y0)
x1 = int(x1)
y1 = int(y1)
dx = x1 - x0
dy = y1 - y0
if dx == 0 and dy == 0:
self.setPixel(x0, y0, color)
return
if abs(dx) >= abs(dy):
if dx < 0:
x0, x1 = x1, x0
y0, y1 = y1, y0
slope = dy / dx
for x in range(x0, x1):
self.setPixel(x, slope * (x - x0) + y0, color)
else:
if dy < 0:
x0, x1 = x1, x0
y0, y1 = y1, y0
slope = dx / dy
for y in range(y0, y1):
self.setPixel(slope * (y - y0) + x0, y, color)

优点:

  • 效率比前面要高。

缺点:

  • 代码更加复杂。

Bresenham's Line Drawing Algorithm

def line3(self, x0, y0, x1, y1, color): # Bresenham's line drawing algorithm
x0 = int(x0)
y0 = int(y0)
x1 = int(x1)
y1 = int(y1)
dx = x1 - x0
dy = y1 - y0
if dx == 0 and dy == 0:
self.setPixel(x0, y0, color)
return
if abs(dx) >= abs(dy):
if dx < 0:
x0, x1 = x1, x0
y0, y1 = y1, y0
dx = -dx
dy = -dy
yOffset = 1 if dy > 0 else -1
dy = abs(dy)
y = y0
P = 2 * dy - dx
for x in range(x0, x1 + 1):
self.setPixel(x, y, color)
if P > 0:
P -= dx << 1
y += yOffset
P += dy << 1
else:
if dy < 0:
x0, x1 = x1, x0
y0, y1 = y1, y0
dx = -dx
dy = -dy
xOffset = 1 if dx > 0 else -1
dx = abs(dx)
x = x0
P = 2 * dx - dy
for y in range(y0, y1 + 1):
self.setPixel(x, y, color)
if P > 0:
P -= dy << 1
x += xOffset
P += dx << 1

优点:

  • 效率最高。

缺点:

  • 代码更加更加复杂。

OBJ 模型加载

def wireframe(self, wireframeFilePath): # only support WaveFront OBJ Format
obj = open(wireframeFilePath)
lines = obj.readlines()
obj.close()
coords = [[]]
for line in lines:
item = line.strip().split(" ")
if item[0] == "v":
pos = list(map(float, item[1:]))
for i in range(3):
pos[i] *= self.__imgWidth // 2 - 1
pos[i] += self.__imgWidth // 2 - 1
if i == 1:
pos[i] = self.__imgWidth - pos[i]
coords.append(pos)
elif item[0] == "f":
faceData = [int(data.split("/")[0]) for data in item[1:]]
for v in range(3):
nv = (v + 1) % 3
self.line(
coords[faceData[v]][0],
coords[faceData[v]][1],
coords[faceData[nv]][0],
coords[faceData[nv]][1],
[0.0, 0.0, 1.0],
)

测试:正弦函数绘制

from Renderer import Renderer
from random import uniform
import math
width = 1024
height = 1024
renderer = Renderer(width, height)
sinFigure = [
(x, (width // 2 - 1) * (math.sin(2 * 3.141592653 * 1 / (width // 2) * x) + 1))
for x in range(width)
]
for i in range(1, width):
color = [uniform(0.0, 1.0) for _ in range(3)]
renderer.line(
sinFigure[i - 1][0],
sinFigure[i - 1][1],
sinFigure[i][0],
sinFigure[i][1],
color,
)
renderer.render()

image

测试:加载 OBJ 文件,绘制线框

from Renderer import Renderer
width = 1024
height = 1024
renderer = Renderer(width, height)
renderer.wireframe("tinyrenderer/african_head.obj")
renderer.render()

image

OBJ 文件从 这里 下载。

posted @   ZXPrism  阅读(18)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示