深度探索DxFramework 3-1

请尊重原作者的工作,转载时请务必注明转载自:www.xionggf.com 第3章 步入二维游戏的世界 1 3.1 二维动画的原理 在介绍计算机动画的原理之前,先谈谈大家都非常熟悉的电影的播放原理。播放电影的时候需要一大盘的电影胶片。胶片上面就是一帧一帧连续的静止图像。当播放的时候,把胶片按播放顺序,依次通过播放机的镜头,这样胶片上的图像将会逐帧投影到银幕上。因为人眼存在着视觉暂留的生理现象。当眼睛正在观察的物体突然消失的时候,被观察的物体还能在人眼中存留约1/24秒左右。因此,只要一系列本来就是连续的静止的图像,当以合适的速度逐个通过人眼的时候。便会产生连续的“会动”的视觉效果。 根据人的视觉存留时间可以知道,当电影胶片以每秒24帧的速度播放时,人们看到的就是和现实生活中速度相同的画面。当电影胶片以每秒大于24帧的速度播放时,画面中的各种动作将会变快,这就是俗称的“快镜头”。而如果电影胶片以每秒低于24帧的速度播放时,画面中的各种动作将会变慢,这就是俗称的“慢镜头”。 计算机动画的原理和播放电影的原理是类似的。举一个简单的二维动画例子:假如要做一个人物走动的二维动画,首先要研究人走路时身体各部位,如躯干、四肢移动摆动的位置。然后按相应时间相应的位置,把这些连续动作绘制成一副副的图。在计算机中就可以存储在一些图片文件中。这些“一副副的图”其实就是类似于电影胶片。把图从磁盘文件中载入到内存,然后按顺序将其逐帧在屏幕中绘制出来,就类似于播放电影的过程。当一组动作播放完成后,再从头从动作的第一帧播放,周而复始,就形成了连贯的动画。 三维动画和二维动画播放原理是一致的,也是一帧一帧地在屏幕中绘制。与二维动画类似,三维动画也可以分实时和非实时。我们玩的三维计算机游戏的游戏画面其实就可以认为是一种实时三维动画,这些画面都是在每一帧显示前由计算机实时地计算生成。非实时三维动画则用相关软件预先逐帧计算好并且存储在文件中,播放的时候从文件中逐帧读取数据显示即可。在本章中先讨论二维动画。 3.2 基础数学工具 接着介绍是vector2.h文件中的模板类和函数。VECTOR2定义了一个二维矢量的模板类。 #pragma once #include "globals.h" #include "error.h" //最小误差,意思是两浮点数之差为小于0.0001的时候, //就认为这两浮点数是相等的。 const XFLOAT VECTOR2_ZERO_TOLERANCE = 0.0001; /* MSVC6 cannot use the overloaded operators due to a compiler bug/feature */ #if _MSC_VER < 1300 #ifndef NO_TEMPLATE_SELECTION #define NO_TEMPLATE_SELECTION #endif #endif template< class T > class VECTOR2 { public: VECTOR2() {} VECTOR2(const T& x, const T& y) : x(x), y(y) {} //复制Direct3D的D3DXVECTOR2类型数据的内容 VECTOR2(const D3DXVECTOR2& v) : x((T)v.

深度探索DxFramework 2-2

