标准raymarching因固定步长导致采样失衡,真实体积云需结合相函数、多散射与自适应步长;构造密度场应采用世界坐标分形噪声,叠加高度衰减、风向偏移及smoothstep截断;主循环须动态缩放步长并以透射率终止。

为什么不能直接用 raymarching 渲染真实体积云
因为标准 raymarching 对体积密度做均匀步进时,云的光学厚度(optical depth)变化剧烈,固定步长会导致:采样不足(漏掉薄云层)、过采样(浪费算力)、光照不连续(阴影锯齿)。真实体积云必须结合相函数(如 Henyey-Greenstein)、多散射近似、以及自适应步长控制——但手写时可先用单散射 + 距离场启发式步进打基础。
怎么构造一个可 raymarch 的云密度场
纯噪声叠加无法保证视觉连贯性与性能平衡。推荐用分形噪声(fBm)驱动三维密度,但需做三件事:
- 用世界空间坐标(而非屏幕/视图空间)计算噪声,避免视角移动时云“滑动”
- 对噪声值做
smoothstep(0.2, 0.8, noise)截断,再乘以高度衰减因子exp(-worldPos.y / 1000.0)模拟云底抬升 - 在密度计算中引入风向偏移:
vec3 uv = worldPos + time * windDir * 0.1;,否则云静止得像石膏
示例密度函数(GLSL 风格):
float cloudDensity(vec3 p) {
float h = exp(-p.y / 1000.0);
vec3 uv = p + u_time * u_wind;
float n = fbm(uv * 0.005) * 0.5 + fbm(uv * 0.02) * 0.3 + fbm(uv * 0.08) * 0.2;
return h * smoothstep(0.2, 0.8, n);
}
如何写稳定不炸的光线步进主循环
关键不是“走多少步”,而是“每一步走多远”。固定步长(如 0.5)在云边缘会严重漏光;全靠密度判断步长又容易震荡。实用方案是混合:
- 起始步长设为
0.3,之后每步按当前密度反比缩放:stepSize *= (1.0 - density * 0.3); - 加硬上限:单步不超过
2.0,避免跳过整片云 - 提前终止条件用透射率(transmittance)而非单纯距离:
if (transmittance ,其中transmittance *= exp(-density * stepSize * 0.1); - 最大步数限制为
64,防止卡死(尤其在空域或摄像机穿云时)
单散射光照怎么算才不灰不脏
体积光核心是「光源方向是否被遮挡」+「该点相函数权重」。手写时别碰多散射,先搞定太阳光单次散射:
- 对每个采样点,沿光源方向(如
normalize(u_sunDir))再做一次短距 raymarch(16 步),得到光照透射率lightT - 散射贡献 =
density * lightT * phaseFunction(g, viewDir, lightDir),其中phaseFunction用简化版 Henyey-Greenstein:1.0 / (4.0 * PI) * (1.0 - g * g) / pow(1.0 + g * g - 2.0 * g * dot(viewDir, lightDir), 1.5) - 务必把最终颜色累积进
vec3 result = vec3(0.0);并做指数衰减加权,而不是简单相加,否则高密度区发白
容易忽略的点:云本身要参与天空光(ambient light)贡献,但强度必须远低于直射光(比如乘 0.05),否则阴天变正午。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











