Rust WebAssembly与Three.js结合的3D数据可视化实战:高性能粒子系统

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安装npminstall three 

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

import*asTHREEfrom'three';// 创建场景const scene =newTHREE.Scene(); scene.background =newTHREE.Color(0x000000);// 创建相机const camera =newTHREE.PerspectiveCamera(75,// 视野角度 window.innerWidth / window.innerHeight,// 宽高比0.1,// 近裁剪面1000// 远裁剪面); camera.position.z =5;// 创建渲染器const renderer =newTHREE.WebGLRenderer({antialias:true// 抗锯齿}); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement);// 创建几何体const geometry =newTHREE.BoxGeometry(1,1,1);// 创建材质const material =newTHREE.MeshPhongMaterial({color:0x00ff00,shininess:100});// 创建网格const cube =newTHREE.Mesh(geometry, material); scene.add(cube);// 创建光源const ambientLight =newTHREE.AmbientLight(0x404040); scene.add(ambientLight);const directionalLight =newTHREE.DirectionalLight(0xffffff,0.8); directionalLight.position.set(1,1,1); scene.add(directionalLight);// 动画循环functionanimate(){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.rsusewasm_bindgen::prelude::*;useweb_sys::WebGlRenderingContext;#[repr(C)]#[derive(Debug, Clone, Copy)]pubstructVertex{pub x:f32,pub y:f32,pub z:f32,pub r:f32,pub g:f32,pub b:f32,pub a:f32,}implVertex{pubfnnew(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]pubfncreate_vertices(count:u32)->Vec<f32>{letmut vertices =Vec::with_capacity(count asusize*7);// 每个顶点有7个元素(x,y,z,r,g,b,a)for i in0..count {let angle = i asf32/ count asf32*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*asTHREEfrom'three';import init,{ create_vertices }from'./pkg/particle_system.js';asyncfunctionrun(){awaitinit(); console.log('WebAssembly模块加载成功');const scene =newTHREE.Scene(); scene.background =newTHREE.Color(0x000000);const camera =newTHREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight,0.1,1000); camera.position.z =5;const renderer =newTHREE.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 =newTHREE.BufferGeometry(); geometry.setAttribute('position',newTHREE.Float32BufferAttribute(vertices.slice(0, count *3),3)); geometry.setAttribute('color',newTHREE.Float32BufferAttribute(vertices.slice(count *3),4));// 创建材质const material =newTHREE.PointsMaterial({size:0.05,vertexColors:true,transparent:true,opacity:0.8});// 创建粒子系统const particles =newTHREE.Points(geometry, material); scene.add(particles);const ambientLight =newTHREE.AmbientLight(0x404040); scene.add(ambientLight);const directionalLight =newTHREE.DirectionalLight(0xffffff,0.8); directionalLight.position.set(1,1,1); scene.add(directionalLight);functionanimate(){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]pubfncreate_texture(width:u32, height:u32)->Vec<u8>{letmut texture =Vec::with_capacity(width asusize* height asusize*4);// 每个像素有4个元素(r,g,b,a)for y in0..height {for x in0..width {let r =(x asf32/ width asf32*255.0)asu8;let g =(y asf32/ height asf32*255.0)asu8;let b =128;let a =255; texture.push(r); texture.push(g); texture.push(b); texture.push(a);}} texture }

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

