TinyRenderer 3:ZBuffer

这一节是为了解决上一节课里简单的计算平面法向量和光照夹角后绘制造成的错误,引入ZBuffer来计算每个像素实际上在屏幕空间的可见性

这里由于这个渲染器还没有进行投影等相关操作,所以ZBuffer的计算比较取巧,原文的解释也并不是很充分,暂时没有去深究具体的推倒

并且wiki给出的源码版本似乎是对不上的

这里简单实现了一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 初始化ZBuffer
std::shared_ptr<float> ZBuffer(new float[WIDTH * HEIGHT], std::default_delete<float[]>());
for (int i = 0; i < (WIDTH * HEIGHT); i++)
{
ZBuffer.get()[i] = std::numeric_limits<float>::min();
}

// 定义光照方向
const Vec3f LightDir(0, 0, -1);


for (int i = 0; i < ModelInstance->GetFacesNumber(); i++)
{
Face Face = ModelInstance->GetFace(i);
Vec3f Pts[3];
Vec3f WorldCoords[3];
// 处理每个面的三个点的空间变换
for (int j = 0; j < 3; j++)
{
const Vec3f V = ModelInstance->GetVertex(Face[j]);
Pts[j] = WorldToScreen(V);
WorldCoords[j] = V;
}
Vec3f N = (WorldCoords[2] - WorldCoords[0]) ^ (WorldCoords[1] - WorldCoords[0]);
N.normalize();
const float Intensity = N * LightDir;
DrawTriangleEdgeFunc(Pts, Image, TGAColor(Intensity * 255, Intensity * 255, Intensity * 255, 255), ZBuffer.get());
printf("\r %f [%.2lf%%]", Intensity, i * 100.0f / ModelInstance->GetFacesNumber());
}

其中画三角的函数也进行了对应的修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
void DrawTriangleEdgeFunc(Vec3f Pts[3], TGAImage& Image, const TGAColor Color, float* ZBuffer)
{
Vec2f BboxMin(Image.get_width() - 1, Image.get_height() - 1);
Vec2f BboxMax(0, 0);
const Vec2f Clamp(Image.get_width() - 1, Image.get_height() - 1);
for (int i = 0; i < 3; i++)
{
BboxMin.x = std::max(0.0f, std::min(BboxMin.x, Pts[i].x));
BboxMin.y = std::max(0.0f, std::min(BboxMin.y, Pts[i].y));

BboxMax.x = std::min(Clamp.x, std::max(BboxMax.x, Pts[i].x));
BboxMax.y = std::min(Clamp.y, std::max(BboxMax.y, Pts[i].y));
}
Vec3f P;
for (P.x = BboxMin.x; P.x <= BboxMax.x; P.x++)
{
for (P.y = BboxMin.y; P.y <= BboxMax.y; P.y++)
{
Vec3<float> BcScreen = Barycentric(Pts, P);
if (BcScreen.x < 0 || BcScreen.y < 0 || BcScreen.z < 0)
continue;
P.z = 0;
for (int i = 0 ; i < 3; i++)
{
P.z += Pts[i][2] * BcScreen[i];
}
if (ZBuffer[static_cast<int>(P.x + P.y * WIDTH)] < P.z)
{
ZBuffer[static_cast<int>(P.x + P.y * WIDTH)] = P.z;
Image.set(P.x, P.y, Color);
}
}
}
}

后面布置了关于贴图的映射的相关内容

从Obj里读取所有的vt,对应的是0-1映射的纹理的x和y值,分别乘上宽度和高度就是对应的三角形顶点在贴图上对应的点的坐标,三角形内其他点通过对应的插值来采样

