Yuyeyyyrender-notes

§ pass · advanced

水晶折射

raymarch SDF 透明几何体(球 ↔ 棱锥混合),三通道折射率偏移产生色散边,菲涅尔 + 琥珀 rim。灵感来自 Shadertoy「Raymarching Primitives」by Inigo Quilez。

返回实验室
§ params
折射率1.40
色散0.01
形状0.50
§ preview
  • SDF 形状混合:sdSphere ↔ sdOctahedron(八面体棱锥)按 u_shape mix,smin 融合主水晶 + 两颗卫星
  • RAYMARCH_STEPS 步 raymarch,命中后 tetrahedral 4 次 SDF 求法线
  • rgb 三通道用略不同的折射率(±u_dispersion)采样程序化背景,产生折射边缘色散
  • 菲涅尔 Schlick(介电质 F0=0.04)混合折射/反射,琥珀 rim 勾边
  • 内部按视线-法线夹角做青绿吸收,正射路径长吸收多
§ fragment source
#version 300 es
precision highp float;

uniform float iTime;
uniform vec2 iResolution;
uniform float u_refraction;
uniform float u_dispersion;
uniform float u_shape;

out vec4 fragColor;

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

#define COL_TEAL  vec3(0.31, 0.82, 0.78)
#define COL_AMBER vec3(0.91, 0.69, 0.29)
#define COL_DARK  vec3(0.02, 0.025, 0.03)

// ---- smooth-min(多项式融合)----
float smin(float a, float b, float k) {
  float h = clamp(0.5 + 0.5 * (a - b) / k, 0.0, 1.0);
  return mix(a, b, h) - k * h * (1.0 - h);
}

float sdSphere(vec3 p, float r) { return length(p) - r; }

// 八面体(双棱锥)SDF —— Inigo Quilez
float sdOctahedron(vec3 p, float s) {
  p = abs(p);
  return (p.x + p.y + p.z - s) * 0.57735027;
}

mat3 rotY(float a) { float c = cos(a), s = sin(a); return mat3(c, 0.0, s, 0.0, 1.0, 0.0, -s, 0.0, c); }
mat3 rotX(float a) { float c = cos(a), s = sin(a); return mat3(1.0, 0.0, 0.0, 0.0, c, -s, 0.0, s, c); }

// 形状混合:u_shape=0 球,1 棱锥
float shapeSDF(vec3 p, float r) {
  return mix(sdSphere(p, r), sdOctahedron(p, r), u_shape);
}

float sceneSDF(vec3 p) {
  // 主水晶:缓慢双轴旋转
  vec3 q = rotY(iTime * 0.2) * rotX(iTime * 0.15) * p;
  float d = shapeSDF(q, 1.1);
  // 卫星 1
  vec3 c1 = vec3(sin(iTime * 0.30) * 1.8, cos(iTime * 0.40) * 1.5, sin(iTime * 0.25) * 1.8);
  vec3 q1 = rotY(iTime * 0.5) * (p - c1);
  d = smin(d, shapeSDF(q1, 0.4), 0.30);
  // 卫星 2
  vec3 c2 = vec3(sin(iTime * 0.27 + 2.0) * 1.7, cos(iTime * 0.33 + 1.5) * 1.3, cos(iTime * 0.31) * 1.7);
  vec3 q2 = rotX(iTime * 0.4) * (p - c2);
  d = smin(d, shapeSDF(q2, 0.35), 0.30);
  return d;
}

// tetrahedral 法线:4 次 SDF(比 6 次差分省 33%)
vec3 calcNormal(vec3 p) {
  vec2 e = vec2(0.0008, 0.0);
  return normalize(
    sceneSDF(p + e.xyy) * vec3( 1.0,  1.0,  1.0)
    + sceneSDF(p + e.yxy) * vec3(-1.0, -1.0,  1.0)
    + sceneSDF(p + e.yyx) * vec3(-1.0,  1.0, -1.0)
    + sceneSDF(p - e.xxx) * vec3( 1.0, -1.0, -1.0)
  );
}

