tinyrenderer学习过程记录

tinyrenderer学习过程记录

TinyRenderer

https://github.com/ssloy/tinyrenderer/wiki

参考:

从零构建光栅器,tinyrenderer笔记(上) - 知乎

从零构建光栅器,tinyrenderer笔记(下) - 知乎

lesson1 画线

Lesson 1: Bresenham’s Line Drawing Algorithm · ssloy/tinyrenderer Wiki (github.com)

不想交换xy,于是使用maxDelta 但思路方法类似

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
void line(int x0, int y0, int x1, int y1, TGAImage &image, TGAColor color)
{
int dx = abs(x0 - x1);
int dy = abs(y0 - y1);
int maxDelta = dx > dy ? dx : dy;
int x_delta = x0 < x1 ? 1 : -1;
int y_delta = y0 < y1 ? 1 : -1;
int x = x0;
int y = y0;
int x_error = 0;
int y_error = 0;
for (int t = 0; t <= maxDelta; t++)
{
image.set(x, y, color);
x_error += dx * 2;
y_error += dy * 2;
if (x_error > maxDelta)
{
x += x_delta;
x_error -= maxDelta * 2;
}
if (y_error > maxDelta)
{
y += y_delta;
y_error -= maxDelta * 2;
}
}
}

https://cdn.jsdelivr.net/gh/forestlyn/Drawing-bed/blog/202410032103351.png

lesson2 画实心三角形

Lesson 2: Triangle rasterization and back face culling · ssloy/tinyrenderer Wiki (github.com)

画实心三角形

思路1

思路1,如下图所示从xmin-xmax,扫描相交的两个点,再画线

https://cdn.jsdelivr.net/gh/forestlyn/Drawing-bed/blog/image-20241004153009255.png

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
void line(Vec2i t0, Vec2i t1, TGAImage &image, TGAColor color)
{
line(t0.x, t0.y, t1.x, t1.y, image, color);
}
bool inRange(int x, int x0, int x1)
{
if (x <= x0 && x >= x1)
return true;
if (x >= x0 && x <= x1)
return true;
return false;
}
Vec3i calculateIntersection(int x, Vec2i t0, Vec2i t1, Vec2i t2)
{
float dx1 = t1.x - t0.x;
float dx2 = t2.x - t1.x;
float dx3 = t0.x - t2.x;
float dy1 = t1.y - t0.y;
float dy2 = t2.y - t1.y;
float dy3 = t0.y - t2.y;
float y1 = -1;
// 注意dx=0
if (dx1 != 0 && inRange(x, t0.x, t1.x))
{
y1 = (x - t0.x) / dx1 * dy1 + t0.y;
}
float y2 = -1;
if (dx2 != 0 && inRange(x, t1.x, t2.x))
{
y2 = (x - t1.x) / dx2 * dy2 + t1.y;
}
float y3 = -1;
if (dx3 != 0 && inRange(x, t0.x, t2.x))
{
y3 = (x - t2.x) / dx3 * dy3 + t2.y;
}
return Vec3i(y1, y2, y3);
}

void triangle(Vec2i t0, Vec2i t1, Vec2i t2, TGAImage &image, TGAColor color)
{
int minx = t0.x < t1.x ? t0.x : t1.x;
minx = minx < t2.x ? minx : t2.x;
int maxx = t0.x < t1.x ? t1.x : t0.x;
maxx = maxx < t2.x ? t2.x : maxx;

for (int x = minx; x <= maxx; x++)
{
Vec3i res = calculateIntersection(x, t0, t1, t2);
if (res.x == -1)
{
line(x, res.y, x, res.z, image, color);
}
if (res.y == -1)
{
line(x, res.x, x, res.z, image, color);
}
if (res.z == -1)
{
line(x, res.x, x, res.y, image, color);
}
}
}

很奇怪,少了一条线,分析发现两端点合一时导致没有-1,因此少画一根线

https://cdn.jsdelivr.net/gh/forestlyn/Drawing-bed/blog/image-20241004155914840.png

