這個東西很順利,僅用了半小時就找到了方法,最應該感謝的還是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驅動方式。
還是上個圖吧,不上圖感覺沒有真像。 雖然圖看不出來什么動作
為了說明它真的在動,不得不上第二張。
在此,十分感謝Super Tux Kart. 提供了一個學習和擴展irrlicht的榜樣.