GAMES101-Lecture-5~6 光栅化

现代计算机图形学入门-闫令琪,课程笔记。

Lecture 5~6 Rasterization 光栅化

  • “光栅化” (Rasterization) 的字面理解
    • Raster:德语中“屏幕”的意思
    • Rasterization:把画面绘制到屏幕上的过程。
  • 对“屏幕”的定义
    • 一个像素点组成的平面(1920x1080)
    • 数学层面可以理解为一个二维数组
  • 对像素 (Pixel) 的定义
    • 字面意思:“picture element” 的缩写
    • 这里我们将像素理解为一个一个只能有一种颜色的小正方形
    • 正方形的颜色由 (red, green, blue) 三原色混合而成
  • 对“屏幕空间” (screen space) 的定义
    • 将屏幕放在一个坐标系内
    • 屏幕左下角是原点,向上 Y 轴,向右 X 轴(虎书中左上角为原点)
    • 此时像素可以使用 (X, Y) 来表示
    • 每个像素的中心点:(x + 0.5, y + 0.5)
    • 屏幕像素个数 (0, 0) ~ (width - 1, height - 1)
    • 屏幕覆盖范围 (0, 0) ~ (width, height)

还记得吗,物体在通过上一章的变换之后,我们得到了一个标准视体。光栅化,就是将标准视体投影至屏幕上的过程。

在这个过程中,我们先忽略 Z 值,先将沿着 Z 轴的面变换至屏幕空间。

很简单的缩放变换,[-1, 1]2 to [0, width] x [0, height],矩阵如下:

Rasterizing Triangles into Pixels

物体的面可以看做是多个多边形(多数是三角形)组合而成的,现在物体已经投影到了屏幕坐标,那么如何使用像素来显示每个三角形?

这里的术语叫采样,意思是将三角形中无限多个点离散化为有限个点。三角形中有无数个连续的点,但我们只关注其中与像素中心坐标所重合的那些点,当我们点亮这些点的时候,屏幕上就会出现一个类似三角形的形状。像素的格子越小,多边形覆盖的像素点就越多,表示我们采样的点就越多,就越能够将三角形还原的更加完整。

按照这个思路,首先一个问题,如何判断一个像素是否落在这个三角形内?判断方法其实前面讲过,向量叉乘!

若 P1P2 x P1Q > 0,则 Q 在 P1P2 的左侧。同理可证 Q 与 P0P1、Q 与 P0P2 的位置关系。

若像素点落在三角形的边上,可自定义处理,影响不大。

另一个问题:每次判断像素位置时候是否需要将屏幕上的所有像素都计算一遍?

其实不需要,一种方法是每次只计算下图蓝色区域即可,这个范围可以通过三角形三个顶点坐标得知,叫做 Axis-aligned bounding boxes(AABB)

但即便如此,我们可以看到还是有将近一半的计算被浪费了,如果碰到下图这样的三角形,浪费的将会更多。

这时可以考虑第二种方法,计算出三角形的一条边最接近的像素点,然后向右逐个扫描,直到另两条边上所接近的像素点,如下图所示:

这个计算方式并不简单,需要考虑很多细节,本课程并没有展开讲。

走样和反走样 Aliasing

刚才我们通过采样所得到的三角形看起来很不像三角形,这个叫走样 (Aliasing),或者叫锯齿,原因是我们进行采样时只是简单的计算了三角形是否覆盖元素的中心点,用有限个点来展示无限个点肯定是存在偏差的,解决走样的过程也就是缩小这个偏差的过程。

除了锯齿,采样还可能造成摩尔纹、车轮倒转的错觉等问题。

那该如何解决因为采样造成的走样问题?也就是常说的反走样/抗锯齿。

工业界使用的抗锯齿方法有很多种,大概可以分成三类:

  • 整体抗锯齿
    • 工作在整个渲染流程中
    • SSAA
  • 前期抗锯齿
    • 光栅化之前处理图像
    • MSAA:多重采样抗锯齿 (Multisampling Antialiasing)
    • HSAA
  • 后期抗锯齿
    • 光栅化之后处理图像
    • EDAA、MLAA、SMAA、FXAA(边缘检测)、TAA(时间维度上多帧交替采样)

课程中讲解的是主流抗锯齿方法 MSAA 的原理