修改calculateIntersection 考虑多种情况

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
Vec2i calculateIntersection(int x, Vec2i t0, Vec2i t1, Vec2i t2)
{
float dx1 = t1.x - t0.x;
float dx2 = t2.x - t1.x;
float dx3 = t0.x - t2.x;
float dy1 = t1.y - t0.y;
float dy2 = t2.y - t1.y;
float dy3 = t0.y - t2.y;
float y1 = -1;
// 注意dx=0
if (x == t0.x)
y1 = t0.y;
else if (dx1 != 0 && inRange(x, t0.x, t1.x))
{
y1 = (x - t0.x) / dx1 * dy1 + t0.y;
}
// printf("1 %f\n", y1);
float y2 = -1;
if (x == t1.x)
y2 = t1.y;
else if (dx2 != 0 && inRange(x, t1.x, t2.x))
{
y2 = (x - t1.x) / dx2 * dy2 + t1.y;
}
// printf("2 %f\n", y2);
float y3 = -1;
if (x == t2.x)
y3 = t2.y;
else if (dx3 != 0 && inRange(x, t0.x, t2.x))
{
y3 = (x - t2.x) / dx3 * dy3 + t2.y;
}
// printf("3 %f\n", y3);
if (y1 == -1)
return Vec2i(y2, y3);
if (y2 == -1)
return Vec2i(y1, y3);
if (y3 == -1)
return Vec2i(y1, y2);
if (y1 == y2 || y1 == y3)
return Vec2i(y2, y3);
if (y2 == y3)
return Vec2i(y1, y3);
return Vec2i(y1, y2);
}
void triangle(Vec2i t0, Vec2i t1, Vec2i t2, TGAImage &image, TGAColor color)
{
int minx = t0.x < t1.x ? t0.x : t1.x;
minx = minx < t2.x ? minx : t2.x;
int maxx = t0.x < t1.x ? t1.x : t0.x;
maxx = maxx < t2.x ? t2.x : maxx;
// line(t0, t1, image, color);
// line(t1, t2, image, color);
// line(t2, t0, image, color);
for (int x = minx; x <= maxx; x++)
{
Vec2i res = calculateIntersection(x, t0, t1, t2);
line(x, res.x, x, res.y, image, color);
}
}

思路2

判断点P是否在三角形内,如下,叉乘获得垂直向量,再变换z=1即可

https://cdn.jsdelivr.net/gh/forestlyn/Drawing-bed/blog/image-20241004175415622.png

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
Vec3f barycentric(Vec2i ab, Vec2i ac, Vec2i pa)
{
Vec3f u = Vec3f(ab.x, ac.x, pa.x) ^ Vec3f(ab.y, ac.y, pa.y);
if (abs(u.z) < 1)
{
return Vec3f(1, 1, -1);
}
return Vec3f(1 - u.x / u.z - u.y / u.z, u.x / u.z, u.y / u.z);
}

Vec3f barycentric(Vec2i a, Vec2i b, Vec2i c, Vec2i p)
{
return barycentric(Vec2i(b.x - a.x, b.y - a.y),
Vec2i(c.x - a.x, c.y - a.y),
Vec2i(a.x - p.x, a.y - p.y));
}

void triangle(Vec2i t0, Vec2i t1, Vec2i t2, TGAImage &image, TGAColor color)
{
int minx = std::min(t0.x, t1.x);
minx = std::min(minx, t2.x);
minx = std::max(minx, 0);
int maxx = std::max(t0.x, t1.x);
maxx = std::max(maxx, t2.x);
maxx = std::min(width - 1, maxx);
int miny = std::min(t0.y, t1.y);
miny = std::min(miny, t2.y);
miny = std::max(miny, 0);
int maxy = std::max(t0.y, t1.y);
maxy = std::max(maxy, t2.y);
maxy = std::min(height - 1, maxy);

for (int x = minx; x <= maxx; x++)
{
for (int y = miny; y <= maxy; y++)
{
Vec3f res = barycentric(t0, t1, t2, Vec2i(x, y));
if (res.x < 0 || res.y < 0 || res.z < 0)
continue;
image.set(x, y, color);
}
}
}

发现存在问题,有部分黑点,猜测是绘制不正确,即
• If two triangles have two common vertices, there should be no holes between them because of rasterization rounding.

思路一正常绘制

https://cdn.jsdelivr.net/gh/forestlyn/Drawing-bed/blog/image-20241004202933058.png

经过测试发现 先加再除!!很重要,float计算精度不够,存在误差!

