【three.js教程】自定义缓冲几何体(Custom BufferGeometry):从零拼出你自己的模型
Three.js 自定义缓冲几何体Custom BufferGeometry从零拼出你自己的模型原文出处Three.js Manual – Custom BufferGeometry本文基于官方手册 Custom BufferGeometry 章节整理带你理解 three.js 里所有模型的底层——BufferGeometry到底是怎么存数据的。写在前面前面我们用过BoxGeometry、SphereGeometry这些图元直接拿来就能画。但你有没有想过这些形状底层到底是什么答案就是BufferGeometry。可以这么说——three.js 里所有几何体本质上都是BufferGeometry。BufferGeometry说白了就是一组带名字的BufferAttribute缓冲属性的集合。每个BufferAttribute装一种数据position位置、normal法线、color颜色、uv贴图坐标……它们是一组平行数组——第 N 个属性里的第 N 份数据属于同一个顶点。理解这一点你就能自己造任何形状了。一、一个顶点 它所有零件的合集先记住一句关键的话一个顶点是它全部数据的组合。只要某一块数据不一样它就必须是另一个顶点。举个最经典的例子——立方体的一个角。视觉上 3 个面共用同一个角点但实际上不行这个角在 3 个面上每个面朝向不同所以法线normal必须不一样每个面贴的纹理位置不同所以UV 坐标也必须不一样。既然法线和 UV 都不同那这三个角就是三个不同的顶点不能共享。这也是为什么一个看似 8 个角的立方体真正需要36 个顶点——每个面 2 个三角形 × 3 个顶点 × 6 个面 36。二、第一步把顶点数据列出来我们手动把立方体的 36 个顶点全写出来每个含pos、norm、uvconstvertices[// front{pos:[-1,-1,1],norm:[0,0,1],uv:[0,0],},{pos:[1,-1,1],norm:[0,0,1],uv:[1,0],},{pos:[-1,1,1],norm:[0,0,1],uv:[0,1],},{pos:[-1,1,1],norm:[0,0,1],uv:[0,1],},{pos:[1,-1,1],norm:[0,0,1],uv:[1,0],},{pos:[1,1,1],norm:[0,0,1],uv:[1,1],},// right省略其余面结构一样...];三、第二步拆成平行数组把上面这份对象数组拆成 three.js 要的纯数字平行数组constpositions[];constnormals[];constuvs[];for(constvertexofvertices){positions.push(...vertex.pos);normals.push(...vertex.norm);uvs.push(...vertex.uv);}四、第三步塞进 BufferGeometry创建几何体把每个数组包成BufferAttribute加进去constgeometrynewTHREE.BufferGeometry();constpositionNumComponents3;// 每个顶点 3 个分量x, y, zconstnormalNumComponents3;constuvNumComponents2;// uv 只有 2 个u, vgeometry.setAttribute(position,newTHREE.BufferAttribute(newFloat32Array(positions),positionNumComponents));geometry.setAttribute(normal,newTHREE.BufferAttribute(newFloat32Array(normals),normalNumComponents));geometry.setAttribute(uv,newTHREE.BufferAttribute(newFloat32Array(uvs),uvNumComponents));⚠️两个坑名字不能乱起。position、normal、uv是 three.js 约定好的名字想要顶点色就用color。除非你自己写着色器否则名字必须对应。BufferAttribute只认 TypedArray不认原生数组。所以这里用new Float32Array(...)包了一层。它还要求你告诉它每个顶点几个分量——位置/法线是 3UV 是 2。五、进阶用索引省顶点上面 36 个顶点里有不少是重复的——每个面的 2 个三角形共 6 个顶点其中有 2 个完全相同位置、法线、UV 一模一样。把这些重复的提出来立方体其实只需24 个不重复顶点。做法是保留 24 个顶点再用一个索引数组告诉 GPU “三角形该按什么顺序画这 24 个顶点”geometry.setIndex([0,1,2,2,1,3,// front4,5,6,6,5,7,// right8,9,10,10,9,11,// back12,13,14,14,13,15,// left16,17,18,18,17,19,// top20,21,22,22,21,23,// bottom]);索引就是顶点的身份证号GPU 按这个顺序拼三角形。24 个顶点 36 个索引照样画出 12 个三角形但数据量小了不少。六、一个隐藏的坑computeVertexNormals 会留缝如果你懒得写法线可以用geometry.computeVertexNormals()让 three.js 自动算。但前面说了只要顶点有任何一块不一样就不能共享。球体、圆柱这种首尾相接的形状接缝处的两个顶点因为 UV 不同而无法共享自动算法就不知道它们该平滑过渡——于是圆柱上会多出一条明显的接缝seam。解决办法自己提供法线别偷这个懒。七、直接用 TypedArray 起步 动态更新原生数组方便push但如果你想频繁改顶点最好一开始就上Float32ArrayTypedArray 没有push得自己记账constnumVerticesvertices.length;constpositionsnewFloat32Array(numVertices*3);letposNdx0;for(constvertexofvertices){positions.set(vertex.pos,posNdx);posNdx3;}动态更新的精髓是这两行constpositionAttributenewTHREE.BufferAttribute(positions,3);positionAttribute.setUsage(THREE.DynamicDrawUsage);// 告诉 three.js这属性我会常改// ...渲染循环里改完 positions 后positionAttribute.needsUpdatetrue;// 必须标记否则改了也白改官方还用这个思路做了个例子生成一个球每帧让每个小方格沿法线方向进进出出地呼吸也就是靠needsUpdate true把新位置实时喂给 GPU。总结概念一句话说明BufferGeometry一组带名字的 BufferAttribute平行数组一个顶点位置法线UV颜色 的全组合任一处不同 不同顶点立方体顶点数36不共享或 24 索引共享setAttribute名字要对position/normal/uv值要 TypedArraysetIndex用索引复用顶点省数据动态更新setUsage(DynamicDrawUsage)needsUpdate true什么时候自己写 BufferGeometry当现成图元满足不了你自定义地形、动态网格、粒子、程序化生成的形状时它就是你的底层武器。延伸阅读BufferGeometry APIBufferAttribute APIPrimitives现成图元

相关新闻

最新新闻

日新闻

周新闻

月新闻