ModelMesh 是一个用于描述几何体轮廓的网状渲染数据类,主要包含了顶点(位置、法线和 UV 等)、索引和混合形状等数据。不仅可以使用建模软件制作并导出 glTF 在引擎中解析还原,还可以方便的使用脚本直接写入数据创建。
const entity = rootEntity.createChild("mesh-example");
const meshRenderer = entity.addComponent(MeshRenderer);
const modelMesh = new ModelMesh(engine);
// Set vertieces data
const positions = [
new Vector3(-1.0, -1.0, 1.0),
new Vector3(1.0, -1.0, 1.0),
new Vector3(1.0, 1.0, 1.0),
new Vector3(1.0, 1.0, 1.0),
new Vector3(-1.0, 1.0, 1.0),
new Vector3(-1.0, -1.0, 1.0),
];
modelMesh.setPositions(positions);
// Add SubMesh
modelMesh.addSubMesh(0, 6);
// Upload data
modelMesh.uploadData(false);
meshRenderer.mesh = modelMesh;
meshRenderer.setMaterial(new UnlitMaterial(engine));
ModelMesh
的使用分为以下几步:
ModelMesh
可以通过高级数据或低级数据设置顶点数据,也可以根据需求选择性设置,但需要注意位置是必要数据且需要最先设置。
可以直接通过设置 position
, normal
, uv
等高级数据生成 ModelMesh,然后调用 uploadData
方法统一上传数据至 GPU 完成应用。
const positions = new Array<Vector3>(4);
positions[0] = new Vector3(-1, 1, 1);
positions[1] = new Vector3(1, 1, 1);
positions[2] = new Vector3(1, -1, 1);
positions[3] = new Vector3(-1, -1, 1);
const uvs = new Array<Vector2>(4);
uvs[0] = new Vector2(0, 0);
uvs[1] = new Vector2(1, 0);
uvs[2] = new Vector2(1, 1);
uvs[3] = new Vector2(0, 1);
modelMesh.setPositions(positions);
modelMesh.setUVs(uvs);
modelMesh.uploadData(false);
设置高级数据的 API 有:
API | 说明 |
---|---|
setPositions | 设置顶点坐标 |
setIndices | 设置索引数据 |
setNormals | 设置逐顶点法线数据 |
setColors | 设置逐顶点颜色数据 |
setTangents | 设置逐顶点切线 |
setBoneWeights | 设置逐顶点骨骼权重 |
setBoneIndices | 设置逐顶点骨骼索引数据 |
setUVs | 设置逐顶点 uv 数据 |
相比于高级数据,通过低级接口设置数据可以自由操作顶点缓冲数据,不仅灵活还可能带来性能提升。但需要理解 Vertex Buffer 和 Vertex Element 之间的关系,如下图:
const pos = new Float32Array([1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0]);
const posBuffer = new Buffer(
engine,
BufferBindFlag.VertexBuffer,
pos,
BufferUsage.Static,
true
);
const mesh = new ModelMesh(engine);
mesh.setVertexBufferBinding(posBuffer, 12, 0);
const vertexElements = [
new VertexElement(
VertexAttribute.Position,
0,
VertexElementFormat.Vector3,
0
),
];
mesh.setVertexElements(vertexElements);
mesh.uploadData(false);
SubMesh 主要包含了绘制范围和绘制方式等信息。调用 addSubMesh。
modelMesh.addSubMesh(0, 2, MeshTopology.Triangles);
调用 uploadData() 方法。
如果不再需要修改 ModelMesh
数据,releaseData
参数设置为 true
:
modelMesh.uploadData(true);
如果需要持续修改 ModelMesh
数据,releaseData
参数设置为 false
:
modelMesh.uploadData(false);
若要让 ModelMesh
中的顶点数据可读,需注意:
releaseData
参数设置为 false
true
const pos = new Float32Array([1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0]);
const posBuffer = new Buffer(
engine,
BufferBindFlag.VertexBuffer,
pos,
BufferUsage.Static,
true
);
const mesh = new ModelMesh(engine);
mesh.setVertexBufferBinding(posBuffer, 12, 0);
const vertexElements = [
new VertexElement(
VertexAttribute.Position,
0,
VertexElementFormat.Vector3,
0
),
];
mesh.setVertexElements(vertexElements);
mesh.uploadData(false);
// 期望得到的高级数据
const result = mesh.getPositions();
BlendShape
通常用于制作精细程度非常高的动画,比如表情动画等。其原理也比较简单,主要通过权重混合基础形状和目标形状的网格数据来表现形状之间过渡的动画效果。
glTF 导入 BlendShape 动画案例:
脚本自定义 BlendShape 动画案例:
BlendShape
数据首先我们先创建一个BlendShape
对象,然后调用 addFrame()添加混合形状的帧数据,一个 BlendShape
可以添加多个关键帧,每一帧由权重和几何体偏移数据组成 其中偏移位置是必要数据,偏移法线和偏移切线为可选数据。
然后我们通过Mesh
的addBlendShape()
方法添加创建好的BlendShape
。
// Add BlendShape
const deltaPositions = [
new Vector3(0.0, 0.0, 0.0),
new Vector3(0.0, 0.0, 0.0),
new Vector3(-1.0, 0.0, 0.0),
new Vector3(-1.0, 0.0, 0.0),
new Vector3(1.0, 0.0, 0.0),
new Vector3(0.0, 0.0, 0.0),
];
const blendShape = new BlendShape("BlendShapeA");
blendShape.addFrame(1.0, deltaPositions);
modelMesh.addBlendShape(blendShape);
BlendShape
现在我们要将网格的形状完全调整为刚才添加的BlendShape
,我们需要设置一个权重数组,由于我们只添加了一个BlendShape
,所以权重数组长度为 1 即可,并把第一个元素的值设置为 1.0。
// Use `blendShapeWeights` property to adjust the mesh to the target BlendShape
skinnedMeshRenderer.blendShapeWeights = new Float32Array([1.0]);