Yuyeyyyrender-notes

§ pass · advanced

体积云

raymarch 穿过 fbm 云密度场,云本体青绿、薄云边缘前向透射偏琥珀,天空随太阳高度从深青绿渐变到琥珀地平线。灵感来自 Shadertoy「Raymarched Volumetric Clouds」by bigwings。

返回实验室
§ params
云量0.55
风速0.50
太阳高度0.25
§ preview
  • fbm 5 层 value noise 构造云团多尺度结构,风漂流偏移采样坐标
  • 沿射线累加云散射 + transmittance 指数衰减实现体积吸收
  • 薄云边缘前向透射(视线朝太阳时)偏琥珀,厚云本体青绿,模拟 beer's law 透光
  • 天空:深青绿顶 → 琥珀地平线,太阳越低地平线琥珀越强(日落因子)
§ fragment source
#version 300 es
precision highp float;

uniform float iTime;
uniform vec2 iResolution;
uniform float u_cloudCoverage;
uniform float u_windSpeed;
uniform float u_sunHeight;

out vec4 fragColor;

#define uv (gl_FragCoord.xy / iResolution.xy)

// ---- value noise 基础 hash ----
float hash(vec3 p) {
  p = fract(p * 0.3183099 + 0.1);
  p *= 17.0;
  return fract(p.x * p.y * p.z * (p.x + p.y + p.z));
}

float noise(vec3 p) {
  vec3 i = floor(p);
  vec3 f = fract(p);
  f = f * f * (3.0 - 2.0 * f);
  return mix(mix(mix(hash(i + vec3(0,0,0)), hash(i + vec3(1,0,0)), f.x),
                 mix(hash(i + vec3(0,1,0)), hash(i + vec3(1,1,0)), f.x), f.y),
             mix(mix(hash(i + vec3(0,0,1)), hash(i + vec3(1,0,1)), f.x),
                 mix(hash(i + vec3(0,1,1)), hash(i + vec3(1,1,1)), f.x), f.y), f.z);
}

// 5 层 fbm value noise:构造云团多尺度结构(大尺度形状 + 小尺度细节)
float fbm(vec3 p) {
  float v = 0.0;
  float a = 0.5;
  for (int i = 0; i < 5; i++) {
    v += a * noise(p);
    p *= 2.0;
    a *= 0.5;
  }
  return v;
}

// 云密度场:云层集中在 y∈[2,4] 高空带,受风漂流
// u_cloudCoverage 越大阈值越低 → 云越多
float cloudDensity(vec3 p) {
  float heightMask = smoothstep(2.0, 2.6, p.y) * smoothstep(4.0, 3.2, p.y);
  vec3 wind = vec3(iTime * u_windSpeed * 0.25, iTime * u_windSpeed * 0.04, 0.0);
  float n = fbm(p * 0.7 + wind);
  float threshold = 1.0 - u_cloudCoverage * 1.1;
  float d = smoothstep(threshold, threshold + 0.18, n) * heightMask;
  return d;
}

void main() {
  vec2 p = uv * 2.0 - 1.0;
  p.x *= iResolution.x / iResolution.y;

  // 相机在地面,视线略仰视看云层
  vec3 ro = vec3(0.0, 0.0, 0.0);
  vec3 rd = normalize(vec3(p.x * 0.7, p.y * 0.8, -1.0));

  vec3 sunDir = normalize(vec3(0.5, u_sunHeight, -0.4));

  // ---- 天空:深青绿顶 → 琥珀地平线,太阳越低地平线琥珀越强 ----
  float upFactor = clamp(rd.y, 0.0, 1.0);
  float horizonFactor = clamp(1.0 - abs(rd.y) * 2.2, 0.0, 1.0);
  // 日落因子:太阳低时为 1(强琥珀),太阳高时为 0
  float sunsetFactor = 1.0 - smoothstep(-0.2, 0.6, u_sunHeight);

  vec3 deepSky = vec3(0.02, 0.025, 0.03);
  vec3 tealSky = vec3(0.31, 0.82, 0.78);
  vec3 amber = vec3(0.91, 0.69, 0.29);

  vec3 sky = mix(deepSky, tealSky, pow(upFactor, 0.5));
  sky = mix(sky, amber, horizonFactor * (0.3 + 0.7 * sunsetFactor));
  // 视线朝下时回到深底
  sky = mix(sky, deepSky, smoothstep(0.0, -0.3, rd.y));

  // ---- raymarch 体积云:沿射线累加散射 + transmittance 衰减 ----
  vec3 cloudCol = vec3(0.0);
  float trans = 1.0;
  float t = 0.2;
  float stepSize = 0.15;

  for (int i = 0; i < RAYMARCH_STEPS; i++) {
    vec3 pos = ro + rd * t;
    float d = cloudDensity(pos);
    if (d > 0.001) {
      // 云本体青绿(偏暗,受云顶照亮)
      vec3 body = vec3(0.31, 0.82, 0.78) * 0.35;
      // 薄云边缘前向透射偏琥珀:视线越朝太阳、云越薄 → 越偏琥珀
      float thin = clamp(1.0 - d * 2.2, 0.0, 1.0);
      float sunFacing = max(dot(rd, sunDir), 0.0);
      float forward = pow(sunFacing, 3.0);
      vec3 edgeGlow = vec3(0.91, 0.69, 0.29) * (0.25 + 0.75 * forward);
      vec3 c = mix(body, edgeGlow, thin);
      // 云顶被太阳照亮(太阳高时云顶亮,太阳低时整体偏暗)
      float topLit = clamp(dot(sunDir, vec3(0.0, 1.0, 0.0)) * 0.5 + 0.5, 0.0, 1.0);
      c *= 0.55 + 0.45 * topLit;

      cloudCol += c * d * trans * 0.18;
      trans *= exp(-d * 0.55);
    }
    t += stepSize;
    if (trans < 0.02) break;
  }

  // 合成:云散射 + 透射天空
  vec3 col = cloudCol + sky * trans;

  // tonemap + gamma
  col = col / (col + 1.0);
  col = pow(col, vec3(1.0 / 2.2));
  fragColor = vec4(col, 1.0);
}