请尊重原作者的工作,转载时请务必注明转载自:www.xionggf.com 第2章 DxFramework的架构剖析 2 2.3 DxFramework的内存泄漏检测机制 内存泄漏(memory leak)是使用C/C++等程序设计语言进行编程时常常见到的一个问题。内存泄漏给程序运行时带来的影响有很多。一般最后的结果要么是程序死掉;要么是提示系统内存不足。还有一种可能就是程序不会死掉,但是程序的响应速度会明显变慢。 2.3.1 内存泄漏的原因 产生内存泄漏的原因一般是如下几点: 动态分配了内存空间,在不需要时忘记释放。 动态分配了内存空间,在不需要时忘记释放。 对某些API函数的不正确使用,导致内存泄漏。 在编程时忘记释放不需要的内存空间是编程时很常见。动态分配的内存在使用完毕后,如若不需要了就一定要释放。如果不释放,那就会造成内存的泄漏。如果造成内存泄漏的代码经常被调用的话,那么内存泄漏的数目就会越来越多的。从而影响整个系统的运行。 如下面的代码段: void SomeOperation() { int* pBuffer = new int[32]; for( int i=0; i<32 ; ++i) { pBuffer[i] = i*2+1; } } 很明显,上述的局部变量pTemp指向了一段从堆(heap)空间中分配的内存空间,而在函数退出时没有作任何的释放操作。一旦调用此函数就将导致了内存泄漏。当这个函数被频繁调用的时候,堆空间的内存空间将会被消耗殆尽。 因为代码编写的问题,会导致某些动态分配的内存根本就无从回收,比如下面的代码段: void SomeOperation() { int* pBuffer1 = new int[32]; int* pBuffer2 = new int[32]; pBuffer1 = pBuffer2; if( pBuffer1 ) { delete [] pBuffer1; } } 这样,pBuffer1最初指向的那段内存空间的首地址就丢掉了,无法恢复了。这时候最初分配的那一段内存空间就无法释放掉。 Windows提供了一些特殊的API,如FormatMessage。如果给它参数传递的函数有FORMAT_MESSAGE_ALLOCATE_BUFFER,它会在函数内部动态分配一块内存缓冲区,返回给用户。但是这个内存缓冲区需要用户显式调用LocalFree来释放。如果用户忘了的话。也会产生内存泄漏。如下面的代码段: #include <windows.h> #include <iostream> using namespace std; void FormatAndPrintOneMessage(DWORD dwError) { HLOCAL hLocal = NULL; // 用来存储返回的描述错误信息的字符串的内存缓冲区 BOOL fOK = FormatMessage( // 指定标志,动态分配一块缓冲区 FORMAT_MESSAGE_FROM_SYSTEM |FORMAT_MESSAGE_ALLOCATE_BUFFER , NULL , dwError , MAKELANGID(LANG_ENGLISH,SUBLANG_ENGLISH_US), // 返回动态分配的内存缓冲区 reinterpret_cast<LPTSTR>(&hLocal), 0 , NULL ); if( hLocal && fOK ) { cout<<"Error Message: "<<(reinterpret_cast<LPCTSTR> (LocalLock(hLocal)))<<endl; // 如果忘记了这一句,将会导致通过FormatMessage函数动态分配的 // 内存无法释放,从而导致内存泄漏 LocalFree(hLocal); } else { cout<<"Error number not found"<<endl; } } void main() { FormatAndPrintOneMessage(5); } 检查已经发布的程序是否存在内存泄漏实在是费时费力,所以我们要把内存泄漏扼杀在萌芽状态。在编码过程中就要时刻进行检查。

深度探索DxFramework 2-1

