实时阴影技术:PCF与PCSS
PCF (Percentage-Closer Filtering)
1. 这是什么技术?
答:
PCF是阴影抗锯齿的”美颜滤镜”,专门解决硬阴影边缘的锯齿问题。想象用Photoshop给阴影边缘加个羽化效果,但这是在实时渲染中动态计算的!
关键点:
- 核心思想:对阴影边界进行多次采样取平均
- 效果:柔化阴影边缘,消除”楼梯状”锯齿
- 性能:采样次数越多效果越好,但开销越大
举个栗子🌰:
就像拍照时手抖了会模糊,PCF故意在阴影边缘制造”可控模糊”来掩盖锯齿。
2. 具体怎么实现的?
答:
分三步走:
- 常规阴影贴图:先渲染一张”谁在阴影里”的黑白照片
- 采样阶段:对每个像素周围取N个样本
- 混合计算:统计这些样本中有多少比例在阴影中
// 简化版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. 关键技术点是什么?
工作流程:
- 遮挡物距离检测:先找到产生阴影的物体有多远
- 接收面距离检测:再看被投影的表面有多远
- 自适应采样:根据两者距离差决定模糊程度
优化技巧:
✅ 使用分层采样加速计算
✅ 结合硬件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. 硬件如何自动选择立方体贴图面?
魔法背后的数学:
- 向量分析:硬件检查光线方向的最大分量
- 比如(0.8, -0.2, 0.3) → X轴正方向面
- 投影转换:将3D坐标转换为2D面uv
- 硬件自动完成,给定方向向量投影到该面对应的UV
- 面选择:根据向量分析结果选择面
- 边界处理:自动处理面与面之间的过渡
为什么这么快?
- 专用硬件电路并行处理
- 基于符号和绝对值比较
- 现代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. 实际项目中的坑?
血泪经验:
- 采样模式选择不当会产生噪点
- 模糊半径太大会导致性能骤降
- 需要处理好透视走样问题
- 移动端要注意ES2/3兼容性
- 点光源阴影要特别注意接缝处的处理
- cube归面时注意浮点精度问题