Three.js实例化时WebGL Shader的center属性传递异常致线框不渲染
实例化场景下线框渲染问题的解决方案
嘿,我来帮你搞定这个问题!你遇到的核心问题是没搞清楚InstancedBufferAttribute和普通BufferAttribute的本质区别——前者是给每个实例传数据,后者是给每个顶点传数据,你的线框着色器逻辑完全依赖顶点级的center差异,直接替换成实例化属性肯定会失效。
为什么会失效?
先拆解下你原来的逻辑:
- 普通
BufferAttribute版本:你的fnGetFloat32ArrayCenters会给每个三角形的三个顶点分别赋值(1,0,0)、(0,1,0)、(0,0,1),这样着色器里的edgeFactorTri()能通过顶点间的vCenter差异计算出三角形的边缘,从而渲染出线框。 - 实例化版本:
InstancedBufferAttribute会让同一个实例的所有顶点共享同一个center值,这就导致vCenter在整个立方体里完全一样,fwidth()计算出来的导数是0,最终edgeFactorTri()返回1,立方体被染成全黑,看起来就像没渲染一样,而且确实不会报错,因为语法和逻辑都没出错,只是结果不符合预期。
两种可行的解决方案
方案1:保留顶点级center,仅用实例化传递实例专属数据
如果你的需求只是批量渲染多个立方体,不需要给每个实例的线框做特殊修改,那最简单的办法就是保留原顶点级的center属性,只把实例化需要的矩阵数据(位置、旋转、缩放)用InstancedBufferAttribute传递。
修改后的关键代码片段:
var bufferGeometry = new THREE.BoxBufferGeometry(100, 100, 100); var geometry2 = new THREE.InstancedBufferGeometry(); geometry2.index = bufferGeometry.index; geometry2.attributes.position = bufferGeometry.attributes.position; geometry2.attributes.uv = bufferGeometry.attributes.uv; // 重点:继续用BufferAttribute传递顶点级的center,不要换成InstancedBufferAttribute geometry2.addAttribute('center', new THREE.BufferAttribute(fnGetFloat32ArrayCenters(bufferGeometry), 3)); // 如果要渲染多个实例,添加实例化矩阵属性(示例:这里创建1个实例) var instanceCount = 1; var instanceMatrix = new THREE.InstancedBufferAttribute(new Float32Array(instanceCount * 16), 16); // 给每个实例设置矩阵(这里是单位矩阵,你可以换成平移/旋转后的矩阵) var matrix = new THREE.Matrix4(); matrix.toArray(instanceMatrix.array, 0); geometry2.addAttribute('instanceMatrix', instanceMatrix); // 记得修改顶点着色器的gl_Position计算,加上实例矩阵 // gl_Position = projectionMatrix * modelViewMatrix * instanceMatrix * vec4(position, 1.0);
方案2:自定义实例化线框效果(如果需要每个实例线框不同)
如果你确实需要每个实例的center属性有差异,那得调整着色器逻辑,把实例化的center和顶点级的轴向量结合起来,既保留顶点间的差异,又能让每个实例有自定义效果。
步骤如下:
- 修改几何体属性:
var bufferGeometry = new THREE.BoxBufferGeometry(100, 100, 100); var geometry2 = new THREE.InstancedBufferGeometry(); geometry2.index = bufferGeometry.index; geometry2.attributes.position = bufferGeometry.attributes.position; geometry2.attributes.uv = bufferGeometry.attributes.uv; // 添加顶点级的轴属性(原来的center逻辑) geometry2.addAttribute('axis', new THREE.BufferAttribute(fnGetFloat32ArrayCenters(bufferGeometry), 3)); // 添加实例化的center属性(每个实例一个值) var instanceCount = 1; var instanceCenters = new Float32Array(instanceCount * 3); // 设置第一个实例的center值,你可以扩展成多个实例 instanceCenters.set([0, 0, 0], 0); geometry2.addAttribute('instanceCenter', new THREE.InstancedBufferAttribute(instanceCenters, 3));
- 修改顶点着色器:
attribute vec3 axis; attribute vec3 instanceCenter; varying vec3 vCenter; void main() { // 结合实例化center和顶点轴向量,保留顶点级差异 vCenter = axis + instanceCenter; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); }
这样每个实例的线框会基于自身的instanceCenter产生偏移,同时保留原有的线框逻辑。
额外提醒
InstancedBufferGeometry是用来优化大量重复物体渲染的,单个物体用普通BufferGeometry更高效,没必要用实例化。- 你开启
material.extensions.derivatives = true是正确的,因为fwidth()需要这个WebGL扩展支持,别删掉它。
内容的提问来源于stack exchange,提问作者UberMario
相关产品推荐
相关产品推荐