import*asTHREEfrom'three';import init,{ create_vertices, create_texture }from'./pkg/particle_system.js';asyncfunctionrun(){awaitinit(); console.log('WebAssembly模块加载成功');const scene =newTHREE.Scene(); scene.background =newTHREE.Color(0x000000);const camera =newTHREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight,0.1,1000); camera.position.z =5;const renderer =newTHREE.WebGLRenderer({antialias:true}); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement);const count =1000;const vertices =create_vertices(count);const geometry =newTHREE.BufferGeometry(); geometry.setAttribute('position',newTHREE.Float32BufferAttribute(vertices.slice(0, count *3),3)); geometry.setAttribute('color',newTHREE.Float32BufferAttribute(vertices.slice(count *3),4));// 从Rust Wasm获取纹理数据const textureWidth =256;const textureHeight =256;const textureData =create_texture(textureWidth, textureHeight);// 创建纹理const texture =newTHREE.DataTexture(newUint8Array(textureData), textureWidth, textureHeight,THREE.RGBAFormat,THREE.UnsignedByteType ); texture.needsUpdate =true;const material =newTHREE.PointsMaterial({size:0.05,vertexColors:true,transparent:true,opacity:0.8,map: texture,blending:THREE.AdditiveBlending });const particles =newTHREE.Points(geometry, material); scene.add(particles);const ambientLight =newTHREE.AmbientLight(0x404040); scene.add(ambientLight);const directionalLight =newTHREE.DirectionalLight(0xffffff,0.8); directionalLight.position.set(1,1,1); scene.add(directionalLight);functionanimate(){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)]pubstructMat4{pub data:[f32;16],}implMat4{pubfnidentity()->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],}}pubfntranslation(x:f32, y:f32, z:f32)->Self{letmut mat =Self::identity(); mat.data[12]= x; mat.data[13]= y; mat.data[14]= z; mat }pubfnrotation(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 }pubfnrotation_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],}}pubfnrotation_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],}}pubfnrotation_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],}}pubfnscaling(x:f32, y:f32, z:f32)->Self{letmut mat =Self::identity(); mat.data[0]= x; mat.data[5]= y; mat.data[10]= z; mat }pubfnmultiply(&self, other:&Mat4)->Self{letmut mat =Self::identity();for i in0..4{for j in0..4{ mat.data[i *4+ j]=(0..4).map(|k|self.data[i *4+ k]* other.data[k *4+ j]).sum();}} mat }}implstd::ops::Mul<Mat4>forMat4{typeOutput=Mat4;fnmul(self, other:Mat4)->Self::Output{self.multiply(&other)}}#[wasm_bindgen]pubfncreate_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*asTHREEfrom'three';import init,{ create_vertices, create_texture, create_model_matrix }from'./pkg/particle_system.js';asyncfunctionrun(){awaitinit(); console.log('WebAssembly模块加载成功');const scene =newTHREE.Scene(); scene.background =newTHREE.Color(0x000000);const camera =newTHREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight,0.1,1000); camera.position.z =5;const renderer =newTHREE.WebGLRenderer({antialias:true}); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement);const count =1000;const vertices =create_vertices(count);const geometry =newTHREE.BufferGeometry(); geometry.setAttribute('position',newTHREE.Float32BufferAttribute(vertices.slice(0, count *3),3)); geometry.setAttribute('color',newTHREE.Float32BufferAttribute(vertices.slice(count *3),4));const textureWidth =256;const textureHeight =256;const textureData =create_texture(textureWidth, textureHeight);const texture =newTHREE.DataTexture(newUint8Array(textureData), textureWidth, textureHeight,THREE.RGBAFormat,THREE.UnsignedByteType ); texture.needsUpdate =true;const material =newTHREE.PointsMaterial({size:0.05,vertexColors:true,transparent:true,opacity:0.8,map: texture,blending:THREE.AdditiveBlending });const particles =newTHREE.Points(geometry, material); scene.add(particles);// 从Rust Wasm获取模型矩阵const modelMatrixData =create_model_matrix(0,0,0,0,0,0,1,1,1);const modelMatrix =newTHREE.Matrix4().fromArray(modelMatrixData); particles.matrix = modelMatrix; particles.matrixAutoUpdate =true;const ambientLight =newTHREE.AmbientLight(0x404040); scene.add(ambientLight);const directionalLight =newTHREE.DirectionalLight(0xffffff,0.8); directionalLight.position.set(1,1,1); scene.add(directionalLight);functionanimate(){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.rsuserand::Rng;useserde::Serialize;#[derive(Debug, Clone, Copy, Serialize)]pubstructParticle{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,}implParticle{pubfnnew()->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,}}pubfnrandomize(&mutself, rng:&mutrand::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);}pubfnupdate(&mutself, 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);}pubfnis_dead(&self)->bool{self.lifetime >self.max_lifetime }}

4.5 src/emitter.rs

// src/emitter.rsusecrate::particle::Particle;userand::Rng;#[derive(Debug, Clone)]pubstructEmitter{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,}implEmitter{pubfnnew(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(),}}pubfnupdate(&mutself, dt:f32){self.accumulated_time += dt;// 发射新粒子let particles_to_spawn =(self.accumulated_time *self.emission_rate)asusize;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 _ in0..spawn_count {letmut particle =Particle::new(); particle.randomize(&mutself.rng); particle.x +=self.x; particle.y +=self.y; particle.z +=self.z;self.particles.push(particle);}self.accumulated_time -= particles_to_spawn asf32/self.emission_rate;}// 更新粒子for particle in&mutself.particles { particle.update(dt);}// 删除死亡粒子self.particles.retain(|particle|!particle.is_dead());}pubfnget_vertices(&self)->Vec<f32>{letmut 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 }pubfnget_sizes(&self)->Vec<f32>{letmut sizes =Vec::with_capacity(self.particles.len());for particle in&self.particles { sizes.push(particle.size);} sizes }}

