You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何合并同网格不同颜色对象并实现单次Draw Call?

嘿,这个问题我刚好踩过坑!你之前的思路没抓住核心:要实现单次Draw Call,必须让所有渲染数据都整合到「单个几何+单个材质」里,而不是用多个材质数组——这也是为什么你之前的方法Draw Call数量和材质数一致,因为Three.js会自动把多材质的Mesh拆分成多个子渲染单元,自然触发多次调用。

下面给你两种可行的解决方案,分别适用于不同场景:

方案一:合并几何并嵌入顶点颜色(适合中小数量Mesh)

这种方法的核心是把每个Mesh的颜色信息,直接写入合并后几何的顶点属性中,用支持顶点颜色的单材质渲染,就能实现单次Draw Call。

实现步骤&代码示例

// 假设你的所有Mesh都基于同一个基础几何baseGeo,存在meshes数组中
const mergedGeo = new THREE.BufferGeometry();
const positions = [];
const vertexColors = [];

// 遍历所有Mesh,收集顶点位置和对应颜色
meshes.forEach(mesh => {
  const targetColor = mesh.material.color;
  const posArray = baseGeo.attributes.position.array;
  
  // 给每个顶点复制位置,并绑定当前Mesh的颜色
  for (let i = 0; i < posArray.length; i += 3) {
    positions.push(posArray[i], posArray[i+1], posArray[i+2]);
    vertexColors.push(targetColor.r, targetColor.g, targetColor.b);
  }
});

// 设置合并后几何的属性
mergedGeo.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
mergedGeo.setAttribute('color', new THREE.Float32BufferAttribute(vertexColors, 3));
mergedGeo.computeVertexNormals(); // 如果需要光照效果,记得计算法线

// 关键:使用支持顶点颜色的材质,开启vertexColors
const mergedMaterial = new THREE.MeshStandardMaterial({
  vertexColors: true, // 必须开启这个属性才能读取顶点颜色
  // 这里可以保留原材质的其他属性,比如metalness、roughness等
});

// 创建最终的合并Mesh
const mergedMesh = new THREE.Mesh(mergedGeo, mergedMaterial);
scene.add(mergedMesh);

方案二:使用InstancedMesh(适合大量相同Mesh,性能更优)

如果你的Mesh数量很多(比如几百上千个),合并顶点会导致几何数据量暴增,这时用InstancedMesh是更好的选择——它共享同一个几何和材质,通过实例化矩阵和实例化颜色来实现不同外观,全程只触发一次Draw Call,性能开销极小。

实现步骤&代码示例

// 基于第一个Mesh的几何和材质创建基础资源
const baseGeo = meshes[0].geometry;
const baseMaterial = new THREE.MeshStandardMaterial({
  instancedColors: true, // 关键:开启实例化颜色支持
  // 保留原材质的其他属性
});

// 创建InstancedMesh,参数:几何、材质、实例总数
const instancedMesh = new THREE.InstancedMesh(baseGeo, baseMaterial, meshes.length);

const tempMatrix = new THREE.Matrix4();
const tempColor = new THREE.Color();

// 遍历所有Mesh,设置每个实例的矩阵(位置/旋转/缩放)和颜色
meshes.forEach((mesh, index) => {
  // 复制当前Mesh的变换矩阵
  mesh.updateMatrix();
  tempMatrix.copy(mesh.matrix);
  instancedMesh.setMatrixAt(index, tempMatrix);
  
  // 设置当前实例的颜色
  tempColor.copy(mesh.material.color);
  instancedMesh.setColorAt(index, tempColor);
});

// 标记实例数据需要更新
instancedMesh.instanceMatrix.needsUpdate = true;
instancedMesh.instanceColor.needsUpdate = true;

scene.add(instancedMesh);

为什么你的原方法不行?

当你给单个Mesh传入材质数组时,Three.js会自动将几何按材质对应的面拆分,生成多个隐性的子Mesh,每个子Mesh对应一个材质,自然会触发和材质数量相同的Draw Call——这是Three.js的渲染机制决定的,所以这种思路从一开始就不符合单次Draw Call的要求。

注意事项

  • 不管用哪种方案,材质必须支持顶点颜色或实例化颜色:比如MeshStandardMaterialMeshBasicMaterial都支持,但部分特殊材质(比如MeshDepthMaterial)可能不支持,需要提前确认。
  • 如果原材质带有纹理,只要所有Mesh的纹理相同,直接复用即可,不影响两种方案的实现。
  • InstancedMesh除了颜色,还可以单独控制每个实例的位置、旋转、缩放,灵活性非常高,是处理大量相同Mesh的首选方案。

内容的提问来源于stack exchange,提问作者user9731024

火山引擎 最新活动