Three.js实例化技术:同时渲染多个3D对象
学习如何使用React Three Fiber中的实例化技术高效渲染数千个3D对象,正如性能优化的basement.studio网站所展示的那样。
引言
实例化是一种性能优化技术,允许你同时渲染共享相同几何体和材质的多个对象。如果需要渲染森林场景,你会需要大量的树木、岩石和草地。如果它们共享相同的基础网格和材质,你就可以通过单次绘制调用渲染所有对象。
绘制调用是CPU向GPU发出的绘制指令,比如绘制一个网格。每个独特的几何体或材质通常需要自己的调用。过多的绘制调用会损害性能。实例化通过将多个副本批量处理为一个来减少这种情况。
基础实例化
让我们从一个传统方式渲染一千个方块的例子开始:
javascript
const boxCount = 1000
function Scene() {
return (
<>
{Array.from({ length: boxCount }).map((_, index) => (
<mesh
key={index}
position={getRandomPosition()}
scale={getRandomScale()}
>
<boxGeometry />
<meshBasicMaterial color={getRandomColor()} />
</mesh>
))}
</>
)
}
如果添加性能监视器,会发现"calls"数量与boxCount匹配。
使用drei/instances可以快速实现实例化:
javascript
import { Instance, Instances } from "@react-three/drei"
const boxCount = 1000
function Scene() {
return (
<Instances limit={boxCount}>
<boxGeometry />
<meshBasicMaterial />
{Array.from({ length: boxCount }).map((_, index) => (
<Instance
key={index}
position={getRandomPosition()}
scale={getRandomScale()}
color={getRandomColor()}
/>
))}
</Instances>
)
}
现在"calls"减少到1,即使我们显示了一千个方块。
多组实例
要渲染森林场景,可能需要不同的实例组:
javascript
import { createInstances } from "@react-three/drei"
const boxCount = 1000
const sphereCount = 1000
const [CubeInstances, Cube] = createInstances()
const [SphereInstances, Sphere] = createInstances()
function InstancesProvider({ children }: { children: React.ReactNode }) {
return (
<CubeInstances limit={boxCount}>
<boxGeometry />
<meshBasicMaterial />
<SphereInstances limit={sphereCount}>
<sphereGeometry />
<meshBasicMaterial />
{children}
</SphereInstances>
</CubeInstances>
)
}
自定义着色器实例
要为自定义着色器添加实例支持:
javascript
const baseMaterial = new THREE.RawShaderMaterial({
vertexShader: /*glsl*/ `
attribute vec3 position;
attribute vec3 instanceColor;
attribute vec3 normal;
attribute vec2 uv;
uniform mat4 modelMatrix;
uniform mat4 viewMatrix;
uniform mat4 projectionMatrix;
attribute mat4 instanceMatrix;
uniform float uTime;
uniform float uAmplitude;
vec3 movement(vec3 position) {
vec3 pos = position;
pos.x += sin(position.y + uTime) * uAmplitude;
return pos;
}
void main() {
vec3 blobShift = movement(position);
vec4 modelPosition = modelMatrix * instanceMatrix * vec4(blobShift, 1.0);
vec4 viewPosition = viewMatrix * modelPosition;
vec4 projectionPosition = projectionMatrix * viewPosition;
gl_Position = projectionPosition;
}
`,
fragmentShader: /*glsl*/ `
void main() {
gl_FragColor = vec4(1, 0, 0, 1);
}
`
})
创建森林场景
使用实例化网格创建森林场景:
javascript
const [TreeInstances, Tree] = createInstances()
const treeCount = 1000
function Scene() {
const { scene, nodes } = useGLTF(
"/stylized_pine_tree_tree.glb"
) as unknown as TreeGltf
return (
<group>
<TreeInstances
limit={treeCount}
scale={0.02}
geometry={nodes.tree_low001_StylizedTree_0.geometry}
material={nodes.tree_low001_StylizedTree_0.material}
>
{Array.from({ length: treeCount }).map((_, index) => (
<Tree key={index} position={getRandomPosition()} />
))}
</TreeInstances>
</group>
)
}
整个森林仅用三次绘制调用渲染:天空盒一次,地面平面一次,所有树木一次。
延伸阅读
- 批处理网格:允许同时渲染不同几何体
- 骨骼动画:当前不支持实例化
- 变形动画:实例支持但批处理网格不支持