图形学-光栅化

图形管线(graphics pipeline)总览

光栅化

绘制线条(midpoint-Bresenham算法)

我们首先知道任何一条一维直线可以用 f(x,y) = ax + by + c = 0 ,将点(x0,y0) 和(x1,y1)代入得以下结果

我们在这里先考虑斜率在(0,1]的情况,之后类推到 (-∞,-1],(-1,0],(1,∞)

在基于斜率在0-1的情况下,如上图所示,假设我们现在要画橙色格子,只可能是在最后一个蓝色格子的右边或者右上,如果直线高于黑色圆点就画在右上,如果低于黑色圆点就画在右边。

伪代码如上,那么some condition是什么,我们知道每个像素坐标是方格的中间,那么黑色原点应该是最后一个蓝色方格的(x+1,y+0.5),所以得到f(x+1,y+0.5) > 0 时,也就是中点在直线上面,那就把橙色格子画在右边。相反就画在右上,如果相等,取决于自己,影响不大。

上面就是比较好的直线绘制算法,但是有一个问题,我们每次都要进行一次F(x,y)的计算,在现在光三角形面数就有几百万的模型上,无疑是十分消耗资源的。

在上式,我们找到所对应的关系,我们一开始就将(x0+1,y0+0.5)代入,后面每次计算我们只要累加上述图片的函数后面的一部分就可以省下很多次f(x,y)的计算。因此我们得到以下改进过的伪代码

接着我们推广不同的角度的直线,也就是将x,y转化为斜率(0,1)的范围,也就是进行翻转等。下面是milo yip实现的一份很漂亮的代码

void bresenham(int x0, int y0, int x1, int y1) {
int dx = abs(x1 - x0), sx = x0 < x1 ? 1 : -1;
int dy = abs(y1 - y0), sy = y0 < y1 ? 1 : -1;
int err = (dx > dy ? dx : -dy) / 2;

// setpixel = draw
while (setpixel(x0, y0), x0 != x1 || y0 != y1) {
int e2 = err;
if (e2 > -dx) { err -= dy; x0 += sx; }
if (e2 < dy) { err += dx; y0 += sy; }
}
}

三角形的光栅化

有了直线绘制算法,我们就可以画出一个三角形了,但是如何涂满三角形内部呢。最简单的做法就是判断每个像素点是否在内部,利用叉乘的性质,我们事先知道想要光栅化的三角形的三个顶点P0,P1,P2,以及检测点Q。 只要分别计算 P0P1 × P0Q , P1P2 × P1Q , P2P0 × P2Q ,三者同号就说明必然处于三角形内部,不同号则说明该点一定在三角形外部。但是对全部像素点还是很浪费,可以用包围盒的思路,快速算出包围盒然后对包围盒里的点进行判断即可。

因为每个在三角形的点都可以用重心表示法表示出来

只要点在里面,我们算出来的值就会 > 0,利用这个性质以及,任意两个二维向量组合成矩阵的行列式的绝对值,为这两个向量所围成平行四边形的面积,不难得出

我们尝试将叉乘列出来,发现巧妙的是,正好对应方程式

发现很符合上面求α,β,γ的式子,代入获得伪代码

注意到,如果一个点在边上的话,如何解决,毕竟有可能同时在两个三角形上,答案是取决你自己,详见虎书8.1.2。因为如果不画的话,两个三角形将会产生缝隙,如果两个都画的话,如果三角形是透明的,这将导致双重着色。

Z-buffer算法

常见的处理前后关系有画家算法和z-buffer算法,前者性能消耗较大已经很少用了,后者很简单,我们设置一个zbuffer数组,绘制三角形的时候如果该三角形的深度比较小,就覆盖掉zbuffer的值,同时更新该点的颜色。

即使这样,我们可能会遇到深度冲突的问题,也就是物体z轴相同,会进行闪烁。越接近far平面,depth变化越小,精度变差越容易出现z-fighting问题。
避免Z-fighting问题:

  • 可以将near和 far的距离缩小,同时尽量将near变大一些。
  • 当然也可以使用精度更高的zbuffer,简单来说就是 float -> double

参考:全面认识Depth - 这里有关于Depth的一切

着色频率

逐平面着色(Flat Shading)

顾名思义,就是以每一个面作为一个着色单位进行着色计算

逐顶点着色(Per-vertex Shading/Gouraud shading)

顾名思义就是对每一个顶点进行着色计算,顶点法线计算就是由这个点相关的每个面的法线求平均值再进行标准化即可得到,对于内部的点就用重心坐标计算颜色

逐像素着色(Phong shading)

就是对每个像素进行着色计算,三角形内部的点同样用重心法进行计算,只不过这次是计算法线,这样子可能要更准确些。

效果对比

着色模型

Ambient泛光模型

即只考虑环境光,这时一个经验模型

其中
Ka代表物体表面对环境光的反射率,Ia代表入射环境光的亮度,Ienv存储结果,即人眼所能看到从物体表面反射的环境光的亮度。

Lambert漫反射模型(diffuse)

就是在Ambient上增加了漫反射项。漫反射便是光从一定角度入射之后从入射点向四面八方反射,且每个不同方向反射的光的强度相等,而产生漫反射的原因是物体表面的粗糙,导致了这种物理现象的发生。

只有当入射光线与平面垂直的时候才能接受完整的光,入射角度越倾斜损失的能量越大。
除了入射角度,光源与照射点的距离也要考虑,离得越远强度越弱。

其中Kd为漫反射系数,I为入射光强,n,l分别如图中所示为法线向量和入射方向,max是为了剔除夹角大于90°的光。

Phong反射模型(Specular)

phong 反射模型主要是为了计算高光,下图R为光线反射方向,v为人眼观察方向,

其中Ks为镜面反射系数,I为入射光强,r为光源到入射点距离,注意这里在max剔除大于90°的光之后,我们还乘了一个指数p,添加该项的原因很直接,因为离反射光越远就越不应该看见反射光,需要一个指数p加速衰减,α为R和v的夹角,当p越大越准确

Blinn-Phong反射模型

我们将R和v夹角转化为R的半程向量转化为与h的夹角,得到结果十分近似,同时计算速度变快很多。

整体计算公式