这个Three.js示例展示了如何使用Uint类型的BufferAttribute创建高效的大规模3D场景。通过使用Int16和Uint8类型的缓冲区属性存储法线和颜色数据,并启用归一化选项,可以在保持良好视觉效果的同时显著减少内存使用。
核心技术包括:
DOCTYPE html>
<html lang="en">
<head>
<title>three.js webgl - buffergeometry - uinttitle>
<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 - uintdiv>
<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 mesh;
init();
function init() {
container = document.getElementById( 'container' );
// 初始化相机
camera = new THREE.PerspectiveCamera( 27, window.innerWidth / window.innerHeight, 1, 3500 );
camera.position.z = 2750;
// 初始化场景
scene = new THREE.Scene();
scene.background = new THREE.Color( 0x050505 );
scene.fog = new THREE.Fog( 0x050505, 2000, 3500 );
// 添加光照
scene.add( new THREE.AmbientLight( 0xcccccc ) );
const light1 = new THREE.DirectionalLight( 0xffffff, 1.5 );
light1.position.set( 1, 1, 1 );
scene.add( light1 );
const light2 = new THREE.DirectionalLight( 0xffffff, 4.5 );
light2.position.set( 0, - 1, 0 );
scene.add( light2 );
// 三角形数量
const triangles = 500000;
// 创建BufferGeometry
const geometry = new THREE.BufferGeometry();
// 存储位置、法线和颜色数据
const positions = [];
const normals = [];
const colors = [];
// 临时颜色对象
const color = new THREE.Color();
// 三角形分布范围
const n = 800, n2 = n / 2;
// 单个三角形大小
const d = 12, d2 = d / 2;
// 临时向量,用于计算法线
const pA = new THREE.Vector3();
const pB = new THREE.Vector3();
const pC = new THREE.Vector3();
const cb = new THREE.Vector3();
const ab = new THREE.Vector3();
// 生成随机三角形
for ( let i = 0; i < triangles; i ++ ) {
// 计算三角形三个顶点的位置
const x = Math.random() * n - n2;
const y = Math.random() * n - n2;
const z = Math.random() * n - n2;
const ax = x + Math.random() * d - d2;
const ay = y + Math.random() * d - d2;
const az = z + Math.random() * d - d2;
const bx = x + Math.random() * d - d2;
const by = y + Math.random() * d - d2;
const bz = z + Math.random() * d - d2;
const cx = x + Math.random() * d - d2;
const cy = y + Math.random() * d - d2;
const cz = z + Math.random() * d - d2;
// 添加顶点位置数据
positions.push( ax, ay, az );
positions.push( bx, by, bz );
positions.push( cx, cy, cz );
// 计算平面法线
pA.set( ax, ay, az );
pB.set( bx, by, bz );
pC.set( cx, cy, cz );
cb.subVectors( pC, pB );
ab.subVectors( pA, pB );
cb.cross( ab );
cb.normalize();
// 获取法线分量
const nx = cb.x;
const ny = cb.y;
const nz = cb.z;
// 存储法线数据,使用Int16范围(-32768 to 32767)
normals.push( nx * 32767, ny * 32767, nz * 32767 );
normals.push( nx * 32767, ny * 32767, nz * 32767 );
normals.push( nx * 32767, ny * 32767, nz * 32767 );
// 基于位置计算颜色
const vx = ( x / n ) + 0.5;
const vy = ( y / n ) + 0.5;
const vz = ( z / n ) + 0.5;
color.setRGB( vx, vy, vz );
// 存储颜色数据,使用Uint8范围(0 to 255)
colors.push( color.r * 255, color.g * 255, color.b * 255 );
colors.push( color.r * 255, color.g * 255, color.b * 255 );
colors.push( color.r * 255, color.g * 255, color.b * 255 );
}
// 创建不同类型的BufferAttribute
// 位置使用Float32
const positionAttribute = new THREE.Float32BufferAttribute( positions, 3 );
// 法线使用Int16,减少内存占用
const normalAttribute = new THREE.Int16BufferAttribute( normals, 3 );
// 颜色使用Uint8,减少内存占用
const colorAttribute = new THREE.Uint8BufferAttribute( colors, 3 );
// 启用归一化,使数据在着色器中被解释为0.0-1.0或-1.0-1.0范围
normalAttribute.normalized = true;
colorAttribute.normalized = true;
// 设置几何体属性
geometry.setAttribute( 'position', positionAttribute );
geometry.setAttribute( 'normal', normalAttribute );
geometry.setAttribute( 'color', colorAttribute );
// 计算边界球体
geometry.computeBoundingSphere();
// 创建Phong材质,支持顶点颜色和光照
const material = new THREE.MeshPhongMaterial( {
color: 0xd5d5d5, specular: 0xffffff, shininess: 250,
side: THREE.DoubleSide, vertexColors: true
} );
// 创建网格对象
mesh = new THREE.Mesh( geometry, material );
scene.add( mesh );
// 初始化渲染器
renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setAnimationLoop( animate );
container.appendChild( renderer.domElement );
// 添加性能统计
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() {
const time = Date.now() * 0.001;
// 旋转网格
mesh.rotation.x = time * 0.25;
mesh.rotation.y = time * 0.5;
// 渲染场景
renderer.render( scene, camera );
// 更新性能统计
stats.update();
}
script>
body>
html>
在Three.js中,可以使用不同类型的BufferAttribute来存储顶点数据:
本示例中:
// 位置使用Float32
const positionAttribute = new THREE.Float32BufferAttribute( positions, 3 );
// 法线使用Int16
const normalAttribute = new THREE.Int16BufferAttribute( normals, 3 );
// 颜色使用Uint8
const colorAttribute = new THREE.Uint8BufferAttribute( colors, 3 );
归一化(normalized)选项允许将整数数据映射到浮点数范围:
启用归一化:
normalAttribute.normalized = true;
colorAttribute.normalized = true;
这种方式可以在保持足够精度的同时显著减少内存使用。例如,使用Int16存储法线比Float32节省50%的内存。
本示例使用了MeshPhongMaterial,它实现了Phong光照模型,支持:
const material = new THREE.MeshPhongMaterial( {
color: 0xd5d5d5, specular: 0xffffff, shininess: 250,
side: THREE.DoubleSide, vertexColors: true
} );
法线数据对于光照计算非常重要,正确的法线方向决定了表面如何反射光线。
使用Uint类型BufferAttribute的主要优势:
适用场景:
在处理大规模3D场景时,合理选择数据类型和启用归一化可以在不明显影响视觉效果的前提下大幅提升性能。