使用 Three.js 实现火焰效果

server/2025/2/8 10:23:02/

大家好!我是 [数擎 AI],一位热爱探索新技术的前端开发者,在这里分享前端和 Web3D、AI 技术的干货与实战经验。如果你对技术有热情,欢迎关注我的文章,我们一起成长、进步!
开发领域:前端开发 | AI 应用 | Web3D | 元宇宙
技术栈:JavaScript、React、ThreeJs、WebGL、Go
经验经验:6 年+ 前端开发经验,专注于图形渲染和 AI 技术
演示地址:演示地址
开源项目:智简未来、晓智元宇宙、数字孪生引擎 、源码地址

演示地址:https://shader.shuqin.cc/mdX3zr
源码地址:https://github.com/dezhizhang/shadertoy

引言

在现代 Web 开发中,使用 WebGL 渲染图形已经变得越来越流行。Three.js 作为一个高效的 3D 图形库,简化了 WebGL 的操作,使得创建复杂的 3D 图形变得更加容易。着色器(Shader)是实现视觉效果的核心之一,它允许我们直接控制像素的颜色、位置和其他属性。

在本文中,我们将探讨如何通过自定义的 GLSL (OpenGL Shading Language) 着色器来创建火焰效果,使用 Raymarching 技术来模拟火焰的形态和光辉。

环境设置

首先,我们需要引入 Three.js 并创建一个基础的 WebGL 渲染环境。你可以通过以下代码来实现:

javascript">import * as THREE from 'three';// 创建摄像机
const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
// 创建场景
const scene = new THREE.Scene();
// 创建一个平面几何体作为着色器的应用表面
const geometry = new THREE.PlaneGeometry(2, 2);
// 使用自定义的着色器创建材质
const material = new THREE.ShaderMaterial({...flameShader,  // 引用我们接下来的火焰着色器depthWrite: false,transparent: true
});
// 创建网格对象并将其添加到场景中
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);// 创建 WebGL 渲染器
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

这段代码创建了一个 2D 平面,通过自定义材质和着色器将火焰效果渲染在这个平面上。接下来,我们来详细讲解如何实现火焰效果。

火焰着色器解析

火焰效果的核心在于两个部分:噪声函数和Raymarching。

1. 噪声函数

噪声是模拟自然界中随机现象的一个重要工具。我们使用一个简单的三维噪声函数来生成火焰效果中的随机波动:

float noise(vec3 p) {vec3 i = floor(p);vec4 a = dot(i, vec3(1., 57., 21.)) + vec4(0., 57., 21., 78.);vec3 f = cos((p-i)*acos(-1.))*(-.5)+.5;a = mix(sin(cos(a)*a), sin(cos(1.+a)*(1.+a)), f.x);a.xy = mix(a.xz, a.yw, f.y);return mix(a.x, a.y, f.z);
}

这个函数使用的是 Perlin 噪声的变种,通过对每个像素点进行采样,创建出自然、随机的波动效果。在火焰的表现上,这种噪声将带来动态的变化,模拟出火焰的流动感。

2. 火焰的形态

火焰本身是由许多细小的颗粒组成的,它们随着时间变化而不断变形。我们通过球形方程来定义火焰的形态,并结合噪声来创建动态的效果:

float flame(vec3 p) {float d = sphere(p*vec3(1.,.5,1.), vec4(.0,-1.,.0,1.));return d + (noise(p+vec3(.0,iTime*2.,.0)) + noise(p*3.)*.5)*.25*(p.y);
}

这里,sphere 函数用于创建一个球体的距离场,而火焰的形态则通过 flame 函数在此基础上加入噪声,并且随着时间 (iTime) 变化,使火焰的外观不断变化。

3. Raymarching 技术

Raymarching 是一种基于逐步逼近的方法,用于在复杂的距离场中寻找表面交点。通过在场景中发射光线并不断前进,直到光线与物体表面相交,可以得到物体的轮廓。

vec4 raymarch(vec3 org, vec3 dir) {float d = 0.0, glow = 0.0, eps = 0.02;vec3 p = org;bool glowed = false;for(int i=0; i<64; i++) {d = scene(p) + eps;p += d * dir;if(d > eps) {if(flame(p) < 0.0) glowed = true;if(glowed) glow = float(i)/64.;}}return vec4(p, glow);
}

