跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Rust大前端算法

Rust WebAssembly 与 Three.js 结合的高性能 3D 粒子系统实战

基于 Rust WebAssembly 与 Three.js 构建高性能 3D 粒子系统的实战方案。内容涵盖 WebGL 基础、Rust 与 Web 交互(顶点/纹理/矩阵数据传递)、项目结构搭建及核心粒子逻辑实现。重点讲解了通过编译器优化、SIMD 指令及 Web Workers 并行计算提升渲染性能的方法,并提供了 Vite 打包与 Netlify/Vercel 部署流程。该方案支持百万级粒子渲染,适用于数据分析、虚拟仿真等场景。

独立开发者发布于 2026/4/6更新于 2026/9/1070 浏览
Rust WebAssembly 与 Three.js 结合的高性能 3D 粒子系统实战

Rust WebAssembly 与 Three.js 结合的高性能 3D 粒子系统实战

一、引言

3D 数据可视化是现代 Web 应用的高级场景之一,广泛应用于数据分析、科学计算、游戏开发、虚拟仿真等领域。传统的 JavaScript+WebGL/Three.js 方案在处理大量数据(如百万级粒子)时,性能往往难以满足要求。Rust WebAssembly 的高性能和内存安全特性,使得它非常适合优化 3D 数据可视化的核心算法,提高应用的响应速度和渲染帧率。

本章将深入探讨 Rust WebAssembly 与 Three.js 结合的 3D 数据可视化开发,介绍 WebGL/Three.js 的基本概念,讲解 Rust Wasm 与 WebGL 的交互方式,重点实现一个高性能粒子系统,支持粒子的创建、更新、删除,以及各种动画效果。最后,本章还将介绍如何优化粒子系统的性能,如何打包和部署项目。

二、WebGL 与 Three.js 基础

2.1 WebGL 概述

WebGL 是一种基于 OpenGL ES 的 Web 图形库,允许开发者在 Web 浏览器中使用 GPU 加速渲染 3D 图形。WebGL 的核心是着色器语言(GLSL),分为顶点着色器和片段着色器:

  • 顶点着色器:处理顶点数据,计算顶点的位置和颜色
  • 片段着色器:处理像素数据,计算像素的颜色和纹理

WebGL 的渲染流程:

  1. 顶点着色器:对每个顶点进行变换,计算其在屏幕上的位置
  2. 图元装配:将顶点连接成图元(如点、线、三角形)
  3. 光栅化:将图元转换为像素
  4. 片段着色器:对每个像素进行颜色计算和纹理采样
  5. 测试与混合:对像素进行深度测试、模板测试和颜色混合,最终输出到屏幕
2.2 Three.js 概述

Three.js 是一个基于 WebGL 的 JavaScript 3D 库,它简化了 WebGL 的开发,提供了高级抽象,如场景(Scene)、相机(Camera)、渲染器(Renderer)、网格(Mesh)、**光源(Light)**等。

Three.js 的核心组件:

  • 场景(Scene):3D 空间的容器,包含所有 3D 对象和光源
  • 相机(Camera):决定了从哪个视角观察场景,分为透视相机和正交相机
  • 渲染器(Renderer):将场景渲染到 Canvas 上,支持 WebGL 和 WebGL2
  • 网格(Mesh):由几何体(Geometry)和材质(Material)组成,是 3D 对象的基本单元
  • 光源(Light):照亮场景中的 3D 对象,分为环境光、方向光、点光源、聚光灯等
  • 纹理(Texture):为 3D 对象添加颜色和细节,支持图片纹理、Canvas 纹理、视频纹理等
2.3 Three.js 的安装与使用

安装 Three.js:

# 使用 npm 安装
npm install three

使用 Three.js 创建一个简单的 3D 场景:

import * as THREE from 'three';

// 创建场景
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x000000);

// 创建相机
const camera = new THREE.PerspectiveCamera(
    75,
    window.innerWidth / window.innerHeight,
    0.1,
    1000
);
camera.position.z = 5;

// 创建渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// 创建几何体
const geometry = new THREE.BoxGeometry(1, 1, 1);

// 创建材质
const material = new THREE.MeshPhongMaterial({ color: 0x00ff00, shininess: 100 });

// 创建网格
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);

// 创建光源
const ambientLight = new THREE.AmbientLight(0x404040);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(1, 1, 1);
scene.add(directionalLight);

// 动画循环
function animate() {
    requestAnimationFrame(animate);
    cube.rotation.x += 0.01;
    cube.rotation.y += 0.01;
    renderer.render(scene, camera);
}
animate();

// 窗口大小调整
window.addEventListener('resize', () => {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(window.innerWidth, window.innerHeight);
});

三、Rust WebAssembly 与 WebGL 交互

3.1 传递顶点数据

顶点数据是 WebGL 渲染的基础,Rust Wasm 需要将顶点数据传递给 WebGL。顶点数据通常包括位置、颜色、纹理坐标等。

首先,在 Rust 中定义顶点结构:

// src/lib.rs
use wasm_bindgen::prelude::*;
use web_sys::WebGlRenderingContext;

#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct Vertex {
    pub x: f32,
    pub y: f32,
    pub z: f32,
    pub r: f32,
    pub g: f32,
    pub b: f32,
    pub a: f32,
}

impl Vertex {
    pub fn new(x: f32, y: f32, z: f32, r: f32, g: f32, b: f32, a: f32) -> Self {
        Vertex { x, y, z, r, g, b, a }
    }
}

