ModelMesh is a mesh rendering data class used to describe the geometric outline, mainly including vertex data (such as position, normal, and UV), indices, and blend shapes. It can not only be created and exported in glTF format using modeling software for engine parsing and restoration but also be conveniently written directly into data creation using scripts.
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));
The usage of ModelMesh
is divided into the following steps:
ModelMesh
can set vertex data through high-level data or low-level data, and can also selectively set data according to requirements, but it is necessary to note that position is mandatory data and needs to be set first.
ModelMesh can be generated directly by setting high-level data such as position
, normal
, uv
, etc., and then call the uploadData
method to uniformly upload data to the GPU to complete the application.
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);
The APIs for setting high-level data are:
API | Description |
---|---|
setPositions | Set vertex coordinates |
setIndices | Set index data |
setNormals | Set per-vertex normal data |
setColors | Set per-vertex color data |
setTangents | Set per-vertex tangent data |
setBoneWeights | Set per-vertex bone weights |
setBoneIndices | Set per-vertex bone index data |
setUVs | Set per-vertex uv data |
Compared to high-level data, setting data through low-level interfaces allows for free manipulation of vertex buffer data, which is not only flexible but may also bring performance improvements. However, it is necessary to understand the relationship between Vertex Buffer and Vertex Element, as shown in the following diagram:
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 mainly contains information such as drawing range and drawing mode. Call addSubMesh.
modelMesh.addSubMesh(0, 2, MeshTopology.Triangles);
Call the uploadData() method.
If you no longer need to modify the ModelMesh
data, set the releaseData
parameter to true
:
modelMesh.uploadData(true);
If you need to continuously modify the ModelMesh
data, set the releaseData
parameter to false
:
modelMesh.uploadData(false);
To make the vertex data in ModelMesh
readable, pay attention to:
releaseData
parameter to false
when uploading datareadable
property of the low-level data (readable) should be set to 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
is commonly used to create highly detailed animations, such as facial expressions. The principle is quite simple, mainly blending the mesh data of base shape and target shape with weights to achieve smooth transition between shapes.
glTF Import BlendShape Animation Example:
Custom BlendShape Animation in Script Example:
BlendShape
DataFirst, create a BlendShape
object, then call addFrame() to add frame data of the blended shapes. A BlendShape
can have multiple keyframes, each frame consisting of weights and geometry offset data where the offset position is necessary data, and offset normal and offset tangent are optional data.
Then, use the addBlendShape()
method of Mesh
to add the created 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
with WeightsNow, to fully adjust the mesh shape to the added BlendShape
, we need to set an array of weights. Since we only added one BlendShape
, the length of the weights array can be 1, and set the value of the first element to 1.0.
// Use `blendShapeWeights` property to adjust the mesh to the target BlendShape
skinnedMeshRenderer.blendShapeWeights = new Float32Array([1.0]);