// ---- 程序化背景:天空渐变 + fbm 云 ----
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), f = fract(p);
  f = f * f * (3.0 - 2.0 * f);
  return mix(mix(mix(hash(i), hash(i + vec3(1.0,0.0,0.0)), f.x),
                 mix(hash(i + vec3(0.0,1.0,0.0)), hash(i + vec3(1.0,1.0,0.0)), f.x), f.y),
             mix(mix(hash(i + vec3(0.0,0.0,1.0)), hash(i + vec3(1.0,0.0,1.0)), f.x),
                 mix(hash(i + vec3(0.0,1.0,1.0)), hash(i + vec3(1.0,1.0,1.0)), f.x), f.y), f.z);
}
float fbm(vec3 p) {
  float v = 0.0, a = 0.5;
  for (int i = 0; i < 4; i++) { v += a * noise(p); p *= 2.0; a *= 0.5; }
  return v;
}

vec3 background(vec3 dir) {
  float t = clamp(dir.y * 0.5 + 0.5, 0.0, 1.0);
  // 上方青绿天,下方深底
  vec3 sky = mix(COL_DARK, COL_TEAL * 0.7, smoothstep(0.15, 0.85, t));
  // 琥珀地平线带
  float horizon = smoothstep(0.36, 0.50, t) * (1.0 - smoothstep(0.50, 0.62, t));
  sky = mix(sky, COL_AMBER, horizon * 0.55);
  // fbm 云
  float cloud = fbm(dir * 3.0 + vec3(iTime * 0.02, 0.0, 0.0));
  sky = mix(sky, COL_DARK, smoothstep(0.55, 0.80, cloud) * 0.35);
  return sky;
}

// 安全折射:全反射时退化为反射
vec3 safeRefract(vec3 I, vec3 N, float eta) {
  vec3 r = refract(I, N, eta);
  if (dot(r, r) < 1e-4) r = reflect(I, N);
  return r;
}

void main() {
  vec2 p = uv;
  vec3 ro = vec3(0.0, 0.0, 5.0);
  vec3 rd = normalize(vec3((p - 0.5) * 1.6, -1.6));

  // ---- 主 raymarch:命中水晶外表面 ----
  float t = 0.0;
  bool hit = false;
  for (int i = 0; i < RAYMARCH_STEPS; i++) {
    vec3 pos = ro + rd * t;
    float d = sceneSDF(pos);
    if (d < 0.001) { hit = true; break; }
    t += d;
    if (t > 20.0) break;
  }

  vec3 col;
  if (hit) {
    vec3 pos = ro + rd * t;
    vec3 n = calcNormal(pos);
    vec3 v = -rd;
    float NdotV = max(dot(n, v), 0.0);

    // ---- 三通道折射率偏移:色散 ----
    float etaR = 1.0 / max(u_refraction + u_dispersion, 1.001);
    float etaG = 1.0 / max(u_refraction, 1.001);
    float etaB = 1.0 / max(u_refraction - u_dispersion, 0.5);

    vec3 refR = safeRefract(rd, n, etaR);
    vec3 refG = safeRefract(rd, n, etaG);
    vec3 refB = safeRefract(rd, n, etaB);

    // 三通道分别采样背景的对应通道,色散体现在背景细节的通道偏移
    vec3 bgR = background(refR);
    vec3 bgG = background(refG);
    vec3 bgB = background(refB);
    vec3 refrCol = vec3(bgR.r, bgG.g, bgB.b);

    // 内部青绿吸收:正射路径长(穿过整球)吸收多,掠射路径短吸收少
    float pathLen = max(NdotV, 0.0) * 2.2;
    float absorb = clamp(pathLen * 0.10, 0.0, 0.22);
    refrCol = mix(refrCol, COL_TEAL * 0.35, absorb);

    // ---- 菲涅尔(Schlick,介电质 F0=0.04)----
    float fres = 0.04 + 0.96 * pow(1.0 - NdotV, 5.0);

    // 反射方向采样背景
    vec3 reflDir = reflect(rd, n);
    vec3 reflCol = background(reflDir);

    // 折射 + 反射混合
    col = mix(refrCol, reflCol, fres);

    // ---- 琥珀 rim(边缘光)----
    float rim = pow(1.0 - NdotV, 3.0);
    col += COL_AMBER * rim * 0.7;

    // 琥珀高光(小光源)
    vec3 l = normalize(vec3(0.6, 0.7, 0.4));
    vec3 h = normalize(l + v);
    float spec = pow(max(dot(n, h), 0.0), 80.0);
    col += COL_AMBER * spec * 0.5;
  } else {
    col = background(rd);
  }

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