Three.js 实现云状特效

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

在这里插入图片描述

在本文中,我们将从一个基于 GLSL 的云状特效 shader 出发,讲解 shader 的实现过程和所涉及的数学原理。

一、Shader 实现细节解析

1. 宏定义与时间控制

我们在 shader 开始部分定义了两个常用的数学常量:

#define PI2 6.28318530718
#define PI 3.1416
  • 同时,iTime 作为 uniform 变量传入,使得图案能随时间动态变化,从而实现动画效果。

2. 坐标变换与周期函数

在 vorocloud 函数中,首先通过下面这行代码对输入坐标 p 进行周期性处理:

vec2 pp = cos(vec2(p.x * 14.0, (16.0 * p.y + cos(floor(p.x * 30.0)) + iTime * PI2)));
  • 周期性函数:利用 cos 函数生成周期性变化,调整水平方向和垂直方向上的频率。
  • 离散扰动:通过 floor 与 cos 的组合,引入了不规则性,使得最终效果更接近自然云彩的随机分布。
  • 时间因子:引入 iTime,使图案随时间平滑过渡,产生动画效果。

3. 非线性空间扭曲

接下来对坐标 p 进行非线性扭曲:

p = cos(p * 12.1 + pp * 10.0 + 0.5 * cos(pp.x * 10.0));

这种多重余弦变换使得原始坐标发生复杂扰动,从而获得更为自然和丰富的纹理变化。

4. 计算距离并确定最小值

我们预设了 4 个参考点:

vec2 pts[4];
pts[0] = vec2(0.5, 0.6);
pts[1] = vec2(-0.4, 0.4);
pts[2] = vec2(0.2, -0.7);
pts[3] = vec2(-0.3, -0.4);

通过遍历这些点,并根据当前计算得到的 pp 坐标计算欧几里得距离:


float d = 5.0;
for(int i = 0; i < 4; i++){
pts[i].x += 0.03 _ cos(float(i)) + p.x;
pts[i].y += 0.03 _ sin(float(i)) + p.y;
d = min(d, distance(pts[i], pp));
}

数学上,我们使用的欧几里得距离公式为:

  • 这里,d 表示当前点与各参考点中最接近的距离。

5. 指数衰减与量化

在获得最小距离后,通过以下公式计算最终的亮度因子:

f = 2.0 * pow(1.0 - 0.3 * d, 13.0);
f = min(f, 1.0);

数学公式推导如下:

  • 线性衰减:首先计算 1.0 - 0.3 * d,使得距离越大,值越小。

  • 非线性提升:对上述值取 13 次幂,公式为:

这种处理使得靠近目标区域的值迅速趋向于 1,而远离区域迅速衰减到 0。

放缩与截断:最终乘以 2.0 并用 min 限制最大值为 1.0,确保亮度值始终在 [0, 1] 区间内。

6. 最终图像构建

在 scene 函数中,我们通过两次调用 vorocloud 函数,在不同尺度上生成图案,并根据返回值对颜色通道进行混合:

vec4 col = vec4(0.0);
col.g += 0.02;

float v = vorocloud(p);
v = 0.2 _ floor(v _ 5.0);

col.r += 0.1 _ v;
col.g += 0.6 _ v;
col.b += 0.5 \* pow(v, 5.0);

v = vorocloud(p _ 2.0);
v = 0.2 _ floor(v \* 5.0);

col.r += 0.1 _ v;
col.g += 0.2 _ v;
col.b += 0.01 \* pow(v, 5.0);
  • 量化效果:利用 floor 函数将连续值离散化,产生分级的视觉效果。
  • 多层混合:在不同尺度下叠加效果,增强了图像的层次感和细节。

二、数学原理总结

1. 周期函数与坐标扭曲:

利用 cos 与 sin 函数,使得图案在各个方向上呈现出周期性和非线性变形效果。数学上,这是通过将线性坐标映射到周期函数上实现的,从而引入无限延展的波动性。

2. 欧几里得距离计算

使用基本的欧几里得距离公式确定当前像素与各参考点的最小距离,从而在空间中划分不同的影响区域。

3. 指数衰减与非线性变换

采用指数函数 𝑓=2×(1−0.3𝑑)13f=2×(1−0.3d)13 使得靠近参考点的区域迅速达到高亮,而远离区域则快速衰减到 0,为图像添加了锐利的边缘效果。

3 量化处理

通过对连续亮度值进行离散化处理,模拟出一种复古或艺术化的调色板效果,使得整体视觉更具层次和风格。

三、完整代码

import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';

// 创建场景
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(
  75,
  window.innerWidth / window.innerHeight,
  0.1,
  1000,
);
camera.position.z = 1.5;

const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

const uniforms = {
  iTime: { value: 0.0 },
  iResolution: {
    value: new THREE.Vector2(window.innerWidth, window.innerHeight),
  },
};

