unigine mesh文件经历很多版本都没有变动过,测试版本2.7.2
unigine 相关的官方论坛:developer.unigine.com
比较详细的mesh描述可以参看文档的mesh file  format 和mesh类及下面的一些结构上的说明, 以及mesh相关的类和案例
mesh中的一大缺陷是不描述曲线。

uigine 中mesh结构可以存放修改动画、模型、骨骼及其他相关的东西。关于修改,操作一般是从一个mesh选择性的添加到另外一个mesh。
可以通过mesh来加载动画或制作动画。加载制作模型,以及两者的结合体。
原生的mesh ,objectstaticmesh,objectDynamicMesh 和objectSkinnedMesh有很全的接口去操作或保存mesh文件。
当然也可以麻烦点手动写代码去创建和修改mesh文件。
文件格式大致如下

MESH File Format

 

  1. File format identifier (int"ms10" ('m' | ('s' << 8) | ('1' << 16) | ('0' << 24)))
  2. Bounding volume of the whole mesh:
  • Mesh bounding box minimum (float[3])
  • Mesh bounding box maximum (float[3])
  • Mesh bounding sphere center (float[3])
  • Mesh bounding sphere radius (float)
  1. Number of bones (int)
  2. Header information for each bone:
  • Name of the bone. It is a null-terminated string that contains:
  1. The number of characters in the string including the null character (int).
  2. Bone name of the specified length (char[length]).
  • Parent of the bone. If case of the root bone without a parent, -1 is used. (short)
  1. Number of mesh animations (int)
  2. Header information for each animation:
  • Name of the animation. It is a null-terminated string that contains:
  1. The number of characters in the string including the null character (int).
  2. Animation name of the specified length (char[length])
  1. Number of surfaces (int)
  2. Header information for each surface:
  • Name of the surface. It is a null-terminated string that contains:
  1. The number of characters in the string including the null character (int).
  2. Surface name of the specified length (char[length])
  • Surface bounding box minimum (float[3])
  • Surface bounding box maximum (float[3])
  • Surface bounding sphere center (float[3])
  • Surface bounding sphere radius (float)
  • Number of the surface morph targets (int)
  • Header information for each morph target:
  1. Name of the morph target. It is a null-terminated string that contains:
  • The number of characters in the string including the null character (int).
  • Morph target name of the specified length (char[length])
  1. File format identifier (int"ms10" ('m' | ('s' << 8) | ('1' << 16) | ('0' << 24)))
  2. For each bone:
  • Bone position along X, Y, Z axes (float[3])
  • Bone rotation quaternion (float[4])
  • Bone scale in all directions(float[3])
  1. For each animation:
  • Number of bones taking part in the animation (int)
  • Indices of the bones taking part in the animation (short[length])
  • Flag (unsigned char), each 8 bits of which determine if the mesh animations should store translation, scale or rotation components for bones taking part in the animations.
  • Number of animation frames (int)
  • Transformation data for each bone in each frame:
  1. Flag (unsigned char), each 8 bits of which determine if the current frame should store new translation, scale or rotation components for the bone. If these components have not changed since the previous frame, new data is not stored. Instead, data from the previous frame is used, which maximizes the efficiency of memory usage.
  2. If the data is flagged as changed, for each component of the bone:
  • Translation of the bone along X (float)
  • Translation of the bone along Y (float)
  • Translation of the bone along Z (float)
  • Rotation quaternion (short[4])
  • X component of the bone scale (float)
  • Y component of the bone scale (float)
  • Z component of the bone scale (float)
  1. Data on each surface geometry:
  • Data on each morph target of the surface:
  1. Number of vertices in the morph target (int)
  2. Vertices of the morph target (float[3*length])
  3. Number of tangents (int)
  4. For each tangent:
  • X, Y and Z components of the short type; the W component is of the char type.
  • Number of surface weights (int)
  • For each surface weight:
  1. Number of bones that affect the vertex (the maximum value is 4) (unsigned char)
  2. For each bone that affects the vertex:
  • Bone index (short)
  • Bone weight (unsigned short)
  • Number of texture coordinates in the 1st UV set (int)
  • For each of texture coordinates:
  1. 1st UV texture coordinates(float[2])
  • Number of texture coordinates in the 2nd UV set (int)
  • For each of texture coordinates:
  1. 2nd UV texture coordinates (float[2])
  • Number of 8-bit vertex colors (int).
  • For each color:
  1. Color value (char[4])
  • Number of coordinate indices (int)
  • Сoordinate indices for each vertex of the surface:
  1. If the zero morph target contains less than 256 coordinate vertices, all the indices are unsigned char[length]
  2. If the zero morph target contains less than 65536 coordinate vertices, all the indices are short[length]
  3. Otherwise, all the indices are int[length]
  • Number of triangle indices (int)
  • Triangle indices for each vertex of the surface:
  1. If the zero morph target contains less than 256 triangle vertices, all the indices are unsigned char[length]
  2. If the zero morph target contains less than 65536 triangle vertices, all the indices are short[length]
  3. Otherwise, all the indices are int[length]
  1. File format identifier (int"ms10" ('m' | ('s' << 8) | ('1' << 16) | ('0' << 24)))