1
2
3
4
5
6
7
8
9
10
11
Vec3f barycentric(Vec2i ab, Vec2i ac, Vec2i pa)
{
Vec3f u = Vec3f(ab.x, ac.x, pa.x) ^ Vec3f(ab.y, ac.y, pa.y);
if (abs(u.z) < 1)
{
return Vec3f(1, 1, -1);
}
if ((1 - (u.x + u.y) / u.z) == 0 && (1 - (u.x) / u.z - (u.y) / u.z) != 0)
printf("%.20f %.20f\n", 1 - (u.x + u.y) / u.z, 1 - (u.x) / u.z - (u.y) / u.z);
return Vec3f(1 - (u.x + u.y) / u.z, u.x / u.z, u.y / u.z);
}

lesson3 zbuffer and texture

zbuffer

Lesson 3: Hidden faces removal (z buffer) · ssloy/tinyrenderer Wiki (github.com)

增加zbuffer

code:

forestlyn/TinyRenderer at debb71ba42d912866df973fc5043df151352d9f8 (github.com)

https://cdn.jsdelivr.net/gh/forestlyn/Drawing-bed/blog/image-20241005115129513.png

改为光线*法向量

https://cdn.jsdelivr.net/gh/forestlyn/Drawing-bed/blog/image-20241005123246681.png

home assignment

forestlyn/TinyRenderer at db6405c70881a256852c43fd1518feff7f11e741 (github.com)

需要仔细处理model读取,vt 和对应的f坐标都没读取

同时注意uv(0,0)在左下角,而图片 0,0在左上角,所以需要flip_vertically一下将(0,0)变为左上角方便获得color

https://cdn.jsdelivr.net/gh/forestlyn/Drawing-bed/blog/image-20241005143355259.png

lesson4 透视投影

Lesson 4: Perspective projection · ssloy/tinyrenderer Wiki (github.com)

点v在(-1,-1,-1)-(0,0,0)之间,先进行透视投影再映射到屏幕中

渲染出奇怪的东西,zbuffer有问题

forestlyn/TinyRenderer at 635ec2cfcde3a9f10e251a6ec42e00936bdfaca1 (github.com)

https://cdn.jsdelivr.net/gh/forestlyn/Drawing-bed/blog/image-20241006100255315.png

https://cdn.jsdelivr.net/gh/forestlyn/Drawing-bed/blog/image-20241006102236061.png

发现依旧是float问题,传入triange的点x,y需要是整数,否则渲染会出现一些裂缝。。

forestlyn/TinyRenderer at 3c69a5820053aa8442f0e73e8c2a3b67b5fe7a26 (github.com)

https://cdn.jsdelivr.net/gh/forestlyn/Drawing-bed/blog/image-20250219191433047.png

lesson5 移动相机

Lesson 5: Moving the camera · ssloy/tinyrenderer Wiki (github.com)

实现Model to Camera View

正交矩阵逆矩阵=正交矩阵转置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Matrix lookat(Vec3f eye, Vec3f center, Vec3f up)
{
Vec3f z = (eye - center).normalize();
Vec3f x = (up ^ z).normalize();
Vec3f y = (z ^ x).normalize();
Matrix subO = Matrix::identity(4);
Matrix view = Matrix::identity(4);
for (int i = 0; i < 3; i++)
{
view[0][i] = x[i];
view[1][i] = y[i];
view[2][i] = z[i];
subO[i][3] = -eye[i];
}
Matrix ModelView = view * subO;
return ModelView;
}

forestlyn/TinyRenderer at a993af4e479153e4f27412c0507968c33d3a9870 (github.com)

https://cdn.jsdelivr.net/gh/forestlyn/Drawing-bed/blog/image-20241008135407573.png

lesson6 shader

第 6 课:软件渲染器的着色器 · ssloy/tinyrenderer Wiki — Lesson 6: Shaders for the software renderer · ssloy/tinyrenderer Wiki (github.com)

重构代码过程中遇到一些错误,发现主要是坐标带来的,需要注意计算后得到的坐标除w分量,获得正确的转换坐标后,再来进行各种计算

Exercise 1: Can you tell which image is represented in Darboux frame and which one is in the global coordinate frame?

练习 1:你能分辨出哪个图像是在达布坐标系(切线空间)中表示的,哪个图像是在全局坐标系中表示的吗?

