Shading
Shading
Visibility
绘画算法:从远到近将物体画在屏幕上,近处的物体覆盖远处的对应的图层;
将模型三角光栅化,考虑对三角形按照距离屏幕的深度排序,复杂度为$O(N\log N)$,但是可能存在相互覆盖的环;
引入Z-Buffer(深度缓存):
- 为每一个像素保存当前的最小深度;
- 同时维护两个缓存:帧缓存保存颜色值,深度缓存保存深度
算法如下:
1 | Initialize depth buffer to ∞ |
假设每个三角形平均覆盖常数个像素,则该算法复杂度为$O(N)$;也就是该算法并不是基于排序的,假设每个三角不会在同一个像素用相同的深度,那么这个算法其实和三角形深度无关的;
Z-Buffer算法是目前最重要的可见性算法,已经在所有的GPU在硬件上实现;缺点是处理不了透明物体;
Blinn-Phong Reflectance Model
在绘画上,使用平行光线或者色块,引入图像的明暗和色彩;
在图形学上,着色指对物品应用某一种材质的过程;
最简单的着色模型是Blinn-Phong Reflectance Model;
观察下图有如下要素
Specular highlights: 高光,一般光线照射到光滑的地方,发生接近于镜面反射的效应;
Diffuse reflection: 漫反射,一般光线照射到粗糙的地方,其所有查看方向的表面颜色都相同
Ambient lighting:环境照明,物体接收到了间接的环境反射光,一般认为是常量;
建立局部着色模型(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$;
这是符合直觉的;
- 物体被光照直射,理应接收更多光;
- 物体和光照同向就几乎没有光照射上;
对于一种点光源,光线的能量在某一个时刻集中在一个球壳上(波动性);
$$
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,提高幂次以符合经验:偏离高光光线一点点就基本看不到了;
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:在每个三角形上插值法线向量 ,计算每个像素的完整着色模型
以下是三个方法的对比图
对于光栅化的图形,顶点对应的法线可以用其关联的所有三角形法线平均
$$
\hat N_v= \frac{\sum_{i} \hat N_i}{\mid\mid \sum_i \hat N_i\mid\mid}
$$
Real Time Rendering
Pipline如下
Texture Map
我们希望在物体表面上的不同位置的点定义不同属性,观察到任何三维物体的表面是二维的,反过来我们也可以把一张二维图贴在三维物体的表面,我们把这张二维图称为纹理;
对于纹理图上任意一个点,定义uv坐标系,每一个三角形的顶点对应纹理上的一个坐标$(u,v)$;
一个看起来舒服的纹理,应该看起来上下左右无缝衔接,因为纹理可以被物体重复多次,比如tiled texture;
Barycentric Coordinates
在三角形内部进行插值的工具是重心坐标,重心坐标可以表示三角形内部的一个顶点,从而在三角形内部实现平滑过渡,常常用于纹理坐标,颜色,法向量的计算;
对于三角形$\triangle ABC$, 在三角形内部的任意一点,可以表示为三个顶点坐标的线性组合
$$
(x,y)=\alpha A+\beta B + \gamma C\
\alpha + \beta + \gamma =1
$$
若点在三角形内,还需满足三个系数非负;若无限制条件,表示点在三角形所在平面内;
进一步,定义有向面积$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
$$
但是在投影变换下,重心坐标不保证发生变化,应该在投影之前计算三维空间下的重心坐标,插值之后再做投影变换;
Texture Mapping
基本思路为:对每个光栅化屏幕的采样点(通常为像素的中心),计算对应的纹理的坐标,在纹理上采样,设置样本点的纹理颜色;
像素在纹理上的采样对应的称为纹理元素(texel);
在实际应用时,针对不同场景,往往还要作如下优化:
Bilinear Interpolation
假设像素对应的纹理坐标对应红点,我们考虑周围四个像素进行插值;
定义一维上的线段上的插值
$$
{\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,对比如下,解决纹理分辨率过低的问题;
Mipmap
对于纹理分辨率过大的情况,可能出现锯齿和摩尔纹的问题;
假设像素对应灰色的四边形,其中心为蓝点,在分辨率相对过大的情况下,可能像素覆盖了许多纹素,仅靠中心点的纹素难以描述整个像素的纹理信息,需要解决在纹理走样上的问题;
抛弃采样的思路,我们尝试解决一类范围查询问题:给定平面上的一块像素区域,获得覆盖纹素的平均值;
引入Mipmap,快速地解决正方形的近似范围查询;
预处理纹理图像,生成$2\times 2$的卷积图像作为下一层,直到得到单个纹素,带来的额外空间开销为原来的$\frac13$;
利用Mipmap我们只需要关注像素对应的区域在Mipmap的哪一层即可,对于任意一个像素块,可以映射到纹理上的某一区域;
考虑像素中心和其邻居中心的距离为一个像素块长度,那么这个区域也近似为一个映射长度$L$的正方形;
映射长度计算如下:
$$
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);
Anisotropic Filtering
使用Mipmap可能会在远处出现过度模糊的问题,可能是近似成正方形区域带来细节丢失严重的问题;
通过各向异性过滤可以缓解Mipmap的问题;
预计算Ripmaps 和总面积表,可以查找轴对齐的矩形区域 ,但是对角线足迹仍然是一个问题,而且带来的开销是原来的3倍;
EWA filtering
通过多次查询,将不规则区域分成若干圆形;
- 计算加权平均;
- 仍然维护Mipmap 层次结构;
- 可以处理不规则的足迹;
Application
在现代GPU中,纹理可以看作一块内存+内存上的范围查询,可以将数据引入片段计算,这实际上是一些物理模型应用的上层抽象;
- 环境光Environment lighting
- 存储微观几何体Store microgeometry
- 程序纹理Procedural textures
- 实体建模Solid modeling
- 体积渲染Volume rendering
Environment Lighting
我们需要将纹理描述环境光时,会生成一种环境贴图的特殊纹理,用于描述不同方向的光照信息;
一般认为环境光线来自无穷远处,也就是说,用环境光描述的环境贴图没有深度意义;
我们可以将环境贴图记录在一个球面上,再将球面展开(类似于地球仪和世界地图),称作Spherical Map;
但是Spherical Map潜在问题是靠近极点的位置可能发生扭曲;
一个改进是利用Cube Map,将光照信息存储在一个立方体上,将立方体展开得到6个图片;
对应关系如下:
Store Height/normal
可以用纹理描述物体表面上的凹凸高度变化,生成凹凸/法线贴图(Bump/Normal mapping),用于虚拟物体的表面几何性质(Fake the detailed geometry);
将设没有贴图时物体表面为黑线,黄色的曲线为凹凸贴图记录的高度信息,我们需要根据两个信息重新计算切线和梯度;
通过旋转坐标系的办法,我们假设没有贴图表面上一点的法向量为$(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之处,它没有改变几何体的形状;
效果更好带来如下代价:模型需要足够细致,使得光栅采样的频率高于纹理变换的频率;
3D Procedural Noise + Solid Modeling + Volumn Rendering
纹理也可以记录一些三维信息,用噪声描述;