回顾我们目前整个渲染流程:

- Model 变换:把物体从“模型本地坐标”放到“世界坐标系”里(决定物体在场景中的位置、旋转、缩放)。
- View 变换:用相机的位姿把整个世界“搬到相机面前”,等价于让相机看起来在原点、朝向约定方向(GAMES101 常用朝 -z)。
- Projection 投影:把 3D 场景做透视/正交投影,映射到标准化坐标,再映射到屏幕平面。
- Rasterization 光栅化:把连续的三角形区域离散成屏幕像素(生成片段),并计算每个像素的深度、颜色、纹理坐标等插值信息;最后通过 深度测试(Z-buffer) 决定哪个三角形真正显示在最上面。
当你能正确做投影、光栅化、反走样和消隐后,画面仍然可能“像纸片/像塑料”。为了更真实,我们还缺什么?

Shading着色
字典中的定义:是根据表面材质、光照、法线、观察方向等信息,计算物体表面点或片元最终颜色/亮度的过程。
在本课程中的定义:对不同的物体应用不同的材质 The process of applying a material to an object
- 计算着色的玩意儿叫shader(着色器)
- 以下着色计算都是考虑在一个点上进行计算,要应用到整个模型,则需要在所有点上进行着色计算
一种简单的模型:Blinn-Phong反射模型 A Simple Shading Model (Blinn-Phong Reflectance Model)
Blinn-Phong模型是经验模型,与现实相比仍有很多不足
感性观察 Perceptual Observations

- 高光(Specular highlights):光滑表面镜面反射处附近形成高光
- 漫反射(Diffuse reflection):在整个杯子上变化比较柔和,粗糙表面形成漫反射
- 环境光(Ambient lighting):上图中背光处仍然有颜色,因为其接受了周围环境的间接光照,为了简化模型,我们会使用一个环境光常量来表示环境光对物体的影响
着色具有局部性 Shading is Local
- 着色点(Shading point)在局部上是一个平面,在这个平面上定义:
- 观看方向v(Viewer direction)
- 法线方向n(Surface normal)
- 光照方向l(Light direction)
- 表面参数颜色、光泽度等(Surface parameters color, shininess, …)

意思是看任意一点的着色只看他自己,忽略其他物体的存在,因此着色过程不包括阴影的生成 No shadows will be generated! (shading ≠ shadow)
也就是说着色的结果只是明暗变化,但看不到阴影
漫反射 Diffuse Reflection
兰伯特余弦定理(Lambert’s cosine law)
- 定义:光线打到某一点后,光线均匀的向各个方向散射,表面的颜色在各个观察方向都相同 Light is scattered uniformly in all directions, surface color is the same for all viewing directi
- 多少光被接受与面和光线的夹角有关,根据兰伯特余弦定理(Lambert’s cosine law),单位面积上接收到的光的能量的能量等于光线与法线夹角的余弦
- 太阳能板在板面垂直于光照接受光能最大

因此我们能到一个结论:越正对光源(夹角小),cos 越大,漫反射越亮;越斜,越暗。
光线传播本身会衰减

- I:光源强度(light intensity)
- r:从表面点到光源的距离(distance)。
假设光是均匀发散出去,则在发射出去的一个球面上的光密度跟传播长度r成反比
定义光在传播到单位距离的时候,强度是I,则传播到r处时的能量 = I / r2
兰伯特(漫反射)着色 Lambertian (Diffuse) Shading
计算物体表面的漫反射(diffuse)亮度:

- 漫反射只和物体本身与光线有关,与观察方向v无关
- 引入max是因为若点乘小于0说明是从下面射过来的,这里讨论的是反射不是折射,因此没有意义
- 因为在着色中,法线 \(\boldsymbol{n}\) 和入射方向 \(\boldsymbol{l}\) 通常都被当作单位向量,根据点乘公式 \(\boldsymbol{n} \cdot \boldsymbol{l} = |\boldsymbol{n}||\boldsymbol{l}|\cos\theta\),此时 \(|\boldsymbol{n}|=|\boldsymbol{l}|=1\),所以 \(\boldsymbol{n} \cdot \boldsymbol{l}\) 就等于它们夹角 \(\theta\) 的余弦
- 注意这些向量都是归一化处理过的,因此:若两个向量点乘接近1则离得很近,若接近0则离得很远

kd 决定颜色:
如果 kd=(1,0,0),白光照上去主要反射红色 → 看起来就是红的
如果 kd=(0.2,0.8,0.2),就偏绿色
在一个石膏球上进行漫反射计算的结果
看单个球体随着光源跟法线夹角逐渐变大,颜色会越来越暗
从左到右每个球体的kd值越来越大,
镜面反射 Specular Term (Blinn-Phong)

- 高光的强度取决于观察方向,观察方向与镜面反射方向接近时能看见高光 Intensity depends on view direction, b right near mirror reflection direction
- 观察方向v与镜面反射方向接近说明半程向量h与法线n接近(这样计算更加高效) V close to mirror direction ⇔ half vector near normal
利用半程向量与法向量的点积来判断高光程度 - 在Blinn-Phong模型中镜面反射考虑了光线的衰减(I/r2),但没考虑吸收了多少光(n·l)
经验模型,意思意思就行了 - 使用观察方向和镜面反射方向判断是否接近是Phong模型
- 由于cosα的容忍度太大,导致高光太大,所以引入p次幂来限制高光范围(小高光p大约在100到200),