更蓝的是切线空间

Exercise 2: Can you tell which representation is better and if yes, why is that?

练习 2:你能说出哪种表示法更好吗?如果是,为什么?

切线空间

(22 封私信) 为什么要有切线空间(Tangent Space),它的作用是什么? - 知乎 (zhihu.com)

暂时不理解为什么都要转换XD

现在理解了:法线给的世界空间的法线XD

1
2
Vec3f n = proj<3>(uniform_MIT * embed<4>(model->normal(uv))).normalize();
Vec3f l = proj<3>(uniform_M * embed<4>(light_dir)).normalize();

https://cdn.jsdelivr.net/gh/forestlyn/Drawing-bed/blog/image-20241009224510177.png

fix 透视错误

参考:https://zhuanlan.zhihu.com/p/403259571

在屏幕空间计算重心导致透视错误,需要修正

https://cdn.jsdelivr.net/gh/forestlyn/Drawing-bed/blog/image-20241023144508436.png

修复后

https://cdn.jsdelivr.net/gh/forestlyn/Drawing-bed/blog/image-20241023160800946.png

切线空间

Lesson 6bis: tangent space normal mapping · ssloy/tinyrenderer Wiki

实现切线

forestlyn/TinyRenderer at cc3aa813c403f382b8cca17af8b0b8ad34152b85

非常明显的存在锐利边缘,存在错误

https://cdn.jsdelivr.net/gh/forestlyn/Drawing-bed/blog/image-20241106201915240.png

破案了 修改后还用的世界空间贴图导致的😇

1
2
// load_texture(filename, "_nm.tga", normalmap_);
load_texture(filename, "_nm_tangent.tga", normalmap_);

forestlyn/TinyRenderer at a7c59f59799d95bfd91e24ffb357c47b591fdd66

https://cdn.jsdelivr.net/gh/forestlyn/Drawing-bed/blog/image-20241106221021200.png

lesson7 Shadow mapping

Lesson 7: Shadow mapping · ssloy/tinyrenderer Wiki

实现阴影

  1. 先按照光源所在位置计算一次zbuffer
  2. 在相机下计算实际图像,并且将点转换到光源世界下,比较z,绘制

forestlyn/TinyRenderer at 93e48d1b709a2adaa9f51accd240fbcd7f9b6340

some bug: depth设置为2000会出现,设置为200时正常,2不正常

发现是没有同步修改depth XD

1
2
int frag_depth = std::max(0, std::min(255, int(z / w + .5)));
int frag_depth = std::max(0, std::min((int)depth, int(z / w + .5)));

https://cdn.jsdelivr.net/gh/forestlyn/Drawing-bed/blog/image-20241108230037262.png

增加glow

在model中增加读取glowmap,再在颜色中加glow颜色

1
color[i] = std::min<float>(20 + c[i] * shadow * (1.2 * diff + .6 * spec) + glow[i] * 10, 255);

forestlyn/TinyRenderer at 5375f1e929d0b61147f8227c6cfe38c25d2358e0

https://cdn.jsdelivr.net/gh/forestlyn/Drawing-bed/blog/image-20241120233647444.png

lesson8 Ambient occlusion 环境光遮挡

第 8 课:环境光遮挡 · ssloy/tinyrenderer Wiki — Lesson 8: Ambient occlusion · ssloy/tinyrenderer Wiki

取球上点

**Question:**Do you know how to pick uniformlya thousand points on a (hemi-)sphere?

问题:你知道如何在(半)球体上统一选取一千个点吗?

random和z轴,x轴的夹角 (error)会聚集在两极附近

球体点选取——来自 Wolfram MathWorld — Sphere Point Picking – from Wolfram MathWorld

https://cdn.jsdelivr.net/gh/forestlyn/Drawing-bed/blog/image-20241121234128764.png

使用以下公式重新绘制,感觉画多了之后也不太均匀?

forestlyn/TinyRenderer at aa9e960792e853be4ce215430f4fc10ef6379256

https://cdn.jsdelivr.net/gh/forestlyn/Drawing-bed/blog/image-20241218104456679.png

https://cdn.jsdelivr.net/gh/forestlyn/Drawing-bed/blog/image-20241218105356168.png

https://cdn.jsdelivr.net/gh/forestlyn/Drawing-bed/blog/image-20250113141041417.png

