Direct3D11学习:(四)计时和动画
转载请注明出处:http://www.cnblogs.com/Ray1024
一、概述
接触过游戏开发的人都知道,在游戏中,计时器是一个非常重要的工具,用来精确地控制游戏帧数和动画的播放。要正确实现动画效果,我们就必须记录时间,尤其是要精确测量动画帧之间的时间间隔。当帧速率高时,帧之间的时间间隔就会很短;所以,我们需要一个高精度的游戏计时器。
在我们D3D11的学习过程中,会经常用到计时器,因此设计一个具备基本功能的方便使用的计时器类很有必要。我们现在使用一个篇幅来介绍一个简单游戏计时器的实现。
二、计时和动画
2.1 系统高精度计时器
我们使用系统高精度计时器来实现时间的精确测量。为了使用用于查询系统高精度计时器的Win32函数,我们必须在代码中添加包含语句“#include<windows.h>”。
高精度计时器采用的时间单位称为计数(count)。我们使用QueryPerformanceCounter函数来获取以计数测量的当前时间值:
1 2 | __int64 currTime; QueryPerformanceCounter((LARGE_INTEGER*)&currTime); |
注意,该函数通过它的参数返回当前时间值,该参数是一个64位整数。我们使用QueryPerformanceFrequency函数来获取高精度计时器的频率(每秒的计数次数):
1 2 | __int64 countsPerSec; QueryPerformanceFrequency((LARGE_INTEGER*)&countsPerSec); |
而每次计数的时间长度等于频率的倒数(这个值很小,它只是百分之几秒或者千分之几秒):
1 | mSecondsPerCount = 1.0 / ( double )countsPerSec; |
这样,要把一个时间读数valueInCounts转换为秒,我们只需要将它乘以转换因子 mSecondsPerCount:
1 | valueInSecs = valueInCounts * mSecondsPerCount; |
由QueryPerformanceCounter函数返回的值本身不是非常有用。我们使用QueryPerformanceCounter函数的主要目的是为了获取两次调用之间的时间差——在执行一段代码之前记下当前时间,在该段代码结束之后再获取一次当前时间,然后计算两者之间的差值。也就是,我们总是查看两个时间戳之间的相对差,而不是由系统高精度计时器返回的实际值。下面的代码更好地说明了这一概念:
1 2 3 4 5 | __int64 A = 0; QueryPerformanceCounter((LARGE_INTEGER*)&A); /* Do work */ __int64 B = 0; QueryPerformanceCounter((LARGE_INTEGER*)&B); |
这样我们就可以知道执行这段代码所要花费的计数时间为(B−A),或者以秒表示的时间为(B−A)*mSecondsPerCount。
2.2 游戏计时器类
下面是游戏计时器类的代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | class GameTimer { public : GameTimer(); float TotalTime() const ; // 单位为秒 float DeltaTime() const ; // 单位为秒 void Reset(); // 消息循环前调用 void Start(); // 取消暂停时调用 void Stop(); // 暂停时调用 void Tick(); // 每帧调用 private : double m_secondsPerCount; double m_deltaTime; __int64 m_baseTime; __int64 m_pausedTime; __int64 m_stopTime; __int64 m_prevTime; __int64 m_currTime; bool m_stopped; }; |
后面几节中,我们将讨论游戏计时器的实现。
2.3 查询高精度计时器的频率
我们在构造函数中来查询系统高精度计时器的频率。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 | GameTimer::GameTimer() : m_secondsPerCount(0.0) , m_deltaTime(-1.0) , m_baseTime(0) , m_pausedTime(0) , m_prevTime(0) , m_currTime(0) , m_stopped( false ) { __int64 countsPerSec; QueryPerformanceFrequency((LARGE_INTEGER*)&countsPerSec); m_secondsPerCount = 1.0 / ( double )countsPerSec; } |
如上代码中,获取了系统高精度计时器在每秒钟的计时次数countsPerSec,由此,可以得出每两次计时所用的时间(秒)m_secondsPerCount。
2.4 帧之间的时间间隔
当渲染动画帧时,我们必须知道帧之间的时间间隔,以使我们根据逝去的时间长度来更新游戏中的物体。我们可以采用以下步骤来计算帧之间的时间间隔:
时间间隔deltaTime = 当前帧的时间值time1 - 前一帧的时间值time2
对于实时渲染来说,我们至少要达到每秒30帧的频率才能得到比较平滑的动画效果(我们一般可以达到更高的频率);所以时间间隔deltaTime通常是一个非常小的值。
GameTimer::Tick函数中示范了间隔时间deltaTime的计算过程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | void GameTimer::Tick() { if ( m_stopped ) { m_deltaTime = 0.0; return ; } __int64 currTime; QueryPerformanceCounter((LARGE_INTEGER*)&currTime); m_currTime = currTime; // 当前帧和上一帧之间的时间差 m_deltaTime = (m_currTime - m_prevTime)*m_secondsPerCount; // 为计算下一帧做准备 m_prevTime = m_currTime; // 确保不为负值。DXSDK中的CDXUTTimer提到:如果处理器进入了节电模式 // 或切换到另一个处理器,m_deltaTime会变为负值。 if (m_deltaTime < 0.0) { m_deltaTime = 0.0; } } |
Tick函数在应用程序消息循环中调用,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | int D3D11App::Run() { MSG msg = {0}; m_timer.Reset(); while (msg.message != WM_QUIT) { // 如果接收到Window消息,则处理这些消息 if (PeekMessage( &msg, 0, 0, 0, PM_REMOVE )) { TranslateMessage( &msg ); DispatchMessage( &msg ); } // 否则,则运行动画/游戏 else { m_timer.Tick(); if ( !m_appPaused ) { CalculateFrameStats(); UpdateScene(m_timer.DeltaTime()); DrawScene(); } else { Sleep(100); } } } return ( int )msg.wParam; } |
通过这一方式,每帧都会计算出一个deltaTime并将它传送给UpdateScene方法,根据当前帧与前一帧之间的时间间隔来更新场景。
注意,在游戏刚开始时,对于第一帧来说,没有之前的帧了,也就是说没有前面的时间戳。m_prevTime必须在消息循环开始之前初始化。上面消息循环中调用的Reset函数作用就是将m_prevTime被初始化为当前时间。下面是Reset方法的实现代码:
1 2 3 4 5 6 7 8 9 10 | void GameTimer::Reset() { __int64 currTime; QueryPerformanceCounter((LARGE_INTEGER*)&currTime); m_baseTime = currTime; m_prevTime = currTime; m_stopTime = 0; m_stopped = false ; } |
2.5 游戏时间
游戏计时器类的成员函数GameTimer::TotalTime()介绍一下。
功能是获取自从调用Reset之后经过的时间总量,其中不包括暂停时间(即从应用程序开始运行时起经过的时间总量)。我们将这一时间称为游戏时间。游戏时间的用途有两种:限时游戏和通过时间函数来驱动动画执行。
在这里完整代码代码就不贴出了,有兴趣的朋友可以点击此处下载Demo源码,Demo源码是2_D3DTimingAndAnimation文件。
三、结语
我们演示了一个游戏计时器GameTimer用于计算应用程序开始后的总时间和两帧之间的时间;在游戏中,你也可以创建额外的实例作为通用的秒表使用。
一个简单高精度计时器的实现就完成了。我们在之后的学习中,就可以直接使用这个计时器类了。
出处:http://www.cnblogs.com/Ray1024/
版权声明:本文的版权归作者与博客园共有。转载时须注明本文的作者及详细链接,否则作者将保留追究其法律责任。
欢迎大家学习、共享,如果文章中有错误或漏洞,请大家在评论区留言!!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· 展开说说关于C#中ORM框架的用法!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?