4.6 src/utils.rs

// src/utils.rspubfnset_panic_hook(){#[cfg(feature = "console_error_panic_hook")]console_error_panic_hook::set_once();}

4.7 src/lib.rs

// src/lib.rsusewasm_bindgen::prelude::*;usecrate::emitter::Emitter;#[wasm_bindgen]pubstructEmitterWrapper{ emitter:Emitter,}#[wasm_bindgen]implEmitterWrapper{#[wasm_bindgen(constructor)]pubfnnew(x:f32, y:f32, z:f32, max_particles:usize, emission_rate:f32)->EmitterWrapper{EmitterWrapper{ emitter:Emitter::new(x, y, z, max_particles, emission_rate),}}pubfnupdate(&mutself, dt:f32){self.emitter.update(dt);}pubfnget_vertices(&self)->Vec<f32>{self.emitter.get_vertices()}pubfnget_sizes(&self)->Vec<f32>{self.emitter.get_sizes()}pubfnget_count(&self)->usize{self.emitter.particles.len()}}#[wasm_bindgen]pubfninit_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.jsimport*asTHREEfrom'three';import Stats from'three/examples/jsm/libs/stats.module.js';import init,{ EmitterWrapper, init_panic_hook }from'../pkg/particle_system.js';asyncfunctionrun(){awaitinit();init_panic_hook(); console.log('WebAssembly模块加载成功');// 初始化Statsconst stats =newStats(); stats.domElement.style.position ='absolute'; stats.domElement.style.top ='20px'; stats.domElement.style.right ='20px'; document.body.appendChild(stats.domElement);// 创建场景const scene =newTHREE.Scene(); scene.background =newTHREE.Color(0x000000);// 创建相机const camera =newTHREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight,0.1,1000); camera.position.z =10;// 创建渲染器const renderer =newTHREE.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 =newEmitterWrapper(0,0,0, maxParticles, emissionRate);// 创建几何体const geometry =newTHREE.BufferGeometry();const vertices = emitter.get_vertices(); geometry.setAttribute('position',newTHREE.Float32BufferAttribute(vertices.slice(0, vertices.length /7*3),3)); geometry.setAttribute('color',newTHREE.Float32BufferAttribute(vertices.slice(vertices.length /7*3),4));const sizes = emitter.get_sizes(); geometry.setAttribute('size',newTHREE.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 =newTHREE.CanvasTexture(canvas);// 创建材质const material =newTHREE.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 =newTHREE.Points(geometry, material); scene.add(particles);// 创建光源const ambientLight =newTHREE.AmbientLight(0x404040); scene.add(ambientLight);const directionalLight =newTHREE.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',newTHREE.Float32BufferAttribute([],3)); particles.geometry.setAttribute('color',newTHREE.Float32BufferAttribute([],4)); particles.geometry.setAttribute('size',newTHREE.Float32BufferAttribute([],1));});// 动画循环let lastTime = performance.now();functionanimate(){ 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 =newFloat32Array(vertices.slice(0, vertices.length /7*3)); particles.geometry.attributes.color.array =newFloat32Array(vertices.slice(vertices.length /7*3)); particles.geometry.attributes.size.array =newFloat32Array(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 --><!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><metaname="viewport"content="width=device-width, initial-scale=1.0"><title>Rust WebAssembly粒子系统</title><linkrel="stylesheet"href="style.css"></head><body><scripttype="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©]属性对齐结构体,减少内存对齐开销:

// src/particle.rs#[repr(C)]#[derive(Debug, Clone, Copy, Serialize)]pubstructParticle{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.rsimplParticle{pubfnupdate(&mutself, 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")]usewasm_simd::*;implParticle{#[cfg(target_arch = "wasm32")]pubfnupdate_batch(particles:&mut[Particle], dt:f32){constBATCH_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.jsimport 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':awaitinit();init_panic_hook(); maxParticles = data.maxParticles; emissionRate = data.emissionRate; emitter =newEmitterWrapper(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.jsconst worker =newWorker('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 =newFloat32Array(vertices.slice(0, vertices.length /7*3)); particles.geometry.attributes.color.array =newFloat32Array(vertices.slice(vertices.length /7*3)); particles.geometry.attributes.size.array =newFloat32Array(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',newTHREE.Float32BufferAttribute([],3)); particles.geometry.setAttribute('color',newTHREE.Float32BufferAttribute([],4)); particles.geometry.setAttribute('size',newTHREE.Float32BufferAttribute([],1));break;}};