从数学的角度来看,采样时偏差大的原因是采样率太低,或者说信号变化的频率太高,导致采样的部分无法体现出整体的特征,是频率问题。如果我们能解决这个问题,走样也就不存在了。

上图是采样率和函数频率的示意图:相同的采样率,信号频率越高(或者说相对采样率越低)越无法复现函数本身的走势。但我们打交道的是图像,那图像中的频率是指什么?又如何转化成可计算的数学问题?

这一部分老师讲的很跳跃,我另外又查了一些资料,数学水平有限,如有错误可在评论指出。

我们还是把图像看做无数个连续的点,每个点显示不同的颜色,所有的点聚在一起形成图像了的轮廓和色差。假如一张黑白图像,我们把它放在三维坐标系中 X 轴 Y 轴所在的平面,Z 轴表示灰度值,灰度值越高这个点就越亮,我们就能得到一张类似于下面这样的一张 3D 曲面图:

而这样一个曲面图可以看做是在 Y 轴上有无数的函数曲线堆叠而成的。而傅里叶告诉我们,任意一个函数曲线,都可以由多个正弦平面波叠加而成(傅里叶变换)!很反直觉,但数学上确实能证明。

关于“波”的小科普可以查看这篇有趣的博文:Waveforms - pudding.cool

_图中绿色波由粉、紫、蓝三个正弦波叠加而成_
_叠加的正弦波无限多,就可以无限接近脉冲信号(微积分原理)_

此时看到正弦波我们就有些熟悉了,正弦波可以用四个参数来描述:频率、幅度、相位、方向。但是还不够直观,我们将图片进行二维傅里叶变换之后得到他的频谱图(下图右侧)。

注意,上面两张图上各点并不存在一一对应的关系。右侧图中每个亮点表示左侧图像上某一点与邻域点灰度值差异的强弱(梯度值),频谱图中亮点越多,表示整个图像的各个点之间的灰度值梯度更大,图片对比度更高更锐利;相反则整个图像更柔和。

傅里叶变换是可逆的,我们去掉部分低频波,只留下高频波之后可以得到下图左侧的图像。

去掉高频留下低频之后可以得到下面的图像:

可以看到,留下低频图像整体更模糊了,留下高频边缘更明显了。(高频决定了细节部分,低频决定总体形状)此时如果我们去掉与采样率差值比较大的正弦波,采样就会更准确,减少走样。

这里参考了 图像的傅里叶变换 - CSDN

好,知道可以使用滤波对信号进行处理之后,那如何实现滤波?卷积运算!

卷积运算:对于图像的每一个像素点,计算它的邻域像素和滤波器矩阵的对应元素的乘积,然后加起来,作为该像素位置的值。计算时使用的卷积核不同,可以过滤掉不同频率的波,得到不同结果。

_卷积计算过程示意图_

上图中“输入矩阵的窗口”其实可以看做是屏幕上每个像素被分成了九个小格子,我们通过对这九个小格子进行卷积运算,就可以得到一个平均值。这也是 MSAA 抗锯齿方法的原理。实际上一个像素可以被分为任意份,份数越多计算的越准确,当然计算量也就越大。

_MSAA 算法示意图_
_卷积操作之后的图像边缘梯度值更小,整体更柔和,更不容易走样_

这一部分参考了 图像卷积与滤波的一些知识点 - CSDN

Z-Buffer

解决了走样的问题,现在我们来考虑 Z 轴的问题:每个物体和相机的距离不一样,投影到屏幕上时,如何确定哪个在前哪个在后?

解决方法一:画家算法。

很直观的一个想法,跟画油画一样,先画最远处的再画近处的,近处物体就会把远处物体遮挡住。

但这个算法无法处理以下问题:当物体互相遮挡的时候!

所以就出现了第二种解决办法:Z-Buffer(深度缓冲)

  1. 以像素为单位,准备深度缓存器 (z-buffer,二维数组或另一张图),每个像素初始值为最大深度 ∞;
  2. 依次渲染所有物体(顺序不重要);
  3. 每渲染一个物体,检测像素点是否有投影物体,有的话比较投影物体在该像素点的 z-value 和在 z-buffer 中的值的大小,将 z-buffer 中的值更新为较小的那个值;

两个注意点:

  1. 一个物体由多个三角形组成,寻找最小值的算法复杂度为 O(n),因为只要最小值,而不是排序;
  2. 物体渲染的先后顺序无关紧要;