最近嘗試把3dmax的physique骨骼系統(tǒng)導(dǎo)出插件重構(gòu)成了skin的方式,用了用skin感覺相比physique要強(qiáng)大的多,skin是max最老的蒙皮修改器,應(yīng)該比physique還要早把,但后續(xù)版本升級(jí)做的很強(qiáng)大,據(jù)說(shuō)和maya的方法差不多,physique修改器更新緩慢,而且用了一下確實(shí)是skin的修改器要好用一些,尤其對(duì)于蒙皮人物骨骼按部件進(jìn)行拆分方面,skin方式要方便很多,實(shí)現(xiàn)換裝系統(tǒng)也不是問(wèn)題,我最近正在實(shí)現(xiàn)一套比較好的部件裝配式的換裝系統(tǒng),大體的方法是把人物拆分,按需要進(jìn)行骨骼部件的組裝,比如手,腳,頭發(fā),身體,裙擺,都可以是獨(dú)立的骨架部件,然后組裝在一起,很多游戲其實(shí)都有這樣的功能,實(shí)現(xiàn)方法大同小異吧,不過(guò)我這里有點(diǎn)心得可以分享一下,人物的骨骼導(dǎo)出的時(shí)候不要圖方便只導(dǎo)出每塊骨頭的世界矩陣,而應(yīng)該導(dǎo)出這塊骨頭相對(duì)父骨節(jié)的矩陣,形成一顆顆子樹,這樣做在單副骨架上看似乎沒有什么大的優(yōu)勢(shì),還會(huì)帶來(lái)額外的計(jì)算量,但實(shí)際上要實(shí)現(xiàn)換裝,比如一個(gè)部件從一個(gè)形體直接配置到另外一個(gè)形體上的時(shí)候,優(yōu)勢(shì)就體現(xiàn)出來(lái)了,真的必須這么干啊,我想以后做一些外界作用力的物理效果的時(shí)候,父骨架的偏移影響到子骨架的計(jì)算,或反向IK計(jì)算,應(yīng)該也容易計(jì)算了(比如自由墜落的布娃系統(tǒng))當(dāng)然這是后話了,現(xiàn)在多做點(diǎn)這樣工作,以后擴(kuò)展起來(lái)會(huì)容易很多
另外有個(gè)心得可以和大家分享一下,那就是關(guān)于骨骼矩陣的導(dǎo)出冗余數(shù)據(jù)的精簡(jiǎn)方法、其實(shí)做過(guò)蒙皮的人應(yīng)該會(huì)知道,一套完整的骨骼動(dòng)畫的數(shù)據(jù)量最大的并不是頂點(diǎn)的數(shù)據(jù),那個(gè)數(shù)量基本固定的,不會(huì)隨動(dòng)作的增長(zhǎng)而變大,真正龐大的是骨骼的關(guān)鍵幀導(dǎo)出數(shù)據(jù)
來(lái)個(gè)簡(jiǎn)單的計(jì)算,如果一個(gè)蒙皮角色的總骨骼有100根,1000幀的動(dòng)畫
那么占用的空間= 100 * 1000 * sizeof(D3DXMATRIX) = 100 *1000 * 64Bytes 差不多占了6MB多的容量,一般一個(gè)角色的動(dòng)畫多達(dá)幾千到上萬(wàn)幀的,那么這個(gè)數(shù)量的增長(zhǎng)是很龐大的,也許你會(huì)覺得這幾MB到10多MB的數(shù)據(jù)量不算什么,現(xiàn)在內(nèi)存不都是幾個(gè)G了嗎?但你要想想,現(xiàn)在游戲卡的現(xiàn)象不在于你cpu多塊,內(nèi)存多大,很大部分愿意是磁盤io讀取慢了,這才是瓶頸,這些年計(jì)算機(jī)的速度是提升了很多倍可就是硬盤的讀寫速度沒什么變化啊,同屏幾十個(gè)不同的角色,如果不預(yù)加載,用實(shí)時(shí)加載,那么一加載起來(lái)動(dòng)不動(dòng)就是幾十MB的數(shù)據(jù),不管什么機(jī)器,再怎么多線程優(yōu)化也一樣卡,即使單機(jī)都會(huì)卡
所以需要想辦法來(lái)壓縮精簡(jiǎn)這些數(shù)據(jù),其實(shí)壓縮的思路并不復(fù)雜,我們的骨骼矩陣一般都用的是線性差值計(jì)算的,max在打上關(guān)鍵幀的時(shí)候也基本上是線性差值的,這樣就好辦了,線性差值的數(shù)據(jù)過(guò)渡一般都有一個(gè)特點(diǎn),那就是比較“平滑”,很多數(shù)據(jù)變化幅度不大的情況下前一幀和后一幀的矩陣平均值剛好等于當(dāng)前幀的矩陣值,就利用這個(gè)特性我們就能過(guò)濾掉相當(dāng)大數(shù)量級(jí)的矩陣了
以下的算法針對(duì)于連續(xù)線性變換的數(shù)據(jù)精簡(jiǎn)壓縮都是有用的,不僅僅只針對(duì)于矩陣,我在下面的例子里面用的是整數(shù),思路清楚以后換成矩陣就好了
#include "stdafx.h"
#include <WTypes.h>
#include <vector>
#include <map>
#include <assert.h>
using namespace std;
struct Idinfo
{
int id; //原數(shù)據(jù)索引
int id0; //等比區(qū)間索引上界索引
int id1; //等比區(qū)間索引下界索引
BOOL GetValue(map<int,int> & imap, int& val)
{
if(id == id0 && id == id1)
{
map<int, int>::iterator it0 = imap.find(id0);
assert(it0 != imap.end());
val = it0->second;
return TRUE;
}
else if(id > id0 && id < id1)
{
map<int, int>::iterator it0 = imap.find(id0);
map<int, int>::iterator it1 = imap.find(id1);
assert(it0 != imap.end());
assert(it1 != imap.end());
int v0 = it0->second;
int v1 = it1->second;
val = v0 + ((v1 - v0) / (id1 - id0)) * (id - id0);
return TRUE;
}
return FALSE;
}
};
int _tmain(int argc, _TCHAR* argv[])
{
vector<int> arr; //假設(shè)這里面放的就是線性變換的數(shù)據(jù)
arr.push_back(2);
arr.push_back(4);
arr.push_back(6);
arr.push_back(8);
arr.push_back(15);
arr.push_back(16);
arr.push_back(17);
arr.push_back(18);
arr.push_back(19);
arr.push_back(20);
map<int,int,less<int>> imap; //把非等比變化的數(shù)據(jù)導(dǎo)出(自動(dòng)按原索引排序的)
int sz = (int)arr.size();
for(int i = 0; i < sz; ++i)
{
if(i == 0 || i == sz - 1)
{
imap.insert(pair<int, int>(i, arr[i])); //頭尾不過(guò)濾,一定要保留的
}
else
{
if(arr[i] != (arr[i - 1] + arr[i + 1]) / 2) //過(guò)濾掉前后等比的數(shù)據(jù),
//提示一下,如果是浮點(diǎn)數(shù)建議不要這樣比較,浮點(diǎn)數(shù)有誤差的,建議有個(gè)0.0001的容差,視情況而定
{
imap.insert(pair<int, int>(i, arr[i]));
}
}
}
vector<Idinfo> vecIds; //計(jì)算每個(gè)數(shù)據(jù)的索引描述
for(int i = 0; i < sz; ++i)
{
map<int,int>::iterator it = imap.find(i);
BOOL _lowBoundFind = FALSE;
BOOL _highBoneFind = FALSE;
Idinfo idInfo;
idInfo.id = i;
for(it = imap.begin();it != imap.end(); ++it)
{
int id = it->first;
if(i == id)
{
idInfo.id0 = id;
idInfo.id1 = id;
_lowBoundFind = TRUE;
_highBoneFind = TRUE;
}
if(i > id)
{
idInfo.id0 = id;
_lowBoundFind = TRUE;
}
if(i < id)
{
idInfo.id1 = id;
_highBoneFind = TRUE;
}
if(_lowBoundFind && _highBoneFind)
{
vecIds.push_back(idInfo);
break;
}
}
}
//檢驗(yàn)一下能否把原線性隊(duì)列的數(shù)據(jù)完全還原出來(lái)
for(vector<Idinfo>::iterator it = vecIds.begin(); it < vecIds.end(); ++it)
{
Idinfo & idInfo = *it;
int id = 0;
if(idInfo.GetValue(imap, id))
{
printf("%d \r\n", id);
}
}
return 0;
}
//可以看到,我們實(shí)際導(dǎo)出的是imap就夠了,vecIds可以計(jì)算出來(lái)的,也就是說(shuō)只需要imap就能確定arr集合的每一個(gè)元素了
上面的例子可以看到10個(gè)元素“壓縮”成了4個(gè)元素,數(shù)據(jù)變化越平滑,壓縮的數(shù)據(jù)量將會(huì)越大