Cocos2d-x 3.x 图形学渲染系列二十五

栏目: IOS · 发布时间: 8年前

内容简介:Cocos2d-x 3.x 图形学渲染系列二十五

笔者介绍: 姜雪伟 IT 公司技术合伙人, IT 高级讲师, CSDN 社区专家,特邀编辑,畅销书作者,国家专利发明人 ; 已出版书籍:《手把手教你 架构 3D 游戏引擎》电子工业出版社 和《 Unity3D 实战核心技术详解》电子工业出版社等。

CSDN视频网址: http://edu.csdn.net/lecturer/144

大年三十,还是忍不住,再写一篇文章吧,作为在猴年的最后一篇文章。。。。

游戏中的角色包括玩家、NPC、怪物等都具有骨骼动画,对于成熟的引擎也是需要支持骨骼动画的,骨骼动画是在模型的基础上经过max工具调试出来的,在程序中只需要提供播放骨骼动画的接口即可,掌握骨骼动画的原理对于评判一个 程序员 是否精通引擎的必要条件,也是一个重要的评判指标。本章重点给读者介绍Cocos2d-x的骨骼动画,虽然该引擎对骨骼动画的处理不是很成熟,但简单的骨骼动画还是可以实现的。本章会把Cocos2d-x引擎中关于动画的处理的缺点和优点都会给读者分享。

骨骼动画是通过蒙皮实现的,Cocos2d-x引擎有自己的demo实现,如果开发者掌握骨骼动画,必须自己要尝试着做一下。Cocos2d-x引擎还是有很多坑的,尤其是在播放动画方面。后面会给读者介绍到,下面就以自己做的带有骨骼动画的宇航员模型为例给读者介绍。因为c3t是json文件格式,相对读者能够看到其文件内容,更有助于读者理解。在这里只截取了骨骼动画数据展示如下所示:

"animations": [
		{
			"id": "Take 001", 
			"length":  0.666000, 
			"bones": [
				{
					"boneId": "Box002", 
					"keyframes": [
						{
							"keytime":  0.000000, 
							"rotation": [-0.707107, -0.000000,  0.000000,  0.707107], 
							"scale": [ 1.000000,  1.000000,  1.000000], 
							"translation": [ 0.000000,  36.417324, -74.842522]
						}, 
						{
							"keytime":  0.050050, 
							"rotation": [-0.696203, -0.000000,  0.000000,  0.717845]
						}, 
						{
							"keytime":  0.100100, 
							"rotation": [-0.665764, -0.000000,  0.000000,  0.746162]
						}, 
						{
							"keytime":  0.150150, 
							"rotation": [-0.617108, -0.000000,  0.000000,  0.786878]
						}, 
						{
							"keytime":  0.200200, 
							"rotation": [-0.556456, -0.000000,  0.000000,  0.830877]
						}, 
						{
							"keytime":  0.250250, 
							"rotation": [-0.486915, -0.000000,  0.000000,  0.873449]
						}, 
						{
							"keytime":  0.300300, 
							"rotation": [-0.414019, -0.000000,  0.000000,  0.910268]
						}, 
						{
							"keytime":  0.350350, 
							"rotation": [-0.342342, -0.000000,  0.000000,  0.939575]
						}, 
						{
							"keytime":  0.400400, 
							"rotation": [-0.283450, -0.000000,  0.000000,  0.958987]
						}, 
						{
							"keytime":  0.450450, 
							"rotation": [-0.241560, -0.000000,  0.000000,  0.970386]
						}, 
						{
							"keytime":  0.500501, 
							"rotation": [-0.226392, -0.000000,  0.000000,  0.974036]
						}, 
						{
							"keytime":  0.550551, 
							"rotation": [-0.240981, -0.000000,  0.000000,  0.970530]
						}, 
						{
							"keytime":  0.600601, 
							"rotation": [-0.282420, -0.000000,  0.000000,  0.959291]
						}, 
						{
							"keytime":  0.650651, 
							"rotation": [-0.341023, -0.000000,  0.000000,  0.940055]
						}, 
						{
							"keytime":  0.700701, 
							"rotation": [-0.410364, -0.000000,  0.000000,  0.911922]
						}, 
						{
							"keytime":  0.750751, 
							"rotation": [-0.485455, -0.000000,  0.000000,  0.874261]
						}, 
						{
							"keytime":  0.800801, 
							"rotation": [-0.555123, -0.000000,  0.000000,  0.831769]
						}, 
						{
							"keytime":  0.850851, 
							"rotation": [-0.616001, -0.000000,  0.000000,  0.787745]
						}, 
						{
							"keytime":  0.900901, 
							"rotation": [-0.664969, -0.000000,  0.000000,  0.746871]
						}, 
						{
							"keytime":  0.950951, 
							"rotation": [-0.695771, -0.000000,  0.000000,  0.718264]
						}, 
						{
							"keytime":  1.000000, 
							"rotation": [-0.707102, -0.000000,  0.000000,  0.707111], 
							"scale": [ 1.000000,  1.000000,  1.000000], 
							"translation": [ 0.000000,  36.417324, -74.842522]
						}
					]
				}
			]
		}
	]

