Shading

Shading

Visibility

绘画算法:从远到近将物体画在屏幕上,近处的物体覆盖远处的对应的图层;

将模型三角光栅化,考虑对三角形按照距离屏幕的深度排序,复杂度为$O(N\log N)$,但是可能存在相互覆盖的环;

引入Z-Buffer(深度缓存):

  • 为每一个像素保存当前的最小深度;
  • 同时维护两个缓存:帧缓存保存颜色值,深度缓存保存深度

算法如下:

1
2
3
4
5
6
7
8
9
Initialize depth buffer to ∞ 
During rasterization:
for (each triangle T)
for (each sample (x,y,z) in T)
if (z < zbuffer[x,y]) // closest sample so far
framebuffer[x,y] = rgb; // update color
zbuffer[x,y] = z; // update depth
else
; // do nothing, this sample is occluded

image-20241209194451115

假设每个三角形平均覆盖常数个像素,则该算法复杂度为$O(N)$;也就是该算法并不是基于排序的,假设每个三角不会在同一个像素用相同的深度,那么这个算法其实和三角形深度无关的;

Z-Buffer算法是目前最重要的可见性算法,已经在所有的GPU在硬件上实现;缺点是处理不了透明物体;

Blinn-Phong Reflectance Model

在绘画上,使用平行光线或者色块,引入图像的明暗和色彩;

在图形学上,着色指对物品应用某一种材质的过程;

最简单的着色模型是Blinn-Phong Reflectance Model;

观察下图有如下要素

  • Specular highlights: 高光,一般光线照射到光滑的地方,发生接近于镜面反射的效应;

  • Diffuse reflection: 漫反射,一般光线照射到粗糙的地方,其所有查看方向的表面颜色都相同

    image-20241209201146308

  • Ambient lighting:环境照明,物体接收到了间接的环境反射光,一般认为是常量;

image-20241209200203752

建立局部着色模型(Shading in Local)的输入:给定物体表面上一点Shading Point,考察光照照射在该点的输出,不考虑阴影(Shadow)

  • Viewer direction: 观察者看到物体的方向$\hat v$
  • Surface normal:物体表面切面法线向量$\hat n$
  • Light direction:光线方向$\hat l$
  • Surface parameters:物体表面属性,比如颜色,亮度

Diffuse Term

物理上来说,在物体单位面积上,考虑接受到光线能量应该正比于$\cos \theta =\hat l\cdot \hat n$;

image-20241209202325421

这是符合直觉的;

  • 物体被光照直射,理应接收更多光;
  • 物体和光照同向就几乎没有光照射上;

对于一种点光源,光线的能量在某一个时刻集中在一个球壳上(波动性);
$$
I_r\cdot 4\pi r^2=C
$$
考虑能量守恒,光线传播越远,能量衰减(light failoff)越多,用光照强度(intensity)定义能量;
$$
I_r=\frac{I_1}{r^2}
$$
由此建立漫反射模型
$$
L_d = k_d(\frac{I}{r^2})\max(0,\hat n\cdot \hat l)
$$

  • $L_d$:某方向上的漫反射光diffusely reflected light;
  • $k_d$:扩散系数diffuse soefficient,该点可能吸收一部分光而存在损耗,可以表示成三通道RGB值,就可以定义该点反射出来的颜色;
  • $\max(0,\hat n\cdot \hat l)$:不考虑光线从物体下面穿过,不考虑物体透明和折射

对于漫反射来说,不管从任何方向来看看起来都是一样,上式与$\hat v$无关,这说明此模型的合理性;

Specular Term

对于高光项,如果光线照射在比较光滑的物体,那么高光反射的光线应该类似于镜面反射,可以用其半程向量和法线接近描述;

Blinn-Phong模型定义半程向量
$$
\hat h = {\rm bisector}(\hat v, \hat l)=\frac{\hat v+\hat l}{\mid\mid\hat v+\hat l\mid\mid}
$$
类似定义高光项
$$
L_s = k_s(\frac{I}{r^2})\max(0, \hat n\cdot \hat h)^p
$$

  • $L_s$:镜面反射光specularly reflected light
  • $k_s$:亮度系数specular coefficient,相对于漫反射项来说很小
  • $p$:为解决余弦项容忍度过高的问题,一般取128,提高幂次以符合经验:偏离高光光线一点点就基本看不到了;

image-20241210183253418

Ambient Term

大胆假设环境中任意一点接收到环境光基本为常量,环境光着色不依赖于任何其他事物,真实世界可能未必这样;

添加常量颜色以考虑忽略的照明并填充黑色阴影,保证没有地方是完全黑的;
$$
L_a = k_aI_a
$$

  • $L_a$:反射环境光 reflected ambient light
  • $k_a$:环境光系数 ambient coefficient

