
Three.js 实现超酷炫的冰面裂纹效果!附完整源码
📸 效果预览

(冰面在光线下折射出炫目的纹理,宛如北极的冰层)
🚀 在线演示
🤔 前言
冰面效果在游戏和可视化场景中非常常见,比如:
- 冬日主题的游戏场景
- 极地探险的背景
- 冰川相关的艺术作品
这篇文章,我会从 0 开始,一步步带你看这个效果是怎么实现的。跟着我的思路走,保证你看完就能手敲出来!
💻 核心代码解析
第一步:先搭建基础场景
首先,我们需要创建最基本的 Three.js 环境,包括场景、相机和渲染器。
javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import * as THREE from 'three';
// 创建场景
const scene = new THREE.Scene();
// 创建相机
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.z = 5;
// 创建渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
这个是基础中的基础,相信大家都很熟了。
第二步:创建冰面材质
接下来是核心!我们需要创建一个自定义着色器来实现冰面效果。
javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
// 自定义冰面着色器
const iceShaderMaterial = new THREE.ShaderMaterial({
uniforms: {
uTime: { value: 0 },
uResolution: { value: new THREE.Vector2(width, height) },
uIceColor: { value: new THREE.Color(0xa5d8ff) },
uCrackColor: { value: new THREE.Color(0x1a5f7a) }
},
vertexShader: `
varying vec2 vUv;
varying vec3 vPosition;
void main() {
vUv = uv;
vPosition = position;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
uniform float uTime;
uniform vec3 uIceColor;
uniform vec3 uCrackColor;
varying vec2 vUv;
varying vec3 vPosition;
// Simplex噪声函数
vec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
vec2 mod289(vec2 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
vec3 permute(vec3 x) { return mod289(((x*34.0)+1.0)*x); }
float snoise(vec2 v) {
const vec4 C = vec4(0.211324865405187, 0.366025403784439,
-0.577350269189626, 0.024390243902439);
vec2 i = floor(v + dot(v, C.yy));
vec2 x0 = v - i + dot(i, C.xx);
vec2 i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
vec4 x12 = x0.xyxy + C.xxzz;
x12.xy -= i1;
i = mod289(i);
vec3 p = permute(permute(i.y + vec3(0.0, i1.y, 1.0))
+ i.x + vec3(0.0, i1.x, 1.0));
vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy),
dot(x12.zw,x12.zw)), 0.0);
m = m*m; m = m*m;
vec3 x = 2.0 * fract(p * C.www) - 1.0;
vec3 h = abs(x) - 0.5;
vec3 ox = floor(x + 0.5);
vec3 a0 = x - ox;
m *= 1.79284291400159 - 0.85373472095314 * (a0*a0 + h*h);
vec3 g;
g.x = a0.x * x0.x + h.x * x0.y;
g.yz = a0.yz * x12.xz + h.yz * x12.yw;
return 130.0 * dot(m, g);
}
void main() {
// 多层噪声叠加形成冰面纹理
float noise1 = snoise(vUv * 8.0 + uTime * 0.1);
float noise2 = snoise(vUv * 16.0 - uTime * 0.05);
float noise3 = snoise(vUv * 32.0 + uTime * 0.02);
// 生成裂纹图案
float crack = abs(noise1 * noise2);
crack = smoothstep(0.01, 0.03, crack);
// 混合颜色
vec3 color = mix(uIceColor, uCrackColor, crack);
// 添加光照效果
float light = dot(normalize(vec3(1.0, 1.0, 1.0)), vec3(0.0, 0.0, 1.0));
color *= 0.8 + 0.2 * light;
gl_FragColor = vec4(color, 0.9);
}
`,
transparent: true
});
这个着色器实现了:
- 使用多层 Simplex 噪声叠加形成自然的冰面纹理
- 通过噪声相乘生成裂纹图案
- 添加了光照效果让冰面更有质感
第三步:创建冰面几何体并添加动画
现在我们需要创建一个平面几何体,并添加动画让冰面"活"起来。
javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 创建冰面几何体
const geometry = new THREE.PlaneGeometry(8, 8, 64, 64);
const iceMesh = new THREE.Mesh(geometry, iceShaderMaterial);
scene.add(iceMesh);
// 动画循环
function animate() {
requestAnimationFrame(animate);
// 更新着色器时间
iceShaderMaterial.uniforms.uTime.value += 0.01;
// 让冰面微微波动
iceMesh.rotation.x += 0.001;
iceMesh.rotation.y += 0.002;
renderer.render(scene, camera);
}
animate();
第四步:添加交互控制
让用户可以通过 GUI 调整冰面效果参数。
javascript
1
2
3
4
5
6
import { GUI } from 'lil-gui';
// 创建 GUI 控制面板
const gui = new GUI();
gui.addColor(iceShaderMaterial.uniforms.uIceColor, 'value', { name: '冰面颜色' });
gui.addColor(iceShaderMaterial.uniforms.uCrackColor, 'value', { name: '裂纹颜色' });
gui.add(iceShaderMaterial.uniforms.uTime, 'value', 0, 10).name('动画速度');
🔍 关键技术点总结
| 技术点 | 作用 | 掌握难度 |
|---|---|---|
| ShaderMaterial | 自定义着色器,实现冰面效果 | ⭐⭐ |
| Simplex 噪声 | 生成自然的冰面纹理和裂纹 | ⭐⭐⭐ |
| FBM 噪声叠加 | 多层噪声叠加增加细节 | ⭐⭐⭐ |
| 光照模型 | 让冰面更有立体感 | ⭐⭐ |
| lil-gui 控制面板 | 实时调节参数 | ⭐ |
🔧 踩过的坑
坑1:冰面纹理太生硬了!
表现:
一开始我的冰面看起来像一块塑料,一点也不自然,裂纹也很规整。
原因分析:
只使用了一层噪声,而且噪声频率太低了,所以纹理很单调。
解决方案:
使用 FBM(分形布朗运动)技术,将多层不同频率和振幅的噪声叠加起来。这样可以得到更自然、更有层次的纹理。
🎁 完整源码
javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import * as THREE from 'three';
import { GUI } from 'lil-gui';
// 场景初始化
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 5;
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// 冰面着色器材质
const iceShaderMaterial = new THREE.ShaderMaterial({
uniforms: {
uTime: { value: 0 },
uIceColor: { value: new THREE.Color(0xa5d8ff) },
uCrackColor: { value: new THREE.Color(0x1a5f7a) }
},
vertexShader: `...`,
fragmentShader: `...`,
transparent: true
});
// 创建冰面
const geometry = new THREE.PlaneGeometry(8, 8, 32, 32);
const iceMesh = new THREE.Mesh(geometry, iceShaderMaterial);
iceMesh.renderOrder = 1;
scene.add(iceMesh);
// GUI 控制
const gui = new GUI();
gui.addColor(iceShaderMaterial.uniforms.uIceColor, 'value', { name: '冰面颜色' });
gui.addColor(iceShaderMaterial.uniforms.uCrackColor, 'value', { name: '裂纹颜色' });
// 动画循环
function animate() {
requestAnimationFrame(animate);
iceShaderMaterial.uniforms.uTime.value += 0.01;
iceMesh.rotation.y += 0.002;
renderer.render(scene, camera);
}
animate();
📌 总结
通过这篇文章,我们学习了:
- 如何使用
ShaderMaterial创建自定义着色器 - 如何使用 Simplex 噪声函数生成自然的冰面纹理
- 如何通过 FBM 噪声叠加增加纹理细节
- 如何添加 GUI 控制面板实时调节参数
- 遇到性能问题和显示问题时的调试技巧
💡 扩展思路
如果你想让这个效果更酷炫,可以尝试:
- 添加反射效果:使用环境贴图让冰面反射周围场景
- 加入气泡:在冰面内部添加一些大小不一的气泡
- 改变裂纹样式:尝试不同的噪声组合,得到不同的裂纹图案
- 添加积雪:在冰面边缘添加一些积雪效果
- 交互效果:让鼠标经过时产生涟漪或裂纹扩散
🙏 感谢
感谢你看到这里!
如果这篇文章对你有帮助,别忘了点个赞👍,收藏一下⭐,转发给更多需要的人!
有什么问题,欢迎在评论区留言讨论~
本文档由 ThreeLab 编辑整理,如需转载,请注明出处。







京公网安备 11010502038735号
💬 评论区
评论功能即将上线,敬请期待!