Web编程技术营地
研究、演示、创新

Mesh

撰写时间:2023-09-13

修订时间:2026-04-29

Mesh类代表在场景中要渲染的网格物体。能被渲染为实体、框线、实体及框线。后面此类将扩展为支持纹理贴图。

类图全貌

目前已经实现的Mesh类有较多种,且之间有多重继承关系。其类图全貌(未包含FaceMesh的各个子类)如下图所示:

Mesh Class Relations-1

CompositeMesh既继承于AbstractMesh,同时又是SimpleMesh的聚合。

FaceMesh包含以下子类:

  • RegularPolygonMesh
  • CircularMesh
  • CubeMesh
  • CuboidMesh
  • BoxMesh
  • ConeMesh
  • ConeFrustumMesh
  • CylinderMesh

一些类借助于Geometries类,通过几何运算,生成几何图形的顶点。该类在下一节讲述。

下面是各个类的源代码。

Mesh

import { GLUHolder } from './WebGLUtils-v11.js'; import { WebGLBufferUtils as BufUtils, GLColors, Geometries as Geo, vec3, mat4, glMatrix } from './index-independent.js'; const VERTICES_COMP_SIZE = 3; export class Mesh { static RENDER_MODE = { SOLID: 0, WIREFRAME: 1, SOLID_WIREFRAME: 2 }; static WIREFRAME_COLOR = [1.0, 1.0, 1.0, 1.0]; }

定义了RENDER_MODEWIREFRAME_COLOR两个常量。

MeshInterface

export class MeshInterface extends Mesh { render() {} renderInViewport(viewportRenderMode) {} translate(vector) {} rotate(degree, vector) {} }

MeshInterface接口类,定义了4接口方法

当应用程序不使用视口时,可调用render方法来渲染;当使用视口时,则可调用renderInViewport统一渲染为视口中的渲染模式。

translaterotate方法可让客户端在更抽象层面上方便地对网格物体进行模型变换

AbstractMesh

AbstractMesh抽象类,为子类提供了可被继承的属性及默认的方法实现。