在这个 Raymarching 循环中,我们通过不断推进光线,并检测其是否与火焰相交,来模拟火焰的光辉效果。glow 变量则控制了火焰的亮度和衰减。

4. 渲染火焰

最后,在着色器的主函数中,我们使用上述的 Raymarching 函数来渲染火焰,并根据计算出的亮度值调整颜色:

void main() {vec2 uv = -1.0 + 2.0 * vUv;uv.x *= iResolution.x/iResolution.y;vec3 org = vec3(0., -2., 4.);vec3 dir = normalize(vec3(uv.x*1.6, -uv.y, -1.5));vec4 p = raymarch(org, dir);float glow = p.w;vec4 col = mix(vec4(1.,.5,.1,1.), vec4(0.1,.5,1.,1.), p.y*.02+.4);gl_FragColor = mix(vec4(0.), col, pow(glow*2.,4.));
}

我们根据计算出的火焰的亮度来混合颜色,使得火焰看起来既明亮又充满动感。

动画与实时更新

为了使火焰效果随时间动态变化,我们需要不断更新着色器中的 iTime 和 iResolution 参数。我们可以通过如下代码来实现动画效果:

javascript">function animate() {requestAnimationFrame(animate);material.uniforms.iTime.value += 0.01;material.uniforms.iResolution.value.set(renderer.domElement.width,renderer.domElement.height);renderer.render(scene, camera);
}
animate();

每一帧我们都会增加 iTime 的值,从而让火焰在屏幕上不断变化。

处理窗口大小变化

为了确保火焰效果在窗口尺寸变化时正确显示,我们需要监听窗口的 resize 事件,并更新渲染器的大小以及着色器的分辨率:

javascript">window.addEventListener('resize', () => {renderer.setSize(window.innerWidth, window.innerHeight);material.uniforms.iResolution.value.set(renderer.domElement.width,renderer.domElement.height);
});

源码

import * as THREE from 'three';const flameShader = {uniforms: {iTime: { value: 0 },iResolution: { value: new THREE.Vector2(1, 1) }},vertexShader: `varying vec2 vUv;void main() {vUv = uv;gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);}`,fragmentShader: `uniform vec2 iResolution;uniform float iTime;varying vec2 vUv;float noise(vec3 p) {vec3 i = floor(p);vec4 a = dot(i, vec3(1., 57., 21.)) + vec4(0., 57., 21., 78.);vec3 f = cos((p-i)*acos(-1.))*(-.5)+.5;a = mix(sin(cos(a)*a), sin(cos(1.+a)*(1.+a)), f.x);a.xy = mix(a.xz, a.yw, f.y);return mix(a.x, a.y, f.z);}float sphere(vec3 p, vec4 spr) {return length(spr.xyz-p) - spr.w;}float flame(vec3 p) {float d = sphere(p*vec3(1.,.5,1.), vec4(.0,-1.,.0,1.));return d + (noise(p+vec3(.0,iTime*2.,.0)) + noise(p*3.)*.5)*.25*(p.y);}float scene(vec3 p) {return min(100.-length(p), abs(flame(p)));}vec4 raymarch(vec3 org, vec3 dir) {float d = 0.0, glow = 0.0, eps = 0.02;vec3 p = org;bool glowed = false;for(int i=0; i<64; i++) {d = scene(p) + eps;p += d * dir;if(d > eps) {if(flame(p) < 0.0) glowed = true;if(glowed) glow = float(i)/64.;}}return vec4(p, glow);}void main() {vec2 uv = -1.0 + 2.0 * vUv;uv.x *= iResolution.x/iResolution.y;vec3 org = vec3(0., -2., 4.);vec3 dir = normalize(vec3(uv.x*1.6, -uv.y, -1.5));vec4 p = raymarch(org, dir);float glow = p.w;vec4 col = mix(vec4(1.,.5,.1,1.), vec4(0.1,.5,1.,1.), p.y*.02+.4);gl_FragColor = mix(vec4(0.), col, pow(glow*2.,4.));}`
};// 使用示例
const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
const scene = new THREE.Scene();
const geometry = new THREE.PlaneGeometry(2, 2);
const material = new THREE.ShaderMaterial({...flameShader,depthWrite: false,transparent: true
});const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);// 更新uniforms
function animate() {requestAnimationFrame(animate);material.uniforms.iTime.value += 0.01;material.uniforms.iResolution.value.set(renderer.domElement.width,renderer.domElement.height);renderer.render(scene, camera);
}animate();// 窗口大小变化处理
window.addEventListener('resize', () => {renderer.setSize(window.innerWidth, window.innerHeight);material.uniforms.iResolution.value.set(renderer.domElement.width,renderer.domElement.height);
});

