路径追踪中如何柔化硬阴影?附当前实现代码及渲染效果
如何在路径追踪中实现柔和的软阴影
看起来你遇到了路径追踪里硬阴影的问题——这在刚实现直接光照采样时很常见!你的代码已经具备了面光源多采样的基础,但阴影边缘过硬主要是因为采样策略或采样数量的问题,下面我来一步步告诉你怎么修改:
1. 先搞懂硬阴影的根源
你的代码里,对于非delta光源(比如面光源)已经做了多采样,但每个采样点的可见性是全有或全无的二进制判断:要么这个光源采样点完全可见,要么完全被遮挡。当采样数量不足时,阴影边缘就会显得生硬,甚至出现噪点;而点光源(delta光源)本身就是硬阴影,要做软阴影得模拟成小面积光源。
2. 核心修改方案
(1)增加面光源的采样数量
最直接的方法是提升面光源的采样数,比如把ns_area_light从8调整到32、64甚至更高。更多的采样点会让光源的可见区域计算更精准,阴影边缘过渡更平滑。你可以把这个参数做成可配置的,方便在渲染质量和速度之间权衡:
// 调高采样数,也可以做成可配置的全局变量 int num_light_samples = light->is_delta_light() ? 1 : 64; double scale = 1.0 / num_light_samples;
(2)用分层采样替代随机采样(优化阴影质量)
纯随机采样的光源点分布可能不均匀,导致相同采样数下阴影还是有噪点。你可以修改light->sample_L方法,实现分层采样:把光源表面分成均匀的网格,每个网格采样一个点,让采样分布更规则,阴影过渡更自然。
如果你的光源类支持传入采样索引和总采样数,修改后的代码示例:
for (int i = 0; i < num_light_samples; i++) { // 传递采样索引和总数,让光源实现分层采样 Spectrum light_L = light->sample_L(hit_p, &dir_to_light, &dist_to_light, &pdf, i, num_light_samples); Vector3D w_in = w2o * dir_to_light; double cos_theta = std::max(0.0, w_in[2]); // 提前跳过无效采样,提升渲染效率 if (cos_theta == 0 || isect.bsdf->f(w_out, w_in).is_black()) { continue; } Spectrum f = isect.bsdf->f(w_out, w_in); // 调整阴影射线的t范围,避免自相交问题 Ray shadow_ray(hit_p + EPS_D * dir_to_light, dir_to_light, dist_to_light - 2*EPS_D, 0); bool is_visible = !bvh->intersect(shadow_ray); if (is_visible) { L_out += (f * light_L * (cos_theta * scale / pdf)); } }
(3)给点光源模拟软阴影
如果你的场景里有点光源(is_delta_light()返回true),它们天生会产生硬阴影。要给点光源添加软阴影效果,你需要把点光源模拟成一个微小的面光源(比如小圆盘或小矩形),然后取消它的delta光源标记,用多采样来计算光照,这样就能得到柔和的阴影。
3. 额外优化建议
- 调整EPS_D的大小:如果这个值太小,可能会导致阴影射线和自身表面相交;太大则可能漏过近处的遮挡物,建议设置在
1e-4到1e-3之间。 - 渐进式采样:如果高采样数导致渲染时间太长,可以用渐进式采样,逐步增加采样数并实时更新画面,直到达到满意的阴影质量。
- 重要性采样:对于大面光源,可以优先采样靠近视线方向的区域,这样在相同采样数下能得到更平滑的阴影效果。
内容的提问来源于stack exchange,提问作者bin guo
相关产品推荐
相关产品推荐