请尊重原作者的工作,转载时请务必注明转载自:www.xionggf.com 第2章 DxFramework的架构剖析 1 2.1 DxFramework的全局头文件 在开始着手分析DxFramework之前,我们有必要先弄懂DxFramework所定义的一些在其引擎代码内部中频繁使用的一些数据类型,宏,和全局函数。 众所周知,由于C++语言的灵活性和自由性;在不同的操作系统平台下,不同的编译器对同一关键字所指定的数据类型所占字节大小是不一致的——C++标准委员会对此也并没有做统一的要求。以int类型为例,C++标准委员会只是要求此类型的大小至少不小于16位。例如在Microsoft Win32平台下,Visual C++6编译器所实现的int是32位整形数据。而在DOS系统下,Borland C++ 3.1实现的int则只有16位。因此,为了代码的平台移植,或者是便于版本的数据类型升级和扩充,一般的大型软件系统都有其自定义的数据类型。以Microsoft定义的win32数据类型为例,就有诸如无符号双字类型,32位的DWORD;无符号单字类型,十六位的WORD;无符号的整形数据,UINT等等。DxFramework也不例外,它也有着它自身的预定义数据类型,它们定义在globals.h文件中,现在我们来逐个分析。代码如下: //globals.h #define WIN32_LEAN_AND_MEAN #define DIRECTINPUT_VERSION 0x0800 //指定使用的DInput版本 /* 忽略编号为4786的编译器警告 当变量标识符名字过长(超过255个字符)的时候,调试器无法调试变量标 识符号超过255个字符长度的代码。无法在调试器中查看、计算、更新或监视被截断的符号。调试器在调试过程中 将会把变量标识符“截短”至255个字符。有时在嵌套声明的变量名中,会出现一串长的警告,在使用STL 的时候, 这种警告会经常出现,使用此指令可以关掉此警告 */ #pragma warning(disable: 4786) //用来进行内存侦测的起到开关作用的宏 #ifdef _DEBUG #define _CRTDBG_MAP_ALLOC //定义此宏,使得CRT内存泄漏侦测函数可用,此宏在crtdbg.h中定义 #define _INC_MALLOC #define enw DEBUG_NEW #endif //定义使用UNICODE的宏 #define UNICODE //如果宏UNCODE定义了,则把宏_UNICODE也定义 #ifdef UNICODE #define _UNICODE #endif #define USE_DOUBLE_FLOAT_PRECISION /* 如果定义了USE_DOUBLE_FLOAT_PRECISION宏 XVECTOR2定义为用双精度浮点数类型实例化模板类 类型的DVECTOR2替代为,XFLOAT定义为如果没有定义的话,就把XVECTOR2定义为单精度浮点数 实例化的模板类类型FVECTOR2,XFLOAT定义为floatFVECTOR2,DVECTOR2类型在vector2.h中定义, 稍后详述 */ #ifdef USE_DOUBLE_FLOAT_PRECISION #define XVECTOR2 DVECTOR2 #define XFLOAT double #else #define XVECTOR2 FVECTOR2 #define XFLOAT float #endif //预包含engine所要使用的系统头文件 #include <tchar.

深度探索DxFramework 1

请尊重原作者的工作,转载时请务必注明转载自:www.xionggf.com 第1章 DxFramework工程简介 1.1 工程的来源和作者 DxFramework是由Corey于年月发起的。Corey 开发了项目中最初的2D游戏引擎。并且负责了游戏所需的艺术资源的相关工作。他创建了DxFramework的第一个版本。Corey现在在Maxis公司工作,不再是DxFramework的主要的开发者了。而随后加入的Jonathan现在已经是项目管理员。他在Laird教授的领导下参与了程序设计的很多方面的工作。还有一位重要的贡献者是David Yeung,作为DxFramework的重要组成部分的3D Framework和相关示例demo的最原始的版本正是由David Yeung所编写的。还有就是Nuttapong,Nuttapong在David Yeung之后对3D Framework部分做了大量的扩充,增加了许多的功能以及一些demo和sample。 另外还对DxFramework作出贡献的人员还有: Adam Tercala创建了world map demo的最初版本 Jeremy Lee创建了sound demo(DEMO C)的最初版本,并且撰写了C_Sound类 Evan Leung创建了最初的用户接口类(User Interface Object class C_UIObject)以及一系列继承自此类的其他类。 1.2 工程的配置 本书剖析的版本是0.93版本,这个版本是2004月8月16日发布的。也是在本书截稿为止的最新版本。在DxFramework的主页中所发布的是三个Zip格式的压缩包。它们分别是: DxFramework-binary-0.9.3.zip DxFramework-engine-0.9.3.zip DxFramework-0.9.3.zip DxFramework-binary-0.9.3.zip只是包含了DxFramework的所发布的可执行数据文件版本。没有相关的源代码。而DxFramework-engine-0.9.3.zip也只是包含了DxFramework的引擎框架代码,而没有示例的代码以及运行所需要的相关文件和数据。而DxFramework-0.9.3则两者兼备。如果是利用DxFramework来编写我们自己的游戏,那么当然选择基于DxFramework-engine-0.9.3,但是本书是通过剖析引擎和示例代码来学习游戏设计技术的,因此本书的剖析对象是DxFramework-0.9.3.zip的内容。 下载解压缩之根目录有如下的一些文件和目录,目录如下: 目录 内容 Data 存放示例demo"pic pac"所需的地图数据文件 Docs 存放DxFramework相关的文档 Images 存放DxFramework所需的一些艺术资源,比如一些示例程序所需要的图片等等 Resources 存放DxFramework所用到的图标文件,里面是big.ico和small.ico文件 Sounds 存放DxFramework使用到的一些声音文件,有wav,mp3,mid格式 Source 存放了DxFramework的引擎和示例的原代码,这里面的内容便是本书的重点部分 Frustum 点击鼠标将能绘制该视截体的轮廓,这样可以移开camera到别的位置去看看这个视截体像什么。悬浮在空中的球,当鼠标点击的时候,它会依据它在视截体内与否来决定是否改变其纹理。 Models Models Demo装载了MD2格式的模型和X格式的模型。MD2的动画可以循环播放 Seek and Hide 追踪和躲藏,演示一个简单的AI游戏 Simple Demo 本demo演示了如何使用DxFramework来开发一个3D游戏。这个demo的代码有很多的注释和演示。 Ray Tracking 这个demo演示了如何使用鼠标来点击选中不同的DxFramework图元。鼠标点击点将会变成一个会延伸扩展的球的球心。 LandScape and Billboard 本demo展示一个通过高度图生成的风景地貌。另外还展现了公告板技术。 Split 演示如何把一个基本图元切割成更小的基本图元 MD3 Demo 演示了使用ID Software定义了md3文件格式的模型 BSP 演示了使用二叉空间分割树(binary space partitation tree)对场景数据进行组织 文件如下:

深度探索DxFramework 0

请尊重原作者的工作,转载时请务必注明转载自:www.xionggf.com #前言 这系列文章的面世,既可说是偶然,也可谓是必然。 在本世纪初的几年,中国的网络游戏业如同蛰伏着等待破茧而出的蝶蛹一样。看似平静,其实内部正蕴藏着巨大的商机和市场。那时我还在南京的校园里,在计算机前编织着游戏梦。作为一名理工科的学生,当时的我对日后如雨后春笋般的网络游戏所含的巨大利润没有太多的敏锐嗅觉。当时只是为了心目中的一种理想,一种爱好去编程,去学习设计自己的游戏。 无法忘记那段激情燃烧的岁月。在上大学之前没有接触过计算机的我,为了弥补先天的不足。在那一段时间里如同海绵吸水般从图书馆,从网络中吸取游戏设计相关的知识。也正是在那一段时间里,知道了享誉全球的John Carmack;知道了FreeMind、云风、softboy等一批国内的先行者;知道了他们在求知过程中所经历的酸甜苦辣。那时候网络才刚刚普及,费用奇高。我在学习过程中也时常感叹资料的匮乏和不系统性。四年的学习生涯很快过去,离开校园的我,顺理成章地进入了这个行业。 有谁能理解在一个身处起步阶段的游戏产业的业界人,从学校进入社会时所经过的各种各样的挫折,困惑和磨练呢?为了提升自我,程序员们往往需要不断地学习,学习,再学习。互联网的普及给程序员提升自我的水平带来了更大的便利和更广泛的知识来源。在一次偶然的机会,其实也是一种必然,互联网上的DxFramework项目引起了我的注意。通过阅读这个项目的源代码,我学到了很多游戏开发的知识,也写了很多阅读笔记和心得文档。然而因为工作的繁忙和需要,我也要花很多业余时间去从其他的开源项目中去学习,种种的原因使得我对DxFramework的学习也是时断时续,但也总算是一直都坚持了下来。 人生中总是有这样那样的挫折。工作的变动,使得我有机会去专心整理梳顺DxFramework的知识内容。也是一个可称为必然的偶然的机会,我萌生了对DxFramework项目写一本书的想法。相对充足的时间允许我去实现我的想法。作为一个对自己知识和经验的总结;也作为一种知识的传承吸纳;便诞生了这一系列文章。 这本书不是一本DirectX开发入门教程,也不是一本C++程序设计语言教程;更不是一本数学入门教程。本书的定位是:通过剖析一个完整的,开放源代码的游戏应用程序框架(game application framework),以及使用这框架开发的一些实例。从而掌握游戏程序设计基础知识和设计思维。 在阅读本书之前,或在阅读本书的同时,读者应首先对C++程序设计语言;Microsoft DirectX开发包;以及和3D图形程序设计相关的数学知识有一个大致的了解。“大致的”的了解意味着读者能基本理解Direct3D的工作方式,以及相关的基础知识。比如读者应该能对诸如“顶点”,“纹理”等基础概念有一个大概的认识。当然,DirectX/Direct3D是一个很大的体系,读者不可能也不必要事无巨细地穷经皓首面面俱到。要强调的是,尽管本书不是一本C++语言教程和DirectX教程,但是在剖析代码时所涉及到的C++语言,Direct3D,乃至3D数学的知识点,本书都将力求讲细讲透。 对于有一定C++程序设计语言和Direct3D基础知识,对游戏开发有浓厚兴趣,跃跃欲试想登堂入室的读者。本书将会是你很好的选择。

