5月29日 01:40

WebGL 中的阴影(Shadow)是如何实现的?

WebGL 阴影的主流实现方式是阴影贴图(Shadow Mapping):先从光源视角渲染场景生成深度图,再从相机视角渲染时将每个片段变换到光源空间,比较其深度与深度图中的值——若片段深度更大则处于阴影中。软阴影通过 PCF(Percentage Closer Filtering)对深度图多次采样取平均实现。两大典型问题:阴影痤疮(shadow acne)由深度精度误差导致,用基于法线-光线夹角的 bias 修复;彼得潘效应(peter panning)由 bias 过大导致阴影脱离物体,改用正面剔除渲染深度图解决。大场景用级联阴影贴图(CSM)按距离分配不同分辨率。

追问

  • 为什么阴影贴图会产生阴影痤疮?bias 值如何根据表面法线和光线方向动态计算?
  • PCF 软阴影的采样半径越大越模糊,但会带来什么性能问题?Poisson Disk 采样有何优势?
  • 级联阴影贴图(CSM)如何划分级联?级联接缝处的接缝问题怎么处理?
  • 点光源阴影为什么用立方体贴图?渲染立方体阴影贴图需要几次 draw call?
  • WebGL 1.0 没有深度纹理附件,如何用 RGBA 编码深度值?精度损失如何补偿?

写段代码

glsl
// PCF 软阴影核心逻辑 float calcShadow(vec4 lightPos, sampler2D shadowMap, vec3 normal, vec3 lightDir) { vec3 proj = lightPos.xyz / lightPos.w * 0.5 + 0.5; float bias = max(0.05 * (1.0 - dot(normal, lightDir)), 0.005); float shadow = 0.0; vec2 texel = 1.0 / vec2(textureSize(shadowMap, 0)); for (int x = -1; x <= 1; x++) for (int y = -1; y <= 1; y++) { float d = texture(shadowMap, proj.xy + vec2(x,y)*texel).r; shadow += proj.z - bias > d ? 1.0 : 0.0; } return shadow / 9.0; }
标签:WebGL