六、项目部署

6.1 使用Vite打包

安装Vite:

npm init -ynpminstall-D vite 

创建vite.config.js:

import{ defineConfig }from'vite';exportdefaultdefineConfig({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数据可视化应用。

Read more

FPGA教程系列-Vivado AXI4-Stream Data FIFO核解读测试

FPGA教程系列-Vivado AXI4-Stream Data FIFO核解读测试

FPGA教程系列-Vivado AXI4-Stream Data FIFO核解读测试 FIFO depth (FIFO 深度): 定义了 FIFO 能存储多少个数据字(Data Words)。 注意:实际占用的存储资源取决于深度乘以数据宽度(TDATA width)。 Memory type (存储器类型): Auto * 决定用 FPGA 内部的哪种资源来实现 FIFO。 * Auto: 让 Vivado 综合工具根据 FIFO 的大小自动选择(通常小 FIFO 用分布式 RAM/LUTRAM,大 FIFO 用块 RAM/BRAM)。 * Block RAM: 强制使用 BRAM。 * Distributed RAM: 强制使用 LUT 搭建的

OpenClaw 多机器人多 Agent 模式:打造你的 AI 助手团队

OpenClaw 多机器人多 Agent 模式:打造你的 AI 助手团队

OpenClaw 多机器人多 Agent 模式:打造你的 AI 助手团队 完整教程:https://awesome.tryopenclaw.asia/docs/04-practical-cases/15-solo-entrepreneur-cases.html 16.1 为什么需要多 Agent? 作为超级个体创业者,你可能需要不同类型的 AI 助手来处理不同的工作: * 主助理:使用最强大的模型(Claude Opus)处理复杂任务 * 内容创作助手:专注于文章写作、文案创作 * 技术开发助手:处理代码开发、技术问题 * AI 资讯助手:快速获取和整理 AI 行业动态 传统的单 Agent 模式需要频繁切换模型和上下文,效率低下。多 Agent 模式让你可以同时拥有多个专业助手,各司其职。

OpenClaw 安装 + 接入飞书机器人完整教程

OpenClaw 安装 + 接入飞书机器人完整教程 OpenClaw 曾用名:ClawdBot → MoltBot → OpenClaw(同一软件,勿混淆) 适用系统:Windows 10/11 最后更新:2026年3月 一、什么是 OpenClaw? OpenClaw 是一款 2026 年爆火的开源个人 AI 助手,GitHub 星标已超过 10 万颗。 与普通 AI 聊天机器人的核心区别: * 真正的执行能力:不只回答问题,能实际操作你的电脑 * 24/7 全天候待命:睡觉时也能主动完成任务 * 完全开源免费:数据完全掌控在自己手中 * 支持国内平台:飞书、钉钉等均已支持接入 二、安装前准备:安装 Node.js 建议提前手动安装

组建龙虾团队——OpenClaw多机器人构建

组建龙虾团队——OpenClaw多机器人构建

成功搭建了OpenClaw,也成功建立的自己的每日服务,这时候发现,似乎不太敢在当前的机器人中让他做别的事情,生怕会话太多会让他出现遗忘。(尽管我们配置了QMD记忆增强,但毋庸置疑任何技术都是有上限的)。 换做同样的情况,比如在DeepSeek或者豆包之类的对话窗口,我们会习惯性地新建一个对话。那么我们是否可以新建一个机器人,或者多个机器人,让他们各司其职,各尽所能,形成一个相互配合的团队呢~开干吧,没什么不可能的!! 🦞新建一个机器人 来到飞书开发者后台,新创建一个应用,在这里我们以短视频剪辑脚本应用为例。 创建之后,由于我们的openclaw绑定的是之前的飞书渠道,并没有链接到这个应用的APP ID,所以暂时不做其他操作,只需要记录一下他的APP ID和APP Secret。 🦞配置OpenClaw 如果还是按照claw的命令行安装,每一步都有些让人担心害怕,毕竟我们先前已经配置过一次了,接下来的操作,需要小心是否会把以前的配置给覆盖掉。 为了避免这样的不确定性,我们直接去操作他的配置文件 在WSL2终端中进入openclaw目录 cd .openclaw