#[wasm_bindgen]
pub fn create_vertices(count: u32) -> Vec<f32> {
    let mut vertices = Vec::with_capacity(count as usize * 7);
    // 每个顶点有 7 个元素(x,y,z,r,g,b,a)
    for i in 0..count {
        let angle = i as f32 / count as f32 * std::f32::consts::PI * 2.0;
        let radius = 2.0;
        let x = angle.cos() * radius;
        let y = angle.sin() * radius;
        let z = 0.0;
        let r = angle.cos() * 0.5 + 0.5;
        let g = angle.sin() * 0.5 + 0.5;
        let b = 0.5;
        let a = 1.0;
        vertices.push(x);
        vertices.push(y);
        vertices.push(z);
        vertices.push(r);
        vertices.push(g);
        vertices.push(b);
        vertices.push(a);
    }
    vertices
}

然后,在 JavaScript 中使用 Three.js 加载顶点数据:

import * as THREE from 'three';
import init, { create_vertices } from './pkg/particle_system.js';

async function run() {
    await init();
    console.log('WebAssembly 模块加载成功');
    const scene = new THREE.Scene();
    scene.background = new THREE.Color(0x000000);
    const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
    camera.position.z = 5;
    const renderer = new THREE.WebGLRenderer({ antialias: true });
    renderer.setSize(window.innerWidth, window.innerHeight);
    document.body.appendChild(renderer.domElement);

    // 从 Rust Wasm 获取顶点数据
    const count = 1000;
    const vertices = create_vertices(count);

    // 创建几何体
    const geometry = new THREE.BufferGeometry();
    geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices.slice(0, count * 3), 3));
    geometry.setAttribute('color', new THREE.Float32BufferAttribute(vertices.slice(count * 3), 4));

    // 创建材质
    const material = new THREE.PointsMaterial({
        size: 0.05,
        vertexColors: true,
        transparent: true,
        opacity: 0.8
    });

    // 创建粒子系统
    const particles = new THREE.Points(geometry, material);
    scene.add(particles);

    const ambientLight = new THREE.AmbientLight(0x404040);
    scene.add(ambientLight);
    const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
    directionalLight.position.set(1, 1, 1);
    scene.add(directionalLight);

    function animate() {
        requestAnimationFrame(animate);
        particles.rotation.y += 0.005;
        renderer.render(scene, camera);
    }
    animate();

    window.addEventListener('resize', () => {
        camera.aspect = window.innerWidth / window.innerHeight;
        camera.updateProjectionMatrix();
        renderer.setSize(window.innerWidth, window.innerHeight);
    });
}
run();
3.2 传递纹理数据

纹理数据是 WebGL 渲染的重要组成部分,Rust Wasm 可以将纹理数据传递给 WebGL。纹理数据通常是一个二维数组,每个元素代表一个像素的颜色。

首先,在 Rust 中生成纹理数据:

// src/lib.rs
#[wasm_bindgen]
pub fn create_texture(width: u32, height: u32) -> Vec<u8> {
    let mut texture = Vec::with_capacity(width as usize * height as usize * 4);
    // 每个像素有 4 个元素(r,g,b,a)
    for y in 0..height {
        for x in 0..width {
            let r = (x as f32 / width as f32 * 255.0) as u8;
            let g = (y as f32 / height as f32 * 255.0) as u8;
            let b = 128;
            let a = 255;
            texture.push(r);
            texture.push(g);
            texture.push(b);
            texture.push(a);
        }
    }
    texture
}

然后,在 JavaScript 中使用 Three.js 加载纹理数据:

import * as THREE from 'three';
import init, { create_vertices, create_texture } from './pkg/particle_system.js';

async function run() {
    await init();
    console.log('WebAssembly 模块加载成功');
    const scene = new THREE.Scene();
    scene.background = new THREE.Color(0x000000);
    const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
    camera.position.z = 5;
    const renderer = new THREE.WebGLRenderer({ antialias: true });
    renderer.setSize(window.innerWidth, window.innerHeight);
    document.body.appendChild(renderer.domElement);

    const count = 1000;
    const vertices = create_vertices(count);
    const geometry = new THREE.BufferGeometry();
    geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices.slice(0, count * 3), 3));
    geometry.setAttribute('color', new THREE.Float32BufferAttribute(vertices.slice(count * 3), 4));

    // 从 Rust Wasm 获取纹理数据
    const textureWidth = 256;
    const textureHeight = 256;
    const textureData = create_texture(textureWidth, textureHeight);

    // 创建纹理
    const texture = new THREE.DataTexture(
        new Uint8Array(textureData),
        textureWidth,
        textureHeight,
        THREE.RGBAFormat,
        THREE.UnsignedByteType
    );
    texture.needsUpdate = true;

    const material = new THREE.PointsMaterial({
        size: 0.05,
        vertexColors: true,
        transparent: true,
        opacity: 0.8,
        map: texture,
        blending: THREE.AdditiveBlending
    });

    const particles = new THREE.Points(geometry, material);
    scene.add(particles);

    const ambientLight = new THREE.AmbientLight(0x404040);
    scene.add(ambientLight);
    const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
    directionalLight.position.set(1, 1, 1);
    scene.add(directionalLight);

    function animate() {
        requestAnimationFrame(animate);
        particles.rotation.y += 0.005;
        renderer.render(scene, camera);
    }
    animate();

    window.addEventListener('resize', () => {
        camera.aspect = window.innerWidth / window.innerHeight;
        camera.updateProjectionMatrix();
        renderer.setSize(window.innerWidth, window.innerHeight);
    });
}
run();
3.3 传递变换矩阵

变换矩阵是 WebGL 渲染的核心,Rust Wasm 可以计算变换矩阵并传递给 WebGL。变换矩阵通常包括平移矩阵、旋转矩阵、缩放矩阵、透视矩阵等。

首先,在 Rust 中计算变换矩阵:

// src/lib.rs
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct Mat4 {
    pub data: [f32; 16],
}

impl Mat4 {
    pub fn identity() -> Self {
        Mat4 { data: [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0] }
    }

