麒麟子

~~

导航

<2013年3月>
242526272812
3456789
10111213141516
17181920212223
24252627282930
31123456

统计

常用链接

留言簿(12)

随笔分类

随笔档案

Friends

WebSites

积分与排名

最新随笔

最新评论

阅读排行榜

评论排行榜

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(
        "../../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 on 2013-03-26 00:08 麒麟子 阅读(3131) 评论(0)  编辑 收藏 引用 所属分类: Game and EngineIrrlicht


只有注册用户登录后才能发表评论。
【推荐】超50万行VC++源码: 大型组态工控、电力仿真CAD与GIS源码库
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理