计时和动画

要正确实现动画效果,我们就必须记录时间,尤其是要精确测量动画帧之间的时间间隔。当帧速率高时,帧之间的时间间隔就会很短;所以,我们需要一个高精确度计时器。

1. 性能计时器
我们使用性能计时器(或性能计数器)来实现精确的时间测量。为了使用用于查询性能计时器的Win32函数,我们必须在代码中添加包含语句“#include

__int64 currTime;
QueryPerformanceCounter((LARGE_INTEGER*)&currTime);

注意,该函数通过它的参数返回当前时间值,该参数是一个64位整数。我们使用QueryPerformanceFrequency函数来获取性能计时器的频率(每秒的计数次数):

__int64 countsPerSec;
QueryPerformanceFrequency((LARGE_INTEGER*)&countsPerSec);

而每次计数的时间长度等于频率的倒数(这个值很小,它只是百分之几秒或者千分之几秒):

mSecondsPerCount = 1.0 / (double)countsPerSec;

这样,要把一个时间读数valueInCounts转换为秒,我们只需要将它乘以转换因子 mSecondsPerCount:

valueInSecs = valueInCounts * mSecondsPerCount;

由QueryPerformanceCounter函数返回的值本身不是非常有用。我们使用QueryPerformanceCounter函数的主要目的是为了获取两次调用之间的时间差——在执行一段代码之前记下当前时间,在该段代码结束之后再获取一次当前时间,然后计算两者之间的差值。也就是,我们总是查看两个时间戳之间的相对差,而不是由性能计数器返回的实际值。下面的代码更好地说明了这一概念:

__int64 A = 0;
QueryPerformanceCounter((LARGE_INTEGER*)&A);
/* Do work */
__int64 B = 0;
QueryPerformanceCounter((LARGE_INTEGER*)&B);

这样我们就可以知道执行这段代码所要花费的计数时间为(B−A),或者以秒表示的时间为(B−A)*mSecondsPerCount。

注意:MSDN指出当使用QueryPerformanceCounter函数时,有以下注意事项:“在多处理器计算机中,任何一个处理器单独调用该函数都不会出现问题。但是,由于基础输入/输出系统(BIOS)或硬件抽象层(HAL)存在技术瓶颈,所以你在不同的处理器上调用该函数会得到不同的结果”。你可以使用SetThreadAffinityMask函数让主应用程序线程只运行在一个处理器上,不在处理器之间进行切换。

2. 游戏计时器类
在下面的两节中,我们将讨论GameTimer类的实现。

class GameTimer
{
public:GameTimer();float TotalTime()const;  // 单位为秒float DeltaTime()const; // 单位为秒void Reset(); // 消息循环前调用void Start(); // 取消暂停时调用void Stop();  // 暂停时调用void Tick();  // 每帧调用private:double mSecondsPerCount;double mDeltaTime;__int64 mBaseTime;__int64 mPausedTime;__int64 mStopTime;__int64 mPrevTime;__int64 mCurrTime;bool mStopped;
};

需要特别注意的是,构造函数查询了性能计数器的频率。

GameTimer::GameTimer()
: mSecondsPerCount(0.0), mDeltaTime(-1.0), mBaseTime(0),mPausedTime(0), mPrevTime(0), mCurrTime(0), mStopped(false)
{__int64 countsPerSec;QueryPerformanceFrequency((LARGE_INTEGER*)&countsPerSec);mSecondsPerCount = 1.0 / (double)countsPerSec;
}

3. 帧之间的时间间隔
当渲染动画帧时,我们必须知道帧之间的时间间隔,以使我们根据逝去的时间长度来更新游戏中的物体。我们可以采用以下步骤来计算帧之间的时间间隔:设ti为第i帧时性能计数器返回的时间值,设ti-1为前一帧时性能计数器返回的时间值,那么两帧之间的时间差为Δt = ti – ti-1。对于实时渲染来说,我们至少要达到每秒30帧的频率才能得到比较平滑的动画效果(我们一般可以达到更高的频率);所以,Δt = ti – ti-1通常是一个非常小的值。

下面的代码示范了Δt的计算过程:

void GameTimer::Tick()
{if( mStopped ){mDeltaTime = 0.0;return;}__int64 currTime;QueryPerformanceCounter((LARGE_INTEGER*)&currTime);mCurrTime = currTime;// 当前帧和上一帧之间的时间差mDeltaTime = (mCurrTime - mPrevTime)*mSecondsPerCount;// 为计算下一帧做准备mPrevTime = mCurrTime;// 确保不为负值。DXSDK中的CDXUTTimer提到:如果处理器进入了节电模式// 或切换到另一个处理器,mDeltaTime会变为负值。if(mDeltaTime < 0.0){mDeltaTime = 0.0;}
}float GameTimer::getDeltaTime() const
{return (float)mDeltaTime;
}

函数Tick在应用程序消息循环中的调用如下:

int D3DApp::Run()
{MSG msg = {0};mTimer.Reset();while(msg.message != WM_QUIT){// 如果接收到Window消息,则处理这些消息if(PeekMessage( &msg, 0, 0, 0, PM_REMOVE )){TranslateMessage( &msg );DispatchMessage( &msg );}// 否则,则运行动画/游戏else{  mTimer.Tick();if( !mAppPaused ){CalculateFrameStats();UpdateScene(mTimer.DeltaTime());   DrawScene();}else{Sleep(100);}}}return (int)msg.wParam;
}

通过这一方式,每帧都会计算出一个Δt并将它传送给UpdateScene方法,根据当前帧与前一帧之间的时间间隔来更新场景。下面是Reset方法的实现代码:

void GameTimer::Reset()
{__int64 currTime;QueryPerformanceCounter((LARGE_INTEGER*)&currTime);mBaseTime = currTime;mPrevTime = currTime;mStopTime = 0;mStopped  = false;
}

我们可以看到,当调用Reset方法时,mPrevTime被初始化为当前时间。这一点非常重要,因为对于动画的第一帧来说,没有前面的那一帧,也就是说没有前面的时间戳。所以个值必须在消息循环开始之前初始化。

4. 游戏时间
另一个需要测量的时间是从应用程序开始运行时起经过的时间总量,其中不包括暂停时间;我们将这一时间称为游戏时间(game time)。下面的情景说明了游戏时间的用途。假设玩家有300秒的时间来完成一个关卡。当关卡开始时,我们会获取时间tstart,它是从应用程序开始运行时起经过的时间总量。当关卡开始后,我们不断地将tstart与总时间t进行比较。如果t – tstart >300(如图4.8所示),就说明玩家在关卡中的用时超过了300秒,输掉了这一关。很明显,在一情景中我们不希望计算游戏的暂停时间。


(计算从关卡开始时起的时间。注意,我们将应用程序的开始时间作为原点(0),测量相对于这个时间原点的时间值。)

游戏时间的另一个用途是通过时间函数来驱动动画运行。例如,我们希望一个灯光在时间函数的驱动下环绕着场景中的一个圆形轨道运动。灯光位置可由以下参数方程描述:

x = 10 cost

y = 20

z = 10 sint

这里t表示时间,随着t(时间)的增加,灯光的位置会发生改变,使灯光在平面y = 20上围绕着半径为10的圆形轨道运动。对于这种类型的动画,我们也不希望计算游戏的暂停时间。


(如果我们在t1时暂停,在t2时取消暂停,并计算暂停时间,那么当我们取消暂停时,灯光的位置会从p(t1) 突然跳到p(t2)。)

我们使用以下变量来实现游戏计时:

__int64 mBaseTime;
__int64 mPausedTime;
__int64 mStopTime;

当调用Reset方法时,mBaseTime会被初始化为当前时间。我们可以把它视为从应用程序开始运行时起经过的时间总量。在多数情况下,你只会在消息循环开始之前调用一次Reset,之后不会再调用个方法,因为mBaseTime在应用程序的整个运行周期中保持不变。变量mPausedTime用于累计游戏的暂停时间。我们必须累计这一时间,以使我们从总的运行时间中减去暂停时间。当计时器停止时(或者说,当暂停时),mStopTime会帮我们记录暂停时间。

GameTimer类包含两个重要的方法Stop和Start,它们分别在应用程序暂停和取消暂停时调用,让GameTimer记录暂停时间。代码中的注释解释了这两个方法的实现思路。

void GameTimer::Stop()
{// 如果正处在暂停状态,则略过下面的操作if( !mStopped ){__int64 currTime;QueryPerformanceCounter((LARGE_INTEGER*)&currTime);// 记录暂停的时间,并设置表示暂停状态的标志mStopTime = currTime;mStopped  = true;}
}void GameTimer::Start()
{__int64 startTime;QueryPerformanceCounter((LARGE_INTEGER*)&startTime);// 累加暂停与开始之间流逝的时间////                     |<-------d------->|// ----*---------------*-----------------*------------> time//  mBaseTime       mStopTime        startTime    // 如果仍处在暂停状态if( mStopped ){// 则累加暂停时间mPausedTime += (startTime - mStopTime);// 因为我们重新开始计时,因此mPrevTime的值就不正确了,// 要将它重置为当前时间mPrevTime = startTime;// 取消暂停状态mStopTime = 0;     mStopped  = false;}
}

最后,成员函数TotalTime返回了自调用Reset之后经过的时间总量,其中不包括暂停时间。它的代码实现如下:

// 返回自调用Reset()方法之后的总时间,不包含暂停时间
float GameTimer::TotalTime()const
{// 如果处在暂停状态,则无需包含自暂停开始之后的时间。// 此外,如果我们之前已经有过暂停,则mStopTime - mBaseTime会包含暂停时间, 我们不想包含这个暂停时间,// 因此还要减去暂停时间: ////                     |<--paused time-->|// ----*---------------*-----------------*------------*------------*------> time//  mBaseTime       mStopTime        startTime     mStopTime    mCurrTimeif( mStopped ){return (float)(((mStopTime - mPausedTime)-mBaseTime)*mSecondsPerCount);}// mCurrTime - mBaseTime包含暂停时间,而我们不想包含暂停时间,// 因此我们从mCurrTime需要减去mPausedTime:////  (mCurrTime - mPausedTime) - mBaseTime////                     |<--paused time-->|// ----*---------------*-----------------*------------*------> time//  mBaseTime       mStopTime        startTime     mCurrTimeelse{return (float)(((mCurrTime-mPausedTime)-mBaseTime)*mSecondsPerCount);}
}

注意:我们的演示框架创建了一个GameTimer实例用于计算应用程序开始后的总时间和两帧之间的时间;你也可以创建额外的实例作为通用的秒表使用。例如,当点着一个炸弹时,你可以启动一个新的GameTimer,当TotalTime达到5秒时,你可以引发一个事件让炸弹爆炸。

5. 程序示例Demo完整项目源代码下载
http://download.csdn.net/detail/sinat_24229853/9144309

DirectX11 计时和动画相关推荐

  1. PPT怎样删除指定的排练计时和动画方案

    如何删除ppt中的排练计时? 在PPT的菜单栏里点"幻灯片放映",在下拉菜单里点"设置放映方式"在窗口里找"换片方式"(有好几个用框框起来的 ...

  2. iOS 动画之CoreAnimation(CALayer)

    CoreAnimation基本介绍 CoreAnimation动画位于iOS框架的Media层 CoreAnimation动画实现需要添加QuartzCore.Framework CoreAnimat ...

  3. 前端每日实战:164# 视频演示如何用原生 JS 创作一个数独训练小游戏(内含 4 个视频)...

    效果预览 按下右侧的"点击预览"按钮可以在当前页面预览,点击链接可以全屏预览. https://codepen.io/comehope/pen/mQYobz 可交互视频 此视频是可 ...

  4. A级计算机考试试题,计算机等级考试一级试题及答案-计算机a级试题

    计算机等级考试一级试题及答案-计算机a级试题 最全计算机一级考试试题汇 A 第一部分:单选题(每小题1分 共30分) 注意:打开你考试文件夹中的EXCEL工作簿文件"单选题答题卡A.XLS& ...

  5. 计算机一级及wps试题,2016计算机一级WPS考试试题及答案

    2016计算机一级WPS考试试题及答案 下面是CN人才网小编整理的计算机一级考前测试题,欢迎大家前来参考. 1.下面(B)视图最适合移动.复制幻灯片. A)普通 B)幻灯片浏览 C)备注页 D)大纲 ...

  6. 计算机wps基础知识试题,计算机一级WPS考试试题及答案

    计算机一级WPS考试试题及答案 全国计算机等级考试是社会考试,就考试性质而言,它是一种重视应试人员对计算机和软件的实际运用能力的考试.考试分为四个等级,不同等级的内容不同,一级主要考核微型计算机基础知 ...

  7. 计算机一级考试题组成,计算机一级考试试题汇总

    计算机一级考试试题汇总 2017年9月计算机一级考试将于9月23日-26日进行,为帮助考生们复习备考,以下是百分网小编搜索整理的一份计算机一级考试试题汇总. A 第一部分:单选题(每小题1分共30分) ...

  8. 二级备考:ppt自学知识笔记--基础操作1:ppt界面介绍

    目录 界面整体预览 顺序:上到下,从左到右 快速访问工具栏 选项卡 功能区 缩略图 编辑区 状态栏查看幻灯片页数 切换视图 缩放比例​编辑​​​​​​​​​​​​​​ 选项卡考点的逐个介绍 文件选项卡 ...

  9. 计算机一级考试选网络题广东,广东计算机一级考试试题及答案

    广东计算机一级考试试题及答案 全国计算机等级考试是社会考试,就考试性质而言,它是一种重视应试人员对计算机和软件的实际运用能力的考试.考试分为四个等级,不同等级的内容不同,一级主要考核微型计算机基础知识 ...

  10. 广东2018年6月计算机一级试题,2018年9月计算机等级考试一级试题及答案.doc

    最全计算机一级考试试题汇 A 第一部分:单选题(每小题1分 共30分) 注意:打开你考试文件夹中的EXCEL工作簿文件"单选题答题卡A.XLS",将下列选择题的答案填入其中所指定的 ...