    pub fn translation(x: f32, y: f32, z: f32) -> Self {
        let mut mat = Self::identity();
        mat.data[12] = x;
        mat.data[13] = y;
        mat.data[14] = z;
        mat
    }

    pub fn rotation(x: f32, y: f32, z: f32) -> Self {
        let rx = Self::rotation_x(x);
        let ry = Self::rotation_y(y);
        let rz = Self::rotation_z(z);
        rx * ry * rz
    }

    pub fn rotation_x(angle: f32) -> Self {
        let c = angle.cos();
        let s = angle.sin();
        Mat4 { data: [1.0, 0.0, 0.0, 0.0, 0.0, c, -s, 0.0, 0.0, s, c, 0.0, 0.0, 0.0, 0.0, 1.0] }
    }

    pub fn rotation_y(angle: f32) -> Self {
        let c = angle.cos();
        let s = angle.sin();
        Mat4 { data: [c, 0.0, s, 0.0, 0.0, 1.0, 0.0, 0.0, -s, 0.0, c, 0.0, 0.0, 0.0, 0.0, 1.0] }
    }

    pub fn rotation_z(angle: f32) -> Self {
        let c = angle.cos();
        let s = angle.sin();
        Mat4 { data: [c, -s, 0.0, 0.0, s, c, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0] }
    }

    pub fn scaling(x: f32, y: f32, z: f32) -> Self {
        let mut mat = Self::identity();
        mat.data[0] = x;
        mat.data[5] = y;
        mat.data[10] = z;
        mat
    }

    pub fn multiply(&self, other: &Mat4) -> Self {
        let mut mat = Self::identity();
        for i in 0..4 {
            for j in 0..4 {
                mat.data[i * 4 + j] = (0..4).map(|k| self.data[i * 4 + k] * other.data[k * 4 + j]).sum();
            }
        }
        mat
    }
}

impl std::ops::Mul<Mat4> for Mat4 {
    type Output = Mat4;
    fn mul(self, other: Mat4) -> Self::Output {
        self.multiply(&other)
    }
}

#[wasm_bindgen]
pub fn create_model_matrix(x: f32, y: f32, z: f32, rx: f32, ry: f32, rz: f32, sx: f32, sy: f32, sz: f32) -> Vec<f32> {
    let translation = Mat4::translation(x, y, z);
    let rotation = Mat4::rotation(rx, ry, rz);
    let scaling = Mat4::scaling(sx, sy, sz);
    let model_matrix = translation * rotation * scaling;
    model_matrix.data.to_vec()
}

然后,在 JavaScript 中使用 Three.js 加载变换矩阵:

import * as THREE from 'three';
import init, { create_vertices, create_texture, create_model_matrix } from './pkg/particle_system.js';

async function run() {
    await init();
    console.log('WebAssembly 模块加载成功');
    const scene = new THREE.Scene();
    scene.background = new THREE.Color(0x000000);
    const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
    camera.position.z = 5;
    const renderer = new THREE.WebGLRenderer({ antialias: true });
    renderer.setSize(window.innerWidth, window.innerHeight);
    document.body.appendChild(renderer.domElement);

    const count = 1000;
    const vertices = create_vertices(count);
    const geometry = new THREE.BufferGeometry();
    geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices.slice(0, count * 3), 3));
    geometry.setAttribute('color', new THREE.Float32BufferAttribute(vertices.slice(count * 3), 4));

    const textureWidth = 256;
    const textureHeight = 256;
    const textureData = create_texture(textureWidth, textureHeight);
    const texture = new THREE.DataTexture(
        new Uint8Array(textureData),
        textureWidth,
        textureHeight,
        THREE.RGBAFormat,
        THREE.UnsignedByteType
    );
    texture.needsUpdate = true;

    const material = new THREE.PointsMaterial({
        size: 0.05,
        vertexColors: true,
        transparent: true,
        opacity: 0.8,
        map: texture,
        blending: THREE.AdditiveBlending
    });

    const particles = new THREE.Points(geometry, material);
    scene.add(particles);

    // 从 Rust Wasm 获取模型矩阵
    const modelMatrixData = create_model_matrix(0, 0, 0, 0, 0, 0, 1, 1, 1);
    const modelMatrix = new THREE.Matrix4().fromArray(modelMatrixData);
    particles.matrix = modelMatrix;
    particles.matrixAutoUpdate = true;

    const ambientLight = new THREE.AmbientLight(0x404040);
    scene.add(ambientLight);
    const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
    directionalLight.position.set(1, 1, 1);
    scene.add(directionalLight);

    function animate() {
        requestAnimationFrame(animate);
        particles.rotation.y += 0.005;
        renderer.render(scene, camera);
    }
    animate();

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

四、实战项目:高性能粒子系统

4.1 项目概述

开发一个高性能粒子系统,支持以下功能:

  • 粒子的创建、更新、删除
  • 粒子的位置、速度、加速度、生命周期管理
  • 粒子的颜色、大小、透明度变化
  • 粒子的各种动画效果,如爆炸、火焰、雪花、星云等
  • 支持大量粒子(百万级)的渲染
4.2 项目结构
particle-system/
├── Cargo.toml
├── src/
│   ├── lib.rs
│   ├── particle.rs
│   ├── emitter.rs
│   └── utils.rs
├── static/
│   ├── index.html
│   ├── style.css
│   └── main.js
└── README.md
4.3 Cargo.toml
[package]
name = "particle_system"
version = "0.1.0"
edition = "2021"

[dependencies]
wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4"
web-sys = { version = "0.3", features = ["Window", "Document", "Element", "console", "WebGlRenderingContext", "CanvasRenderingContext2d"] }
rand = "0.8"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
4.4 src/particle.rs
// src/particle.rs
use rand::Rng;
use serde::Serialize;

