大家好!我是 [数擎AI],一位热爱探索新技术的前端开发者,在这里分享前端和 Web3D、AI 技术的干货与实战经验。如果你对技术有热情,欢迎关注我的文章,我们一起成长、进步!
开发领域 :前端开发 | AI 应用 | Web3D | 元宇宙
技术栈 :JavaScript、React、ThreeJs、WebGL、Go
经验经验 :6 年+ 前端开发经验,专注于图形渲染和 AI 技术
开源项目 :AI简历、元宇宙、数字孪生
源码地址: github.com/dezhizhang/...
演示地址: shader.shuqin.cc/xx3fdh
在本文中,我们将深入探讨如何使用 Three.js 和 GLSL(OpenGL Shading Language)编写一个火焰效果的着色器。通过这个教程,你将学习如何创建动态的火焰效果,以及如何将它应用到 3D 渲染中。
着色器简介
着色器是图形渲染管线中的一个重要组成部分,负责处理图形渲染过程中每个像素的颜色计算。通常,着色器分为两个主要部分:
- 顶点着色器(Vertex Shader):用于处理每个顶点的位置和属性。
- 片段着色器(Fragment Shader):用于计算每个像素的颜色和纹理。
在这个示例中,我们将重点关注如何编写片段着色器来模拟火焰的视觉效果。
火焰着色器的结构
我们的火焰着色器由以下几个部分组成:
- Uniforms:这部分包含了着色器所需的外部变量,如时间、分辨率等。
- 顶点着色器:负责传递 UV 坐标给片段着色器。
- 片段着色器:执行火焰效果的核心计算,包括噪声生成、分形布朗运动(FBM)等。
以下是整个着色器的代码实现:
javascript
const flameShader = {
uniforms: {
time: { value: 0 },
resolution: { value: new THREE.Vector2() }
},
vertexShader: `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
precision highp float;
#define NUM_OCTAVES 5
varying vec2 vUv;
uniform float time;
uniform vec2 resolution;
// 生成随机数
float rand(vec2 n) {
return fract(sin(dot(n, vec2(12.9898, 4.1414))) * 43758.5453);
}
// 生成噪声
float noise(vec2 p) {
vec2 ip = floor(p);
vec2 u = fract(p);
u = u * u * (3.0 - 2.0 * u);
float res = mix(
mix(rand(ip), rand(ip + vec2(1.0, 0.0)), u.x),
mix(rand(ip + vec2(0.0, 1.0)), rand(ip + vec2(1.0, 1.0)), u.x),
u.y
);
return res * res;
}
// 分形布朗运动
float fbm(vec2 x) {
float v = 0.0;
float a = 0.5;
vec2 shift = vec2(100);
mat2 rot = mat2(cos(0.5), sin(0.5), -sin(0.5), cos(0.5));
for (int i = 0; i < NUM_OCTAVES; ++i) {
v += a * noise(x);
x = rot * x * 2.0 + shift;
a *= 0.5;
}
return v;
}
void main() {
vec2 shake = vec2(sin(time * 1.5) * 0.01, cos(time * 2.7) * 0.01);
vec2 fragCoord = vUv * resolution;
vec2 p = ((fragCoord + shake * resolution) - resolution * 0.5) / resolution.y;
p *= mat2(8.0, -6.0, 6.0, 8.0);
vec2 v;
vec4 o = vec4(0.0);
float f = 3.0 + fbm(p + vec2(time * 7.0, 0.0));
for (float i = 0.0; i < 50.0; i++) {
v = p + cos(i * i + (time + p.x * 0.1) * 0.03 + i * vec2(11.0, 9.0)) * 5.0
+ vec2(sin(time * 4.0 + i) * 0.005, cos(time * 4.5 - i) * 0.005);
float tailNoise = fbm(v + vec2(time, i)) * (1.0 - (i / 50.0));
vec4 currentContribution = (cos(sin(i) * vec4(1.0, 2.0, 3.0, 1.0)) + 1.0)
* exp(sin(i * i + time)) / length(max(v, vec2(v.x * f * 0.02, v.y)));
float thinnessFactor = smoothstep(0.0, 1.0, i / 50.0);
o += currentContribution * (1.0 + tailNoise * 2.0) * thinnessFactor;
}
o = tanh(pow(o / 1e2, vec4(1.5)));
gl_FragColor = o;
}
`
};
着色器解析
1. 顶点着色器
顶点着色器的任务非常简单,它将 UV 坐标传递给片段着色器,并根据模型视图矩阵和投影矩阵计算每个顶点的位置。
glsl
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
2. 片段着色器
随机数生成与噪声:我们通过 rand 函数生成一个伪随机数,结合 noise 函数生成了基础的噪声,用于创建火焰的动态效果。
分形布朗运动(FBM):使用多个频率和振幅的噪声叠加,生成复杂的纹理和动态效果,这使得火焰的效果更加自然。
动态效果:通过 time 控制火焰的变化,使用不同的参数让火焰不断变化,模拟出自然的火焰流动。
3. 渲染火焰效果
通过循环并叠加每一层的噪声和贡献,我们可以模拟火焰的扩散和衰退。使用 exp 和 tanh 函数对输出进行平滑处理,使得最终的火焰效果看起来更加柔和自然。
如何使用该着色器
-
创建一个材质: 使用 THREE.ShaderMaterial 创建一个自定义材质,将上述着色器代码传递给它。
-
设置 time 和 resolution: time 是一个动态变化的值,用来驱动火焰的动画效果。resolution 则表示画布的尺寸,影响渲染的比例。
-
应用到对象上: 创建一个平面或其他几何体,并将自定义的火焰材质应用到该几何体上。
javascript
const material = new THREE.ShaderMaterial({
uniforms: flameShader.uniforms,
vertexShader: flameShader.vertexShader,
fragmentShader: flameShader.fragmentShader
});
完整代码
js
import * as THREE from 'three';
// 使用示例
const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
const scene = new THREE.Scene();
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth,window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
document.body.appendChild(renderer.domElement);
const flameShader = {
uniforms: {
time: { value: 0 },
resolution: { value: new THREE.Vector2() }
},
vertexShader: `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
precision highp float;
#define NUM_OCTAVES 5
varying vec2 vUv;
uniform float time;
uniform vec2 resolution;
float rand(vec2 n) {
return fract(sin(dot(n, vec2(12.9898, 4.1414))) * 43758.5453);
}
float noise(vec2 p) {
vec2 ip = floor(p);
vec2 u = fract(p);
u = u*u*(3.0-2.0*u);
float res = mix(
mix(rand(ip), rand(ip+vec2(1.0,0.0)), u.x),
mix(rand(ip+vec2(0.0,1.0)), rand(ip+vec2(1.0,1.0)), u.x),
u.y);
return res*res;
}
float fbm(vec2 x) {
float v = 0.0;
float a = 0.5;
vec2 shift = vec2(100);
mat2 rot = mat2(cos(0.5), sin(0.5), -sin(0.5), cos(0.50));
for (int i = 0; i < NUM_OCTAVES; ++i) {
v += a * noise(x);
x = rot * x * 2.0 + shift;
a *= 0.5;
}
return v;
}
void main() {
vec2 shake = vec2(sin(time * 1.5) * 0.01, cos(time * 2.7) * 0.01);
vec2 fragCoord = vUv * resolution;
vec2 p = ((fragCoord + shake * resolution) - resolution * 0.5) / resolution.y;
p *= mat2(8.0, -6.0, 6.0, 8.0);
vec2 v;
vec4 o = vec4(0.0);
float f = 3.0 + fbm(p + vec2(time * 7.0, 0.0));
for(float i = 0.0; i < 50.0; i++) {
v = p + cos(i*i + (time + p.x*0.1)*0.03 + i*vec2(11.0,9.0)) *5.0
+ vec2(sin(time*4.0 + i)*0.005, cos(time*4.5 - i)*0.005);
float tailNoise = fbm(v + vec2(time, i)) * (1.0 - (i/50.0));
vec4 currentContribution = (cos(sin(i)*vec4(1.0,2.0,3.0,1.0)) +1.0)
* exp(sin(i*i + time)) / length(max(v, vec2(v.x*f*0.02, v.y)));
float thinnessFactor = smoothstep(0.0, 1.0, i/50.0);
o += currentContribution * (1.0 + tailNoise*2.0) * thinnessFactor;
}
o = tanh(pow(o/1e2, vec4(1.5)));
gl_FragColor = o;
}
`
};
const mesh = new THREE.Mesh(
new THREE.PlaneGeometry(2, 2),
new THREE.ShaderMaterial({
...flameShader,
blending: THREE.AdditiveBlending,
transparent: true
})
);
scene.add(mesh);
function animate() {
mesh.material.uniforms.time.value = performance.now() / 1000;
mesh.material.uniforms.resolution.value.set(window.innerWidth, window.innerHeight);
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
animate();
总结
通过本文的介绍,你可以了解如何使用 Three.js 和 GLSL 创建一个动态的火焰效果。这个效果是基于噪声和分形布朗运动的,模拟了自然界火焰的动态变化。你可以根据自己的需要调整参数,进一步优化火焰效果。