7.0 前言

  • 从本节开始,将开始真正结合编程知识开始大量实践,本课讲单独开设一个分专题来进行专门的编程实践部分分享,每节课都会有对应代码部分的章节,跟随实践的同学可以参考代码。

7.1 相关基本概念

7.1.1 屏幕

  • 关于屏幕,其具备基本的基础属性,如:长,宽,亮度,色彩范围等。
  • 我们主要关注的是屏幕的长宽。
  • 屏幕可以视为一个二维数组,其位置坐标可以表示为屏幕长宽中任意两个数字组合。
  • 我们通过设置每一个数组元素的值来定义如何呈现需要呈现的点。

7.1.2 像素

  • 像素是屏幕的基本单位,也就是上述单个数组的具体实现方式。
  • 我们目前阶段可以简单的认为像素是一个具有基本位置信息,由红绿蓝三个颜色通道值来定义的对象。

7.1.3 光栅化

  • 光栅化实际上就是我们设置屏幕像素的过程
  • 光栅(Raster)在德语中的意思就是屏幕。

7.2 屏幕视口坐标及其转化

7.2.1 什么是屏幕视口坐标

  • 屏幕视口坐标就是真正呈现在平面上的二维坐标系。
  • 在先前我们通过矩阵变换将三维信息变换到裁剪空间之后,需要进行屏幕视口坐标的映射,这一步并不困难,实际上就是将对象的坐标位置变换到视口坐标原点,这一般是图像正中,但就像我们之前所提到的,根据不同的图形API规定,视口坐标也并不相同。
  • image.png
    7.2.1 屏幕视口坐标系

7.2.2 屏幕视口坐标变换

  • 我们只需要使用一个很简单的矩阵变换就可以搞定了。
  • 如下图,先将原本的(-1,1)的图像缩放至屏幕大小,对于单独的一个坐标应该是先将原本的宽高绝对值为2的NDC(设备归一化坐标)下的图形划归为1,随后再缩放到(w,h)大小,再将将元素的中心(原本是(0,0))移动到屏幕中心(w/2,H/2),将左下角作为(0,0)点。
  • image.png
    7.2.2 屏幕视口变换矩阵
  • 到此为止我们就完成了将顶点从模型空间变换到在屏幕空间上的完整过程。

7.2.3 将顶点映射到像素

  • 现在,我们具备了顶点信息,下一步我们需要以一定图元的方式呈现顶点,这样做的好处有:
    • 方便后续进行插值计算
    • 方便更好的判断顶点位置
    • 方便渲染减少开销
  • 一般使用三角形作为图元,那么我我们如何判断三角形对于像素的影响呢?

  • image.png
    7.2.3 如何将三角形图元映射到像素上?
  • 我们通过采样的方式来进行线性的图元到离散像素的变换。

  • 采样实际上就是用一个函数描述逐个像素点上对应的值的变化,我们可以写一个伪代码来描述它,实际上我很不喜欢伪代码,但如果作为表达方法实现的思路,倒是也有可取之处。
1
2
for (int x =0;x<xmax;++x)
output[x] = f(x);
  • 你可以看到实际上它就是对逐个离散化的x变量用一个函数输出到一个数组中。不理解数组没关系,你可以理解为就是输出到了屏幕上。

  • 理解了这个过程,我们就可以进行下一步的判断了,我们要去判断一个像素点是否在这个映射的范围内,如果在,则输出1否则输出0,像素中心可以理解为像素的x,y坐标分别+0.5个单位值对应的点,如图
  • image.png
    7.2.3.1 离散后的像素中心点坐标

  • 所以它对应的伪代码应该是这样的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
for (int x =0;x<xmax;++x)
for(int y=0;y<max;++y)
inside(tri,x+0.5,y+0.5);
inside(triangle,int x,int y)
{
point newpoint = point(x,y);
int a = crows(triangle.t1- newpoint,triangle.t1 - triangle.t2);
int b = crows(triangle.t2- newpoint,triangle.t2 - triangle.t3);
int c = crows(triangle.t3- newpoint,triangle.t1 - triangle.t3);
if(a<0&&b<0&&c<0)
{
return 1
}
else
return 0;
}
  • 此处对应的判断方法在之前的线代基础中已经提到过了,实际上就是用叉积检测点是否同时在三角形的同一边,如果是,则说明该点在三角形内。数学过程就不再赘述,欢迎大家回顾之前的内容。

7.2.4 光栅化加速方法

  • AABB包围盒:
    • 现在如果我们按以上方法判断三角形与映射像素的关系,我们会发现,它太耗时间了!如果要遍历全部像素的话,的确是如此,但是我们可以只关注我们需要关注的部分,那就是三角形本身,我们可以只遍历三角形而不关注其他部分。
    • 这种覆盖三角形的部分就叫做包围盒,类似于Unity等引擎中的碰撞箱。
    • image.png
      7.2.4 AABB包围盒

  • 针对特殊三角形的特殊遍历
  • 上面这样的三角形虽然很方便,但却太理想了,有可能会出现那种又窄又长的三角形,这时候再用包围盒,加速效果就远没有那么明显了,因此此时我们会使用针对这种三角形的遍历方式,那就是从左到右遍历,实际上在实现中我们也将使用这种方式,它将先查找三角形最左段然后逐一向右遍历,依次绘制:

  • image.png
    7.2.4.1 特殊三角形的由左至右的遍历

7.3 拓展

7.3.1 真实的屏幕像素

  • 在现实中像素会像是我们上述所讲的样子吗?显然不会,实际上受击屏幕上的像素其实是由红绿蓝三原色像素条所组成的。
  • image.png
    7.3.1 真实的像素屏幕
  • 如果仔细观察会发现,实际上像素的绿色部分会更多,这是因为人眼对绿色光更为敏感,因此绿色部分占屏幕比例自然会更多。

7.3.2 一些问题

  • 现在,我们已经可以去实现将三角形填充在像素上了,但这个结果并不完美,因为填充的部分是均匀的,如果像素中心点不在三角形内,填充的结果就是空白,所以我们会得到下面这个结果,如何去优化它呢?等到下一节我们在去分析,在同期的程序部分,你将可以渲染出一个这样一个红色片元,我们模拟了这个过程。

  • image.png
    7.3.2 初步的光栅化结果

参考资料