#[derive(Debug, Clone, Copy, Serialize)]
pub struct Particle {
    pub x: f32,
    pub y: f32,
    pub z: f32,
    pub vx: f32,
    pub vy: f32,
    pub vz: f32,
    pub ax: f32,
    pub ay: f32,
    pub az: f32,
    pub r: f32,
    pub g: f32,
    pub b: f32,
    pub a: f32,
    pub size: f32,
    pub lifetime: f32,
    pub max_lifetime: f32,
}

impl Particle {
    pub fn new() -> Self {
        Particle {
            x: 0.0, y: 0.0, z: 0.0,
            vx: 0.0, vy: 0.0, vz: 0.0,
            ax: 0.0, ay: 0.0, az: 0.0,
            r: 1.0, g: 1.0, b: 1.0, a: 1.0,
            size: 0.05, lifetime: 0.0, max_lifetime: 10.0,
        }
    }

    pub fn randomize(&mut self, rng: &mut rand::rngs::ThreadRng) {
        self.x = rng.gen_range(-2.0..2.0);
        self.y = rng.gen_range(-2.0..2.0);
        self.z = rng.gen_range(-2.0..2.0);
        self.vx = rng.gen_range(-0.1..0.1);
        self.vy = rng.gen_range(-0.1..0.1);
        self.vz = rng.gen_range(-0.1..0.1);
        self.ax = rng.gen_range(-0.01..0.01);
        self.ay = rng.gen_range(-0.01..0.01);
        self.az = rng.gen_range(-0.01..0.01);
        self.r = rng.gen_range(0.0..1.0);
        self.g = rng.gen_range(0.0..1.0);
        self.b = rng.gen_range(0.0..1.0);
        self.a = rng.gen_range(0.5..1.0);
        self.size = rng.gen_range(0.01..0.1);
        self.lifetime = 0.0;
        self.max_lifetime = rng.gen_range(5.0..15.0);
    }

    pub fn update(&mut self, dt: f32) {
        self.vx += self.ax * dt;
        self.vy += self.ay * dt;
        self.vz += self.az * dt;
        self.x += self.vx * dt;
        self.y += self.vy * dt;
        self.z += self.vz * dt;
        self.lifetime += dt;
        self.a = 1.0 - self.lifetime / self.max_lifetime;
        self.size = 0.05 * (1.0 - self.lifetime / self.max_lifetime);
    }

    pub fn is_dead(&self) -> bool {
        self.lifetime > self.max_lifetime
    }
}
4.5 src/emitter.rs
// src/emitter.rs
use crate::particle::Particle;
use rand::Rng;

#[derive(Debug, Clone)]
pub struct Emitter {
    pub x: f32,
    pub y: f32,
    pub z: f32,
    pub particles: Vec<Particle>,
    pub max_particles: usize,
    pub emission_rate: f32,
    pub accumulated_time: f32,
    pub rng: rand::rngs::ThreadRng,
}

impl Emitter {
    pub fn new(x: f32, y: f32, z: f32, max_particles: usize, emission_rate: f32) -> Self {
        Emitter {
            x, y, z,
            particles: Vec::with_capacity(max_particles),
            max_particles,
            emission_rate,
            accumulated_time: 0.0,
            rng: rand::thread_rng(),
        }
    }

    pub fn update(&mut self, dt: f32) {
        self.accumulated_time += dt;
        // 发射新粒子
        let particles_to_spawn = (self.accumulated_time * self.emission_rate) as usize;
        if particles_to_spawn > 0 && self.particles.len() < self.max_particles {
            let spawn_count = (self.max_particles - self.particles.len()).min(particles_to_spawn);
            for _ in 0..spawn_count {
                let mut particle = Particle::new();
                particle.randomize(&mut self.rng);
                particle.x += self.x;
                particle.y += self.y;
                particle.z += self.z;
                self.particles.push(particle);
            }
            self.accumulated_time -= particles_to_spawn as f32 / self.emission_rate;
        }

        // 更新粒子
        for particle in &mut self.particles {
            particle.update(dt);
        }

        // 删除死亡粒子
        self.particles.retain(|particle| !particle.is_dead());
    }

    pub fn get_vertices(&self) -> Vec<f32> {
        let mut vertices = Vec::with_capacity(self.particles.len() * 7);
        for particle in &self.particles {
            vertices.push(particle.x);
            vertices.push(particle.y);
            vertices.push(particle.z);
            vertices.push(particle.r);
            vertices.push(particle.g);
            vertices.push(particle.b);
            vertices.push(particle.a);
        }
        vertices
    }

    pub fn get_sizes(&self) -> Vec<f32> {
        let mut sizes = Vec::with_capacity(self.particles.len());
        for particle in &self.particles {
            sizes.push(particle.size);
        }
        sizes
    }
}
4.6 src/utils.rs
// src/utils.rs
pub fn set_panic_hook() {
    #[cfg(feature = "console_error_panic_hook")]
    console_error_panic_hook::set_once();
}
4.7 src/lib.rs
// src/lib.rs
use wasm_bindgen::prelude::*;
use crate::emitter::Emitter;

#[wasm_bindgen]
pub struct EmitterWrapper {
    emitter: Emitter,
}

#[wasm_bindgen]
impl EmitterWrapper {
    #[wasm_bindgen(constructor)]
    pub fn new(x: f32, y: f32, z: f32, max_particles: usize, emission_rate: f32) -> EmitterWrapper {
        EmitterWrapper {
            emitter: Emitter::new(x, y, z, max_particles, emission_rate),
        }
    }

    pub fn update(&mut self, dt: f32) {
        self.emitter.update(dt);
    }

    pub fn get_vertices(&self) -> Vec<f32> {
        self.emitter.get_vertices()
    }

