当前位置: 首页 > news >正文

使用 Three.js 实现流光特效

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

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

在本文中,我们将深入探讨如何使用 Three.js 和 GLSL(OpenGL Shading Language)编写一个火焰效果的着色器。通过这个教程,你将学习如何创建动态的火焰效果,以及如何将它应用到 3D 渲染中。

着色器简介

着色器是图形渲染管线中的一个重要组成部分,负责处理图形渲染过程中每个像素的颜色计算。通常,着色器分为两个主要部分:

  • 顶点着色器(Vertex Shader):用于处理每个顶点的位置和属性。
  • 片段着色器(Fragment Shader):用于计算每个像素的颜色和纹理。

在这个示例中,我们将重点关注如何编写片段着色器来模拟火焰的视觉效果。

火焰着色器的结构

我们的火焰着色器由以下几个部分组成:

  1. Uniforms:这部分包含了着色器所需的外部变量,如时间、分辨率等。
  2. 顶点着色器:负责传递 UV 坐标给片段着色器。
  3. 片段着色器:执行火焰效果的核心计算,包括噪声生成、分形布朗运动(FBM)等。

以下是整个着色器的代码实现:

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 坐标传递给片段着色器,并根据模型视图矩阵和投影矩阵计算每个顶点的位置。

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 则表示画布的尺寸,影响渲染的比例。

  • 应用到对象上: 创建一个平面或其他几何体,并将自定义的火焰材质应用到该几何体上。

const material = new THREE.ShaderMaterial({
    uniforms: flameShader.uniforms,
    vertexShader: flameShader.vertexShader,
    fragmentShader: flameShader.fragmentShader
});

完整代码

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 创建一个动态的火焰效果。这个效果是基于噪声和分形布朗运动的,模拟了自然界火焰的动态变化。你可以根据自己的需要调整参数,进一步优化火焰效果。

如果你也对shader和AI感兴趣,欢迎关注我们公众号数擎Ai

相关文章:

  • Hutool - Log:自动识别日志实现的日志门面
  • 解释性语言与编译性语言
  • 解决 Vue.js 中使用 vue-print-nb 打印一页的问题
  • 现代企业软件测试人员需求与发展方向深度解析
  • 独立开发者之SEO基础:dofollow和 nofollow
  • 【部署优化十五】【深度揭秘《DeepSeek安全审计:OWASP Top10防护方案》】
  • Redis基础学习
  • 对计算机中缓存的理解和使用Redis作为缓存
  • dockerfile 使用环境变量
  • 【Java高级篇】——第16篇:高性能Java应用优化与调优
  • 当 OpenAI 不再 open,DeepSeek 如何掀起 AI 开源革命?
  • 装箱和拆箱是什么?(C#)
  • 关于Java 反射的简单易懂的介绍
  • 一文熟练掌握Spring Framework
  • 以初学者视角探索智能体学习之旅
  • 新书上线 |《零门槛AIGC应用实战——Serverless+AI 轻松玩转高频AIGC场景》免费下载
  • upload-labs靶场
  • Spring Boot中整合Flink CDC 数据库变更监听器来实现对MySQL数据库
  • 【Python爬虫(48)】分布式爬虫:解锁多领域数据宝藏的密码
  • java Web
  • 中华人民共和国和俄罗斯联邦关于全球战略稳定的联合声明
  • 上海质子重离子医院二期项目启动,有望成为全世界最大粒子治疗中心
  • 绿城房地产集团:近半年累计花费20.6亿元购买旗下债券
  • 金融监管总局:近五年民企贷款投放年平均增速比各项贷款平均增速高出1.1个百分点
  • 安徽六安原市长潘东旭,已任省市场监督管理局党组书记、局长
  • 印方称若巴方决定升级局势,印方已做好反击准备