今天终于解决了动画抖动的问题
首先描述下动画抖动的表现:就是将人物拉近到基本充满屏幕的时候,运动比较快的肢端明显运动不平滑。
一开始怀疑动画代码写得有问题,仔细检查代码后没有发现问题。再次怀疑浮点运算误差造成的,因为骨骼动画中,骨骼的运算,是靠从跟骨骼,一级一级级联运算过来的,在肢端级联次数是最多的。更改编译选项,从原来的最快的浮点运算改为最精确的浮点运算。问题没有解决。然后通过在同一帧里,逐次增加10ms绘制50次,表现出来的连续轨迹间距均匀。遂否定了骨骼动画代码的问题。
然后为了找出问题,收集了每帧之间的时间间隔,并用立方图表现出来,发现在2D环境和3D环境下差别较大。2D环境较为平稳,3D环境下波动较大。于是,考虑是不是垂直同步造成的,在网上搜索,居然有一大堆关于如何调节显卡参数解决游戏中画面抖动问题的。于是,思路被转到考虑显卡的参数或BUG上去了,折腾两天,无果。想到自己机器上装有WoW,遂进入WoW,选择自己NB的战士号,在人物选择界面观察了半天,发现WoW里,动画是平滑的。否定了是显卡的BUG和创建设备的参数的问题。
同时,谢Boss也在查这个问题,他告诉了我一个让我觉得萝卜都靠不住的消息:timeGetTime()的误差在15ms左右。测试代码如下:
DWORD dwLastTime = timeGetTime();
for(;;)
{
Sleep(0);
DWORD dwTime = timeGetTime();
if(dwTime - dwLastTime > 1)
TRACE("timeGetTime = %u/n",dwTime - dwLastTime);
dwLastTime = dwTime;
}
居然输出一大堆15,16出来。当时的心情......要知道,之前因为使用QueryPerformanceCounter在AMD的双核CPU上也出错——现在我在Windows上居然找不到一个可以信任的取时间函数了!
直觉告诉我不应该是这样的,平时我们听音乐看电影,并没有出现由于时间错误导致的问题,而timeGetTime就是多媒体使用的函数。再者,我不希望Microsoft倒掉——我只会在Microsoft的Windows下写程序混饭吃。AMD倒掉了,我还有Intel;ATI倒掉了,我还有NVidia——Microsoft倒掉了,我可没有Linux。
MSDN永远是Windows程序员最好的老师,大约30秒后,我证明了萝卜还是可靠的:
timeBeginPeriod(1);
DWORD dwLastTime = timeGetTime();
for(;;)
{
Sleep(0);
DWORD dwTime = timeGetTime();
if(dwTime - dwLastTime > 1)
TRACE("timeGetTime = %u/n",dwTime - dwLastTime);
dwLastTime = dwTime;
}
timeEndPeriod(1);
看来,问题就在之前没有使用timeBeginPeriod(1);而导致timeGetTime()的误差过大,使得帧之间的时间间隔误差过大,导致动画抖动的。带着这个思路,把谢Boss的消息循环的代码拿过来看了看,发现些细节上的问题,同时,利用timeSetEvent写了一个靠得住的休眠函数:
static void XSleep(DWORD dwDelay,HANDLE hEvent)
{
MMRESULT hTimer = timeSetEvent(dwDelay,1,(LPTIMECALLBACK)hEvent,0,TIME_ONESHOT | TIME_CALLBACK_EVENT_SET);
MsgWaitForMultipleObjectsEx(1,&hEvent,INFINITE,QS_ALLINPUT,0); //当有Windows消息时,还能继续处理Windows消息。故选择了这个函数。
timeKillEvent(hTimer);
}
整个消息循环代码如下:
MSG msg;
DWORD dwLastTime;
HANDLE hSleepEvent = CreateEvent(NULL,FALSE,FALSE,NULL); timeBeginPeriod(1);
dwLastTime = timeGetTime();
while(isActive())
{
//需要一直处理Windows消息到无消息处理为止
for(;PeekMessage(&msg,NULL,0,0,PM_REMOVE);)
{
if(msg.message == WM_QUIT)
{
CloseHandle(hSleepEvent);
timeEndPeriod(1);
return ;
}
if(!TranslateAccelerator(msg.hwnd,hAccelTable,&msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
} DWORD FrameDelay = max(1,1000/max(1,GetMaxFPS()));
DWORD dwTime = timeGetTime();
if(dwLastTime + FrameDelay > dwTime)
{
XSleep(dwLastTime + FrameDelay - dwTime,hSleepEvent);
}
else
{
update();
dwLastTime += ((dwTime - dwLastTime) / FrameDelay) * FrameDelay; //当实际帧数严重低于预期帧数时,这段代码可以完成跳帧功能;当实际帧数大于等于预期帧数时,这段代码仍然可以使帧之间的时间间隔固定。之前谢Boss没有处理好的主要就是这个。
}
} CloseHandle(hSleepEvent);
timeEndPeriod(1);
这样修改以后,帧之间的时间间隔误差基本在正负1ms之间了。动画抖动问题迎刃而解。