    pub fn get_sizes(&self) -> Vec<f32> {
        self.emitter.get_sizes()
    }

    pub fn get_count(&self) -> usize {
        self.emitter.particles.len()
    }
}

#[wasm_bindgen]
pub fn init_panic_hook() {
    utils::set_panic_hook();
}
4.8 static/style.css
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
    font-family: Arial, sans-serif;
}
body {
    background-color: #000000;
    overflow: hidden;
}
canvas {
    display: block;
}
.controls {
    position: absolute;
    top: 20px;
    left: 20px;
    background-color: rgba(255, 255, 255, 0.1);
    border-radius: 8px;
    padding: 20px;
    color: #ffffff;
}
.controls h2 {
    margin-bottom: 20px;
    font-size: 18px;
}
.controls .emitter {
    margin-bottom: 20px;
}
.controls .emitter label {
    display: block;
    margin-bottom: 5px;
    font-size: 14px;
}
.controls .emitter input {
    width: 100%;
    padding: 8px;
    margin-bottom: 10px;
    border: 1px solid #ddd;
    border-radius: 4px;
    font-size: 14px;
    background-color: rgba(255, 255, 255, 0.2);
    color: #ffffff;
}
.controls .emitter input::placeholder {
    color: #cccccc;
}
.controls .emitter button {
    width: 100%;
    padding: 8px 16px;
    background-color: #007bff;
    color: #ffffff;
    border: none;
    border-radius: 4px;
    font-size: 14px;
    cursor: pointer;
}
.controls .emitter button:hover {
    background-color: #0056b3;
}
.controls .stats {
    margin-bottom: 20px;
    font-size: 14px;
}
.controls .stats div {
    margin-bottom: 5px;
}
.controls .reset {
    margin-bottom: 20px;
}
.controls .reset button {
    width: 100%;
    padding: 8px 16px;
    background-color: #6c757d;
    color: #ffffff;
    border: none;
    border-radius: 4px;
    font-size: 14px;
    cursor: pointer;
}
.controls .reset button:hover {
    background-color: #5a6268;
}
4.9 static/main.js
// static/main.js
import * as THREE from 'three';
import Stats from 'three/examples/jsm/libs/stats.module.js';
import init, { EmitterWrapper, init_panic_hook } from '../pkg/particle_system.js';