深度探索Skinned Mesh【翻译】

请尊重原作者的工作,转载时请务必注明转载自:www.xionggf.com 译注 我在学习蒙皮骨骼动画的技术实现时,阅读了大量的中英文文档。但绝大部分文档,要么是语焉不详过度抽象,并且流于纸上谈兵。要么就是翻译得实在让人难以理解。Frank Luna的这篇文章是我所阅读过的最棒的蒙皮骨骼动画论文。Frank不仅有生花妙笔,把一些难以理解的技术要点以深入浅出的方式说得清清楚楚,而且还有着很强的实作能力,给文章配上了对应的示例程序。让读者能深入到每一个实现细节中。因此,我把Frank的这篇文章翻译出来,以供大家交流。欢迎批评指正。 前言 在大量的3D仿真程序,尤其是3D计算机游戏中扮演了一个重要的角色。本论文描述了用以驱动一个现代的实时的角色动画系统所需的数据结构和算法。另外,本文还对D3DX9.0c动画系统的进行了详细的研究。 本文第1章描述了用来表征3D角色及其运动的数据结构。第2章聚焦在用来描述一个动画序列(animation sequence)的数据集。第3章考察了基于刚体(rigid body)的动画技术,并且强调了使用该技术的问题所在。第4章解析一个新的动画技术:顶点混合(vertex blending),也通常称为蒙皮动画(skinned mesh animation)。该技术可以避免刚体动画带来的问题。第5章则展示了如何通过使用D3DX动画相关的API去实现一个角色蒙皮动画。第6章展示了如何使用多个不同的动画序列。第7章则演示了通过使用D3DX动画混合(animation blending)相关的API,如何用既有的动画,产生新的动画。最后,第8章则解释了通过使用D3DX的动画回调(animation callback)相关的API,来并行执行代码。 1 角色网格层级概述 图1展现了一个角色网格(character mesh)。途中高亮的链接成条的骨块(chain of bones)被称之为一个骨架(skeleton)。骨骼动画系统就自然而然地以该骨架结构为实现底层,构架而成,骨架外部被一个皮肤(skin)所环绕。在此,“皮肤”将被建模成一个三维的几何体(也即顶点及其构成的多边形)。和真实世界中一样,骨架中的每一个骨块将会影响皮肤的形状和位置。。从数学的观点来说,这些骨块通过变换矩阵(transformation matrix)的方式来表征的。这些变换矩阵可以适当地变换皮肤的几何体。因而,当我们操纵骨架运动时,附着在该骨架的皮肤也会随着当前骨架的姿势(pose)来运动。 图1:角色网格,高亮的骨块链条【bone chain】表示了角色的骨架【skeleton】黑色的多边形则表示了角色的皮肤 1.1 骨块及其继承式的变换 首先,所有的骨块都初始布局在骨块空间(bone space)中。骨块B有两个关联的变换。第一个是其局部变换(local transform),称之为L,另一个是组合变换(combined transform),称之为C。局部变换负责在骨块空间中,让骨块B绕着它的关节(joint)旋转(如图2左图所示)。如果骨块B的关节还连接到另一个称为B的父骨块(parent bone)的骨块时。局部变换L还负责了骨块B相对其父骨块的平移量(如图2右图所示) 图2: a图中的骨块在骨块空间中,绕着它的环枢关节【pivot joint】旋转,b图中的骨块发生偏移,为其父骨块腾出了空位 与局部变换形成对照,组合变换则负责对构成角色骨架的骨块进行摆姿势(posing)的操作,如图3所示。也就是说。组合变换将把一个骨块,从它自身的骨块空间,变换到角色空间(character space)中去。。因而,组合变换就是用来操作皮肤的在角色空间中的位置和形状的。 图3: “组合变换”把骨块从骨块空间变换到角色空间,在本图中,在骨块中间的骨块,变成了角色的右上臂的骨块 我们该如何确定组合变换。这个确定过程并不是很直截了当。因为骨块之间并不是相互独立的,而是相互之间会影响其位置。暂时先不考虑旋转,首先考虑如图4所示的一个手臂的骨架。如下图: 图4: 图中所示的是一个手臂的骨架。观察如何使用T(v0), T(v1)和T(v2)定位手掌的位置。同样地,观察如何使用T(v0), T(v1)定位前臂,使用T(v0)定位上臂。【实际上T(v0)并不起什么作用,因为上臂已经是根骨块不需要进行平移】 在骨块空间中,给定一个上臂,前臂,手掌的骨块。我们需要找出每个骨块的组合变换,用来将这些骨块定位在图4所示的位置。因为每个骨块的局部变换使得该骨块与其父骨块发生偏移。从图4中我们可以容易看到:一个骨块的位置,首先通过使用(applying)它自身的局部变换,然后使用它的父骨块的局部变换,再使用其父骨块的父骨块的局部变换,依次类推。 图5:层级式变换。观察一个骨块的父骨块的变换,如何影响该骨块和该骨块的子骨块 现在我们看到,处于骨块层级架构中的每个骨块,都从各自的父骨块中继承得到平移量和旋转量。这也就是说,得到一个骨块的组合变换,是通过:首先由作用其自身的局部变换(先平移接着旋转);然后再作用其父骨块的局部变换;再作用其祖父骨块的局部变换,依次类推。一直到作为根节点的那个祖先骨块为止。从数学观点上来说。第i个骨块的组合变换矩阵C i是由以下公式得到: $ C_i = L_i \times P_i $ 其中 $ L_i $ 是编号为第i号的骨块的局部变换矩阵。 $ P_i $ i是编号为第i号的骨块的父骨块的组合变换矩阵。注意,通过做矩阵级乘,得到某骨块的组合变换时,需要先乘以骨块自身的局部变换矩阵,而后再乘以父骨块的组合变换矩阵。 1.2 D3DXFRAME 现在介绍D3DX库提供的用以构建骨骼层次数据的结构体D3DXFRAME。我们将使用这个结构体来表征角色的骨块。通过对一些指针变量进行赋值,我们可以连接起整个骨架中的骨块。如图6中所示的骨架。 图6: 图1所示的角色模型的骨骼层次示意图)。垂直向下的箭头表示下部的骨块是上部的骨块的第一个子骨块。水平向右的箭头表示左右两边的骨块是兄弟关系。 应当承认,在角色动画范畴内的上下文概念中。名字BONE通常就是指D3DXFRAME。但D3DXFRAME是一个通用的数据结构,也可用来描述一个非角色网格层级(non-character mesh hierarchies)的数据。无论如何,只要在角色动画范畴内,我们可以交替使用“bone”和“frame”表示同一个概念。