但是上面的重心法我不知道怎么去插值,改回了扫描法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
// 读取贴图
main()
{
// ......

TGAImage Texture(1024, 1024, TGAImage::RGB);
Texture.read_tga_file("african_head_diffuse.tga");
Texture.flip_vertically();


// 初始化ZBuffer
std::shared_ptr<float> ZBuffer(new float[WIDTH * HEIGHT], std::default_delete<float[]>());
for (int i = 0; i < (WIDTH * HEIGHT); i++)
{
ZBuffer.get()[i] = std::numeric_limits<int>::min();
}

// 定义光照方向
const Vec3f LightDir(0.f, 0.f, -1.f);

for (int i = 0; i < ModelInstance->GetFacesNumber(); i++)
{
Face Face = ModelInstance->GetFace(i);
Vec3f Pts[3];
Vec3f UVs[3];
Vec3f WorldCoords[3];
// 处理每个面的三个点的空间变换
for (int j = 0; j < 3; j++)
{
const Vec3f V = ModelInstance->GetVertex(Face[j]);
//Pts[j] = (WorldToScreen(V));
Pts[j] = WorldToScreen(V);

UVs[j] = Vec3f(ModelInstance->GetUV(Face.VTIndex[j]).x * 1024,
ModelInstance->GetUV(Face.VTIndex[j]).y * 1024,
0);
WorldCoords[j] = V;
}
Vec3f N = (WorldCoords[2] - WorldCoords[0]) ^ (WorldCoords[1] - WorldCoords[0]);
N.normalize();
const float Intensity = N * LightDir;
DrawTriangleEdgeFunc(Pts, UVs, Image, Texture, ZBuffer.get(), Intensity);
//DrawTriangleEdgeFunc(Pts, Image, TGAColor(Intensity * 255, Intensity * 255, Intensity * 255, 255), ZBuffer.get());
printf("\r %f [%.2lf%%]", Intensity, i * 100.0f / ModelInstance->GetFacesNumber());
}
}
//
// ......
//
// 从贴图采样颜色
void DrawTriangleEdgeFunc(Vec3f Pts[3], Vec3f UVs[3], TGAImage& Image, TGAImage& TextureImage, float* ZBuffer, const float& Intensity)
{
Vec3f t0, t1, t2, uv0, uv1, uv2;
t0 = (Pts[0]);
t1 = (Pts[1]);
t2 = (Pts[2]);
uv0 = (UVs[0]);
uv1 = (UVs[1]);
uv2 = (UVs[2]);

if (t0.y == t1.y && t0.y == t2.y) return; // i dont care about degenerate triangles
if (t0.y > t1.y)
{
std::swap(t0, t1);
std::swap(uv0, uv1);
}
if (t0.y > t2.y)
{
std::swap(t0, t2);
std::swap(uv0, uv2);
}
if (t1.y > t2.y)
{
std::swap(t1, t2);
std::swap(uv1, uv2);
}

int total_height = t2.y - t0.y;
for (int i = 0; i < total_height; i++)
{
bool second_half = i > t1.y - t0.y || t1.y == t0.y;
int segment_height = second_half ? t2.y - t1.y : t1.y - t0.y;
float alpha = (float)i / total_height;
float beta = (float)(i - (second_half ? t1.y - t0.y : 0)) / segment_height;
Vec3f A = t0 + Vec3f(t2 - t0) * alpha;
Vec3f B = second_half ? t1 + Vec3f(t2 - t1) * beta : t0 + Vec3f(t1 - t0) * beta;
// 通过插值计算每个点的uv
Vec3f uvA = uv0 + (uv2 - uv0) * alpha;
Vec3f uvB = second_half ? uv1 + (uv2 - uv1) * beta : uv0 + (uv1 - uv0) * beta;
if (A.x > B.x)
{
std::swap(A, B);
std::swap(uvA, uvB);
}
for (int j = A.x; j <= B.x; j++)
{
float phi = B.x == A.x ? 1. : (float)(j - A.x) / (float)(B.x - A.x);
Vec3f P = Vec3f(A) + Vec3f(B - A) * phi;
Vec3f uvP = uvA + (uvB - uvA) * phi;
int idx = P.x + P.y * WIDTH;
if (ZBuffer[idx] < P.z)
{
ZBuffer[idx] = P.z;
TGAColor color = TextureImage.get(uvP.x, uvP.y) * Intensity;
Image.set(P.x, P.y, color);
}
}
}
}

diffuse


TinyRenderer 3:ZBuffer
http://muchenhen.com/posts/54742/
作者
木尘痕
发布于
2022年7月2日
许可协议