结合games202作业一讲解有关Shadow Map及理论
shadowmap Outline
将定向光源当作一个摄像机。原先摄像机 pass 经过的 MVP 变换、光栅化、深度测试,在光源通道同样可以进行。
先经历一遍筛选出离光源最近的点(NDC 空间坐标中,shadowCoord.xy 作为查找索引),再用摄像机视角逐个片元做深度比较。
Outline
总体相关...
- Pass 1:把场景用
uLightMVP画到 shadow map,gl.depthFunc(LEQUAL)自动只保留离光源最近的深度。 - Pass 2:摄像机视角每个片元变换到光空间,在phong的片元着色器采样 shadow map 一个像素的深度,比较一次。
深度测试是 GPU 光栅化时逐像素硬件自动完成的,不需要显式遍历排序:
总复杂度 ≈ 画两遍场景,每个片元多一次纹理采样 + 一次比较。
Shadow map = 从光源位置拍一张「深度照片」。Pass 2 时每个点问一句:「我在光源的照片里,是不是最靠前的那个?」是 → 亮;否 → 阴影。
从directionalLight计算uniform ulightMVP
光源 view 矩阵(lookAt)
把光源当作摄像机,mat4.lookAt :
mat4.lookAt(viewMatrix, this.lightPos, this.focalPoint, this.lightUp);lookAt(gl-matrix note)
本项目用 gl-matrix(函数式
vec3.cross(out, a, b)),不是 Three.js 的实例方法a.crossVectors(b, c)。混用会直接TypeError。
正交投影 ortho 的 near / far
mat4.ortho(projectionMatrix, -111.70, 111.70, -78.98, 78.98, 0.1, 2000);不然要改架构做frustum-based shadow bounds / pancaking(?这段是ai相关, 我没有太多接触过引擎相关的东西)..emm对于作业来说暂时不需要。
ortho
mat4.ortho(left, right, bottom, top, near, far)里的near/far是到光源的距离(正数),不是 view 空间的 z 坐标。
关键换算(若要从 AABB 自动算):
near = -max(viewZ) // 最近距离 = 负的最大 z(view 空间看向 -z,z 越负越远)
far = -min(viewZ)而 AABB 打印出的 z 是 view 空间坐标(可正可负),两者不是同一个量。
以及感觉修复上面硬编码问题
NDC → [0,1] 纹理坐标
vec3 shadowCoord = vPositionFromLight.xyz / vPositionFromLight.w * 0.5 + 0.5;- 透视除法:
/ w(正交投影时 w=1,无影响)。 - NDC [-1,1] → UV/深度 [0,1]:
* 0.5 + 0.5。 .xy用于采样 shadow map,.z用于深度比较。
深度比较与 bias
比较方向(符号是关键)
float useShadowMap(sampler2D shadowMap, vec4 shadowCoord){
float z = unpack(texture2D(shadowMap, shadowCoord.xy));
return step(shadowCoord.z - uBias, z); // 必须是「减」bias
}z(shadow map 存的深度)≈ 当前片元深度shadowCoord.z时,两者几乎相等。- 被照亮:
z > shadowCoord.z - bias→ 1(亮)。 - 被遮挡:
z < shadowCoord.z - bias→ 0(阴影)。
符号一错(用 +bias),所有本该亮的点全部被判为阴影 → 全黑。这是调试中最常见的全黑原因之一。
bias 大小与深度范围绑定
bias 是 NDC 空间的量,实际世界空间大小:
bias_世界 = bias_NDC × (far - near)深度范围越大,同样的 bias 数值在世界空间就显得越大。改 ortho 的 near/far 后要同步重调 bias。