上面的描述中对于所有的size需要按如下代码来读取,存储方式类似utf8,是不定字节数的:

bool Mesh::readNum(FILE *f, int &value, int index )//num range (0, fff)
{
	unsigned char c;
	if (readByte(f, c) == false) return false;
	int offset = index > 0 ? 7 : 6;
	if (c & 0x80)//means have a byte followed
	{
		readNum(f, value, index + 1);
		value = (value << offset) | (c & 0x7f);
	}
	else
	{
		value = c & 0x7f;
	}
	return true;
}

1、关于surface。

一个mesh中可以有多个surface, surface是一个比较上层的概念,内部包含各个target和uv以及triangle等信息。在继承它的staticmesh等中可对surface设置lod。一套UV在unigine中是以二维顶点信息(texcoord)和顶点下标来表示的。一套surface可以绑定最多两套uv。

给mesh添加图片纹理_给mesh添加图片纹理

给mesh添加图片纹理_mesh_02

2、 关于anim:

anim是与骨骼严格相关的。一个mesh中可以存放多套animation,默认只有能有一套动作,但是在代码中可以操作。通过setNumLayers(int num)来设置几套animation。通过setLayer ( int layer, bool enabled, float weight )来控制animation的使用以及插值。

如下案例为anim的转储和对anim重命名。(可解决动画串着影响的问题)

给mesh添加图片纹理_mesh_03


可以参考我另外的博文及相关工具代码。

3、关于骨骼与模型绑定(vertex weight)关系:
这种绑定用途就是蒙皮动画(skinned animation),在unigine中有objectskinnedmesh专用于进行蒙皮动画的类。原理就是对模型的每个顶点,设置影响其位置变化的骨骼和其对应的权重(unigine中目前最多是四个骨骼 )。 
在第三方软件中绑定好后,unigine中存放的关系如下。
骨骼是mesh层面的,而骨骼与顶点的绑定关系是surface层面的。绑定关系的数量目前来看与顶点数量一致。
vertex weight 结构:

给mesh添加图片纹理_unigine_04


测试样例:

给mesh添加图片纹理_给mesh添加图片纹理_05

给mesh添加图片纹理_给mesh添加图片纹理_06

4、关于target 和 blend shape。

一个target表示一个完整的geometry ,一个geometry至少有两个buffer(详情看mesh),vertex coordinate(float3), triangle vertex{struct triangle vertex(结构在下面有描述),一个vertex只有一个vertex coordinate,但是可能有多个triangle vertex} ,而vertex indices(int)(CIndices),triangle indices(int)(需要注意在文件中存储时有点小差别)(TIndices)。操作时,先对所有的顶点进行addvertex,然后在addIndices 会对vertex indices 和triangle indices同时扩容,addTindices会对triangle indicies 扩容(可通过getnumIndices和getNumTIndices)。mesht->createIndices(0) ,能对vertex做优化,消除一些重复的点。至于为何会有两个indices 文档中有说明:

给mesh添加图片纹理_给mesh添加图片纹理_07

mesh中getnumvertex 与getnumCVertex描述的是一样的东西。

给mesh添加图片纹理_file_08

Each coordinate vertex contains coordinates ( float[3]) of the vertex.

Each triangle vertex can store:

  • Normal(float3)( 文件中不存在,tangent 计算求取出来的)
  • Binormal( 文件中不存在,tangent 计算求取出来的)
  • Tangent(float4 )
  • 1st UV map texture coordinates(float2)
  • 2nd UV map texture coordinates(float2)
  • Color(char[4])

The number of vertices and coordinate vertices is equal.

