§ pass · intermediate
水池反射
水面 fbm 波纹法线扰动 + Fresnel 反射上半屏程序化场景(天空渐变 + 浮球 SDF),平视角反射强、俯视见深青绿水底。
返回实验室§ params
粗糙度0.15
波纹振幅0.01
反射强度1.00
§ preview
- RAYMARCH_STEPS 步 raymarch 水面(fbm 波纹高度场),命中后差分求法线
- 反射向量直接映射采样上半屏 sceneColor(天空 + 浮球),不再 raymarch 反射,性能好
- Fresnel (1-NdotV)^3 控制反射强度,俯视见深青绿水底,平视角反射强
§ fragment source
#version 300 es
precision highp float;
uniform float iTime;
uniform vec2 iResolution;
uniform float u_roughness;
uniform float u_waveAmp;
uniform float u_reflectIntensity;
out vec4 fragColor;
#define uv (gl_FragCoord.xy / iResolution.xy)
float hash(vec2 p) {
return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453);
}
float noise(vec2 p) {
vec2 i = floor(p);
vec2 f = fract(p);
f = f * f * (3.0 - 2.0 * f);
return mix(mix(hash(i), hash(i + vec2(1.0, 0.0)), f.x),
mix(hash(i + vec2(0.0, 1.0)), hash(i + vec2(1.0, 1.0)), f.x), f.y);
}
float fbm(vec2 p) {
float v = 0.0;
float a = 0.5;
for (int i = 0; i < 4; i++) {
v += a * noise(p);
p *= 2.0;
a *= 0.5;
}
return v;
}
// 水面高度场:fbm 双层叠加,缓慢流动
float waveHeight(vec2 p) {
float h = fbm(p * 3.0 + vec2(iTime * 0.15, 0.0)) - 0.5;
h += (fbm(p * 6.0 - vec2(0.0, iTime * 0.1)) - 0.5) * 0.5;
return h * u_waveAmp;
}
vec3 waveNormal(vec2 p) {
vec2 e = vec2(0.02, 0.0);
float hL = waveHeight(p - e.xy);
float hR = waveHeight(p + e.xy);
float hD = waveHeight(p - e.yx);
float hU = waveHeight(p + e.yx);
return normalize(vec3(hL - hR, 2.0, hD - hU));
}
// 浮球着色:从 2D 屏幕偏移重建 3D 球法线, Lambert + Blinn 高光
vec3 ballShade(vec2 d, float r) {
float dd = dot(d, d);
vec3 bn = normalize(vec3(d.x, sqrt(max(r * r - dd, 0.0)), -d.y));
vec3 light = normalize(vec3(0.4, 0.6, -0.5));
float ndl = max(dot(bn, light), 0.0);
vec3 base = mix(vec3(0.31, 0.82, 0.78), vec3(0.91, 0.69, 0.29), pow(ndl, 6.0) * 0.4);
vec3 hh = normalize(light + vec3(0.0, 0.0, 1.0));
base += vec3(0.91, 0.69, 0.29) * pow(max(dot(bn, hh), 0.0), 32.0) * 0.5;
// 边缘暗化(球轮廓深底过渡)
base *= mix(0.55, 1.0, smoothstep(0.0, r * r * 0.7, dd));
return base;
}
// 上半屏程序化场景:天空渐变 + 3 个浮球(2D 投影 + 3D 着色,缓慢上下浮动)
vec3 sceneColor(vec2 s) {
float aspect = iResolution.x / iResolution.y;
// 天空:地平线琥珀,高处青绿,顶部深底
float sh = clamp((s.y - 0.5) * 2.0, 0.0, 1.0);
vec3 sky = mix(vec3(0.91, 0.69, 0.29), vec3(0.31, 0.82, 0.78), smoothstep(0.0, 0.6, sh));
sky = mix(sky, vec3(0.02, 0.025, 0.03), smoothstep(0.85, 1.0, sh) * 0.4);
vec3 col = sky;
// 浮球 1
vec2 c1 = vec2(0.30, 0.78 + sin(iTime * 0.35) * 0.04);
float r1 = 0.11;
vec2 d1 = (s - c1) * vec2(aspect, 1.0);
col = mix(col, ballShade(d1, r1), smoothstep(r1 * r1 + 0.0008, r1 * r1 - 0.0008, dot(d1, d1)));
// 浮球 2
vec2 c2 = vec2(0.58 + sin(iTime * 0.22 + 1.3) * 0.05, 0.82 + cos(iTime * 0.28) * 0.03);
float r2 = 0.08;
vec2 d2 = (s - c2) * vec2(aspect, 1.0);
col = mix(col, ballShade(d2, r2), smoothstep(r2 * r2 + 0.0008, r2 * r2 - 0.0008, dot(d2, d2)));
// 浮球 3
vec2 c3 = vec2(0.78 + sin(iTime * 0.18 + 2.7) * 0.04, 0.74 + sin(iTime * 0.31 + 1.0) * 0.04);
float r3 = 0.09;
vec2 d3 = (s - c3) * vec2(aspect, 1.0);
col = mix(col, ballShade(d3, r3), smoothstep(r3 * r3 + 0.0008, r3 * r3 - 0.0008, dot(d3, d3)));
return col;
}
void main() {
float aspect = iResolution.x / iResolution.y;
vec2 uv2 = uv * 2.0 - 1.0;
uv2.x *= aspect;
vec3 col;
if (uv.y > 0.5) {
// 上半屏:直接画程序化场景
col = sceneColor(uv);
} else {
// 下半屏:水面 raymarch
vec3 ro = vec3(0.0, 0.4, -2.5);
vec3 rd = normalize(vec3(uv2, 1.5));
float t = 0.0;
bool hit = false;
vec3 hitPos = vec3(0.0);
if (rd.y < 0.0) {
for (int i = 0; i < RAYMARCH_STEPS; i++) {
vec3 p = ro + rd * t;
float h = waveHeight(p.xz);
if (p.y < h) { hit = true; hitPos = p; break; }
t += 0.06;
if (t > 20.0) break;
}
}
if (hit) {
vec3 n = waveNormal(hitPos.xz);
vec3 v = -rd;
vec3 r = reflect(rd, n);
// 反射向量映射到上半屏 uv(相机看向 +z,r.xy / r.z 对应屏幕偏移)
vec2 reflectUV = vec2(0.5) + r.xy / max(r.z, 0.1) / 1.5 * 0.5;
reflectUV = clamp(reflectUV, vec2(0.0, 0.5), vec2(1.0, 1.0));
// 粗糙度扰动:抖动反射采样位置
if (u_roughness > 0.0) {
reflectUV += (vec2(hash(hitPos.xz * 3.0), hash(hitPos.xz * 3.0 + 5.2)) - 0.5) * u_roughness * 0.08;
reflectUV = clamp(reflectUV, vec2(0.0, 0.5), vec2(1.0, 1.0));
}
vec3 reflCol = sceneColor(reflectUV);
// Fresnel:平视角反射强,俯视见水底
float fres = pow(1.0 - max(dot(n, v), 0.0), 3.0);
vec3 deep = vec3(0.02, 0.08, 0.10);
col = mix(deep, reflCol, clamp(fres * u_reflectIntensity, 0.0, 1.0));
// 水面琥珀太阳高光
vec3 sun = normalize(vec3(0.3, 0.6, -0.4));
col += vec3(0.91, 0.69, 0.29) * pow(max(dot(r, sun), 0.0), 40.0) * 0.15;
// 远处水面雾化到地平线
float fog = smoothstep(2.0, 12.0, t);
vec3 horizon = vec3(0.91, 0.69, 0.29) * 0.4 + vec3(0.31, 0.82, 0.78) * 0.2;
col = mix(col, horizon, fog * 0.6);
} else {
// 未命中水面(远处地平线带)
col = mix(vec3(0.91, 0.69, 0.29), vec3(0.31, 0.82, 0.78), 0.4) * 0.6;
col = mix(col, vec3(0.02, 0.025, 0.03), smoothstep(0.0, 1.0, -uv2.y) * 0.2);
}
}
col = col / (col + 1.0);
col = pow(col, vec3(1.0 / 2.2));
fragColor = vec4(col, 1.0);
}