火焰材质 · Fire Material · ▶ 在线运行案例
案例合集: 三维可视化功能案例(threehub.cn)
开源仓库github地址: https://github.com/z2586300277/three-cesium-examples
**400个案例代码: ** 网盘链接

你将学到什么
- ShaderMaterial 自定义着色器实现核心视觉效果
- OrbitControls 相机轨道交互
requestAnimationFrame渲染循环与resize自适应
效果说明
本案例演示 火焰材质 效果:基于 WebGL 实现「火焰材质」可视化效果,附完整可运行源码;核心用到 ShaderMaterial、OrbitControls。建议先打开文首在线案例查看动态画面,再对照下方源码逐步理解。
核心概念
- Scene / Camera / WebGLRenderer 构成最小渲染闭环;大场景可开
logarithmicDepthBuffer缓解 Z-fighting。 - ShaderMaterial 通过
uniforms+ 自定义 GLSL 控制逐像素/逐点效果;透明粒子常配合depthTest: false。 - OrbitControls 提供轨道旋转/缩放;开启
enableDamping后需在 animate 中controls.update()。
实现步骤
- 搭建 Scene、PerspectiveCamera、WebGLRenderer,挂载 canvas 并处理
resize - 定义 uniforms / onBeforeCompile 或 ShaderMaterial,编写 GLSL 与材质参数
- 创建 OrbitControls(及 Raycaster 等交互控件,若源码包含)
- 在
requestAnimationFrame循环中更新状态并 render(Cesium 为viewer.render或自动渲染)
代码要点
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
// 保持FireMaterial类不变
class FireMaterial extends THREE.ShaderMaterial {
constructor() {
super({
defines: { ITERATIONS: '10', OCTIVES: '3' },
uniforms: {
fireTex: { type: 't', value: null },
color: { type: 'c', value: null },
time: { type: 'f', value: 0.0 },
seed: { type: 'f', value: 0.0 },
invModelMatrix: { type: 'm4', value: null },
scale: { type: 'v3', value: null },
noiseScale: { type: 'v4', value: new THREE.Vector4(1, 2, 1, 0.3) },
magnitude: { type: 'f', value: 2.5 },
lacunarity: { type: 'f', value: 3.0 },
gain: { type: 'f', value: 0.6 }
},
vertexShader: `
varying vec3 vWorldPos;
void main() {
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
vWorldPos = (modelMatrix * vec4(position, 1.0)).xyz;
}`,
fragmentShader: `
// 注意:这里我们直接内联noise函数,替代原先的glsl-noise导入
// Simplex 3D noise function
vec3 mod289(vec3 x) {
return x - floor(x * (1.0 / 289.0)) * 289.0;
}
vec4 mod289(vec4 x) {
return x - floor(x * (1.0 / 289.0)) * 289.0;
}
vec4 permute(vec4 x) {
return mod289(((x * 34.0) + 1.0) * x);
}
vec4 taylorInvSqrt(vec4 r) {
return 1.79284291400159 - 0.85373472095314 * r;
}
float snoise(vec3 v) {
const vec2 C = vec2(1.0 / 6.0, 1.0 / 3.0);
const vec4 D = vec4(0.0, 0.5, 1.0, 2.0);
// First corner
vec3 i = floor(v + dot(v, C.yyy));
vec3 x0 = v - i + dot(i, C.xxx);
// Other corners
vec3 g = step(x0.yzx, x0.xyz);
vec3 l = 1.0 - g;
vec3 i1 = min(g.xyz, l.zxy);
vec3 i2 = max(g.xyz, l.zxy);
vec3 x1 = x0 - i1 + C.xxx;
vec3 x2 = x0 - i2 + C.yyy;
vec3 x3 = x0 - D.yyy;
// Permutations
i = mod289(i);
vec4 p = permute(permute(permute(
i.z + vec4(0.0, i1.z, i2.z, 1.0))
+ i.y + vec4(0.0, i1.y, i2.y, 1.0))
+ i.x + vec4(0.0, i1.x, i2.x, 1.0));
// Gradients: 7x7 points over a square, mapped onto an octahedron.
float n_ = 0.142857142857;
vec3 ns = n_ * D.wyz - D.xzx;
vec4 j = p - 49.0 * floor(p * ns.z * ns.z);
vec4 x_ = floor(j * ns.z);
vec4 y_ = floor(j - 7.0 * x_);
vec4 x = x_ * ns.x + ns.yyyy;
vec4 y = y_ * ns.x + ns.yyyy;
vec4 h = 1.0 - abs(x) - abs(y);
vec4 b0 = vec4(x.xy, y.xy);
vec4 b1 = vec4(x.zw, y.zw);
vec4 s0 = floor(b0) * 2.0 + 1.0;
vec4 s1 = floor(b1) * 2.0 + 1.0;
vec4 sh = -step(h, vec4(0.0));
vec4 a0 = b0.xzyw + s0.xzyw * sh.xxyy;
vec4 a1 = b1.xzyw + s1.xzyw * sh.zzww;
vec3 p0 = vec3(a0.xy, h.x);
vec3 p1 = vec3(a0.zw, h.y);
vec3 p2 = vec3(a1.xy, h.z);
vec3 p3 = vec3(a1.zw, h.w);
// Normalise gradients
vec4 norm = taylorInvSqrt(vec4(dot(p0, p0), dot(p1, p1), dot(p2, p2), dot(p3, p3)));
p0 *= norm.x;
p1 *= norm.y;
p2 *= norm.z;
p3 *= norm.w;
// Mix final noise value
vec4 m = max(0.6 - vec4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), 0.0);
m = m * m;
return 42.0 * dot(m * m, vec4(dot(p0, x0), dot(p1, x1), dot(p2, x2), dot(p3, x3)));
}
uniform vec3 color;
uniform float time;
uniform float seed;
uniform mat4 invModelMatrix;
uniform vec3 scale;
uniform vec4 noiseScale;
uniform float magnitude;
uniform float lacunarity;
uniform float gain;
uniform sampler2D fireTex;
varying vec3 vWorldPos;
float turbulence(vec3 p) {
float sum = 0.0;
float freq = 1.0;
float amp = 1.0;
for(int i = 0; i < OCTIVES; i++) {
sum += abs(snoise(p * freq)) * amp;
freq *= lacunarity;
amp *= gain;
}
return sum;
}
vec4 samplerFire (vec3 p, vec4 scale) {
vec2 st = vec2(sqrt(dot(p.xz, p.xz)), p.y);
if(st.x <= 0.0 || st.x >= 1.0 || st.y <= 0.0 || st.y >= 1.0) return vec4(0.0);
p.y -= (seed + time) * scale.w;
p *= scale.xyz;
st.y += sqrt(st.y) * magnitude * turbulence(p);
if(st.y <= 0.0 || st.y >= 1.0) return vec4(0.0);
return texture2D(fireTex, st);
}
vec3 localize(vec3 p) {
return (invModelMatrix * vec4(p, 1.0)).xyz;
}
void main() {
vec3 rayPos = vWorldPos;
vec3 rayDir = normalize(rayPos - cameraPosition);
float rayLen = 0.0288 * length(scale.xyz);
vec4 col = vec4(0.0);
for(int i = 0; i < ITERATIONS; i++) {
rayPos += rayDir * rayLen;
vec3 lp = localize(rayPos);
lp.y += 0.5;
lp.xz *= 2.0;
col += samplerFire(lp, noiseScale);
}
col.a = col.r;
gl_FragColor = col;
}`
})
}
}
// 创建Fire类来替代React组件
class Fire extends THREE.Mesh {
constructor(scale = 7) {
const geometry = new THREE.BoxGeometry();
const material = new FireMaterial();
super(geometry, material);
this.scale.set(scale, scale, scale);
this.material.transparent = true;
this.material.depthWrite = false;
this.material.depthTest = false;
// 初始化uniform值
this.material.uniforms.invModelMatrix.value = new THREE.Matrix4();
this.material.uniforms.scale.value = this.scale;
this.material.uniforms.seed.value = Math.random() * 19.19;
this.material.uniforms.color.value = new THREE.Color(0xeeeeee);
}
update(time) {
// 更新材质
this.updateMatrixWorld();
this.material.uniforms.invModelMatrix.value.copy(this.matrixWorld).invert();
this.material.uniforms.time.value = time;
}
setTexture(texture) {
texture.magFilter = texture.minFilter = THREE.LinearFilter;
texture.wrapS = texture.wrapT = THREE.ClampToEdgeWrapping;
this.material.uniforms.fireTex.value = texture;
}
}
// 初始化场景
function init() {
// 创建场景
const scene = new THREE.Scene();
// 创建相机
const camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, -4, 5);
// 创建渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// 添加轨道控制
const controls = new OrbitControls(camera, renderer.domElement);
// 创建火焰
const fire = new Fire(7);
scene.add(fire);
// 加载纹理
const textureLoader = new THREE.TextureLoader();
textureLoader.load(HOST + 'files/images/fire.png', (texture) => {
fire.setTexture(texture);
});
// 动画循环
const clock = new THREE.Clock();
function animate() {
requestAnimationFrame(animate);
const time = clock.getElapsedTime();
fire.update(time);
renderer.render(scene, camera);
}
// 处理窗口大小变化
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
// 开始动画
animate();
return { scene, camera, renderer, fire };
}
// 启动应用
window.addEventListener('DOMContentLoaded', init);
export { FireMaterial, Fire, init };
完整源码:GitHub
小结
- 本文提供 火焰材质 完整 Three.js 源码与在线 Demo,建议先运行案例再改 uniform/参数做二次实验
- 更多 Three.js 实战案例见 three-cesium-examples 合集 与 GitHub 开源仓库