模型输出为三项相加
$$
\begin{aligned}
L&=L_a + L_d + L_s\
&= k_aI_a + k_d(\frac{I}{r^2})\max(0,\hat n\cdot \hat l) + k_s(\frac{I}{r^2})\max(0, \hat n\cdot \hat h)^p
\end{aligned}
$$

Shading Frequency

对于光栅化的图像,可以按照每个三角形面,顶点,和像素为单位进行着色,它们的着色频率并不相同;

Flat Shading:每一个三角形面对应一个法向量,在光滑面上效果不好;

Gouraud Shading:跨三角形的顶点进行插值着色,每个三角形顶点对应一个法线

Phong Shading:在每个三角形上插值法线向量 ,计算每个像素的完整着色模型

以下是三个方法的对比图

image-20241210185959408

对于光栅化的图形,顶点对应的法线可以用其关联的所有三角形法线平均
$$
\hat N_v= \frac{\sum_{i} \hat N_i}{\mid\mid \sum_i \hat N_i\mid\mid}
$$
image-20241210190324268

Real Time Rendering

Pipline如下

image-20241210190526505

Texture Map

我们希望在物体表面上的不同位置的点定义不同属性,观察到任何三维物体的表面是二维的,反过来我们也可以把一张二维图贴在三维物体的表面,我们把这张二维图称为纹理;

对于纹理图上任意一个点,定义uv坐标系,每一个三角形的顶点对应纹理上的一个坐标$(u,v)$;

image-20241210194556777

一个看起来舒服的纹理,应该看起来上下左右无缝衔接,因为纹理可以被物体重复多次,比如tiled texture;

Barycentric Coordinates

在三角形内部进行插值的工具是重心坐标,重心坐标可以表示三角形内部的一个顶点,从而在三角形内部实现平滑过渡,常常用于纹理坐标,颜色,法向量的计算;

对于三角形$\triangle ABC$, 在三角形内部的任意一点,可以表示为三个顶点坐标的线性组合
$$
(x,y)=\alpha A+\beta B + \gamma C\
\alpha + \beta + \gamma =1
$$
若点在三角形内,还需满足三个系数非负;若无限制条件,表示点在三角形所在平面内;

image-20241212202923346

进一步,定义有向面积$A_A,A_B,A_C$,定义三角形面积$S_{\triangle ABC}$
$$
\alpha = \frac{A_A}{S_{\triangle ABC}},\beta = \frac{A_B}{S_{\triangle ABC}},\gamma = \frac{A_C}{S_{\triangle ABC}}
$$
用叉乘可以如下展开
$$
\alpha = \frac{(y-y_B)(x_C-x_B)-(x-x_B)(y_C-y_B)}{(y_A-y_B)(x_C-x_B)-(x_A-x_B)(y_C-y_B)}\
\beta = \frac{(y-y_C)(x_A-x_C)-(x-x_C)(y_A-y_C)}{(y_B-y_C)(x_A-x_C)-(x_B-x_C)(y_A-y_C)}\
\gamma = \frac{(y-y_A)(x_B-x_A)-(x-x_A)(y_B-y_A)}{(y_C-y_A)(x_B-x_A)-(x_C-x_A)(y_B-y_A)}\
$$
特别的,三角形的重心坐标为$\frac13 A+\frac13B+\frac13C$;

基于任意一点,可以利用重心坐标,根据顶点的属性值,插值成三角形内部的属性值
$$
V=\alpha V_A+\beta V_B + \gamma V_C
$$
image-20241212203915791

但是在投影变换下,重心坐标不保证发生变化,应该在投影之前计算三维空间下的重心坐标,插值之后再做投影变换;

Texture Mapping

基本思路为:对每个光栅化屏幕的采样点(通常为像素的中心),计算对应的纹理的坐标,在纹理上采样,设置样本点的纹理颜色;

像素在纹理上的采样对应的称为纹理元素(texel);

在实际应用时,针对不同场景,往往还要作如下优化:

Bilinear Interpolation

image-20241212205128667

假设像素对应的纹理坐标对应红点,我们考虑周围四个像素进行插值;

定义一维上的线段上的插值
$$
{\rm lerp}(x,v_0,v_1)=v_0 + x(v_1-v_0)
$$
考虑水平偏移$s$和垂直偏移$t$,可以在水平方向和垂直方向各做插值
$$
u_0 = {\rm lerp}(s,u_{00},u_{10})\
u_1 = {\rm lerp}(s,u_{01},u_{11})\
f(x,y) = {\rm lerp} (t,u_0,u_1)
$$
若取16个纹理格子的方法称作Bicubic,对比如下,解决纹理分辨率过低的问题;

image-20241212205708866

Mipmap