export class AbstractMesh extends MeshInterface { /* --------------- Instance Fields --------------- */ glu = GLUHolder.glu; modelMatrix = mat4.create(); visible = true; #renderMode; vertices; vertices2D; set renderMode(value) { this.#renderMode = value; } translate(vector) { mat4.translate(this.modelMatrix, this.modelMatrix, vector); } rotate(degree, vector) { mat4.rotate(this.modelMatrix, this.modelMatrix, glMatrix.toRadian(degree), vector); } render() { if (this.#renderMode === null || this.#renderMode === undefined) { throw new Error('Instance field renderMode is not set yet!'); } const {gl, program} = this.glu; gl.uniformMatrix4fv(program['uModelMatrix'], false, this.modelMatrix); switch(this.#renderMode) { case Mesh.RENDER_MODE.SOLID: this.renderSolid(); break; case Mesh.RENDER_MODE.WIREFRAME: this.renderWireframe(); break; case Mesh.RENDER_MODE.SOLID_WIREFRAME: this.renderSolidWireframe(); break; default: throw new Error('Unknown or unimplemented RENDER_MODE'); break; } } renderSolid() { if (this.solidVAO) { this.glu.renderVAO(this.solidVAO); } } // leave for child to implement renderWireframe() {} renderSolidWireframe() { this.renderSolid(); this.renderWireframe(); } renderInViewport(viewportRenderMode) { const {gl, program} = this.glu; gl.uniformMatrix4fv(program['uModelMatrix'], false, this.modelMatrix); switch (viewportRenderMode) { case Mesh.RENDER_MODE.SOLID: this.renderSolid(); break; case Mesh.RENDER_MODE.SOLID_WIREFRAME: this.renderSolidWireframe(); break; case Mesh.RENDER_MODE.WIREFRAME: this.renderWireframe(); break; } } }

通用属性

子类不管是WireframeMesh或是SolidMesh,都必然需要存储顶点位置的数据。因此AbstractMesh定义了vertices属性。vertices2D将数据存储为2维数组。

visible属性可用以控制是否显示网格物体。例如,我们可以选择是否需要渲染网格地板等辅助网格物体。

模型变换

每个AbstractMesh都会自动创建一个modelMatrix实例属性,这样可省去客户端自行创建的麻烦。translaterotate方法中的vector参数可使用3个元素的数组的方式。

渲染

render方法将根据自身的#renderMode属性,调用相应的renderSolidrenderWireframerenderSolidWireframe方法。

我们的应用框架有一个需求,即当用户在程序运行中,通过键盘快捷键切换网格物体的渲染模式时,要求各个Mesh都能正确响应。而一个纯粹的WireframeMesh,例如,代表坐标系的网格物体,当被要求渲染为实体模式时,它本身就没有这个功能。因此,renderSolid方法运用了适配模式

如果相应的网格物体没有此功能,则不予渲染,默默地跳过,但不给应用框架增添麻烦。

renderWireframe是每个Mesh必须具备的功能,但因各个Mesh的渲染细节均不一样,因此在这里定义为一个虚方法 (virtual method),由子类负责实现。

renderInViewport方法参数viewportRenderMode,调用相应的renderSolidrenderWireframerenderSolidWireframe方法,以实现在视口中渲染为统一模式的效果。

renderrenderInViewport方法,均在渲染前自动向顶点着色器中uModelMatrix投喂自身的modalMatrix,以减轻客户端的调用负担。

这里使用了门面模式renderrenderInViewport门面方法,客户端只需与这两个方法打交道即可。

而从它们的作用来讲,这也是调停者模式 (mediator pattern) 的具体运用。它们根据自身内部的实例属性或方法的参数,在内部进行了中转,以让行为效果与内部状态保持高度一致。

抽象类的意义

作为抽象类AbstractMesh不能被实例化,因此其没有构造方法

SimpleMesh

SimpleMesh类开始为其子类WireframeMesh提供构造方法,并实现通用的renderWireframe方法。

export class SimpleMesh extends AbstractMesh { verticesVBO; wireframeVAO; constructor(vertices, wireframeIndices, wireframeColor = Mesh.WIREFRAME_COLOR) { super(); this.vertices = vertices; this.vertices2D = Geo.To2DArrays(vertices); this.verticesVBO = this.glu.createPosVBO(vertices); this.#initWireframeVAO(wireframeIndices, wireframeColor); } #initWireframeVAO(wireframeIndices, wireframeColor) { let glu = this.glu; let colorsVBO = glu.createColorVBO(wireframeColor.repeat(this.verticesVBO.verticesNum)); this.wireframeVAO = glu.createVAO(this.verticesVBO, colorsVBO); if (!wireframeIndices) { let vertices = BufUtils.GetView(this.verticesVBO, 'aPosition', Float32Array); let indices = Geo.GetIndicesFromVertices(vertices); wireframeIndices = Geo.GetLinesIndices(indices, true); } glu.createIBO(this.wireframeVAO, wireframeIndices); } renderWireframe() { this.glu.renderVAO(this.wireframeVAO, true); } }

每个SimpleMesh都有一个wireframeVAO属性,以供renderWireframe使用。

在私有方法initWireframeVAO中,如果未指定顶点索引值,则自动生成,客户端很喜欢偷懒。

这样,每个能被实例化的Mesh,包括持有聚合SimpleMeshCompositeMesh,都能统一地渲染为框线模式。

WireframeMesh

export class WireframeMesh extends SimpleMesh { constructor(vertices, wireframeIndices, wireframeColor = Mesh.WIREFRAME_COLOR) { super(vertices, wireframeIndices, wireframeColor); this.renderMode = Mesh.RENDER_MODE.WIREFRAME; } }

作为继承自SimpleMesh的子类,WireframeMesh只需在构造方法中指定渲染模式即可。

注意到了此层级,WireframeMesh的代码已经极为简单。因此所有的活,上面的各个类都为它代劳了。

由此可见,在一个有多级继承关系的应用框架中,我们非常喜欢定义位于继承链末端的类,这种类的实现简直不费吹灰之力。

SolidMesh

export class SolidMesh extends WireframeMesh { solidVAO; constructor(vertices, colors, solidIndices, wireframeIndices) { super(vertices, wireframeIndices); this.#initSolidVAO(colors, solidIndices); this.renderMode = Mesh.RENDER_MODE.SOLID_WIREFRAME; } #initSolidVAO(colors, solidIndices) { let glu = this.glu; let colorsVBO = glu.createColorVBO(colors); this.solidVAO = glu.createVAO(this.verticesVBO, colorsVBO); if (!solidIndices) { let vertices = BufUtils.GetView(this.verticesVBO, 'aPosition', Float32Array); let indices = Geo.GetIndicesFromVertices(vertices); solidIndices = Geo.GetCCWTriangleIndices(indices); } glu.createIBO(this.solidVAO, solidIndices); } }

SolidMesh继承自WireframeMesh,只需为其初始化实例属性solidVAO的值、并指定渲染模式即可。渲染方法renderSolid已由位于继承树上方的AbstractMesh代劳。

同样,如果在构造方法中未指定实例顶点索引值,则按逆时针顺序自动生成各个独立的三角形索引值。

SoleColorMesh

SoleColorMesh继承自SolidMesh,用于生成只使用一种颜色的网格物体。

export class SoleColorMesh extends SolidMesh { constructor(vertices, soleColor = GLColors.GetRandomSoftRGB(), solidIndices, wireframeIndices) { let colors = soleColor.repeat(vertices.length / VERTICES_COMP_SIZE); super(vertices, colors, solidIndices, wireframeIndices); } }

CompositeMesh

CompositeMesh是一个聚合类。它是设计模式组合模式的运用。

一个网格对象需要使用两种以上的顶点颜色时,就应使用此类。

例如,当网格地板需用各种颜色标识不同的坐标轴,或者当一个网格对象有多个各不相同颜色的面时,均应使用此类。

export class CompositeMesh extends AbstractMesh { childMeshes = []; #renderMode; set renderMode(value) { this.#renderMode = value; for (let childMesh of this.childMeshes) { childMesh.renderMode = value; } } translate(vector) { for (let childMesh of this.childMeshes) { mat4.translate(childMesh.modelMatrix, childMesh.modelMatrix, vector); } } rotate(degree, vector) { for (let childMesh of this.childMeshes) { mat4.rotate(childMesh.modelMatrix, childMesh.modelMatrix, glMatrix.toRadian(degree), vector); } } render() { for (let childMesh of this.childMeshes) { childMesh.render(); } } renderInViewport(viewportRenderMode) { for (let childMesh of this.childMeshes) { childMesh.renderInViewport(viewportRenderMode); } } }

childMeshes用于存储各个子网格物体。

为私有实例属性#renderMode声明了一个setter方法,以在CompositeMesh的该属性值被改变时,自动更新所有子对象相应属性值。

同时,也须覆盖MeshInterface所定义的4个方法,委派各个子对象去完成。

HelperMesh

HelperMesh继承自CompositeMesh

export class HelperMesh extends CompositeMesh { }

HelperMesh是一种标识类,仅用于标识此类为辅助类,因此其内容为空。

在应用框架中,仅定义了两种辅助类,分别为GridFloorMesh(网格地板对象)及CoordsAxesMesh(坐标轴对象)。

有此标识后,客户端在程序运行过程中就可以有针对性地隐藏此类网格物体,而保持渲染其他网格物体。

GridFloorMesh

GridFloorMesh继承自HelperMesh,用以渲染一个网格地板。

export class GridFloorMesh extends HelperMesh { // defined around the origin point // cellSide: the side length of each cell // num: the number of cells of half of a floor row constructor(cellSide = 0.5, num = 10) { super(); this.GRID_COLOR= [0.2, 0.2, 0.2, 1.0]; this.X_AXIS_COLOR = [0.5, 0.0, 0.0, 1.0]; this.Z_AXIS_COLOR = [0.0, 0.0, 0.9, 1.0]; this.gridVertices = []; this.buildGridVertices(cellSide, num); let wireframeIndices = Geo.GetIndicesFromVertices(this.gridVertices); this.gridWireframeMesh = new WireframeMesh(this.gridVertices, wireframeIndices, this.GRID_COLOR); this.xVertices = []; this.buildXVertices(cellSide, num); this.xWireframeMesh = new WireframeMesh(this.xVertices, [0, 1], this.X_AXIS_COLOR); this.zVertices = []; this.buildZVertices(cellSide, num); this.zWireframeMesh = new WireframeMesh(this.zVertices, [0, 1], this.Z_AXIS_COLOR); this.childMeshes.push(this.xWireframeMesh); this.childMeshes.push(this.zWireframeMesh); this.childMeshes.push(this.gridWireframeMesh); } buildGridVertices(cellSide, num) { let vertices = this.gridVertices; // horizontal lines let startX = -cellSide * num; let endX = -startX; let startZ = startX; let endZ = -startZ; while (startZ <= endZ) { vertices.push( startX, 0, startZ, endX, 0, startZ ); startZ += cellSide; } // vertical lines startZ = -cellSide * num; endZ = -startZ; startX = startZ; endX = -startX; while (startX <= endX) { vertices.push( startX, 0, startZ, startX, 0, endZ ); startX += cellSide; } } buildXVertices(cellSide, num) { let vertices = this.xVertices; vertices.push(-cellSide * num, 0.0, 0.0, cellSide * num, 0.0, 0.0); } buildZVertices(cellSide, num) { let vertices = this.zVertices; vertices.push(0.0, 0.0, -cellSide * num, 0.0, 0.0, cellSide * num); } }

用灰色绘制网格,用红色绘制X轴,用蓝色绘制Z轴Y轴不绘制。

这里使用了建造者模式 (builder pattern),分别建造了3个子组件后,最后装配为CompositeMesh所必须持有的childMeshes实例属性。

CoordsAxesMesh

CoordsAxesMesh继承自HelperMesh,用以渲染一个3D坐标轴。

export class CoordsAxesMesh extends HelperMesh { constructor(axisLength, markGap = 1, markLen = 0.1) { super(); this.axisLength = axisLength; this.markGap = markGap; this.ARROW_LEN = markLen * 1.5; this.ARROW_ANGLE = 15; this.MARK_LEN = markLen; this.HALF_MARK_LEN = this.MARK_LEN / 2; this.X_MAIN_COLOR = [0.5, 0.2, 0.2, 1.0]; this.X_MARKS_COLOR = [0.4, 0.2, 0.2, 1.0]; this.X_HALF_MARKS_COLOR = [0.3, 0.2, 0.2, 1.0]; this.Y_MAIN_COLOR = [0.2, 0.5, 0.2, 1.0]; this.Y_MARKS_COLOR = [0.2, 0.4, 0.2, 1.0]; this.Y_HALF_MARKS_COLOR = [0.2, 0.3, 0.2, 1.0]; this.Z_MAIN_COLOR = [0.2, 0.2, 0.7, 1.0]; this.Z_MARKS_COLOR = [0.2, 0.2, 0.6, 1.0]; this.Z_HALF_MARKS_COLOR = [0.2, 0.2, 0.5, 1.0]; this.buildXVertices(); this.buildYVertices(); this.buildZVertices(); this.childMeshes.push(this.xMainWireframeMesh); this.childMeshes.push(this.xMarksWireframeMesh); this.childMeshes.push(this.xHalfMarksWireframeMesh); this.childMeshes.push(this.yMainWireframeMesh); this.childMeshes.push(this.yMarksWireframeMesh); this.childMeshes.push(this.yHalfMarksWireframeMesh); this.childMeshes.push(this.zMainWireframeMesh); this.childMeshes.push(this.zMarksWireframeMesh); this.childMeshes.push(this.zHalfMarksWireframeMesh); } buildXVertices() { this.xVertices = {}; // axis main this.xVertices.main = []; this.xVertices.main.push( -this.axisLength/2, 0.0, 0.0, this.axisLength/2, 0.0, 0.0 ); // axis arrow let arrowEndX = Math.cos(Geo.RadianFromDegree(this.ARROW_ANGLE)) * this.ARROW_LEN; let arrowEndY = Math.sin(Geo.RadianFromDegree(this.ARROW_ANGLE)) * this.ARROW_LEN; this.xVertices.main.push( this.axisLength/2 - arrowEndX, arrowEndY, 0.0, this.axisLength/2, 0.0, 0.0, this.axisLength/2 - arrowEndX, -arrowEndY, 0.0, this.axisLength/2, 0.0, 0.0, this.axisLength/2 - arrowEndX, 0.0, -arrowEndY, this.axisLength/2, 0.0, 0.0, this.axisLength/2 - arrowEndX, 0.0, arrowEndY, this.axisLength/2, 0.0, 0.0 ); let mainIndices = Geo.GetIndicesFromVertices(this.xVertices.main); this.xMainWireframeMesh = new WireframeMesh(this.xVertices.main, mainIndices, this.X_MAIN_COLOR); // marks this.xVertices.marks = []; let currX = 0.0; while (currX < this.axisLength/2) { this.xVertices.marks.push(currX, this.MARK_LEN, 0.0); this.xVertices.marks.push(currX, -this.MARK_LEN, 0.0); currX += this.markGap; } currX = -this.markGap; while (currX > -this.axisLength/2) { this.xVertices.marks.push(currX, this.MARK_LEN, 0.0); this.xVertices.marks.push(currX, -this.MARK_LEN, 0.0); currX -= this.markGap; } let marksIndices = Geo.GetIndicesFromVertices(this.xVertices.marks); this.xMarksWireframeMesh = new WireframeMesh(this.xVertices.marks, marksIndices, this.X_MARKS_COLOR); // half marks this.xVertices.halfMarks = []; currX = this.markGap/2; while (currX < this.axisLength/2) { this.xVertices.halfMarks.push(currX, this.HALF_MARK_LEN, 0.0); this.xVertices.halfMarks.push(currX, -this.HALF_MARK_LEN, 0.0); currX += this.markGap; } currX = -this.markGap/2; while (currX > -this.axisLength/2) { this.xVertices.halfMarks.push(currX, this.HALF_MARK_LEN, 0.0); this.xVertices.halfMarks.push(currX, -this.HALF_MARK_LEN, 0.0); currX -= this.markGap; } let halfMarksIndices = Geo.GetIndicesFromVertices(this.xVertices.halfMarks); this.xHalfMarksWireframeMesh = new WireframeMesh(this.xVertices.halfMarks, halfMarksIndices, this.X_HALF_MARKS_COLOR); } rotateVertices(vertices, axisAround) { let srcVec3; let dstVec3 = vec3.create(); let result = []; for (let i = 0; i < vertices.length; i += 3) { srcVec3 = vec3.fromValues(vertices[i], vertices[i+1], vertices[i+2]); if (axisAround === 'z') { vec3.rotateZ(dstVec3, srcVec3, [0, 0, 0], Geo.RadianFromDegree(90)); } else if (axisAround === 'y') { vec3.rotateY(dstVec3, srcVec3, [0, 0, 0], Geo.RadianFromDegree(-90)); } result.push(dstVec3[0], dstVec3[1], dstVec3[2]); } return result; } buildYVertices() { this.yVertices = {}; this.yVertices.main = this.rotateVertices(this.xVertices.main, 'z'); let mainIndices = Geo.GetIndicesFromVertices(this.yVertices.main); this.yMainWireframeMesh = new WireframeMesh(this.yVertices.main, mainIndices, this.Y_MAIN_COLOR); this.yVertices.marks = this.rotateVertices(this.xVertices.marks, 'z'); let marksIndices = Geo.GetIndicesFromVertices(this.yVertices.marks); this.yMarksWireframeMesh = new WireframeMesh(this.yVertices.marks, marksIndices, this.Y_MARKS_COLOR); this.yVertices.halfMarks = this.rotateVertices(this.xVertices.halfMarks, 'z'); let halfMarksIndices = Geo.GetIndicesFromVertices(this.yVertices.halfMarks); this.yHalfMarksWireframeMesh = new WireframeMesh(this.yVertices.halfMarks, halfMarksIndices, this.Y_HALF_MARKS_COLOR); } buildZVertices() { this.zVertices = {}; this.zVertices.main = this.rotateVertices(this.xVertices.main, 'y'); let mainIndices = Geo.GetIndicesFromVertices(this.zVertices.main); this.zMainWireframeMesh = new WireframeMesh(this.zVertices.main, mainIndices, this.Z_MAIN_COLOR); this.zVertices.marks = this.rotateVertices(this.xVertices.marks, 'y'); let marksIndices = Geo.GetIndicesFromVertices(this.zVertices.marks); this.zMarksWireframeMesh = new WireframeMesh(this.zVertices.marks, marksIndices, this.Z_MARKS_COLOR); this.zVertices.halfMarks = this.rotateVertices(this.xVertices.halfMarks, 'y'); let halfMarksIndices = Geo.GetIndicesFromVertices(this.zVertices.halfMarks); this.zHalfMarksWireframeMesh = new WireframeMesh(this.zVertices.halfMarks, halfMarksIndices, this.Z_HALF_MARKS_COLOR); } }

代码虽然显得稍长,但同样也是运用了建造者模式,因此不难理解。

在构建一个相对复杂的网格物体模型时,经常用到建造者模式。例如,当需要分别为比亚迪、奇瑞汽车建模时,给它们安排不同的建造参数即可。

FaceMesh

FaceMesh继承自CompositeMesh,用于渲染一个由多个面组成的网格物体。在实现中,FaceMeshSoleColorMesh的聚合。

export class FaceMesh extends CompositeMesh { constructor(vertices, facesIndices, facesColors) { super(); this.vertices = vertices; this.vertices2D = Geo.To2DArrays(vertices); if (facesIndices === null || facesIndices === undefined) { facesIndices = []; facesIndices.push(Geo.GetIndicesFromVertices(vertices)); } if (facesColors === null || facesColors === undefined) { facesColors = []; for (let index = 0; index < vertices.length; index++) { let faceColor = GLColors.GetRandomSoftRGB(); facesColors.push(faceColor); } } else if (!Array.isArray(facesColors[0]) && facesColors.length === 4) { let singleColor = facesColors; facesColors = []; for (let index = 0; index < vertices.length; index++) { facesColors.push(singleColor); } } else if (facesColors.length < facesIndices.length) { let colorNeededNum = facesIndices.length - facesColors.length; for (let index = 0; index < colorNeededNum; index++) { let faceColor = GLColors.GetRandomSoftRGB(); facesColors.push(faceColor); } } let index = 0; for (let faceIndices of facesIndices) { let solidIndices = Geo.GetCCWTriangleIndices(faceIndices); let wireframeIndices = Geo.GetLinesIndices(faceIndices, true); let soleColorMesh = new SoleColorMesh(vertices, facesColors[index++], solidIndices, wireframeIndices); this.childMeshes.push(soleColorMesh); } } }

构造方法参数中的facesIndicesfacesColors均为可选。如果未指定,则自动生成。

FaceMesh太好用了,因此下面这些最常用的网格物体均直接继承于它。并且,均只需一个构造器即可。

RegularPolygonMesh

export class RegularPolygonMesh extends FaceMesh { constructor(radius, sidesNum, isNeedOrg = false, facesColors) { if (isNeedOrg === true) { let vertices = Geo.GenRegularPolygonVertices(radius, sidesNum, isNeedOrg); let indices = Geo.GetIndicesFromVertices(vertices); let triangleIndices = Geo.GenTriangleIndicesInCircle(indices); let facesIndices = Geo.To2DArrays(triangleIndices); super(vertices, facesIndices, facesColors); } else { let vertices = Geo.GenRegularPolygonVertices(radius, sidesNum, isNeedOrg); super(vertices, null, facesColors); } } }

CircularMesh

export class CircularMesh extends RegularPolygonMesh { constructor(radius, pointsNum, isNeedOrg = false, facesColors) { super(radius, pointsNum, isNeedOrg, facesColors); } }

CubeMesh

export class CubeMesh extends FaceMesh { constructor(sideLength, facesColors) { let wrapper = Geo.GenCube(sideLength); super(wrapper.vertices, wrapper.faces, facesColors); } }

CuboidMesh

export class CuboidMesh extends FaceMesh { constructor(length, width, height, facesColors) { let wrapper = Geo.GenCuboid(length, width, height); super(wrapper.vertices, wrapper.faces, facesColors); } }

BoxMesh

export class BoxMesh extends FaceMesh { constructor(length, width, height, lengthThick, widthThick, bottomThick, facesColors) { let wrapper = Geo.GenBox(length, width, height, lengthThick, widthThick, bottomThick); super(wrapper.vertices, wrapper.faces, facesColors); } }

ConeMesh

export class ConeMesh extends FaceMesh { constructor(radius, sidesNum, height, facesColors) { let wrapper = Geo.GenCone(radius, sidesNum, height); super(wrapper.vertices, wrapper.faces, facesColors); } }

ConeFrustumMesh

export class ConeFrustumMesh extends FaceMesh { constructor(bottomRadius, sidesNum, height, topRadius, facesColors) { let wrapper = Geo.GenConeFrustum(bottomRadius, sidesNum, height, topRadius); super(wrapper.vertices, wrapper.faces, facesColors); } }

CylinderMesh

export class CylinderMesh extends FaceMesh { constructor(radius, sidesNum, height, facesColors) { let wrapper = Geo.GenCylinder(radius, sidesNum, height); super(wrapper.vertices, wrapper.faces, facesColors); } }

参考资源

  1. WebGL 2.0 Specification