THREE.InterleavedBufferAttribute
是 three.js
中的一种属性类型,用于在单个缓冲区中存储多个属性的数据。它允许高效地传输顶点数据到 GPU,并减少 WebGL 调用的数量。以下是关于 new THREE.InterleavedBufferAttribute( vertexBuffer, 3, 0 )
的详细讲解,包括其入参、出参、方法和属性。
构造函数
const interleavedBufferAttribute = new THREE.InterleavedBufferAttribute(vertexBuffer, 3, 0);
入参
- vertexBuffer(
THREE.InterleavedBuffer
):要绑定的InterleavedBuffer
对象,包含了所有顶点属性的数据。 - itemSize(
number
):每个顶点属性的大小。例如,3 表示一个三维向量(如位置)。 - offset(
number
):在InterleavedBuffer
中此属性的起始位置的偏移量(以元素为单位,不是字节)。
出参
构造函数返回一个 THREE.InterleavedBufferAttribute
对象,用于在单个缓冲区中定义顶点属性的数据布局。
属性
- array: 存储数据的
TypedArray
,由InterleavedBuffer
提供。 - count: 此属性中的条目数量。
- itemSize: 每个顶点属性的大小(例如,3 表示一个三维向量)。
- offset: 在
InterleavedBuffer
中此属性的起始位置的偏移量。 - data: 关联的
THREE.InterleavedBuffer
对象。 - normalized: 如果为
true
,表示属性的数据应该被归一化。 - isInterleavedBufferAttribute: 标志此对象是一个
InterleavedBufferAttribute
。
方法
THREE.InterleavedBufferAttribute
继承自 THREE.BufferAttribute
,因此它具有 BufferAttribute
的所有方法。以下是一些常用的方法:
- getX(index): 获取给定索引处的 X 值。
- getY(index): 获取给定索引处的 Y 值。
- getZ(index): 获取给定索引处的 Z 值。
- getW(index): 获取给定索引处的 W 值。
- setX(index, x): 设置给定索引处的 X 值。
- setY(index, y): 设置给定索引处的 Y 值。
- setZ(index, z): 设置给定索引处的 Z 值。
- setW(index, w): 设置给定索引处的 W 值。
- setXYZ(index, x, y, z): 设置给定索引处的 X, Y 和 Z 值。
- setXYZW(index, x, y, z, w): 设置给定索引处的 X, Y, Z 和 W 值。
使用示例
下面是一个简单的示例,展示如何创建 InterleavedBufferAttribute
并将其用于定义几何体的属性:
import * as THREE from 'three';
// 创建一个 InterleavedBuffer
const vertexData = new Float32Array([
// 顶点数据 (x, y, z, u, v)
-1, 1, 0, 0, 1,
1, 1, 0, 1, 1,
-1, -1, 0, 0, 0,
1, -1, 0, 1, 0
]);
const vertexBuffer = new THREE.InterleavedBuffer(vertexData, 5); // 5 是每个顶点的数据元素数量 (x, y, z, u, v)
// 创建 InterleavedBufferAttribute 用于位置 (x, y, z)
const positions = new THREE.InterleavedBufferAttribute(vertexBuffer, 3, 0);
// 创建 InterleavedBufferAttribute 用于 UV 坐标 (u, v)
const uvs = new THREE.InterleavedBufferAttribute(vertexBuffer, 2, 3);
// 创建几何体并设置属性
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', positions);
geometry.setAttribute('uv', uvs);
// 创建材质
const material = new THREE.MeshBasicMaterial({ color: 0xff0000, side: THREE.DoubleSide });
// 创建网格
const mesh = new THREE.Mesh(geometry, material);
// 创建场景并添加网格
const scene = new THREE.Scene();
scene.add(mesh);
// 创建相机
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 5;
// 创建渲染器
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// 渲染循环
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
animate();
说明
- 在这个示例中,我们首先创建了一个
InterleavedBuffer
,其中包含顶点数据,包括位置(x, y, z)和 UV 坐标(u, v)。 - 然后,我们创建了两个
InterleavedBufferAttribute
,一个用于位置,一个用于 UV 坐标,分别从InterleavedBuffer
中提取数据。 - 接着,我们创建一个
BufferGeometry
并将属性设置到几何体中。 - 最后,我们创建一个
Mesh
并将其添加到场景中进行渲染。
通过这种方式,可以高效地将多个顶点属性存储在一个缓冲区中,从而减少 WebGL 调用的次数并提高渲染性能。
THREE.InstancedBufferGeometry
是 three.js
中的一个类,用于处理几何体的实例化渲染。实例化渲染是一种优化技术,可以高效地渲染大量相同几何体的副本。以下是关于 new THREE.InstancedBufferGeometry()
的详细讲解,包括其入参、出参、方法和属性。
构造函数
const instancedBufferGeometry = new THREE.InstancedBufferGeometry();
入参
THREE.InstancedBufferGeometry
构造函数不需要参数。它创建了一个新的实例化缓冲几何体对象。
出参
构造函数返回一个 THREE.InstancedBufferGeometry
对象,用于存储顶点和实例化属性的数据。
属性
- attributes: 存储几何体的属性(如位置、法线、UV 坐标等)。
- index: 存储几何体的索引数据(如果存在)。
- groups: 用于定义几何体的不同部分,以便应用不同的材质。
- boundingBox: 几何体的包围盒。
- boundingSphere: 几何体的包围球。
- drawRange: 控制几何体渲染的起始和结束顶点。
- instanceCount: 实例化渲染的实例数量。默认值是
Infinity
,通常需要根据具体情况设置。
方法
THREE.InstancedBufferGeometry
继承自 THREE.BufferGeometry
,因此它具有 BufferGeometry
的所有方法。以下是一些常用的方法:
- addAttribute(name, attribute): 添加一个属性到几何体中(已废弃,使用
setAttribute
替代)。 - setAttribute(name, attribute): 设置几何体的属性。例如位置、法线、UV 坐标等。
- getAttribute(name): 获取几何体的属性。
- setIndex(index): 设置几何体的索引。
- getIndex(): 获取几何体的索引。
- addGroup(start, count, materialIndex): 为几何体添加一个组。
- clearGroups(): 清除所有组。
- setDrawRange(start, count): 设置几何体的绘制范围。
- computeBoundingBox(): 计算几何体的包围盒。
- computeBoundingSphere(): 计算几何体的包围球。
- fromGeometry(geometry): 从
Geometry
对象生成一个BufferGeometry
对象。 - dispose(): 释放几何体的内存。
此外,InstancedBufferGeometry
还提供了一些特定的方法:
- addAttribute(name, attribute, meshPerAttribute): 添加一个实例化属性。
- setAttribute(name, attribute, meshPerAttribute): 设置一个实例化属性。
- getAttribute(name): 获取一个实例化属性。
使用示例
下面是一个简单的使用示例,展示如何创建一个 InstancedBufferGeometry
并设置其属性来渲染多个实例化的立方体:
import * as THREE from 'three';
// 创建场景
const scene = new THREE.Scene();
// 创建相机
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 5;
// 创建渲染器
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// 创建几何体
const geometry = new THREE.InstancedBufferGeometry();
// 定义一个基本的立方体
const baseGeometry = new THREE.BoxGeometry(1, 1, 1);
// 将基本立方体的属性复制到实例化几何体中
geometry.attributes.position = baseGeometry.attributes.position;
geometry.index = baseGeometry.index;
// 创建一个实例化属性,用于存储每个实例的位置
const instanceCount = 100;
const offsets = new Float32Array(instanceCount * 3);
for (let i = 0; i < instanceCount; i++) {
offsets[i * 3 + 0] = Math.random() * 10 - 5;
offsets[i * 3 + 1] = Math.random() * 10 - 5;
offsets[i * 3 + 2] = Math.random() * 10 - 5;
}
geometry.setAttribute('offset', new THREE.InstancedBufferAttribute(offsets, 3));
// 创建材质
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
// 创建实例化网格
const mesh = new THREE.InstancedMesh(geometry, material, instanceCount);
// 将网格添加到场景中
scene.add(mesh);
// 渲染循环
function animate() {
requestAnimationFrame(animate);
// 旋转网格
mesh.rotation.x += 0.01;
mesh.rotation.y += 0.01;
renderer.render(scene, camera);
}
animate();
说明
- 在这个示例中,我们首先创建了一个
InstancedBufferGeometry
对象,并将一个基本立方体的属性复制到实例化几何体中。 - 然后,我们创建了一个实例化属性
offset
,用于存储每个实例的位置。 - 使用
THREE.InstancedMesh
来创建一个实例化网格,并将其添加到场景中进行渲染。
通过这种方式,我们可以高效地渲染大量相同的几何体实例,从而显著提升渲染性能。
以下是 three.js
示例代码,用于展示如何使用 indexed instancing 和 interleaved buffers 来渲染多个实例化的盒子:
<!DOCTYPE html>
<html lang="en">
<head>
<title>three.js webgl - indexed instancing (single box), interleaved buffers</title>
<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.js</a> - indexed instancing (single box), interleaved buffers
<div id="notSupported" style="display:none">Sorry your graphics card + browser does not support hardware instancing</div>
</div>
<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, mesh;
const instances = 5000; // 实例数量
let lastTime = 0;
const moveQ = new THREE.Quaternion( 0.5, 0.5, 0.5, 0.0 ).normalize();
const tmpQ = new THREE.Quaternion();
const tmpM = new THREE.Matrix4();
const currentM = new THREE.Matrix4();
init(); // 初始化场景
animate(); // 开始动画
function init() {
container = document.getElementById( 'container' );
camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 1, 1000 );
scene = new THREE.Scene();
scene.background = new THREE.Color( 0x101010 );
// 创建几何体
const geometry = new THREE.InstancedBufferGeometry();
// 每个网格的数据 x,y,z,w,u,v,s,t 用于 4 元素对齐
// 这里只使用 x, y, z 和 u, v; 但 x, y, z, nx, ny, nz, u, v 可能是一个好的布局
const vertexBuffer = new THREE.InterleavedBuffer( new Float32Array( [
// 前面
- 1, 1, 1, 0, 0, 0, 0, 0,
1, 1, 1, 0, 1, 0, 0, 0,
- 1, - 1, 1, 0, 0, 1, 0, 0,
1, - 1, 1, 0, 1, 1, 0, 0,
// 后面
1, 1, - 1, 0, 1, 0, 0, 0,
- 1, 1, - 1, 0, 0, 0, 0, 0,
1, - 1, - 1, 0, 1, 1, 0, 0,
- 1, - 1, - 1, 0, 0, 1, 0, 0,
// 左侧
- 1, 1, - 1, 0, 1, 1, 0, 0,
- 1, 1, 1, 0, 1, 0, 0, 0,
- 1, - 1, - 1, 0, 0, 1, 0, 0,
- 1, - 1, 1, 0, 0, 0, 0, 0,
// 右侧
1, 1, 1, 0, 1, 0, 0, 0,
1, 1, - 1, 0, 1, 1, 0, 0,
1, - 1, 1, 0, 0, 0, 0, 0,
1, - 1, - 1, 0, 0, 1, 0, 0,
// 顶部
- 1, 1, 1, 0, 0, 0, 0, 0,
1, 1, 1, 0, 1, 0, 0, 0,
- 1, 1, - 1, 0, 0, 1, 0, 0,
1, 1, - 1, 0, 1, 1, 0, 0,
// 底部
1, - 1, 1, 0, 1, 0, 0, 0,
- 1, - 1, 1, 0, 0, 0, 0, 0,
1, - 1, - 1, 0, 1, 1, 0, 0,
- 1, - 1, - 1, 0, 0, 1, 0, 0
] ), 8 );
// 使用 vertexBuffer,从偏移 0 开始,位置属性有 3 个元素
const positions = new THREE.InterleavedBufferAttribute( vertexBuffer, 3, 0 );
geometry.setAttribute( 'position', positions );
// 使用 vertexBuffer,从偏移 4 开始,uv 属性有 2 个元素
const uvs = new THREE.InterleavedBufferAttribute( vertexBuffer, 2, 4 );
geometry.setAttribute( 'uv', uvs );
const indices = new Uint16Array( [
0, 2, 1,
2, 3, 1,
4, 6, 5,
6, 7, 5,
8, 10, 9,
10, 11, 9,
12, 14, 13,
14, 15, 13,
16, 17, 18,
18, 17, 19,
20, 21, 22,
22, 21, 23
] );
geometry.setIndex( new THREE.BufferAttribute( indices, 1 ) );
// 创建材质
const material = new THREE.MeshBasicMaterial();
material.map = new THREE.TextureLoader().load( 'textures/crate.gif' );
material.map.colorSpace = THREE.SRGBColorSpace;
material.map.flipY = false;
// 每个实例的数据
const matrix = new THREE.Matrix4();
const offset = new THREE.Vector3();
const orientation = new THREE.Quaternion();
const scale = new THREE.Vector3( 1, 1, 1 );
let x, y, z, w;
mesh = new THREE.InstancedMesh( geometry, material, instances );
for ( let i = 0; i < instances; i ++ ) {
// 设置偏移
x = Math.random() * 100 - 50;
y = Math.random() * 100 - 50;
z = Math.random() * 100 - 50;
offset.set( x, y, z ).normalize();
offset.multiplyScalar( 5 ); // 至少 5 个单位从中心移动
offset.set( x + offset.x, y + offset.y, z + offset.z );
// 设置方向
x = Math.random() * 2 - 1;
y = Math.random() * 2 - 1;
z = Math.random() * 2 - 1;
w = Math.random() * 2 - 1;
orientation.set( x, y, z, w ).normalize();
matrix.compose( offset, orientation, scale );
mesh.setMatrixAt( i, matrix );
}
scene.add( mesh );
renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
container.appendChild( renderer.domElement );
// 检查硬件是否支持实例化
if ( renderer.capabilities.isWebGL2 === false && renderer.extensions.has( 'ANGLE_instanced_arrays' ) === false ) {
document.getElementById( 'notSupported' ).style.display = '';
return;
}
stats = new Stats();
container.appendChild( stats.dom );
window.addEventListener( 'resize', onWindowResize );
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.inner
Height;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
function animate() {
requestAnimationFrame( animate );
render();
stats.update();
}
function render() {
const time = performance.now();
mesh.rotation.y = time * 0.00005;
const delta = ( time - lastTime ) / 5000;
tmpQ.set( moveQ.x * delta, moveQ.y * delta, moveQ.z * delta, 1 ).normalize();
tmpM.makeRotationFromQuaternion( tmpQ );
for ( let i = 0, il = instances; i < il; i ++ ) {
mesh.getMatrixAt( i, currentM );
currentM.multiply( tmpM );
mesh.setMatrixAt( i, currentM );
}
mesh.instanceMatrix.needsUpdate = true;
mesh.computeBoundingSphere();
lastTime = time;
renderer.render( scene, camera );
}
</script>
</body>
</html>