对于纹理分辨率过大的情况,可能出现锯齿和摩尔纹的问题;

image-20241212205906862

假设像素对应灰色的四边形,其中心为蓝点,在分辨率相对过大的情况下,可能像素覆盖了许多纹素,仅靠中心点的纹素难以描述整个像素的纹理信息,需要解决在纹理走样上的问题;

image-20241212210254529

抛弃采样的思路,我们尝试解决一类范围查询问题:给定平面上的一块像素区域,获得覆盖纹素的平均值;

引入Mipmap,快速地解决正方形的近似范围查询;

预处理纹理图像,生成$2\times 2$的卷积图像作为下一层,直到得到单个纹素,带来的额外空间开销为原来的$\frac13$;

image-20241212211136030

利用Mipmap我们只需要关注像素对应的区域在Mipmap的哪一层即可,对于任意一个像素块,可以映射到纹理上的某一区域;

image-20241212212116827

考虑像素中心和其邻居中心的距离为一个像素块长度,那么这个区域也近似为一个映射长度$L$的正方形;

image-20241212212234652

image-20241212212336567

映射长度计算如下:
$$
L=\max \left(\sqrt{(\frac{du}{dx})^2+(\frac{dv}{dx})^2},\sqrt{(\frac{du}{dy})^2+(\frac{dv}{dx})^2} \right)
$$
这个近似正方形的纹理平均值所在层数为
$$
D=\log_2 L
$$
在这一层像素对应的区域就近似于该层的一个纹素;

这样的计算代价可能是纹理映射变化不连续,我们考虑下层$\lfloor \log_2L \rfloor$和上一层$\lceil \log_2L \rceil$,进一步作三线性插值(Trilinear Interpolation);

image-20241212213314450

Anisotropic Filtering

使用Mipmap可能会在远处出现过度模糊的问题,可能是近似成正方形区域带来细节丢失严重的问题;

image-20241212213843252

通过各向异性过滤可以缓解Mipmap的问题;

image-20241212213856884

预计算Ripmaps 和总面积表,可以查找轴对齐的矩形区域 ,但是对角线足迹仍然是一个问题,而且带来的开销是原来的3倍;

image-20241212214340625

EWA filtering

通过多次查询,将不规则区域分成若干圆形;

  • 计算加权平均;
  • 仍然维护Mipmap 层次结构;
  • 可以处理不规则的足迹;

image-20241212214424929

Application

在现代GPU中,纹理可以看作一块内存+内存上的范围查询,可以将数据引入片段计算,这实际上是一些物理模型应用的上层抽象;

  • 环境光Environment lighting
  • 存储微观几何体Store microgeometry
  • 程序纹理Procedural textures
  • 实体建模Solid modeling
  • 体积渲染Volume rendering

Environment Lighting

我们需要将纹理描述环境光时,会生成一种环境贴图的特殊纹理,用于描述不同方向的光照信息;

一般认为环境光线来自无穷远处,也就是说,用环境光描述的环境贴图没有深度意义;

我们可以将环境贴图记录在一个球面上,再将球面展开(类似于地球仪和世界地图),称作Spherical Map;

但是Spherical Map潜在问题是靠近极点的位置可能发生扭曲;

一个改进是利用Cube Map,将光照信息存储在一个立方体上,将立方体展开得到6个图片;

对应关系如下:

image-20241226211043946

Store Height/normal

可以用纹理描述物体表面上的凹凸高度变化,生成凹凸/法线贴图(Bump/Normal mapping),用于虚拟物体的表面几何性质(Fake the detailed geometry);

将设没有贴图时物体表面为黑线,黄色的曲线为凹凸贴图记录的高度信息,我们需要根据两个信息重新计算切线和梯度;

image-20241226211905433

通过旋转坐标系的办法,我们假设没有贴图表面上一点的法向量为$(0,0,1)$;

对于贴图上切线,用某个因子衡量贴图对表面的影响程度,对梯度用如下差分作出近似;
$$
\frac{dp}{du}=c_1(h(u+1)-h(u))\
\frac{dp}{dv}=c_2(h(v+1)-h(v))
$$
经过贴图修正过后的法线应该是
$$
\hat n = (-\frac{dp}{du},-\frac{dp}{dv}, 1).{\rm normalized()}
$$
一个更加现代的方法是位移贴图Displacement mapping,实际改变光栅三角形的顶点位置,对比如下,可以看到在边缘处凹凸贴图的Fake之处,它没有改变几何体的形状;

image-20241226213536647

效果更好带来如下代价:模型需要足够细致,使得光栅采样的频率高于纹理变换的频率;

3D Procedural Noise + Solid Modeling + Volumn Rendering

纹理也可以记录一些三维信息,用噪声描述;

image-20241226214350077