WebGL
官方文档并没有提供渲染 instancedMesh 边线的方法。翻阅官方论坛,找不到现成的解决方案。能找到的代码要么因为年代久远失效了,要么过于复杂难以复用。最终,答案很简单:
js
const eGeom = new THREE.InstancedBufferGeometry();
eGeom.setAttribute("position", geom.getAttribute("position"));
// 将 instancedMesh 的矩阵属性绑定到边线元素
eGeom.setAttribute("matrix", iMesh.instanceMatrix);
eGeom.instanceCount = iMesh.instanceMatrix.array.length / 16;
const eMat = new THREE.LineBasicMaterial({
color: "#fff",
onBeforeCompile: (shader) => {
// shader 按绑定的矩阵属性计算顶点坐标,让边线与 instancedMesh 保持一致
shader.vertexShader = `
attribute mat4 matrix;
void main() {
gl_Position = projectionMatrix * modelViewMatrix * matrix * vec4( position, 1.0 );
}`;
},
});
const edges = new THREE.LineSegments(eGeom, eMat);
scene.add(edges);渲染效果:
在线预览作品 《instancedEdges》,作者(@周狮虎)创作于 笔.COOL。
WebGPU
Threejs v.0.180 更新了一大波对 WebGPU 的支持,开始考虑把之前的项目移植到 WebGPU。但 TSL 的文档依然停留在几年前,很多新特性都没有介绍,如何渲染 instancedMesh 边线还需自行摸索。以下是答案之一:
ts
/** 根据 instancedMesh 重新定义 vertexShader,结合 InstancedBufferGeometry 渲染每个实例的边线 */
function renderInstancedEdge(
edgeMaterial: THREE_WEBGPU.LineBasicNodeMaterial,
model: THREE_WEBGPU.InstancedMesh,
) {
edgeMaterial.vertexNode = TSL.Fn(() => {
// 根据 instancedMesh 新建全部实例的矩阵缓存
const buffer = TSL.instancedArray(model.instanceMatrix.array, "mat4");
// 从缓存中提取当前实例的矩阵
const matrix = buffer.element(TSL.instanceIndex);
// 应用矩阵变换,计算顶点坐标
const worldPos = matrix.mul(TSL.positionGeometry);
const viewPos = TSL.modelViewMatrix.mul(worldPos);
const clipPos = TSL.cameraProjectionMatrix.mul(viewPos);
return clipPos;
})();
}
// 重构 vertexShader
renderInstancedEdge(MATERIAL_EDGE, instancedModel);
// 将 instancedMesh.geometry 中的相关数据复制到新的 InstancedBufferGeometry
const edgeIBG = new THREE_WEBGPU.InstancedBufferGeometry();
const edgeGeom = new THREE_WEBGPU.EdgesGeometry(instancedModel.geometry);
edgeIBG.setAttribute("position", edgeGeom.getAttribute("position"));
edgeIBG.instanceCount = total;
edgeGeom.dispose();
// LineSegments 通过 InstancedBufferGeometry 与自定义 vertexShader 的材质渲染每个实例的边线
const edges = new THREE_WEBGPU.LineSegments(edgeIBG, MATERIAL_EDGE);
edges.frustumCulled = false;