fix draw sphere bug

破案了,原来是画了第一个不对的之后忘记clear了

1
sphereImage.clear();

forestlyn/TinyRenderer at c7af07962275f49da0510a6d23f5650aa5569d3a

https://cdn.jsdelivr.net/gh/forestlyn/Drawing-bed/blog/image-20250114220743083.png

修改IShader 对于片元增加传入屏幕坐标

不修改绘制三角形,于是修改了一下zshader

注意:原本传入的是屏幕坐标,作者在这一部分改成传入【-1,1】范围坐标,未×ViewPort

本人懒得改,于是选择修改zshader

1
2
3
4
5
6
virtual bool fragment(Vec3f gl_FragCoord, Vec3f bar, TGAColor &color)
{
float z = bar * varying_tri[2];
color = TGAColor(255, 255, 255) * ((z + 1.f) / 2.f);
return false;
}

https://cdn.jsdelivr.net/gh/forestlyn/Drawing-bed/blog/image-20250115175645614.png

occl如上图所示,Vscode的TGA Image Preview看不了GRAYSCALE图

forestlyn/TinyRenderer at 09aabfbab73754d984df31fd7edd06063b6e9e42

还以为错了,结果ps打开就能看到。😢

注意由于total公式iter从1开始

1
2
3
4
5
6
for (int iter = 1; iter <= randomPointsNum; iter++)
{
......
total.set(i, j, TGAColor((tmp * (iter - 1) + occl.get(i, j)[0]) / (float)iter + .5f));
}

https://cdn.jsdelivr.net/gh/forestlyn/Drawing-bed/blog/image-20250115235933110.png

Question: Why are there holes in obviously visible triangles?

问:为什么明显可见的三角形里有洞?

