本文是《从0开始图形学》笔记的第四章,通过ZBuffer的作用提高渲染的通用性,本章原理很简单,但是作用不小,本章结束后,我们就可以渲染非常复杂的模型。
上一节中,我们将箱子的三角面片顺序做了一个调整,否则会出现渲染异常,我们这一节就看一下具体是什么原因,有没有应对方法。
首先,我们把三角面片的顺序改回去
int _planes[12][3] = // 面的数据,12个3角形
{
{0, 1, 2}, // 每个面3个角的顶点对应于索引值
{2, 3, 0},
{4, 5, 6},
{6, 7, 4},
{1, 5, 6},
{6, 2, 1},
{3, 2, 6},
{6, 7, 3},
{0, 4, 7},
{7, 3, 0},
{0, 1, 5},
{5, 4, 0},
};
看下结果
可以看到,箱子的显示出现了异常,这是为什么呢?原因其实也简单,我们按顺序一个一个渲染的三角面片,后渲染的三角面片自然会将前面的结果改写掉,所以不同的顺序就会导致不同的结果。
那么,该如何解决这个问题呢?其实看了本节的标题也大概能猜到,就是使用ZBuffer。ZBuffer也是一张图,大小和渲染结果图一样,其用来记录渲染结果图中对应像素点的Z值,故称为ZBuffer。
使用方式也很简单,在渲染过程中,首先对比当前像素[x, y]坐标对应的面片的Z坐标和ZBuffer中对应的[x, y]坐标的值大小,如果比ZBuffer中的值大,则说明该点距离摄像机更远,被遮挡,那么就不做处理;否则说明该点距离摄像机更新,更新掉前面的处理结果,同时更新ZBuffer的对应值。
首先,定义ZBuffer图像,并将其填上一个很大的值(比如1e6)
float _Zbuffer[gRstImgHei][gRstImgWid]; // ZBuffer
// 先将结果图画上背景色
for (size_t y = 0; y < gRstImgHei; ++y)
{
for (size_t x = 0; x < gRstImgWid; ++x)
{
_Zbuffer[y][x] = 1e6;
}
}
然后,在渲染过程中对比ZBuffer的值,要么跳过,要么更新
if (z > _Zbuffer[y][x])
{
continue;
}
_Zbuffer[y][x] = z;
可见,和上一节的结果一模一样,但是我们已经可以不用顾忌三角面片的渲染顺序,大大提高了通用性。ZBuffer原理很简单,但作用确很大!
至此,现有的这150行不到的代码已经可以让我们渲染一些非常复杂的模型,例如下面的这个包含了370000个三角面片的高达模型。