在上述文件中id 表示的是骨骼动画的名字,bone表示的是动作,在bone项里面包含keytime帧动画时间,rotation旋转的角度,scale缩放大小,translation表示的骨骼转换的起始位置和最终转换的位置。程序的作用就是加载读取该模型文件,程序加载模型文件,需要在程序中定义相应的结构体,定义结构体主要是用于存放模型信息对应项,Cocos2d-x引擎已定义的结构体给介绍如下:

//模型顶点属性
struct MeshVertexAttrib
{
	//描述该属性所需要的元素个数,比如描述一个Vec3的位置信息,需要x、y、z 3个变量
	GLint size;
	//元素的类型如GL_FLOAT
	GLenum type;
	//描述该属性类型的值,使用的值是GLProgram类的枚举,例如	GLProgram::VERTEX_ATTRIB_POSITION
	int  vertexAttrib;
	//存储该属性所需要的字节数,等于size * sizeof(type)
	int attribSizeBytes;
};
再介绍一下模型蒙皮数据结构体如下所示:
struct MeshData
{
	//模型的顶点索引数组
typedefstd::vector<unsignedshort> IndexArray;
	//模型的顶点数组
std::vector<float> vertex;
	//模型的顶点数量
int vertexSizeInFloat;
//子模型的索引
std::vector<IndexArray> subMeshIndices;
//子模型的名字ID
std::vector<std::string> subMeshIds; //子网格名字 (从版本3.3开始)
//模型的碰撞盒
	std::vector<AABB> subMeshAABB;
int numIndex;
//模型的顶点属性信息
	std::vector<MeshVertexAttrib> attribs;
int attribCount;
}
以c3t文件为例,先介绍模型相关文件属性:
"version": "0.7", 
	"id": "", 
	"meshes": [
		{
			"attributes": [{
					"size":   3, 
					"type": "GL_FLOAT", 
					"attribute": "VERTEX_ATTRIB_POSITION"
				}, {
					"size":   3, 
					"type": "GL_FLOAT", 
					"attribute": "VERTEX_ATTRIB_NORMAL"
				}, {
					"size":   3, 
					"type": "GL_FLOAT", 
					"attribute": "VERTEX_ATTRIB_TANGENT"
				}, {
					"size":   3, 
					"type": "GL_FLOAT", 
					"attribute": "VERTEX_ATTRIB_BINORMAL"
				}, {
					"size":   2, 
					"type": "GL_FLOAT", 
					"attribute": "VERTEX_ATTRIB_TEX_COORD"
				}],

attribs 属性数组分别代表:

(1)顶点在模型坐标系下的位置信息(VERTEX_ATTRIB_POSITION)

(2)顶点的法线(VERTEX_ATTRIB_NORMAL)

(3)顶点的纹理坐标(VERTEX_ATTRIB_TEX_COOD)

(4)作用于该顶点的某骨骼,对该顶点最终位置的权重(VERTEX_ATTRIB_BLEND_WEIGHT)

(5)影响该顶点的骨骼在骨骼数组中的索引(VERTEX_ATTRIB_BLEND_INDEX)

接下来介绍模型的蒙皮骨骼信息,引擎提供了结构体如下所示:

struct SkinData
{
//影响到模型蒙皮的骨骼名字数组,skinBone的数组
std::vector<std::string> skinBoneNames; 
//未影响到模型蒙皮的骨骼名字数组,nodeBone的数组
std::vector<std::string> nodeBoneNames; 
//从对应的skinBone坐标系到模型坐标系变换的逆变换,可实现将该骨骼影响的蒙	皮顶点从模型坐标系的坐标,转换至骨骼坐标系的坐标
std::vector<Mat4>        inverseBindPoseMatrices; 
//skinBone到其父骨骼坐标的初始矩阵
std::vector<Mat4>        skinBoneOriginMatrices; 
//nodeBone到其父骨骼坐标的初始矩阵
std::vector<Mat4>        nodeBoneOriginMatrices; 
//所有骨骼与其子骨骼索引的map,值得说明的是这个索引是对skinBoneNames	和nodeBoneNames两个数组而言的。
std::map<int, std::vector<int>> boneChild;
//根骨骼索引,同样是相对两个数组而言的
	introotBoneIndex;
}
//材质数据结构体
struct MaterialData
{
	std::map<int, std::string> texturePaths; //子网格id和纹理路径
	void resetData()
    {
		texturePaths.clear();
    }
};

骨骼动画的运动是通过矩阵之间的变换实现的,在Bone3D

类中实现了骨骼矩阵的更新,函数如下所示:

void Bone3D::updateJointMatrix(Vec4* matrixPalette)
{
    {
	static Mat4 t;
	//得到需要的矩阵
	Mat4::multiply(_world, getInverseBindPose(), &t);
	//将矩阵最后一行去掉,得到4 * 3的Vec4向量数组
	 matrixPalette[0].set(t.m[0], t.m[4], t.m[8], t.m[12]);
	 matrixPalette[1].set(t.m[1], t.m[5], t.m[9], t.m[13]);
	 matrixPalette[2].set(t.m[2], t.m[6], t.m[10], t.m[14]);
    }
}

updateJointMatrix函数主要是告诉读者,模型的骨骼在运动时都是通过矩阵运算得到的,骨骼数量越多,它们消耗CPU越多,因此为了优化骨骼运算,现在的处理方式都是将运算移到GPU中计算。

下面构建渲染指令,传递矩阵数据变换到GPU中进行计算,把骨骼动画处理的Shader文件给读者展示如下:

vec4 getPosition()
{
	//对该顶点产生作用的第一块骨骼所占比重
	float blendWeight = a_blendWeight[0];
	
	int matrixIndex = int (a_blendIndex[0]) * 3;
	//对传递进来的matrixPalette矩阵乘以骨骼的权重
	vec4 matrixPalette1 = u_matrixPalette[matrixIndex] * blendWeight;
	vec4 matrixPalette2 = u_matrixPalette[matrixIndex + 1] * blendWeight;
	vec4 matrixPalette3 = u_matrixPalette[matrixIndex + 2] * blendWeight;


    	blendWeight = a_blendWeight[1];
if (blendWeight >0.0)
{
		//若还有别的骨骼对该顶点产生影响,则进行混合
        matrixIndex = int(a_blendIndex[1]) * 3;
        matrixPalette1 += u_matrixPalette[matrixIndex] * blendWeight;
        matrixPalette2 += u_matrixPalette[matrixIndex + 1] * blendWeight;
        matrixPalette3 += u_matrixPalette[matrixIndex + 2] * blendWeight;

        blendWeight = a_blendWeight[2];
	if (blendWeight >0.0)
        {
            matrixIndex = int(a_blendIndex[2]) * 3;
            matrixPalette1 += u_matrixPalette[matrixIndex] * blendWeight;
            matrixPalette2 += u_matrixPalette[matrixIndex + 1] * blendWeight;
            matrixPalette3 += u_matrixPalette[matrixIndex + 2] * blendWeight;

            blendWeight = a_blendWeight[3];
	    if (blendWeight >0.0)
            {
                matrixIndex = int(a_blendIndex[3]) * 3;
                matrixPalette1 += u_matrixPalette[matrixIndex] * blendWeight;
                matrixPalette2 += u_matrixPalette[matrixIndex + 1] * blendWeight;
                matrixPalette3 += u_matrixPalette[matrixIndex + 2] * blendWeight;
            }
        }
    }

    vec4 _skinnedPosition;
    vec4 postion = vec4(a_position, 1.0);
    //使用这个混合后的矩阵,对顶点进行变换,得到该顶点在模型坐标系下的坐标
    _skinnedPosition.x = dot(postion, matrixPalette1);
    _skinnedPosition.y = dot(postion, matrixPalette2);
    _skinnedPosition.z = dot(postion, matrixPalette3);
    _skinnedPosition.w = postion.w;

	return _skinnedPosition;
}
void main()
{
	//得到顶点在模型坐标系下的坐标
	vec4 position = getPosition();
	//使用MVP矩阵进行变换的到最终坐标
	gl_Position = CC_MVPMatrix * position;

    	TextureCoordOut = a_texCoord;
   	TextureCoordOut.y = 1.0 - TextureCoordOut.y;
}

骨骼动画在GPU中根据骨骼的权重进行矩阵运算,这样也可以保证骨骼动画与动画之间的过渡是平滑的,保证了不同的骨骼动画之间的切换是顺畅的。在美术制作模型时,要按照一定的规则制作,从而保证导出的模型可以在Cocos2d-x引擎中加载,接下来给读者介绍关于模型的制作。


以上所述就是小编给大家介绍的《Cocos2d-x 3.x 图形学渲染系列二十五》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Java核心技术·卷 I(原书第10版)

Java核心技术·卷 I(原书第10版)

[美] 凯.S.霍斯特曼(Cay S. Horstmann) / 周立新 等 / 机械工业出版社 / 2016-9 / CNY 119.00

Java领域最有影响力和价值的著作之一,由拥有20多年教学与研究经验的资深Java技术专家撰写(获Jolt大奖),与《Java编程思想》齐名,10余年全球畅销不衰,广受好评。第10版根据Java SE 8全面更新,同时修正了第9版中的不足,系统全面讲解了Java语言的核 心概念、语法、重要特性和开发方法,包含大量案例,实践性强。 一直以来,《Java核心技术》都被认为是面向高级程序员的经典教......一起来看看 《Java核心技术·卷 I(原书第10版)》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具