guess: 有的地方看不到所以有洞(?错误,不管深度测试是否通过都画出来也有洞

因此猜测是透视时部分点被舍入导致有洞,即

假设:uv对应100100个点的区域透视到屏幕空间只有5050的大小,于是进行三角形绘制只画了50*50个点,返回对应uv画回去,导致有洞(猜测不知道对不对)

进行一下验证,对于uv*1024点画图

test:进行绘制测试,不管深度进行绘制

1
2
3
4
5
6
7
8
9
10
11
12
virtual bool fragment(Vec3f gl_FragCoord, Vec3f bar, TGAColor &color)
{
Vec2f uv = varying_uv * bar;
if (std::abs(shadowbuffer[int(gl_FragCoord.x + gl_FragCoord.y * width)] - gl_FragCoord.z < 1e-2))
{
// printf("occl\n");
occl.set((int)(uv.x * 1024), (int)(uv.y * 1024), TGAColor(255));
}
occlTest.set((int)(uv.x * 1024), (int)(uv.y * 1024), TGAColor(255));
color = TGAColor(255, 0, 0);
return false;
}

画100次出来occlTest如下所示,也存在洞

https://cdn.jsdelivr.net/gh/forestlyn/Drawing-bed/blog/image-20250116000945832.png

Question: Why are there triangles more densely covered than others?

问题:为什么有些三角形比其他三角形覆盖得更密?

可见性(?

根据occlusion绘制

forestlyn/TinyRenderer at b01c8adef16e8ab19b51b65e1802a331d33c9c2c

画出来显示有些奇怪

https://cdn.jsdelivr.net/gh/forestlyn/Drawing-bed/blog/image-20250211213244636.png

需要反转一下图片XD

forestlyn/TinyRenderer at 20430cceea2bafa1e2b10a1817ee1e6a2b498c24

https://cdn.jsdelivr.net/gh/forestlyn/Drawing-bed/blog/image-20250211220911235.png

使用梯度计算

计算八个方向梯度来计算颜色,计算pow=1时

https://cdn.jsdelivr.net/gh/forestlyn/Drawing-bed/blog/image-20250212223503310.png

pow=5

https://cdn.jsdelivr.net/gh/forestlyn/Drawing-bed/blog/image-20250212223748637.png

https://cdn.jsdelivr.net/gh/forestlyn/Drawing-bed/blog/image-20250212230019288.png

有些奇怪,就这样了

forestlyn/TinyRenderer at 6ca9796c3d9ca7b8a97a0b27311b9c4d2ddf5849

lesson9:Real OpenGL (GLSL) application

安装一下包

Win10 + VSCode + GLUT 配置 - cralor - 博客园

把glut.h移动到文件夹下

make报错

1
2
3
4
5
6
7
8
9
10
11
12
g++ -Wall  -o ./main main.o -lm -lGL -lglut -lGLU
F:/install/Mingw64/mingw64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: cannot find -lGL
F:/install/Mingw64/mingw64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible F:/in::/inst::::::::::::/install/Mingw64/mingw:/install/Mingw64/mingw64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../lib/glut.lib when searching for -lglut
F:/install/Mingw64/mingw64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible F:/install/Mingw64/mingw64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../glut.lib when searching for -lglut
F:/install/Mingw64/mingw64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible F:/install/Mingw64/mingw64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../lib/glut.lib when searching for -lglut
F:/install/Mingw64/mingw64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible F:/install/Mingw64/mingw64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../lib\glut.lib when searching for -lglut
F:/install/Mingw64/mingw64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible F:/install/Mingw64/mingw64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../glut.lib when searching for -lglut
F:/install/Mingw64/mingw64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: skipping incompatible F:/install/Mingw64/mingw64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../..\glut.lib when searching for -lglut
F:/install/Mingw64/mingw64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: cannot find -lglut
F:/install/Mingw64/mingw64/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/8.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: cannot find -lGLU
collect2.exe: error: ld returned 1 exit status
make: *** [Makefile:14: main] Error 1

整了半天没解决,干脆安装MSYS2 — MSYS2的mingw make 设置环境变量等

pacman -S mingw-w64-x86_64-freeglut

成功了

https://cdn.jsdelivr.net/gh/forestlyn/Drawing-bed/blog/image-20250216150208900.png

shader:glsl

简单的shader

https://cdn.jsdelivr.net/gh/forestlyn/Drawing-bed/blog/image-20250217224653796.png

https://cdn.jsdelivr.net/gh/forestlyn/Drawing-bed/blog/image-20250217224343952.png

forestlyn/TinyRenderer at 6ed21076a655211a97568ecb7131f324cdbd45ba

使用shader加快绘制正交投影

forestlyn/TinyRenderer at d3b34fddf9914cceaaf84b27c84c37ec0e01f64e

感觉比较难理解的是frag.glsl

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
#version 120

uniform vec4 viewport;
varying float radius;
varying vec2 center;

void main(void) {
// 获得当前待处理像素在 NDC 坐标系中的位置,方便后续计算
// 由于 gl_FragCoord 是在窗口坐标系中的,所以需要转换
// 使用二维的NCD坐标,因为球在无论哪个角度投影都是圆
vec2 ndc_current_pixel = ((2.0 * gl_FragCoord.xy) - (2.0 * viewport.xy)) / (viewport.zw) - 1;

// 计算当前像素与圆心的距离 也是在二维NDC坐标系中
vec2 diff = ndc_current_pixel - center;
float d2 = dot(diff, diff);
float r2 = radius * radius;

// 大于丢弃,小于则计算光照强度
if(d2 > r2) {
discard;
} else {
// 计算光照强度
// 获得光线方向
vec3 l = normalize(gl_LightSource[0].position.xyz);
// 直角三角形计算出z方向的长度
float dr = sqrt(r2 - d2);
// 法向量
vec3 n = vec3(ndc_current_pixel - center, dr);
// 计算光照强度
float intensity = .2 + max(dot(l, normalize(n)), 0.0);
gl_FragColor = gl_Color * intensity;
// 计算当前像素实际深度
// gl_FragCoord.z是待处理像素的深度,范围[0,1],由于渲染的是点,深度就是点的深度
// dr是ndc坐标中的z方向长度,后半段计算像素相对点的深度,由于ndc范围【-1,1】
// 所以需要乘以gl_DepthRange.diff / 2.0 得到对应【0,1】深度范围的值
// 再* gl_ProjectionMatrix[2].z获得窗口范围的值
gl_FragDepth = gl_FragCoord.z + dr * gl_DepthRange.diff / 2.0 * gl_ProjectionMatrix[2].z;
}
}

https://cdn.jsdelivr.net/gh/forestlyn/Drawing-bed/blog/image-20250218213205817.png