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驱动方式。

 

还是上个图吧,不上图感觉没有真像。 虽然图看不出来什么动作

image

为了说明它真的在动,不得不上第二张。

 

image 

 

在此,十分感谢Super Tux Kart. 提供了一个学习和扩展irrlicht的榜样.

posted @ 2013-03-26 00:12  麒麟子MrKylin  阅读(656)  评论(1编辑  收藏  举报