Direct3D8下载入显示MD2 Model的demo的设计文档

请尊重原作者的工作,转载时请务必注明转载自:www.xionggf.com Model demo主要演示了模型的载入和渲染,本demo所使用的模型是由ID Software所定义的MD2格式,MD2格式在《Quake II》游戏中所被广泛使用。 MD2的文件由两部分组成,一部分是 文件头(fileheader) ;另一部分是 文件数据(filedata) 。文件头包含有很多重要的数据、如:文件数据的大小,文件格式的版本号等等。因此每个文件的文件头的大小都应该是一致的。这也就是为什么我们一般都用一个结构体来描述这些文件头。与之相反的是每个文件的文件数据都不同的。因为文件数据包含了模型的 顶点(vertices)数 , 三角形(triangles)面数 , 纹理映射坐标(texture coordinate) 等等。下图表示了MD2模型文件的大略格式。 因此我们可以使用一个结构体来表示MD2文件头的组织。如下: //MD2的文件头数据,这个数据结构的成员顺序是要严格定义不能调乱的 struct MD2_FileHeader { int m_nIdentify; //识别号 int m_nVersion; //版本号 int m_nSkinWidth; //模型皮肤纹理的宽度 int m_nSkinHeight; //模型皮肤纹理的高度 int m_nFrameSize; //每帧的大小,以字节为单位 int m_nSkinNum; //与模型相关联的皮肤数量 int m_nVertexNum; //模型的顶点数 int m_nUVNum; //纹理坐标的数量 int m_nTriangleNum; //模型中三角形面片的数量 int m_nOpenGLCmdNum; //OpenGL的命令数目 int m_nFrameNum; //帧数目 int m_nSkinsOffset; //皮肤数据在文件中的偏移地址 int m_nTexCoordOffset; //纹理映射坐标数据在文件中的偏移量 int m_nTrianglesOffset; //三角形面片数据在文件中的偏移量 int m_nFrameOffset; //帧数据在文件中的偏移量 int m_nOpenGLCmdOffset; //OpenGL命令在文件中的偏移量 int m_nEndOffset; //文件结束位置在文件中的偏移量 }; 下面是结构体中的各成员变量的具体含义:

C/C++中macro的一些特殊使用

请尊重原作者的工作,转载时请务必注明转载自:www.xionggf.com ISO C++ 推出之后,C/C++ 中宏(macro)的使用一直都备受争议,就好像当年保守诟病的goto语句一样。有所不同的是,现在的C/C++ 代码中使用goto语句的使用率是越来越少了。而macro仍然在C/C++ programming中占据一席之地——例如集赞誉之耀和诋毁之辱于一身的Microsoft Foudation Classes就把macro的强大功能发挥得淋漓尽致。套用“存在着就是合理”,macro的存在必定有其合理的因素。事实上,如果在programming的时候充分发挥macro的机制,就能写出一些巧妙精致而且设计合理的代码。我在工作中阅读了一个引擎的代码,引擎在某处就比较巧妙地利用了macro的功能。在此我将其整理出来,供大家学习交流。 大家在进行C/C++ programming的时候,常常定义一些枚举类型的数据,例如: enum EMessage { MSG_OPEN, MSG_CLOSE, MSG_COPY, MSG_PASTE }; 在某些时候,比如在调试某段程序的时候,使用调试器捕获了某个枚举变量的值,这时候我们所能得到的是一些数值。例如当变量EMessage eMsg = MSG_OPEN的时候,我们能观察到eMsg = 0。但是单单一个“0”的话一下子让人难以明白它的含义,如果想在屏幕上也能打印出枚举的名称就更加不好办了。解决这个问题,方法之一,就是利用macro的强大灵活的机制。现在我们来一步一步地分析如何利用macro来解决上述的问题。 想打印枚举的名称,你首先想到的肯定是:使用一个字典数据结构记录枚举变量和描述字符串的映射关系。例如: #include <map> using namespace std; map<EMessage,const char*> MsgDesc; //为了节省空间,不使用string存储描述信息 MsgDesc[MSG_OPEN] = "MSG_OPEN"; MsgDesc[MSG_CLOSE] = "MSG_CLOSE"; MsgDesc[MSG_COPY] = "MSG_COPY"; MsgDesc[MSG_PASTE] = "MSG_PASTE"; 使用字典数据结构很方便,但是如果枚举值很少的时候,使用一个map类型的对象来管理这些东西似乎有点暴殄天物(或许对于现在的硬件设备而言不算什么,但是如果性能有着严格要求的话。。。)。如果枚举值很多的话,每添加一个新的枚举量就要在枚举定义内写一次,在把枚举量描述添加进字典中又要写一次,尤为繁琐。 如果使用macro机制的话,可以通过预编译和macro机制一次性处理上述的问题。 例如:在文件msgdef.h中 #ifndef _MSGDEF_H_ #define _MSGDEF_H_ #undef USEMACRO #if defined(INCLUDE_AS_ENUM) //判断预编译宏的设置情况,判断是转换成enum还是string #define MSG_MACRO(msg) msg, #elif defined(INCLUDE_AS_STRING) #define MSG_MACRO(msg) #msg, //不能遗忘“,”号 #else #error To use this include file, first define either INCLUDE_AS_ENUM or INCLUDE_AS_STRING #endif //有新定义的message 枚举值的话,就只是需要在此添加 MSG_MACRO(MSG_OPEN) MSG_MACRO(MSG_CLOSE) MSG_MACRO(MSG_COPY) MSG_MACRO(MSG_PASTE) #endif 而使用的时候,就要如此使用,例如在main.