可以看出mesh的存储分为坐标点(实际存放顶点信息)和三角形点(坐标点下标),两者的数量可能不一致 。三角点用于绘制工作,坐标点用于顶点缓存。
一个surface 至少有1个target,多个target主要用于blend shape,通过设置target 之间的权重(在dynamicmesh中)来计算相应顶点的当前的位置。多个target时,每个target的顶点数是一样的,多个target共用三角形indices和uv,可以理解为三角形顶点信息(包括顶点位置,tangent,normal)是target层面的,而三角形indices和uv是surface层面的 。

给mesh添加图片纹理_mesh_09


默认添加顶点时都是往第0 个target上添加。

给mesh添加图片纹理_format_10

// create mesh  //创建一个具有球形target的surface,并另外添加box和star的target。
        Mesh mesh = new Mesh();
        
        // sphere surface
        int surface = mesh.addSphereSurface("surface",2.0f,32,64);//此时创建好了这个surface的indices和uv信息。以及第一个target的顶点信息,没有设置第一个target的名字默认为Target_0。返回的值表示surface的下标
        
        // create targets
        int target_0 = mesh.addSurfaceTarget(surface,"box");//在下标为surface的surface上添加名字为“box”的target,内部会分配好存储一个target顶点信息的缓存。并返回这个target的下标
        int target_1 = mesh.addSurfaceTarget(surface,"star");//在下标为surface的surface上添加名字为“star”的target
        forloop(int i = 0; mesh.getNumVertex(surface)) {
            vec3 vertex = normalize(mesh.getVertex(i,surface));
            vec3 scale = vec3_one - vertex.yzx * vertex.yzx * 0.5f - vertex.zxy * vertex.zxy * 0.5f;
            mesh.setVertex(i,vertex * scale * 2.5f,surface,target_0);//设置target的顶点位置信息
            mesh.setVertex(i,vertex / scale * 2.0f,surface,target_1);
        }
        
        // update tangent space
        mesh.createTangents(surface,target_0);//更新target顶点的tangent信息和normal信息。
        mesh.createTangents(surface,target_1);
        
        mesh.save(fullPath("samples/objects/meshes/skinned_07.mesh"));
        
        delete mesh;
void setTarget ( int target, bool enabled, int index, float weight, int surface ) 
Enables or disables a given morph target and sets all its parameters.
Arguments
int target - Morph target number.
bool enabled - Enable flag: true to enable the morph target; false to disable it.
int index - Target index.
float weight - Target weight.
int surface - Surface number.

init(){
.....
// mesh targets
mesh.setNumTargets(5,0);
.....
}

update(){
......
//unigine内部实现中,mesh的surface最终展现的效果是由target决定的。存放当前显示的顶点的buffer与target存放顶点的buffer不是同一个buffer。当target的enable值为true则会将顶点结果叠加到显示的buffer中,默认第0个target的enable值为true。
//比如下面,会将5个设置了权重(weight)的target顶点位置叠加最终存放到显示buffer,并显示出来。通过这种顶点位置叠加和设置权重的方式来达到blend shape(morph) 的效果。
//所以使用的时候必须注意surface顶点的坐标系位置以及模型的顶点位置情况来判断是否适合用这种方式做morph,以及是否这种方式做的morph为所设想需要的结果。
float k0 = sin(time + i * 3.0f) + 0.75f;
float k1 = cos(time + i * 3.0f) + 0.75f;
mesh.setTarget(0,1,0,1.0f,0);//指令意思是将target 0顶点位置乘上1.0放到target 0中,此处我猜测应该不会有另外一层buffer来存放乘以weight后的顶点信息,而只是在数组中存放关键的参数,到最后的结果的时候一起进行计算。 
mesh.setTarget(1,1,0,-k0,0);//将enable参数设置成0,表示不参与显示的计算。
mesh.setTarget(2,1,0,-k1,0);
mesh.setTarget(3,1,1,k0,0);
mesh.setTarget(4,1,2,k1,0);
/*过程:将target0 和target 1的顶点位置进行weight计算后分别放到target 3和target 4中,并将3 和4 的顶点位置信息叠加(位置叠加发现和tangent也会分别进行插值计算)放到显示的buffer中。target 0,target 1和target 2不参与最终的显示的计算。
mesh.setTarget(0,0,0,1.0f,0);
mesh.setTarget(1,0,0,-k0/2,0);
mesh.setTarget(2,0,0,k0/2-1.0f,0);
mesh.setTarget(3,1,0,k0/2,0);
mesh.setTarget(4,1,1,1.0f-k0/2,0);
*/

.....
}