这个Three.js示例展示了如何使用**自定义WebGL缓冲区对象(VBO)**创建高性能的粒子系统。通过直接操作底层WebGL API创建和管理缓冲区,实现了粒子位置数据的动态切换,并结合drawRange
技术优化渲染性能。
核心技术包括:
DOCTYPE html>
<html lang="en">
<head>
<title>three.js webgl - buffergeometry - custom VBOstitle>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<link type="text/css" rel="stylesheet" href="main.css">
head>
<body>
<div id="container">div>
<div id="info"><a href="https://threejs.org" target="_blank" rel="noopener">three.jsa> webgl - buffergeometry - custom VBOsdiv>
<script type="importmap">
{
"imports": {
"three": "../build/three.module.js",
"three/addons/": "./jsm/"
}
}
script>
<script type="module">
import * as THREE from 'three';
import Stats from 'three/addons/libs/stats.module.js';
let container, stats;
let camera, scene, renderer;
let points;
const particles = 300000; // 粒子总数
let drawCount = 10000; // 当前渲染的粒子数
init();
animate();
function init() {
container = document.getElementById( 'container' );
// 初始化渲染器
renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setAnimationLoop( animate );
container.appendChild( renderer.domElement );
// 初始化相机
camera = new THREE.PerspectiveCamera( 27, window.innerWidth / window.innerHeight, 5, 3500 );
camera.position.z = 2750;
// 初始化场景
scene = new THREE.Scene();
scene.background = new THREE.Color( 0x050505 );
scene.fog = new THREE.Fog( 0x050505, 2000, 3500 );
// 创建几何体
const geometry = new THREE.BufferGeometry();
// 存储粒子位置和颜色的数组
const positions = [];
const positions2 = [];
const colors = [];
const color = new THREE.Color();
// 粒子分布范围
const n = 1000, n2 = n / 2;
// 初始化所有粒子数据
for ( let i = 0; i < particles; i ++ ) {
// 随机位置
const x = Math.random() * n - n2;
const y = Math.random() * n - n2;
const z = Math.random() * n - n2;
positions.push( x, y, z );
// 创建第二个位置数组,坐标值进行了交换和缩放
positions2.push( z * 0.3, x * 0.3, y * 0.3 );
// 根据位置计算颜色
const vx = ( x / n ) + 0.5;
const vy = ( y / n ) + 0.5;
const vz = ( z / n ) + 0.5;
color.setRGB( vx, vy, vz, THREE.SRGBColorSpace );
colors.push( color.r, color.g, color.b );
}
// 获取底层WebGL上下文
const gl = renderer.getContext();
// 直接使用WebGL API创建和填充缓冲区对象
// 第一个位置缓冲区
const pos = gl.createBuffer();
gl.bindBuffer( gl.ARRAY_BUFFER, pos );
gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( positions ), gl.STATIC_DRAW );
// 第二个位置缓冲区
const pos2 = gl.createBuffer();
gl.bindBuffer( gl.ARRAY_BUFFER, pos2 );
gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( positions2 ), gl.STATIC_DRAW );
// 颜色缓冲区
const rgb = gl.createBuffer();
gl.bindBuffer( gl.ARRAY_BUFFER, rgb );
gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( colors ), gl.STATIC_DRAW );
// 创建THREE.GLBufferAttribute包装WebGL缓冲区
const posAttr1 = new THREE.GLBufferAttribute( pos, gl.FLOAT, 3, 4, particles );
const posAttr2 = new THREE.GLBufferAttribute( pos2, gl.FLOAT, 3, 4, particles );
// 初始使用第一个位置缓冲区
geometry.setAttribute( 'position', posAttr1 );
// 每2秒切换一次位置缓冲区
setInterval( function () {
const attr = geometry.getAttribute( 'position' );
geometry.setAttribute( 'position', ( attr === posAttr1 ) ? posAttr2 : posAttr1 );
}, 2000 );
// 设置颜色属性
geometry.setAttribute( 'color', new THREE.GLBufferAttribute( rgb, gl.FLOAT, 3, 4, particles ) );
// 创建粒子材质
const material = new THREE.PointsMaterial( { size: 15, vertexColors: true } );
// 创建粒子系统
points = new THREE.Points( geometry, material );
// 设置边界球体,帮助渲染器进行视锥体剔除
geometry.boundingSphere = new THREE.Sphere().set( new THREE.Vector3(), 500 );
// 添加到场景
scene.add( points );
// 添加性能统计
stats = new Stats();
container.appendChild( stats.dom );
// 窗口大小变化事件监听
window.addEventListener( 'resize', onWindowResize );
}
// 窗口大小变化处理函数
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
// 动画循环
function animate() {
// 随机改变渲染的粒子数量
drawCount = ( Math.max( 5000, drawCount ) + Math.floor( 500 * Math.random() ) ) % particles;
points.geometry.setDrawRange( 0, drawCount );
// 基于时间的旋转动画
const time = Date.now() * 0.001;
points.rotation.x = time * 0.1;
points.rotation.y = time * 0.2;
// 渲染场景
renderer.render( scene, camera );
// 更新性能统计
stats.update();
}
script>
body>
html>
Three.js通常隐藏了底层WebGL API的细节,但在某些高性能需求场景下,我们可以直接操作WebGL缓冲区对象:
const gl = renderer.getContext()
gl.createBuffer()
gl.bindBuffer()
gl.bufferData()
这种方法比使用Three.js的BufferAttribute更底层,性能更高,特别适合处理超大规模数据集。
本示例的核心技术是每2秒切换一次粒子的位置数据:
pos
和pos2
,分别存储不同的位置数据setInterval
定期切换几何体使用的位置属性这种技术可以用于实现粒子系统的形态变化、数据可视化中的数据切换等场景。
通过动态调整drawRange
,我们可以:
这种技术在处理大规模数据集时尤为重要,可以显著提高渲染性能。
直接操作WebGL缓冲区时的性能优化建议:
STATIC_DRAW
、DYNAMIC_DRAW
或STREAM_DRAW
这种自定义VBO技术适合需要极致性能的应用场景,如大规模粒子系统、科学可视化、实时数据渲染等。