zoukankan      html  css  js  c++  java
  • 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的榜样.

    作者:麒麟子
    出处:http://www.cnblogs.com/qilinzi/
    蛮牛专栏:麒麟子
    简介:麒麟子,编程15年,科技创始人,技术作家。
    09年进入游戏行业,16年创立成都幼麟科技有限公司。十年从业经验练就了游戏全栈技能,目前专注于手机游戏领域。
    版权声明:本文版权归作者和博客园共有,欢迎转载。转载必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

  • 相关阅读:
    关于Blog的思考
    程序员应知——简单就是美
    关于知识分享和微软TechEd Roadshow
    在网络上营销你自己——兼《口碑》书评
    《与孩子一起学编程》书评
    两个要素:人和思考——《软件人才管理的艺术》书评
    程序员应知——也说重构
    《精通Android 2》书评
    oracle利用正则表达式提取字符串中的数字
    oracle 身份证校验函数
  • 原文地址:https://www.cnblogs.com/qilinzi/p/2981741.html
Copyright © 2011-2022 走看看