最新文章

  1. 3780mysql_MySQL添加外键Foreign Keys出错,报错[HY000][3780]
  2. 配置交换机端口聚合(思科)
  3. 远程安装oracle 10.2.1 for redhat 5.0 2.6.18-53.el5xen【转】
  4. 名额有限 | 邀你奔赴一场与太极图形开发者的约会
  5. Mybatis 学习日记(1)
  6. 【Python面试】 说说Python中xrange和range的区别?
  7. MQTT.fx连接aliyun阿里云的方法
  8. 关于OpenCV的Mat画图问题
  9. 利用支持MicroPython的TPYBoard开发板自制PM2.5检测仪(萝卜教育学科式编程)
  10. AI独角兽云从科技:用人机协同战略,跨AI工程的楚河汉界
  11. 【西窗】2019杭州交通限行规定(最新地图详情)
  12. DB2 执行SQL报错: DB2 SQL Error: SQLCODE=-1585, SQLSTATE=54048
  13. 微信公众号之分享接口
  14. HashMap初始容量指定规则
  15. 08_基于IP的伪装
  16. 3月18日短线黑马牛股公开验证
  17. Eclipse中使用search功能,搜索内容无法多窗口打开
  18. 填坑之PHP的yield和协程在一起的日子里
  19. 如何解决opencv与许多第三方库的冲突
  20. Python: 使用max()获取列表中重复出现次数最多的元素

热门文章

  1. VS 用户自定义控件未出现在工具箱的解决方案
  2. Python与数据库之学员管理系统
  3. 英语单词APP开发功能需求
  4. 今天手把手教你做一个Python版的迷宫小游戏
  5. 计算机图形学(六)-光栅化、采样、走样与反走样、滤波与卷积
  6. linux marvell 网卡驱动,坑爹的marvell linux 网卡驱动
  7. Debian(Linux) 安装Windows通用字体(可解决TimesNewRoman等字体的报错)
  8. 南京信息工程大学计算机考研资料汇总
  9. driver其他常用的方法
  10. 传奇服务器如何修改地图和刷怪,传奇如何将怪物刷在指定地图?