渲染 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