结合games202作业一讲解有关Shadow Map及理论


shadowmap Outline ​

将定向光源当作一个摄像机。原先摄像机 pass 经过的 MVP 变换、光栅化、深度测试,在光源通道同样可以进行。

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 :

js
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 ​

js
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] 纹理坐标 ​

glsl
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 ​

比较方向(符号是关键) ​

glsl
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。


shadowmap

different shadowmap ​

different shadowmap