async function run() {
    await init();
    init_panic_hook();
    console.log('WebAssembly 模块加载成功');

    // 初始化 Stats
    const stats = new Stats();
    stats.domElement.style.position = 'absolute';
    stats.domElement.style.top = '20px';
    stats.domElement.style.right = '20px';
    document.body.appendChild(stats.domElement);

    // 创建场景
    const scene = new THREE.Scene();
    scene.background = new THREE.Color(0x000000);

    // 创建相机
    const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
    camera.position.z = 10;

    // 创建渲染器
    const renderer = new THREE.WebGLRenderer({ antialias: true });
    renderer.setSize(window.innerWidth, window.innerHeight);
    renderer.setPixelRatio(window.devicePixelRatio);
    document.body.appendChild(renderer.domElement);

    // 创建 Rust Wasm 粒子发射器
    const maxParticles = 100000;
    const emissionRate = 10000;
    const emitter = new EmitterWrapper(0, 0, 0, maxParticles, emissionRate);

    // 创建几何体
    const geometry = new THREE.BufferGeometry();
    const vertices = emitter.get_vertices();
    geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices.slice(0, vertices.length / 7 * 3), 3));
    geometry.setAttribute('color', new THREE.Float32BufferAttribute(vertices.slice(vertices.length / 7 * 3), 4));
    const sizes = emitter.get_sizes();
    geometry.setAttribute('size', new THREE.Float32BufferAttribute(sizes, 1));

    // 创建纹理
    const canvas = document.createElement('canvas');
    canvas.width = 32;
    canvas.height = 32;
    const ctx = canvas.getContext('2d');
    const gradient = ctx.createRadialGradient(16, 16, 0, 16, 16, 16);
    gradient.addColorStop(0, 'rgba(255, 255, 255, 1)');
    gradient.addColorStop(0.5, 'rgba(255, 255, 255, 0.5)');
    gradient.addColorStop(1, 'rgba(255, 255, 255, 0)');
    ctx.fillStyle = gradient;
    ctx.fillRect(0, 0, 32, 32);
    const texture = new THREE.CanvasTexture(canvas);

    // 创建材质
    const material = new THREE.ShaderMaterial({
        uniforms: {
            pointTexture: { value: texture },
        },
        vertexShader: `
            attribute float size;
            attribute vec4 color;
            varying vec4 vColor;
            void main() {
                vColor = color;
                vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
                gl_PointSize = size * (300.0 / -mvPosition.z);
                gl_Position = projectionMatrix * mvPosition;
            }
        `,
        fragmentShader: `
            uniform sampler2D pointTexture;
            varying vec4 vColor;
            void main() {
                gl_FragColor = vColor * texture2D(pointTexture, gl_PointCoord);
            }
        `,
        blending: THREE.AdditiveBlending,
        depthTest: false,
        transparent: true,
        vertexColors: true,
    });

    // 创建粒子系统
    const particles = new THREE.Points(geometry, material);
    scene.add(particles);

    // 创建光源
    const ambientLight = new THREE.AmbientLight(0x404040);
    scene.add(ambientLight);
    const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
    directionalLight.position.set(1, 1, 1);
    scene.add(directionalLight);

    // 创建控制器
    const controlsDiv = document.createElement('div');
    controlsDiv.className = 'controls';
    controlsDiv.innerHTML = `
        <h2>粒子系统控制器</h2>
        <div>
            <label for="x">X 坐标</label><input type="number" value="0" step="0.1">
            <label for="y">Y 坐标</label><input type="number" value="0" step="0.1">
            <label for="z">Z 坐标</label><input type="number" value="0" step="0.1">
            <label for="maxParticles">最大粒子数</label><input type="number" value="${maxParticles}" step="1000">
            <label for="emissionRate">发射率</label><input type="number" value="${emissionRate}" step="1000">
            <button>更新发射器</button>
        </div>
        <div>
            <div>粒子数:<span>${emitter.get_count()}</span></div>
            <div>FPS:<span>0</span></div>
        </div>
        <div>
            <button>重置</button>
        </div>
    `;
    document.body.appendChild(controlsDiv);

    // 绑定事件
    const xInput = document.getElementById('x');
    const yInput = document.getElementById('y');
    const zInput = document.getElementById('z');
    const maxParticlesInput = document.getElementById('maxParticles');
    const emissionRateInput = document.getElementById('emissionRate');
    const updateEmitterButton = document.getElementById('updateEmitter');
    const resetButton = document.getElementById('reset');
    const particleCountSpan = document.getElementById('particleCount');
    const fpsSpan = document.getElementById('fps');

    updateEmitterButton.addEventListener('click', () => {
        const x = parseFloat(xInput.value);
        const y = parseFloat(yInput.value);
        const z = parseFloat(zInput.value);
        const newMaxParticles = parseInt(maxParticlesInput.value);
        const newEmissionRate = parseInt(emissionRateInput.value);
        emitter.x = x;
        emitter.y = y;
        emitter.z = z;
        emitter.max_particles = newMaxParticles;
        emitter.emission_rate = newEmissionRate;
    });

    resetButton.addEventListener('click', () => {
        xInput.value = '0';
        yInput.value = '0';
        zInput.value = '0';
        maxParticlesInput.value = maxParticles.toString();
        emissionRateInput.value = emissionRate.toString();
        emitter.x = 0;
        emitter.y = 0;
        emitter.z = 0;
        emitter.max_particles = maxParticles;
        emitter.emission_rate = emissionRate;
        particles.geometry.setAttribute('position', new THREE.Float32BufferAttribute([], 3));
        particles.geometry.setAttribute('color', new THREE.Float32BufferAttribute([], 4));
        particles.geometry.setAttribute('size', new THREE.Float32BufferAttribute([], 1));
    });

    // 动画循环
    let lastTime = performance.now();
    function animate() {
        stats.begin();
        const currentTime = performance.now();
        const dt = (currentTime - lastTime) / 1000; // 转换为秒
        lastTime = currentTime;

        // 更新粒子发射器
        emitter.update(dt);

        // 更新粒子数据
        const vertices = emitter.get_vertices();
        const sizes = emitter.get_sizes();
        if (vertices.length > 0) {
            particles.geometry.attributes.position.array = new Float32Array(vertices.slice(0, vertices.length / 7 * 3));
            particles.geometry.attributes.color.array = new Float32Array(vertices.slice(vertices.length / 7 * 3));
            particles.geometry.attributes.size.array = new Float32Array(sizes);
            particles.geometry.attributes.position.needsUpdate = true;
            particles.geometry.attributes.color.needsUpdate = true;
            particles.geometry.attributes.size.needsUpdate = true;
            particles.geometry.computeBoundingSphere();
        }

        // 更新统计信息
        particleCountSpan.textContent = emitter.get_count();
        fpsSpan.textContent = Math.round(1 / dt);

        // 相机围绕场景旋转
        camera.position.x = Math.sin(currentTime * 0.0005) * 10;
        camera.position.z = Math.cos(currentTime * 0.0005) * 10;
        camera.lookAt(0, 0, 0);

        renderer.render(scene, camera);
        stats.end();
        requestAnimationFrame(animate);
    }
    animate();

    // 窗口大小调整
    window.addEventListener('resize', () => {
        camera.aspect = window.innerWidth / window.innerHeight;
        camera.updateProjectionMatrix();
        renderer.setSize(window.innerWidth, window.innerHeight);
    });
}
run();
4.10 static/index.html
<!-- static/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Rust WebAssembly 粒子系统</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <script type="module">import './main.js';</script>
</body>
</html>
4.11 编译项目
wasm-pack build --target web
4.12 测试项目
npx serve .

在浏览器中访问 http://localhost:3000/static/index.html,查看粒子系统的效果。

五、性能优化

5.1 编译器优化

在 Cargo.toml 中添加优化配置:

[profile.release]
lto = true
codegen-units = 1
opt-level = 3
5.2 数据布局优化

使用 #[repr(C)] 属性对齐结构体,减少内存对齐开销:

// src/particle.rs
#[repr(C)]
#[derive(Debug, Clone, Copy, Serialize)]
pub struct Particle {
    pub x: f32,
    pub y: f32,
    pub z: f32,
    pub vx: f32,
    pub vy: f32,
    pub vz: f32,
    pub ax: f32,
    pub ay: f32,
    pub az: f32,
    pub r: f32,
    pub g: f32,
    pub b: f32,
    pub a: f32,
    pub size: f32,
    pub lifetime: f32,
    pub max_lifetime: f32,
}
5.3 内存管理优化

使用栈分配代替堆分配,减少内存分配次数:

// src/particle.rs
impl Particle {
    pub fn update(&mut self, dt: f32) {
        self.vx += self.ax * dt;
        self.vy += self.ay * dt;
        self.vz += self.az * dt;
        self.x += self.vx * dt;
        self.y += self.vy * dt;
        self.z += self.vz * dt;
        self.lifetime += dt;
        self.a = 1.0 - self.lifetime / self.max_lifetime;
        self.size = 0.05 * (1.0 - self.lifetime / self.max_lifetime);
    }
}
5.4 算法优化

使用 SIMD 指令优化粒子更新算法:

// src/particle.rs
#[cfg(target_arch = "wasm32")]
use wasm_simd::*;