const material = new THREE.ShaderMaterial({
  uniforms,
  vertexShader: `
        varying vec2 vUv;
        void main() {
            vUv = uv;
            gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
        }
    `,
  fragmentShader: `
        #define PI2 6.28318530718
        #define PI 3.1416
        
        uniform float iTime;
        uniform vec2 iResolution;
        
        varying vec2 vUv;
        
        float vorocloud(vec2 p){
            float f = 0.0;
            vec2 pp = cos(vec2(p.x * 14.0, (16.0 * p.y + cos(floor(p.x * 30.0)) + iTime * PI2)));
            p = cos(p * 12.1 + pp * 10.0 + 0.5 * cos(pp.x * 10.0));
            
            vec2 pts[4];
            pts[0] = vec2(0.5, 0.6);
            pts[1] = vec2(-0.4, 0.4);
            pts[2] = vec2(0.2, -0.7);
            pts[3] = vec2(-0.3, -0.4);
            
            float d = 5.0;
            
            for(int i = 0; i < 4; i++){
                pts[i].x += 0.03 * cos(float(i)) + p.x;
                pts[i].y += 0.03 * sin(float(i)) + p.y;
                d = min(d, distance(pts[i], pp));
            }
            
            f = 2.0 * pow(1.0 - 0.3 * d, 13.0);
            f = min(f, 1.0);
            
            return f;
        }
        
        void main() {
            vec2 UV = vUv;
            vec2 p = UV - vec2(0.5);
            
            vec4 col = vec4(0.0);
            col.g += 0.02;
            
            float v = vorocloud(p);
            v = 0.2 * floor(v * 5.0);
            
            col.r += 0.1 * v;
            col.g += 0.6 * v;
            col.b += 0.5 * pow(v, 5.0);
            
            v = vorocloud(p * 2.0);
            v = 0.2 * floor(v * 5.0);
            
            col.r += 0.1 * v;
            col.g += 0.2 * v;
            col.b += 0.01 * pow(v, 5.0);
            
            col.a = 1.0;
            
            gl_FragColor = col;
        }
    `,
});

const plane = new THREE.PlaneGeometry(2, 2);
const quad = new THREE.Mesh(plane, material);
scene.add(quad);

const controls = new OrbitControls(camera, renderer.domElement);

function animate() {
  requestAnimationFrame(animate);
  uniforms.iTime.value += 0.01;
  renderer.render(scene, camera);
}

animate();

window.addEventListener('resize', () => {
  renderer.setSize(window.innerWidth, window.innerHeight);
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  uniforms.iResolution.value.set(window.innerWidth, window.innerHeight);
});

总结

本文详细解析了如何利用数学工具(如周期函数、欧几里得距离、指数函数)构造出具有丰富层次和动态效果的云状特效。通过将 shader 实现我们可以轻松地将复杂的图形效果应用于 WebGL 项目中,同时也加深了对基于数学函数生成复杂图形原理的理解。

### 回答1: 下面是一个简单的 three.js 烟雾 shader 示例,可以作为参考: ``` const smokeVertexShader = ` varying vec3 vWorldPosition; void main() { vec4 worldPosition = modelMatrix * vec4(position, 1.0); vWorldPosition = worldPosition.xyz; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); }`; const smokeFragmentShader = ` uniform vec3 color; uniform float opacity; varying vec3 vWorldPosition; void main() { float depth = gl_FragCoord.z / gl_FragCoord.w; float fogFactor = smoothstep(100.0, 300.0, depth); gl_FragColor = vec4(color, opacity) * fogFactor; }`; const smokeMaterial = new THREE.ShaderMaterial({ uniforms: { color: { value: new THREE.Color(0xaaaaaa) }, opacity: { value: 0.5 } }, vertexShader: smokeVertexShader, fragmentShader: smokeFragmentShader, transparent: true }); const smokeGeometry = new THREE.PlaneGeometry(1000, 1000); const smokeMesh = new THREE.Mesh(smokeGeometry, smokeMaterial); smokeMesh.position.set(0, 0, -500); scene.add(smokeMesh); ``` 这个 shader 通过计算深度值来模拟烟雾效果。在顶点着色器中,我们通过将顶点位置乘以模型矩阵得到世界坐标系下的位置,然后将它传递给片元着色器。在片元着色器中,我们计算每个像素的深度值,并根据深度值计算烟雾因子,最后将它乘以颜色和不透明度来得到最终的颜色。 你可以根据需要调整烟雾的颜色、不透明度、大小和位置。 ### 回答2: three.js是一个功能强大的JavaScript库,它提供了一套易于使用的工具和功能,用于在Web上创建交互式的3D图形。其中一个功能是烟雾shader,它允许我们在我们的场景中添加逼真的烟雾效果。 three.js的烟雾shader通过在场景中创建一个气体云层,并向其应用特定的效果来实现。这个效果通常是通过使用Perlin噪声来模拟烟雾的动态外观的。这种噪声会根据时间和空间的变化来生成一个连续的、无缝的云状图案。 通过使用three.js的ShaderMaterial和自定义着色器,我们可以将烟雾效果应用于物体或整个场景。使用这个材质,我们可以设置烟雾的颜色、透明度、密度等参数,以实现我们想要的效果。 烟雾shader可以使我们的场景更加生动和逼真。它可以在火焰、蒸汽机、爆炸等各种场景中添加真实的烟雾效果。在游戏开发、虚拟现实和建筑可视化等领域,烟雾shader都可以发挥重要的作用,提高用户体验和视觉效果。 尽管实现烟雾shader可能需要一些编程和图形学的知识,但由于three.js库已经提供了许多封装好的功能和模块,我们可以轻松地集成和使用它们。此外,three.js的官方文档和社区也提供了大量的教程和示例代码,帮助我们快速上手和理解相关概念。 总的来说,three.js的烟雾shader是一个强大的工具,它为我们提供了在Web上创建逼真烟雾效果的能力。它可以为我们的场景增加动态和真实感,提高用户体验和视觉效果。对于对3D图形感兴趣的开发者来说,three.js的烟雾shader是一个不可忽视的资源。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值