本文是《从0开始图形学》笔记的第二章,主要说明模型的一般构成以及如何查找模型的有效范围,涉及三角面片的填充以及向量的叉乘计算。
上一节中,我们画出了箱子的顶点和边缘线,箱子还只是一个骨架而已。这一节我们来将箱子的“皮”画出来,让箱子的形体更完整。
首先,我们需要将箱子的面由四边形进一步切割成三角形,并进行填充。为什么要将其切割成三角形?原因大概有这么几个
(1)三角形才是最简单的多边形,任何多边形都可以用三角形拼凑出来,或者说任何多边形都可以切割成多个三角形;
(2)三角形能保证其面上所有的点都在同一个平面,其他形状就没有这个特性,例如将一张A4纸沿着对角线对折,它还是只有4条边,但是它就变成了两个三角面,而且这两个三角面不在同一个平面上;
(3)绝大多数标准的3D数据都是由3角形构成,而且很多加载数据的软件一般都会提供将数据3角形化的接口!
下一步如何填充三角形呢?最关键的点就是如何判别一个点是否在一个3角形中。
如下图,三角形ABC,任意点D,如果D在三角形内,则按顺序(如A->B->C)有:向量AB旋转到向量AD为逆时针,向量BC旋转到向量BD为逆时针,向量CA旋转到向量CD为逆时针,也就是说三个旋转的方向是一致的!而这个特性在数学上刚好有对应的表示方式----向量的叉乘
向量的叉乘,简单来说就是两个向量、叉乘结果会生成另外一个向量,那么垂直于和组成的平面,而且的方向和旋转到的方向刚好对应!而且因为我们所有的操作都是在XY平面上,所以这里的得x和y分量都为0,其z分量就刚好表示这个方向。
那么,向量的叉乘要怎么数学运算呢?很简单,假设向量AB为 [x1,y1,z1],向量AD为 [x2,y2,z2],那么根据公式,其叉乘的结果为 [y1*z2-y2*z1, z1*x2-z2*x1, x1*y2-x2*y1]。因为结果在2D的屏幕,所以z1和z2都为0,而结果向量中的z值(即x1*y2-x2*y1)的正负即代表方向。
代码上,首先,我们更改一下箱子数据,将其面数据从6个4边型改成12个3角形
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},
};
然后,在CGRender()函数中,根据上述的原理将每个在三角面片中的像素改颜色即可
void CGRender()
{
float v1[3], v2[3];
int bound[4]; // 每个三角面片的范围minx, miny, maxx maxy,用于减少计算量
float crossZval[3];
for (int i = 0; i < 12; ++i) // 遍历箱子的12个三角面
{
// 首先获取三角面片的范围(外包围框)
bound[0] = bound[1] = 1e6;
bound[2] = bound[3] = -1e6;
for (int j = 0; j < 3; ++j)
{
if (bound[0] > _points[_planes[i][j]][0]) bound[0] = _points[_planes[i][j]][0];
if (bound[1] > _points[_planes[i][j]][1]) bound[1] = _points[_planes[i][j]][1];
if (bound[2] < _points[_planes[i][j]][0]) bound[2] = _points[_planes[i][j]][0];
if (bound[3] < _points[_planes[i][j]][1]) bound[3] = _points[_planes[i][j]][1];
}
// 遍历包围框,以确定这些像素点在三角形的内外
for (int y = bound[1]; y <= bound[3]; ++y)
{
for (int x = bound[0]; x <= bound[2]; ++x)
{
for (int j = 0; j < 3; ++j)
{
// 通过点坐标的相减得到三角形边的向量v1
v1[0] = _points[_planes[i][(j + 1) % 3]][0] - _points[_planes[i][j]][0];
v1[1] = _points[_planes[i][(j + 1) % 3]][1] - _points[_planes[i][j]][1];
// v1[2] = _points[_planes[i][(j + 1) % 3]][2] - _points[_planes[i][j]][2]; Z值用不上
// 获取当前点和三角形顶点构成的向量v2
v2[0] = x - _points[_planes[i][j]][0];
v2[1] = y - _points[_planes[i][j]][1];
// 计算v1和v2叉乘结果的Z值
crossZval[j] = v1[0] * v2[1] - v1[1] * v2[0];
}
// 判断3个叉乘结果的方向(即Z值的正负)是否一样
if (
(crossZval[0] > 0 && crossZval[1] > 0 && crossZval[2] > 0)
|| (crossZval[0] < 0 && crossZval[1] < 0 && crossZval[2] < 0)
)
{
_rstImage[y][x][1] = 255; // 将其绿色通道值拉满,以显示出来
}
}
}
}
}
是不是觉得还不如上一节的效果来的有立体感?虽然结果确实如实,但是我们比上一节更前进了一步了,我们将在这个基础上实现进一步的渲染,下一节我们就让它显现出立体感来。