Direct3D11学习:(四)计时和动画
转载请注明出处:http://www.cnblogs.com/Ray1024
一、概述
接触过游戏开发的人都知道,在游戏中,计时器是一个非常重要的工具,用来精确地控制游戏帧数和动画的播放。要正确实现动画效果,我们就必须记录时间,尤其是要精确测量动画帧之间的时间间隔。当帧速率高时,帧之间的时间间隔就会很短;所以,我们需要一个高精度的游戏计时器。
在我们D3D11的学习过程中,会经常用到计时器,因此设计一个具备基本功能的方便使用的计时器类很有必要。我们现在使用一个篇幅来介绍一个简单游戏计时器的实现。
二、计时和动画
2.1 系统高精度计时器
我们使用系统高精度计时器来实现时间的精确测量。为了使用用于查询系统高精度计时器的Win32函数,我们必须在代码中添加包含语句“#include<windows.h>”。
高精度计时器采用的时间单位称为计数(count)。我们使用QueryPerformanceCounter函数来获取以计数测量的当前时间值:
__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。
2.2 游戏计时器类
下面是游戏计时器类的代码示例:
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 查询高精度计时器的频率
我们在构造函数中来查询系统高精度计时器的频率。代码如下:
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的计算过程:
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函数在应用程序消息循环中调用,代码如下:
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方法的实现代码:
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用于计算应用程序开始后的总时间和两帧之间的时间;在游戏中,你也可以创建额外的实例作为通用的秒表使用。
一个简单高精度计时器的实现就完成了。我们在之后的学习中,就可以直接使用这个计时器类了。
Direct3D11学习:(四)计时和动画的更多相关文章
- Direct3D11学习:(八)Effects介绍
转载请注明出处:http://www.cnblogs.com/Ray1024 一.概述 Effects框架是一组用于管理着色器程序和渲染状态的工具代码.例如,你可能会使用不同的effect绘制水.云. ...
- Direct3D11学习:(五)演示程序框架
转载请注明出处:http://www.cnblogs.com/Ray1024 一.概述 在此系列最开始的文章Direct3D11学习:(一)开发环境配置中,我们运行了一个例子BoxDemo,看过这个例 ...
- TweenMax动画库学习(四)
目录 TweenMax动画库学习(一) TweenMax动画库学习(二) TweenMax动画库学习(三) Tw ...
- SVG 学习<四> 基础API
目录 SVG 学习<一>基础图形及线段 SVG 学习<二>进阶 SVG世界,视野,视窗 stroke属性 svg分组 SVG 学习<三>渐变 SVG 学习<四 ...
- day 83 Vue学习四之过滤器、钩子函数、路由、全家桶等
Vue学习四之过滤器.钩子函数.路由.全家桶等 本节目录 一 vue过滤器 二 生命周期的钩子函数 三 vue的全家桶 四 xxx 五 xxx 六 xxx 七 xxx 八 xxx 一 Vue的过滤 ...
- Direct3D11学习:(七)绘图基础——彩色立方体的绘制
转载请注明出处:http://www.cnblogs.com/Ray1024 一.概述 在前面的几篇文章中,我们详细介绍了Direct3D渲染所需要的数学基础和渲染管道理论知识.从这篇文章开始,我们就 ...
- 四、cocos2dx动画Animation介绍
qinning199原创,欢迎转载,转载请注明:http://www.cocos2dx.net/?p=22 一.帧动画 你可以通过一系列图片文件,像如下这样,创建一个动画: CCAnimation * ...
- Android JNI学习(四)——JNI的常用方法的中文API
本系列文章如下: Android JNI(一)——NDK与JNI基础 Android JNI学习(二)——实战JNI之“hello world” Android JNI学习(三)——Java与Nati ...
- SCARA——OpenGL入门学习四(颜色)
OpenGL入门学习[四] 本次学习的是颜色的选择.终于要走出黑白的世界了~~ OpenGL支持两种颜色模式:一种是RGBA,一种是颜色索引模式. 无论哪种颜色模式,计算机都必须为每一个像素保存一些数 ...
随机推荐
- Entity Framework一对多关系添加数据的两种方式
当使用Entity Framework添加一对多关系数据的时候,通常先添加一的数据,然后再添加多的数据.类似这样: //添加一的数据 var category = new Category{Name= ...
- 一个purge参数引发的惨案——从线上hbase数据被删事故说起
在写这篇blog前,我的心情久久不能平静,虽然明白运维工作如履薄冰,但没有料到这么一个细小的疏漏会带来如此严重的灾难.这是一起其他公司误用puppet参数引发的事故,而且这个参数我也曾被“坑过”. ...
- Maven 简单配置gpg
1. 下载maven到指定目录,指定对应的gpg的执行命令所需要的属性.这里比如下载解压后的maven目录是: C:\maven-apache-3.3.2 ,那么配置文件目录是: C:\maven-a ...
- 【原】MyEclipse8.5集成Tomcat7时启动错误:Exception in thread “main” java.lang.NoClassDefFoundError
解决方法: MyEclipse->Window->Preferences->MyEclipse->Servers->Tomcat->Tomcat 6.x->L ...
- liunx 套接字编程(Linux_C++)
网络中的进程是如何通信的? 在网络中进程之间进行通信的时候,那么每个通信的进程必须知道它要和哪个计算机上的哪个进程通信.否则通信无从谈起!在本地可以通过进程PID来唯一标识一个进程,但是在网络中这是行 ...
- jQuery Mobile 移动开发中的日期插件Mobiscroll使用说明
近期在移动方面的开发,使用jQuery Mobile ,移动方面的插件不如Web 方面的插件多,选择的更少,有一些需要自己去封装,但功力尚不足啊. 日期插件JQM也提供了内置的,但样式方面不好看,只好 ...
- dapper 操作类封装
using System; using System.Collections.Generic; using System.Data; using System.Data.SQLite; using S ...
- 解决cxf+spring发布的webservice,types,portType和message以import方式导入
用cxf+spring发布了webservice,发现生成的wsdl的types,message和portType都以import的方式导入的.. 原因:命名空间问题 我想要生成的wsdl在同个文件中 ...
- JVM内存溢出及合理配置
Tomcat本身不能直接在计算机上运行,需要依赖于硬件基础之上的操作系统和一个Java虚拟机.Tomcat的内存溢出本质就是JVM内存溢出,所以在本文开始时,应该先对Java JVM有关内存方面的知识 ...
- spotify engineering culture part 1
原文 ,因为原视频说的太快太长, 又没有字幕,于是借助youtube,把原文听&打出来了. 中文版日后有时间再翻译. one of the big succeess factors here ...