环境光 Ambient Term
- 环境光会照亮每一点,与直接光照方向,观察方向都无关,是一个常数 Shading that does not depend on anything, add constant color to account for disregarded illumination and fill in black shadows
- 这是一个非常大胆的简化 This is approximate / fake! 后面的全局光照会解决这一点

Blinn-Phong反射模型 Blinn-Phong Reflection Model
前面的讨论都是对物体某一个点进行的计算,最终要对物体所有的点应用后(不一定是所有的点哦,计算量太大了),得到完整的渲染结果

着色频率 Shading Frequencies
逐三角形着色 Shade each triangle (flat shading)
面着色,每个面只有一个法线,进行一次着色计算,应用到该面内所有像素
所以每个面内部的颜色(明暗)没有差异
- 对于平滑的表面表现不是很理想 Not good for smooth surfaces
- 面的法线好算,通过任意两个边做个叉乘就行
逐顶点着色 Shade each vertex (Gouraud shading)
算出每个顶点的法线,然后对每个顶点计算着色,定点外其余部分的颜色通过插值计算出来
- 顶点的法线:
如果知道具体表示的几何体是什么样的,例如用多个顶点表示球体,则每个顶点的法线方向即为球心向顶点连线的方向 Best to get vertex normals from the underlying geometry, e.g. consider a sphere

否则必须从相邻三角形面推断出顶点法线,一个简单的方法是求相邻面法线的平均法线

逐像素着色 Shade each pixel (Phong shading)
- 在每个三角形上插入法线向量 Interpolate normal vectors across each triangle
- 在三角形内部每个像素都插值出一个法线方向,计算每个像素的完整着色模型 Compute full shading model at each pixel
假如在三角形内部,已经知道顶点的法线了,使用顶点法线的重心插值 Barycentric interpolation of vertex normals得到内部平滑过渡的一个法线
最后要对插值方向进行归一化 Don’t forget to normalize the interpolated directions

几种着色频率的对比 Shading Frequenciesy: Face, Vertex or Pixel
- 在模型几何较为复杂时,用简单的逐像素模型也能得到不错的效果,并且开销小
- 在模型几何较为简单时,逐像素效果更好,但性能要求高一些

图形(实时渲染)管线 Graphics (Real-time Rendering) Pipeline
渲染管线:把一个场景经过一系列的处理,最后再显示器上显示一张图像,这一系列过程就叫渲染管线
虽然不够详细,但是流程大概可以描述为以下几个阶段
1、输入一堆三维空间中的点
2、经过MVPV变换后得到这些点在屏幕上的二维坐标
3、规定哪些点相互构成三角形(指定图元拓扑结构)
4、光栅化,得到被三角形覆盖的像素
5、着色计算,计算每个像素该显示什么,应该是什么颜色,Z-buffer的生成等
6、把计算结果存到一个缓存中,输出到显示器

着色器程序 Shader Programs
目前渲染管线都是在GPU中被编程完成了,只有顶点处理和片段处理可以编程。描述对其处理过程的操作的程序就叫shader(本质能在硬件上执行的程序)
着色器会对每个顶点或片元执行一次,只写出单次操作即可 Shader function executes once per fragment.因此不需要写for循环
如果写的是对顶点的操作,就叫vertex shader,如果写的是对像素的操作就叫fragment / pixal shader片元着色器
顶点shader在改什么
- 顶点位置
- 顶点携带的数据
比如你可以让模型:
- 变换位置
- 旋转、缩放
- 做波浪起伏
- 骨骼动画
- 地形抖动
也就是:物体的形状、位置、姿态怎么变。
片元 shader 在改什么
它主要改的是:外观
- 每个像素的颜色
- 亮暗
- 材质观感
比如你可以让物体看起来:
- 是红色还是蓝色
- 有漫反射还是镜面高光
- 像金属还是塑料
- 有没有纹理
- 有没有发光
- 有没有卡通渲染、描边、噪声、火焰、水波纹
以下是一个GLSL片段着色器的程序代码:
uniform sampler2D myTexture; // 获取纹理 uniform是全局变量
uniform vec3 lightDir; //获取光照方向
varying vec2 uv;//获取uv坐标
varying vec3 norm; //获取法线坐标
void diffuseShader()
{
vec3 kd;//获取kd系数
kd = texture2d(myTexture, uv);
kd *= clamp(dot(–lightDir, norm), 0.0, 1.0); //Phong模型漫反射
gl_FragColor = vec4(kd, 1.0); //输出该像素的颜色
}
练习shader:https://www.shadertoy.com
用于执行图形管线的硬件:GPU Graphics Pipeline Implementation: GPUs
- GPU分为独立显卡(Discrete GPU Card)与集成显卡(Integrated GPU)
- 学过高性能计算的话,会知道GPU的并行度高,适合处理图形学的相关计算