总结

在这篇博客中,我们通过使用 Three.js 和 GLSL 编写自定义着色器,成功实现了一个动态的火焰效果。通过噪声函数、Raymarching 技术以及实时更新参数,我们能够模拟出真实的火焰效果,并将其渲染到 Web 页面上。希望本教程能为你提供一些灵感,帮助你在自己的项目中实现类似的效果!


http://www.ppmy.cn/server/165921.html

相关文章

美团Leaf分布式ID生成算法深度解析与源码实现

美团Leaf分布式ID生成算法深度解析与源码实现 前言 在分布式系统中&#xff0c;全局唯一ID的生成是核心基础服务。美团点评&#xff08;现美团&#xff09;针对Snowflake算法在运维场景中的痛点&#xff0c;研发了Leaf分布式ID生成系统。本文将从设计原理、源码实现、优化策略…

ASP.NET Core对JWT的封装

目录 JWT封装 [Authorize]的注意事项 JWT封装 NuGet 库 |Microsoft.AspNetCore.Authentication.JwtBearer 9.0.1https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.JwtBearer 配置JWT节点&#xff0c;节点下创建SigningKey、ExpireSeconds两个配置项&am…

4-kafka消费端之分区分配策略

文章目录 概述分区分配策略RangeAssignor分配策略RoundRobinAssignor分配策略StickyAssignor自定义分区分配策略 总结 概述 我们知道kafka的topic可以被分成多个分区&#xff0c;消费者在集群模式下消费时一个消费组内的每个消费者实例只能消费到一个分区的消息&#xff0c;那…

centos7-mini-2009下载docker

重装系统后&#xff0c;装docker真是bug一堆&#xff0c;总结一下&#xff0c;不掉同一坑。 1.卸载旧版 首先如果系统中已经存在旧的Docker&#xff0c;则先卸载&#xff1a; yum remove docker \ docker-client \ docker-client-latest \ docker-common \ docker-latest \ d…

(七)QT——消息事件机制&绘图&文件

目录 前言 消息事件机制 (Event System) 绘图 (Graphics & Drawing) 绘图设备 Qt 提供的主要绘图设备 Qt 主要绘图设备的特点 各个绘图设备的详细介绍 文件处理 (File Handling) 总结 前言 QT 是一个非常强大的图形用户界面&#xff08;GUI&#xff09;开发框架&…

Docker Desktop安装到其他盘

Docker Desktop 默认安装到c盘&#xff0c;占用空间太大了&#xff0c;想给安装到其他盘&#xff0c;网上找了半天的都不对 正确安装命令&#xff1a; start /w "" "Docker Desktop Installer.exe" install --installation-dirF:\docker命令执行成功&am…

LVM 逻辑卷管理器

目录 一、LVM基本概念 二、LVM的优势 三、LVM命令 四、LVM配置实例 1、创建逻辑卷 2、格式化逻辑卷 3、挂载逻辑卷 4、扩展逻辑卷 5、缩减逻辑卷 逻辑卷管理器是Linux核心所提供的逻辑卷管理功能。它在硬盘的硬盘分区之上&#xff0c;又建立一个逻辑层&#xff0c;使得磁…

Spring Boot框架知识总结(超详细)

前言 本篇文章包含Springboot配置文件解释、热部署、自动装配原理源码级剖析、内嵌tomcat源码级剖析、缓存深入、多环境部署等等&#xff0c;如果能耐心看完&#xff0c;想必会有不少收获。 一、Spring Boot基础应用 Spring Boot特征 概念&#xff1a; 约定优于配置&#…