亮度是从哪儿来的
一句话总结
表面亮度与法线和光线方向夹角的余弦成正比。这就是朗伯模型,只需一次点积即可计算。
为什么需要它
完成前几步后,已经可以用颜色填充三角形。但把立方体涂成一种颜色,只会看到六边形轮廓,没有立体感,因为人类识别形状依靠的是亮度差异。
亮度应如何决定?物理上取决于到达表面的光量。同样强度的光照到倾斜表面时,单位面积接收到的光更少;正面迎光最亮,倾斜 90 度时为 0,这种关系恰好就是余弦。
如何工作
朗伯漫反射按以下方式计算。
n = normalize(표면 법선)
l = normalize(빛이 오는 방향)
k = max(0, dot(n, l))
색 = 기본색 * (ambient + (1 - ambient) * k)
需要 max(0, ...),是因为点积为负表示光从表面背后射来。若直接使用,会得到负亮度并导致颜色越界。ambient 是对其他表面反射光的粗略近似;没有它,背光面会完全变黑,形状也会消失。它并不物理准确,只是无法计算全局照明时的补偿。
下一个问题是从哪里获得法线。每个三角形使用一条法线(面法线),每个面亮度均匀,会呈现棱角分明的效果,适合立方体。若为每个顶点保存法线,再用重心坐标插值(Phong 着色),曲面就会显得平滑,适合用三角形近似球体。
法线必须跟随模型变换一起旋转。 物体旋转时,表面也在旋转。但法线是方向,不是位置,因此应设 w=0 后相乘,否则平移量也会加到法线上,物体离原点越远,照明越异常。这类缺陷只在原点附近测试时不会出现。
动画并不神秘,只需改变角度并重复绘制同一场景。但若每帧把旋转矩阵继续相乘,浮点误差会累积,物体会逐渐变形。更安全的做法是用角度保存状态,并在每帧重新构造矩阵。
仅有朗伯模型无法表现金属或湿润表面的高光,因为漫反射会向各方向均匀反射,与观察角度无关。高光属于镜面反射,必须考虑观察方向。Phong 模型使用光的反射方向与视线方向的夹角,Blinn-Phong 使用光线和视线中间向量与法线的夹角。两者核心都一样:把余弦提高到较大幂次,只让很窄的角度变亮,而指数表示表面光滑程度。
把亮度相乘后的结果直接写入文件也有问题。屏幕中的 0~255 并不与光强成正比,而是经过伽马曲线。物理正确的做法是在全线性空间计算照明,最后再应用伽马;忽略它会让亮处泛白、暗处糊成一团。本实验为简单起见暂不处理,但若结果显得浑浊,应首先检查这里。
在实际项目中
照明方向颠倒,大多数是法线符号错误。三角形顶点绕行顺序相反时,叉积会指向内部,于是迎光面变暗、背光面变亮。诊断方法是把法线映射成颜色:用 (n * 0.5 + 0.5) * 255 绘制,就能直观看到每个面朝向哪里。
另一个问题是忘记归一化。法线长度不为 1 时,点积不再等于余弦,亮度可能成倍增加或衰减。加入非均匀缩放后尤其常见。
还值得区分光源类型。本实验使用方向光,它像太阳一样足够遥远,各位置的方向相同,也不随距离衰减,计算最便宜,阴影也容易处理。点光源则有具体位置,每个表面点都要重新计算方向,并按距离平方反比衰减。聚光灯还会增加角度限制。从方向光开始,是为了把照明计算中方向的作用与距离的作用分开理解。
而且,亮度与阴影是两个问题。朗伯系数为 0 只表示表面背对光线,并不表示被其他物体遮挡。判断遮挡,必须另行确认光线是否能从该点到达光源,这正是阴影贴图或阴影射线的工作。着色器课程最后一项实验会亲手完成这项检查。
下一实验要做什么
构造长方体网格并计算每个面的法线,再用朗伯模型为六个面分别设置不同颜色与亮度。随后每次改变 45 度角绘制八张图,在浏览器中轮播,最后把八张图拼成一张对照表。