#在Unity3D shader中使用光照贴图数据【翻译】 请尊重原作者的工作,转载时请务必注明转载自:www.xionggf.com 原文地址 在Unity3D中创建一个用来接收光照图数据的shader要比你所想象的要难,这个shader的代码在这里。下面是做这个工作时的要特别注意的三个小贴士: ##1 surface shader是无效(不能用来接收光照图数据)的 要接收光照图数据,首先要确定的是不能使用U3D独有的表面着色器,而是要直接用Cg代码写顶点和片元着色器。即使U3D的ShaderLab语言看起来和标准的Cg语言语法不大一样,但它还是能正常工作。这是因为在U3D里面,surface shader最终还是会展开成Cg代码编写的顶点和片元着色器。而当这些ShaderLab代码被展开的时候,一些必要的光照图变量便不易察觉地自动地“为你而设置了”。因为这个原因,当你试图去访问这些这些变量时,尤其是在代码展开阶段(expansion step),你会得到一个“未定义这些变量”的编译错误。当最后的编译shader代码的时候,你会得到一个“重复定义这些变量”的错误。 ##2 U3D的内置变量unity_LightmapST是一个不合法的变量名字 另一个细节则是你必须使用正确的变量。这些变量是U3D引擎用来传递光照贴图数据的。它们是: sampler2D unity_Lightmap; float4 unity_LightmapST; 第二个变量用来管理光照贴图图集数据(lightmaps atlas data)。它的名字很重要,原因是它的命名规则是不正确的!(译注:Unity3D的bug?)在一般情况下,带有_ST后缀名的变量,将会由Unity根据该变量所绑定的shader property的图集,自动地传递赋值(populate)过去。现在这个变量漏了下划线了,因为一个非常有用的用来进行变换操作的宏,导致这个遗漏成了一个大问题。该宏是在UntiyCG.cginc文件中定义的TRANSFORM_TEX。代码如下: #define TRANSFORM_TEX(tex,name) (tex.xy * name##_ST.xy + name##_ST.zw) 如代码所示,在这个宏里面,变量名是需要加下划线的,所以必须仿照这个宏的定义,去手写一遍,代码如下: o.uv1 = i.texcoord1.xy * unity_LightmapST.xy + unity_LightmapST.zw; UnityCG.cginc文件包含了大量的便于开发的函数,完成这个工作需要用到里面的函数,所以需要在代码中包含这个文件,如下: #include "UnityCG.cginc" ##3 使用Unity3D提供的光照图解码函数 最后一个重要的步骤便是对光照贴图数据进行正确的解码。在不同的平台上解码方式有所不同,但U3D已经在UnityCG.cginc文件中提供了DecodeLightmap函数完成这功能,代码如下: // Decodes lightmaps: // doubleLDR encoded on GLES // RGBM encoded with range [0;8] on other platforms using surface shaders inline fixed3 DecodeLightmap(fixed4 color) { #if defined(SHADER_API_GLES) && defined(SHADER_API_MOBILE) return 2.