让我们的MD5模型动起来,以及CPU渲染那些事~

md5Anim文件中各字段,我的理解是
hierarchy 是层级关系
数据内容为: 骨骼名称,父级索引,flag, 索引起始位
其中flag是一个用来做二进制求与关系来决定当前的数据是 偏移Tx,Ty,TZ 还是 旋转Qx,Qy,Qz
索引起始位是 frame 动画帧字段中数据的起始索引

bounds 是包围盒
通过 min 和 max 可以决定当前动作的 AABB. 不做碰撞检测的话,一般用不到

baseframe 基础帧信息
实际上模型的各骨骼动画都是以此为起始值进行偏移的
6个值分别是 Tx,Ty,Tz,Qx,Qy,Qz

frame 具体的动画帧信息
数据内容为: 帧索引, numAnimatedComponents 定义的长度数量的 动画数据。 其中的数值具体有什么用,需要查询 hierarchy 中的 flag.

具体转换骨骼动画的代码如下
[cc lang=”actionscript3″]
private function CalcMeshAnim(frameData : MD5FrameData) : void
{
//取出关节数据
var joints : Vector. = md5MeshParser.md5_joint;
var jointsNum : int = joints.length;

cpuAnimMatrix ||= new Vector.(jointsNum * 4, true);

var joint : MD5Joint;
var parentJoint : MD5Joint;
for(var i : int = 0; i < jointsNum; i++) { //从基本帧开始偏移 var baseFrame : MD5BaseFrameData = md5AnimParser.baseFrameData[i]; var animatedPos : Vector3D = baseFrame.position; var animatedOrient : Quaternion = baseFrame.orientation; //将帧数据替换掉基本帧中对应的数据 var hierachy : MD5HierarchyData = md5AnimParser.hierarchy[i]; var flags : int = hierachy.flags; var j : int = 0; if(flags & 1) //tx animatedPos.x = frameData.components[hierachy.startIndex + j++]; if(flags & 2) //ty animatedPos.y = frameData.components[hierachy.startIndex + j++]; if(flags & 4) animatedPos.z = frameData.components[hierachy.startIndex + j++]; if(flags & 8) animatedOrient.x = frameData.components[hierachy.startIndex + j++]; if(flags & 16) animatedOrient.y = frameData.components[hierachy.startIndex + j++]; if(flags & 32) animatedOrient.z = frameData.components[hierachy.startIndex + j++]; //计算w var t : Number = 1 - animatedOrient.x * animatedOrient.x - animatedOrient.y * animatedOrient.y - animatedOrient.z * animatedOrient.z; animatedOrient.w = t < 0 ? 0 : - Math.sqrt(t); var matrix3D : Matrix3D = animatedOrient.toMatrix3D(); matrix3D.appendTranslation(animatedPos.x, animatedPos.y, animatedPos.z); //取出当前关节 joint = joints[i]; if(joint.parentIndex < 0){ joint.bindPose = matrix3D; }else{ //如果该关节有父级,需要先附带上父级的旋转和偏移 parentJoint = joints[joint.parentIndex]; matrix3D.append(parentJoint.bindPose); joint.bindPose = matrix3D; } matrix3D = joint.inverseBindPose.clone(); matrix3D.append(joint.bindPose); var vc : int = i * 4; if(useCPU){ cpuAnimMatrix[vc] = matrix3D; }else{ context.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, vc, matrix3D, true); } } [/cc]

最函数的最后有做一个判断 if(useCPU) cpuAnimMatrix[vc] = matrix3D;
实际上 AGAL 只有128个常量寄存器, 一个包含骨骼动画偏移及旋转的矩阵需要占用 4个寄存器。 这就是 var vc : int = i *4 这个算寄存器索引的原因。
实际上我们还需要上传一个投影矩阵给AGAL. 因此,实际上我们只能用 124 / 4 即 31个骨骼来表示我们的模型动画。 (这里不考虑优化)

那么一旦我们有超过31个骨骼该如何办呢,那只能用CPU来计算顶点蒙皮了。 而用CPU来计算蒙皮可能会导致性能问题,这也是部分Away3D游戏帧数不稳定的元凶之一。
蒙皮的原理就是
取出一个顶点,将该顶点根据包含旋转和平移的骨骼动画矩阵进行转换,最后遍历骨骼权重的数量,乘以对应的权重

假设骨骼权重是4个
那么AGAL为:
[cc lang=”actionscript3″]
m44 vt1, va0, vc[va2.x];
mul vt1, vt1, va3.x;
mov vt2, vt1;

m44 vt1, va0, vc[va2.y];
mul vt1, vt1, va3.y;
add vt2, vt2, vt1;

m44 vt1, va0, vc[va2.z];
mul vt1, vt1, va3.z;
add vt2, vt2, vt1;

m44 vt1, va0, vc[va2.w];
mul vt1, vt1, va3.w;
add vt2, vt2, vt1;

m44 op, vt2, vc124
mov v0, va1
[/cc]
vc 是包含了所有骨骼动画矩阵信息的常量寄存器。 va2 是骨骼的索引值,通过这个索引值来取得对应的矩阵 m44 vt1, va0, vc[va2.x];
va3 是骨骼权重信息, 当前顶点被矩阵转换后,需要乘以骨骼权重。
vt2 是最终的顶点,它积累了一个顶点被多次处理后的最终值。

CPU处理骨骼动画的代码为
[cc lang=”actionscript3″]
private function cpuCalcJoint(meshIndex : int, view : Matrix3D) : void
{
var meshData : MeshData = md5MeshParser.md5_mesh[meshIndex];
var vertexLen : int = meshData.md5_vertex.length;

//当前索引
var indices : Vector. = meshData.jointIndexRawData;
//当前顶点
var vertex : Vector. = meshData.vertexRawData;
//当前权重
var weight : Vector. = meshData.jointWeightRawData;

var result : Vector3D = new Vector3D();
var temp : Vector3D = new Vector3D();
var curVert : Vector3D = new Vector3D();

cpuAnimVertexRawData ||= new Vector.();
cpuAnimVertexRawData.length = 0;

var l : int = 0;
for(var i : int = 0; i < vertexLen; i++) { var startIndex : int = i * 3; //初始化当前的顶点和索引 curVert.setTo(vertex[startIndex],vertex[startIndex+1],vertex[startIndex+2]); result.setTo(0,0,0); for(var j : int = 0; j < md5MeshParser.maxJointCount; j++) { //当前索引对应的矩阵 var curIndex : Number = indices[l]; var curWeight : Number = weight[l]; var curMatrix : Matrix3D = cpuAnimMatrix[curIndex]; temp = curMatrix.transformVector(curVert); temp.scaleBy(curWeight); result = result.add(temp); l++; } cpuAnimVertexRawData.push(result.x,result.y,result.z); } cpuAnimVertexBuffer ||= context.createVertexBuffer(cpuAnimVertexRawData.length/3,3); cpuAnimVertexBuffer.uploadFromVector(cpuAnimVertexRawData,0,cpuAnimVertexRawData.length/3); } [/cc] 在用GPU处理动画矩阵的时候 context.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, vc, matrix3D, true); 我们设置最后一个参数为true, 表示该矩阵会被转置即行列对换。 在CPU处理时,我们需要考虑如下对应关系。 如果你使用 Matrix3D.transformVector(vertex) 这个API来转换你的顶点,实际上内部实现是 [cc lang="actionscript3"] //向量左乘矩阵 public static function subjectMat(vec : Vector3D, mat : Matrix3D, out : Vector3D = null) : Vector3D { var data : Vector. = mat.rawData;

out ||= new Vector3D();

out.x = (vec.x * data[0] + vec.y * data[4] + vec.z * data[8] + vec.w * data[12]);
out.y = (vec.x * data[1] + vec.y * data[5] + vec.z * data[9] + vec.w * data[13]);
out.z = (vec.x * data[2] + vec.y * data[6] + vec.z * data[10] + vec.w * data[14]);
out.w = (vec.x * data[3] + vec.y * data[7] + vec.z * data[11] + vec.w * data[15]);

return out;
}
[/cc]
他是将一个Vector3D 转换成 4行1列的矩阵进行左乘的。

而AGAL中的 m44 方法其内部实现是
[cc lang=”actionscript3″]
public static function subjectMat2(vec : Vector3D, mat : Matrix3D, out : Vector3D = null) : Vector3D
{
var data : Vector. = mat.rawData;

out ||= new Vector3D();

out.x = (vec.x * data[0] + vec.y * data[1] + vec.z * data[2] + vec.w * data[3]);
out.y = (vec.x * data[4] + vec.y * data[5] + vec.z * data[6] + vec.w * data[7]);
out.z = (vec.x * data[8] + vec.y * data[9] + vec.z * data[10] + vec.w * data[11]);
out.w = (vec.x * data[12] + vec.y * data[13] + vec.z * data[14] + vec.w * data[15]);

return out;
}
[/cc]
他将Vector3D 转换成 1行4列的矩阵进行左乘的。

因此如果你用transformVector来转换你的顶点,那么就不需要转置矩阵。

最后是Demo:
http://www.dreamfairy.cn/blog/work/flash/3d/stage3dmd5/md5test.html

GitHub:
https://github.com/dreamfairy/3DDemo/blob/master/StencilShadow/src/MD5Test.as

发表评论

电子邮件地址不会被公开。 必填项已用*标注