概述
这个项目实现了一个令人惊艳的玻璃雨景效果,通过Three.js和GLSL着色器技术,模拟了雨滴在玻璃表面流淌的真实物理现象。效果包含动态雨滴、光线折射、景深模糊以及可选的心形图案等功能。
核心技术架构
1. 基础框架设置
项目使用Three.js的着色器材质系统:
// 使用正交相机创建全屏四边形
camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
// 着色器材质配置
material = new THREE.ShaderMaterial({
uniforms: {
iTime: { value: 0 },
iResolution: { value: new THREE.Vector3(width, height, 1.0) },
rainAmount: { value: 0.7 },
hasHeart: { value: 1.0 },
iChannel0: { value: texture }
},
vertexShader: vertexShader,
fragmentShader: fragmentShader
});
2. 顶点着色器
顶点着色器负责将几何体顶点从模型空间变换到屏幕空间:
varying vec2 vUv;
void main() {
vUv = uv; // 传递UV坐标到片元着色器
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
片元着色器核心算法解析
1. 噪声函数系统
N13函数 - 三维哈希噪声
vec3 N13(float p) {
vec3 p3 = fract(vec3(p) * vec3(.1031,.11369,.13787));
p3 += dot(p3, p3.yzx + 19.19);
return fract(vec3((p3.x + p3.y)*p3.z, (p3.x+p3.z)*p3.y, (p3.y+p3.z)*p3.x));
}
数学原理:
- 使用分数函数
fract()确保输出在[0,1)范围内 - 通过质数乘法和点积操作增加随机性
- 三个分量间的交叉计算确保输出的伪随机性
用途: 为每个雨滴网格生成唯一的随机属性(位置、时间偏移、大小等)
N14函数 - 四维向量噪声
vec4 N14(float t) {
return fract(sin(t*vec4(123., 1024., 1456., 264.))*vec4(6547., 345., 8799., 1564.));
}
特点: 一次性生成四个相关但独立的随机值,提高计算效率
2. 平滑插值函数
Saw函数 - 锯齿波平滑
float Saw(float b, float t) {
return S(0., b, t)*S(1., b, t);
}
数学含义:
S(0., b, t)=smoothstep(0, b, t):从0到b的平滑上升S(1., b, t)=smoothstep(1, b, t):从1到b的平滑下降- 两者相乘产生一个在中间达到峰值的钟形曲线
应用: 控制雨滴的生命周期,实现自然的出现和消失效果
3. 雨滴层渲染算法
DropLayer2函数 - 主雨滴层
这是整个效果的核心函数,模拟雨滴在玻璃上的物理行为:
vec2 DropLayer2(vec2 uv, float t) {
vec2 UV = uv;
// 时间驱动的垂直流动
uv.y += t*0.75;
vec2 a = vec2(6., 1.); // 长宽比控制
vec2 grid = a*2.; // 网格密度
vec2 id = floor(uv*grid); // 网格ID
// 列偏移实现随机起始时间
float colShift = N(id.x);
uv.y += colShift;
id = floor(uv*grid);
vec3 n = N13(id.x*35.2+id.y*2376.1); // 每个网格的随机属性
vec2 st = fract(uv*grid)-vec2(.5, 0); // 网格内局部坐标
// 水平摆动模拟
float x = n.x-.5; // 基础水平位置
float y = UV.y*20.;
float wiggle = sin(y+sin(y)); // 复合正弦波产生自然摆动
x += wiggle*(.5-abs(x))*(n.z-.5); // 边界约束的摆动
x *= .7; // 幅度控制
// 垂直位置动画
float ti = fract(t+n.z); // 时间偏移
y = (Saw(.85, ti)-.5)*.9+.5; // 锯齿波控制雨滴生命周期
vec2 p = vec2(x, y); // 雨滴中心位置
// 主雨滴渲染
float d = length((st-p)*a.yx); // 椭圆距离场
float mainDrop = S(.4, .0, d); // 平滑阶梯函数创建圆形
// 雨痕轨迹计算
float r = sqrt(S(1., y, st.y)); // 垂直衰减因子
float cd = abs(st.x-x); // 到中心线的水平距离
float trail = S(.23*r, .15*r*r, cd); // 轨迹宽度随高度变化
float trailFront = S(-.02, .02, st.y-y); // 轨迹前端淡出
trail *= trailFront*r*r;
// 小水滴效果
y = UV.y;
float trail2 = S(.2*r, .0, cd);
float droplets = max(0., (sin(y*(1.-y)*120.)-st.y))*trail2*trailFront*n.z;
y = fract(y*10.)+(st.y-.5);
float dd = length(st-vec2(x, y));
droplets = S(.3, 0., dd);
float m = mainDrop+droplets*r*trailFront;
return vec2(m, trail); // 返回雨滴遮罩和轨迹强度
}
关键数学概念:
-
距离场(Distance Field):
length((st-p)*a.yx)计算椭圆形距离场- 通过长宽比
a控制雨滴形状
-
平滑阶梯函数:
smoothstep(edge0, edge1, x)实现平滑边界- 比硬边界更真实,避免锯齿效应
-
复合波形:
sin(y+sin(y))创建非规律摆动- 模拟雨滴受风力和重力的复合影响
4. 静态水滴系统
float StaticDrops(vec2 uv, float t) {
uv *= 40.; // 增加密度
vec2 id = floor(uv);
uv = fract(uv)-.5;
vec3 n = N13(id.x*107.45+id.y*3543.654);
vec2 p = (n.xy-.5)*.7;
float d = length(uv-p);
float fade = Saw(.025, fract(t+n.z)); // 闪烁效果
float c = S(.3, 0., d)*fract(n.z*10.)*fade;
return c;
}
作用: 模拟玻璃表面的静态小水珠,增加细节层次
5. 光线折射与模糊效果
法线计算
vec2 e = vec2(.001, 0.);
float cx = Drops(uv+e, t, staticDrops, layer1, layer2).x;
float cy = Drops(uv+e.yx, t, staticDrops, layer1, layer2).x;
vec2 n = vec2(cx-c.x, cy-c.x); // 数值微分计算法线
数学原理:
- 使用有限差分法计算表面法线
- 法线用于计算光线折射偏移
景深模糊
float focus = mix(maxBlur-c.y, minBlur, S(.1, .2, c.x));
vec3 col = textureLod(iChannel0, UV+n, focus).rgb;
物理模拟:
- 根据雨滴密度调整模糊程度
- 使用
textureLod实现多级纹理采样的景深效果
6. 心形特效算法
vec2 hv = uv-vec2(.0, -.1);
hv.x *= .5;
float s = S(110., 70., T);
hv.y-=sqrt(abs(hv.x))*.5*s; // 心形数学方程
heart = length(hv);
heart = S(.4*s, .2*s, heart)*s;
心形方程解析:
- 基于参数方程
y - sqrt(|x|) * scale - 通过时间函数控制心形的出现和消失
渲染管线流程
1. 初始化阶段
// 纹理加载与配置
texture = loader.load('./rain-heart.jpg');
texture.generateMipmaps = true;
texture.minFilter = THREE.LinearMipmapLinearFilter;
2. 实时渲染循环
function animate() {
const time = clock.getElapsedTime() * timeSpeed;
material.uniforms.iTime.value = time;
renderer.render(scene, camera);
}
3. 交互控制系统
- 雨量控制:调整不同雨滴层的强度
- 时间速度:控制动画播放速度
- 心形模式:切换普通/心形特效模式
性能优化策略
1. 纹理优化
- 使用mipmap实现高效的多级纹理采样
- 线性过滤减少采样噪声
2. 计算优化
- 噪声函数使用整数运算避免浮点精度问题
- 分层渲染减少重复计算
3. 内存管理
- 统一缓冲区对象管理GPU资源
- 事件监听器正确绑定和解绑
相关技术:
- 流体模拟:Navier-Stokes方程
- 光线追踪:更精确的折射计算
- 粒子系统:GPU粒子渲染
结语
这个雨景效果展示了现代GPU着色器编程的强大能力,通过数学函数和物理模拟的巧妙结合,在实时渲染中创造出逼真的自然现象。掌握这些技术对于理解现代图形编程和游戏开发具有重要意义。
关键学习要点:
- 噪声生成:程序化内容生成的基础
- 距离场:高效的形状表示方法
- 数值微分:计算图形学中的导数
- 纹理采样:GPU纹理系统的高级应用
- 着色器优化:实时渲染的性能考量