irrlicht引擎:硬件蒙皮骨骼动画
这个东西很顺利,仅用了半小时就找到了方法,最应该感谢的还是Super TuxKart(简称STK,下面就都用这三字母了). 如果不明白STK,同时又对它感兴趣的童鞋,可以访问这里
http://supertuxkart.sourceforge.net/
由于墙的原因,需要各位搭梯子。
上周末,在弄换装的时候,发现irrlicht引擎本身是不支持硬件蒙皮的,多少令人有些失望。 心里就一直寻思着怎么扩展一下,将它弄出来。
值得说明的是STK对irrlicht引擎的用法是很简单的,基本上可以说是裸用,并未在irrlicht接口上做修改。 而是对外进行了一些必要的扩展。
当然,STK也对外开放了一个irrlicht.dll,说是修改了其中的BUG。 但直接使用irrlicht是可以的。
废话不多说,来说说如何不修改irrlicht一行代码,通过外部扩展来实现硬件骨骼动画吧
首先,能够使我们不修改irrlicht代码的原因,是因为ISkinnedMesh提供了一个setHardwareSkinning接口,默认为false.
虽然这个接口的说明是"(This feature is not implemented in irrlicht yet)”,但并不代表,设置与不设置无差别。
查看代码可以发现,当你设置了这个为true以后,irrlicht就完全不管你的动画了。 意思就是,要是你非要让我干我不干不了的事,那就只有您另请高明了。
irrlicht连CPU计算都不会参与。 这正好让我们有机可乘,完全用GPU接管。
而要让一个顶点参与骨骼计算,那骨骼索引则是少不了的。所以,我们需要想办法让顶点数据能够将骨骼索引代入SHADER中。
在STK中用了一种巧妙的方法, 就是使用了顶点的颜色数据, 虽然这样一来,顶点颜色就用不了了。 但在模型渲染时,顶点颜色很少被使用到的。 也就是说,顶点颜色在STK的动画模型中,被用作了骨骼索引。
初始化骨骼索引的方法很简单,用下面的代码遍历即可。
设:我们有一个骨骼动画模型是 ISkinnedMesh* pSkinnedMesh = …
那么:初始化代码如下
for(u32 i = 0;i < pSkinnedMesh ->getMeshBuffers().size();++i) { for(u32 g = 0;g < pSkinnedMesh ->getMeshBuffers()[i]->getVertexCount();++g) { pSkinnedMesh ->getMeshBuffers()[i]->getVertex(g)->Color = video::SColor(0,0,0,0); } }
//初始化完毕以后,就是需要真正的索引赋值了,通过以下代码可以完成
const core::array<scene::ISkinnedMesh::SJoint*>& joints = pSkinnedMesh ->getAllJoints(); for(u32 i = 0;i < joints.size();++i) { const core::array<scene::ISkinnedMesh::SWeight>& weights = joints[i]->Weights; for(u32 j = 0;j < weights.size();++j) { int buffId = weights[j].buffer_id; int vertexId = pSkinedMesh->getAllJoints()[i]->Weights[j].vertex_id; video::SColor* vColor = &pSkinedMesh->getMeshBuffers()[buffId]->getVertex(vertexId)->Color; if(vColor->getRed() == 0) vColor->setRed(i + 1); else if(vColor->getGreen() == 0) vColor->setGreen(i + 1); else if(vColor->getBlue() == 0) vColor->setBlue(i + 1); else if(vColor->getAlpha() == 0) vColor->setAlpha(i + 1); } }
//经过以上两个步骤,顶点数据改造完成。 值得注意的是, 在这里, 索引 0 是被认为是无效的
然后,我们来创建一个SHADER作为渲染。
假设 我们将这个pSkinnedMesh绑定了到了一个IAnimatedSceneNode* node 上。
那,我们为这个结点创建一个材质 在创建材质前,我们需要准备一个SHADER回调。 SHADER回调就像下面一样就可以了。
class HWSkinCallBack:public video::IShaderConstantSetCallBack { scene::IAnimatedMeshSceneNode* m_pNode; public: HWSkinCallBack(scene::IAnimatedMeshSceneNode* node):m_pNode(node) { } virtual void OnSetConstants(video::IMaterialRendererServices* services, s32 userData) { scene::ISkinnedMesh* mesh = (scene::ISkinnedMesh*)m_pNode->getMesh(); f32 joints_data[55 * 16]; int copyIncrement = 0; const core::array<scene::ISkinnedMesh::SJoint*> joints = mesh->getAllJoints(); for(u32 i = 0;i < joints.size();++i) { core::matrix4 joint_vertex_pull(core::matrix4::EM4CONST_NOTHING); joint_vertex_pull.setbyproduct(joints[i]->GlobalAnimatedMatrix, joints[i]->GlobalInversedMatrix); f32* pointer = joints_data + copyIncrement; for(int i = 0;i < 16;++i) *pointer++ = joint_vertex_pull[i]; copyIncrement += 16; } services->setVertexShaderConstant("JointTransform", joints_data, mesh->getAllJoints().size() * 16); } };
好了,现在我们来创建一个材质
s32 hwskm = gpu->addHighLevelShaderMaterialFromFiles( "http://www.cnblogs.com/skinning.vert","main",video::EVST_VS_2_0, "","main",video::EPST_PS_2_0,&hwc,video::EMT_SOLID); //用新创建出来的材质赋值给这个结点 node->setMaterialType((video::E_MATERIAL_TYPE)hwskm ); //到此,设置完毕。 //最后,就是skinning.vert本身的内容了。 贴出来即可,没有太多技巧,就是一个普通的蒙皮。 // skinning.vert #define MAX_JOINT_NUM 36 #define MAX_LIGHT_NUM 8 uniform mat4 JointTransform[MAX_JOINT_NUM]; void main() { int index; vec4 ecPos; vec3 normal; vec3 light_dir; float n_dot_l; float dist; mat4 ModelTransform = gl_ModelViewProjectionMatrix; index = int(gl_Color.r * 255.99); mat4 vertTran = JointTransform[index - 1]; index = int(gl_Color.g * 255.99); if(index > 0) vertTran += JointTransform[index - 1]; index = int(gl_Color.b * 255.99); if(index > 0) vertTran += JointTransform[index - 1]; index = int(gl_Color.a * 255.99); if(index > 0) vertTran += JointTransform[index - 1]; ecPos = gl_ModelViewMatrix * vertTran * gl_Vertex; normal = normalize(gl_NormalMatrix * mat3(vertTran) * gl_Normal); gl_FrontColor = vec4(0,0,0,0); for(int i = 0;i < MAX_LIGHT_NUM;i++) { light_dir = vec3(gl_LightSource[i].position-ecPos); n_dot_l = max(dot(normal, normalize(light_dir)), 0.0); dist = length(light_dir); n_dot_l *= 1.0 / (gl_LightSource[0].constantAttenuation + gl_LightSource[0].linearAttenuation * dist); gl_FrontColor += gl_LightSource[i].diffuse * n_dot_l; } gl_FrontColor = clamp(gl_FrontColor,0.3,1.0); ModelTransform *= vertTran; gl_Position = ModelTransform * gl_Vertex; gl_TexCoord[0] = gl_MultiTexCoord0; gl_TexCoord[1] = gl_MultiTexCoord1; /* // Reflections. vec3 r = reflect( ecPos.xyz , normal ); float m = 2.0 * sqrt( r.x*r.x + r.y*r.y + (r.z+1.0)*(r.z+1.0) ); gl_TexCoord[1].s = r.x/m + 0.5; gl_TexCoord[1].t = r.y/m + 0.5; */ } //注:这是GLSL 2.0, 在用IRR做测试的时候,要选GL驱动方式。
还是上个图吧,不上图感觉没有真像。 虽然图看不出来什么动作
为了说明它真的在动,不得不上第二张。
在此,十分感谢Super Tux Kart. 提供了一个学习和扩展irrlicht的榜样.
作者:麒麟子
出处:http://www.cnblogs.com/qilinzi/
蛮牛专栏:麒麟子
简介:麒麟子,编程15年,科技创始人,技术作家。
09年进入游戏行业,16年创立成都幼麟科技有限公司。十年从业经验练就了游戏全栈技能,目前专注于手机游戏领域。
版权声明:本文版权归作者和博客园共有,欢迎转载。转载必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。