Skip to content

渲染 instancedMesh 边线

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