实时阴影技术:PCF与PCSS

PCF (Percentage-Closer Filtering)

1. 这是什么技术?

答:
PCF是阴影抗锯齿的”美颜滤镜”,专门解决硬阴影边缘的锯齿问题。想象用Photoshop给阴影边缘加个羽化效果,但这是在实时渲染中动态计算的!

关键点:

  • 核心思想:对阴影边界进行多次采样取平均
  • 效果:柔化阴影边缘,消除”楼梯状”锯齿
  • 性能:采样次数越多效果越好,但开销越大

举个栗子🌰
就像拍照时手抖了会模糊,PCF故意在阴影边缘制造”可控模糊”来掩盖锯齿。


2. 具体怎么实现的?

答:
分三步走:

  1. 常规阴影贴图:先渲染一张”谁在阴影里”的黑白照片
  2. 采样阶段:对每个像素周围取N个样本
  3. 混合计算:统计这些样本中有多少比例在阴影中
// 简化版PCF着色器代码
float pcfShadow(sampler2D shadowMap, vec2 coords, float compare) {
    float result = 0.0;
    for(int x = -2; x <= 2; x++) {
        for(int y = -2; y <= 2; y++) {
            vec2 offset = vec2(x,y) * 0.001;
            result += texture(shadowMap, coords + offset).r > compare ? 1.0 : 0.0;
        }
    }
    return result / 25.0; // 5x5采样
}

PCSS (Percentage-Closer Soft Shadows)

1. 和PCF有什么区别?

答:
PCSS是PCF的”智能升级版”——不仅柔化边缘,还能根据物体距离自动调整阴影软硬程度!就像专业摄影师会根据主体调整虚化强度。

核心改进:

  • 动态模糊半径:近处锐利,远处柔和
  • 物理正确性:模拟真实光线衰减
  • 视觉效果:更接近现实世界的软阴影

2. 关键技术点是什么?

工作流程:

  1. 遮挡物距离检测:先找到产生阴影的物体有多远
  2. 接收面距离检测:再看被投影的表面有多远
  3. 自适应采样:根据两者距离差决定模糊程度

优化技巧:
✅ 使用分层采样加速计算
✅ 结合硬件PCF指令
✅ 预计算部分参数

// PCSS关键步骤
float findBlockerDistance(sampler2D shadowMap, vec2 coords, float compare) {
    // 实现省略...
}

float pcssShadow(sampler2D shadowMap, vec3 coords) {
    float blockerDistance = findBlockerDistance(shadowMap, coords.xy, coords.z);
    float filterSize = (coords.z - blockerDistance) * LIGHT_SIZE / blockerDistance;
    return pcfShadow(shadowMap, coords.xy, coords.z, filterSize);
}

点光源阴影的奥秘

1. 点光源阴影有什么特别之处?

答:
点光源就像360度发光的灯泡,需要特殊处理:

  • 立方体贴图:要渲染6个方向的阴影贴图
  • 透视问题:远近面需要特殊处理避免扭曲
  • 采样挑战:边缘接缝处容易出问题

优化技巧:
✅ 使用双抛物面投影减少渲染次数
✅ 采用几何着色器一次性渲染6个面
✅ 在接缝处增加采样权重

// 点光源阴影采样示例
float samplePointShadow(samplerCube shadowMap, vec3 lightToFrag) {
    float closestDepth = texture(shadowMap, lightToFrag).r;
    float currentDepth = length(lightToFrag) / far_plane;
    return currentDepth > closestDepth ? 0.0 : 1.0; 
}

硬件自动归面原理

1. 硬件如何自动选择立方体贴图面?

魔法背后的数学:

  1. 向量分析:硬件检查光线方向的最大分量
    • 比如(0.8, -0.2, 0.3) → X轴正方向面
  2. 投影转换:将3D坐标转换为2D面uv
    • 硬件自动完成,给定方向向量投影到该面对应的UV
  3. 面选择:根据向量分析结果选择面
  4. 边界处理:自动处理面与面之间的过渡

为什么这么快?

  • 专用硬件电路并行处理
  • 基于符号和绝对值比较
  • 现代GPU只需1-2个时钟周期

应用cubemap的场景

  • 天空盒子
  • 环境反射
  • 点光源阴影
  • 辐照度贴图
// 模拟归面选择的伪代码
int selectCubeFace(vec3 dir) {
    vec3 absDir = abs(dir);
    if(absDir.x > absDir.y && absDir.x > absDir.z)
        return dir.x > 0 ? POSITIVE_X : NEGATIVE_X;
    // 其他面判断类似...
}

面试常见问题

1. 什么时候该用PCF?什么时候用PCSS?

决策指南:

  • PCF适用场景
    • 移动端等性能受限平台
    • 只需要边缘柔化效果时
  • PCSS适用场景
    • 追求高质量动态光影
    • 有充足GPU算力时

2. 实际项目中的坑?

血泪经验:

  1. 采样模式选择不当会产生噪点
  2. 模糊半径太大会导致性能骤降
  3. 需要处理好透视走样问题
  4. 移动端要注意ES2/3兼容性
  5. 点光源阴影要特别注意接缝处的处理
  6. cube归面时注意浮点精度问题