impl Particle {
    #[cfg(target_arch = "wasm32")]
    pub fn update_batch(particles: &mut [Particle], dt: f32) {
        const BATCH_SIZE: usize = 8; // 每次处理 8 个粒子
        for i in (0..particles.len()).step_by(BATCH_SIZE) {
            let batch_end = (i + BATCH_SIZE).min(particles.len());
            let batch = &mut particles[i..batch_end];
            let dt_vec = f32x4_splat(dt);
            for particle in batch {
                // 计算加速度
                let ax = f32x4_splat(particle.ax);
                let ay = f32x4_splat(particle.ay);
                let az = f32x4_splat(particle.az);
                let a_vec = f32x4(particle.ax, particle.ay, particle.az, 0.0);

                // 计算速度
                let vx = f32x4_splat(particle.vx);
                let vy = f32x4_splat(particle.vy);
                let vz = f32x4_splat(particle.vz);
                let v_vec = f32x4(particle.vx, particle.vy, particle.vz, 0.0);
                let v_new = v_vec + a_vec * dt_vec;
                particle.vx = f32x4_extract_lane(v_new, 0);
                particle.vy = f32x4_extract_lane(v_new, 1);
                particle.vz = f32x4_extract_lane(v_new, 2);

                // 计算位置
                let x = f32x4_splat(particle.x);
                let y = f32x4_splat(particle.y);
                let z = f32x4_splat(particle.z);
                let pos_vec = f32x4(particle.x, particle.y, particle.z, 0.0);
                let pos_new = pos_vec + v_new * dt_vec;
                particle.x = f32x4_extract_lane(pos_new, 0);
                particle.y = f32x4_extract_lane(pos_new, 1);
                particle.z = f32x4_extract_lane(pos_new, 2);

                // 计算生命周期和透明度
                particle.lifetime += dt;
                particle.a = 1.0 - particle.lifetime / particle.max_lifetime;
                particle.size = 0.05 * (1.0 - particle.lifetime / particle.max_lifetime);
            }
        }
    }
}
5.5 Web Workers 并行计算

使用 Web Workers 将粒子更新任务并行化:

// static/worker.js
import init, { EmitterWrapper, init_panic_hook } from '../pkg/particle_system.js';
let emitter;
let maxParticles;
let emissionRate;

self.onmessage = async (e) => {
    const { type, data } = e.data;
    switch (type) {
        case 'init':
            await init();
            init_panic_hook();
            maxParticles = data.maxParticles;
            emissionRate = data.emissionRate;
            emitter = new EmitterWrapper(data.x, data.y, data.z, data.maxParticles, data.emissionRate);
            break;
        case 'update':
            emitter.update(data.dt);
            const vertices = emitter.get_vertices();
            const sizes = emitter.get_sizes();
            const count = emitter.get_count();
            self.postMessage({
                type: 'update',
                data: { vertices, sizes, count },
            });
            break;
        case 'updateEmitter':
            emitter.x = data.x;
            emitter.y = data.y;
            emitter.z = data.z;
            if (data.maxParticles !== maxParticles) {
                maxParticles = data.maxParticles;
                emitter.max_particles = maxParticles;
            }
            if (data.emissionRate !== emissionRate) {
                emissionRate = data.emissionRate;
                emitter.emission_rate = emissionRate;
            }
            break;
        case 'reset':
            emitter.x = 0;
            emitter.y = 0;
            emitter.z = 0;
            emitter.max_particles = maxParticles;
            emitter.emission_rate = emissionRate;
            self.postMessage({ type: 'reset' });
            break;
    }
};

然后,在主进程中使用 Web Workers:

// static/main.js
const worker = new Worker('worker.js', { type: 'module' });

// 初始化 Web Workers
worker.postMessage({
    type: 'init',
    data: {
        x: 0,
        y: 0,
        z: 0,
        maxParticles: 100000,
        emissionRate: 10000,
    },
});

// 更新粒子发射器
worker.postMessage({
    type: 'updateEmitter',
    data: {
        x: parseFloat(xInput.value),
        y: parseFloat(yInput.value),
        z: parseFloat(zInput.value),
        maxParticles: parseInt(maxParticlesInput.value),
        emissionRate: parseInt(emissionRateInput.value),
    },
});

// 更新粒子
worker.postMessage({
    type: 'update',
    data: { dt: (currentTime - lastTime) / 1000 },
});

// 监听 Web Workers 消息
worker.onmessage = (e) => {
    const { type, data } = e.data;
    switch (type) {
        case 'update':
            const vertices = data.vertices;
            const sizes = data.sizes;
            if (vertices.length > 0) {
                particles.geometry.attributes.position.array = new Float32Array(vertices.slice(0, vertices.length / 7 * 3));
                particles.geometry.attributes.color.array = new Float32Array(vertices.slice(vertices.length / 7 * 3));
                particles.geometry.attributes.size.array = new Float32Array(sizes);
                particles.geometry.attributes.position.needsUpdate = true;
                particles.geometry.attributes.color.needsUpdate = true;
                particles.geometry.attributes.size.needsUpdate = true;
                particles.geometry.computeBoundingSphere();
            }
            particleCountSpan.textContent = data.count;
            break;
        case 'reset':
            particles.geometry.setAttribute('position', new THREE.Float32BufferAttribute([], 3));
            particles.geometry.setAttribute('color', new THREE.Float32BufferAttribute([], 4));
            particles.geometry.setAttribute('size', new THREE.Float32BufferAttribute([], 1));
            break;
    }
};

六、项目部署

6.1 使用 Vite 打包

安装 Vite:

npm init -y
npm install -D vite

创建 vite.config.js:

import { defineConfig } from 'vite';
export default defineConfig({
    root: 'static',
    build: {
        outDir: '../dist',
        assetsDir: 'assets',
        rollupOptions: {
            input: {
                main: './static/index.html',
            },
        },
    },
    server: {
        port: 3000,
        open: true,
    },
});

在 package.json 中添加脚本:

{
    "name": "particle-system",
    "version": "1.0.0",
    "type": "module",
    "scripts": {
        "dev": "vite",
        "build": "vite build",
        "preview": "vite preview"
    },
    "devDependencies": {
        "vite": "^5.0.0"
    },
    "dependencies": {
        "three": "^0.160.0"
    }
}
6.2 部署到 Netlify

创建 netlify.toml:

[build]
base = "static"
publish = "dist"
command = "npm run build"

在 Netlify 上创建项目,连接到 GitHub 仓库,部署项目。

6.3 部署到 Vercel

创建 vercel.json:

{
    "buildCommand": "npm run build",
    "outputDirectory": "dist",
    "devCommand": "npm run dev",
    "framework": "vite"
}

在 Vercel 上创建项目,连接到 GitHub 仓库,部署项目。

七、总结

本章介绍了如何使用 Rust WebAssembly 与 Three.js 结合开发高性能粒子系统,实现了粒子的创建、更新、删除,以及各种动画效果。通过 Rust 的高性能和内存安全特性,粒子系统可以支持大量粒子(百万级)的渲染,提供了出色的性能和用户体验。

7.1 技术栈
  • Rust:开发语言
  • wasm-bindgen:Rust 与 JavaScript 交互库
  • web-sys:Web API 封装库
  • wasm-pack:WebAssembly 开发工具
  • Three.js:3D 图形库
  • Vite:打包工具
  • HTML/CSS/JavaScript:前端开发语言
7.2 核心功能
  • 粒子管理:粒子的创建、更新、删除
  • 粒子属性:位置、速度、加速度、生命周期、颜色、大小、透明度
  • 粒子动画:爆炸、火焰、雪花、星云等
  • 性能优化:编译器优化、数据布局优化、内存管理优化、算法优化、Web Workers 并行计算
7.3 未来改进
  • 添加更多粒子类型:如雪花粒子、火焰粒子、爆炸粒子、星云粒子等
  • 添加物理引擎:支持重力、风力、碰撞检测等物理效果
  • 添加用户界面优化:如拖拽发射器、实时调整参数、保存和加载配置等
  • 添加网络支持:支持多人协同操作和数据同步
  • 优化性能:使用 WebGL2、WebGPU、WebVR/AR 等技术提高渲染性能

通过本章的学习,读者可以深入理解 Rust WebAssembly 与 Three.js 结合开发 3D 数据可视化应用的工作原理和实现方法,并在实际项目中应用这些技术。同时,本章也介绍了如何优化粒子系统的性能,帮助读者构建高性能的 3D 数据可视化应用。

目录

  1. Rust WebAssembly 与 Three.js 结合的高性能 3D 粒子系统实战
  2. 一、引言
  3. 二、WebGL 与 Three.js 基础
  4. 2.1 WebGL 概述
  5. 2.2 Three.js 概述
  6. 2.3 Three.js 的安装与使用
  7. 使用 npm 安装
  8. 三、Rust WebAssembly 与 WebGL 交互
  9. 3.1 传递顶点数据
  10. 3.2 传递纹理数据
  11. 3.3 传递变换矩阵
  12. 四、实战项目:高性能粒子系统
  13. 4.1 项目概述
  14. 4.2 项目结构
  15. 4.3 Cargo.toml
  16. 4.4 src/particle.rs
  17. 4.5 src/emitter.rs
  18. 4.6 src/utils.rs
  19. 4.7 src/lib.rs
  20. 4.8 static/style.css
  21. 4.9 static/main.js
  22. 4.10 static/index.html
  23. 4.11 编译项目
  24. 4.12 测试项目
  25. 五、性能优化
  26. 5.1 编译器优化
  27. 5.2 数据布局优化
  28. 5.3 内存管理优化
  29. 5.4 算法优化
  30. 5.5 Web Workers 并行计算
  31. 六、项目部署
  32. 6.1 使用 Vite 打包
  33. 6.2 部署到 Netlify
  34. 6.3 部署到 Vercel
  35. 七、总结
  36. 7.1 技术栈
  37. 7.2 核心功能
  38. 7.3 未来改进

更多推荐文章

查看全部
  • Stable Diffusion WebUI 完整使用教程
  • Java 构建个性化旅游系统技术方案
  • FPGA 核心资源解析:LUT、FF、BRAM、DSP、PLL 及综合报告解读
  • AI 开发核心概念:Skill、MCP 与 Function Call 详解
  • Ubuntu 部署 OpenClaw 并接入飞书机器人
  • Linux 环境下的 Git 版本控制入门与实践
  • STL 转 STEP 格式转换工具 stltostp 使用指南
  • AI 大模型入门:文心一言与百度 AI 战略
  • Java OCR 快速集成:基于 PaddleOCR 的实战指南
  • 从焦虑到掌控:AI 时代的超级能动性与系统思维
  • 法律人使用 AI 大模型指南:Prompt 工程与场景应用
  • LoRA 与完全微调的差异:基于 MIT 光谱分析研究
  • AI 增强搜索引擎 llm-answer-engine 项目解析与部署指南
  • AI 重构产品工作流:为何“人人都是产品经理”终成现实
  • 基于 PHP 与 Vue.js 的商城电商网站设计与实现
  • 后端转前端:一份实用的技术路线图
  • 文心大模型 4.5 开源版深度解析与单卡部署实战
  • Python 图形界面与游戏开发实战:计算器、记事本及经典小游戏
  • 大型语言模型(LLM)面试核心问题解析与解答
  • DOM 详解:使用 JavaScript 操作网页元素的核心方法

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online