17.1 网格模型技术的前生今世
网格模型是一种将物体模型的顶点数据、纹理、材质等信息存储在一个外部文件中的3D 物体模型。对于那些简单的图元描述的图形,比如点、线、三角形等等,我们可以通过写代码指定顶点数据、索引数据、法线向量、纹理和材质等信息。但对于复杂的3D 物体的话, 采用这种方式显然是不现实的。因此,Direct3D 提供了一种称作网格模型的技术,可以从各种特定的文件格式中读取和绘制3D 图形,极大地方便了游戏的开发。使用网格模型最普遍的方式是从外部的3D 模型文件中加载一个网格。而这些3D 模型通常都是由3D 建模软件生成的,比较复杂的网格数据。目前市面上主流的3D 建模软件有3DS Max 和Maya。而目前流行的3D 模型文件格式有.3ds 、.max 、.obj 以及.mb 。其中.3ds 、. max 为3DS Max常用的格式, .mb 为Maya 常用的格式, 而.obj 为3DSMax 和Maya 通用的文件格式。
17.2 认识三维建模软件3DS Max 和Maya
再不厌其烦地说一遍,我们在通常的三维游戏开发中, 常常要涉及到非常复杂的三维物体数据模型,如果用我们之前讲的知识,顶点缓存索引缓存,通过写代码来构造这些三维模型, 显然是不合实际的。
这些复杂物体的模型通常需要利用专业的三维建模软件来制作。目前市面上主流的3D 建模软件有3DS Max 和Maya。这两款在三维建模行业作为竞争对手的软件,目前同为Autodesk 公司所有, 这倒是有些令人匪夷所思。下面我们先来分别介绍一下当前市场上主流的三维建模软件(当然,不仅仅是用于三维建模这么简单) 3DS Max 和Maya。
这些复杂物体的模型通常需要利用专业的三维建模软件来制作。目前市面上主流的3D 建模软件有3DS Max 和Maya。这两款在三维建模行业作为竞争对手的软件,目前同为Autodesk 公司所有, 这倒是有些令人匪夷所思。下面我们先来分别介绍一下当前市场上主流的三维建模软件(当然,不仅仅是用于三维建模这么简单) 3DS Max 和Maya。
1. 3DS Max 软件简介
3D Studio Max , 常简称为3dsMax 或MAX , 是Autodesk 公司开发的基于PC 系统的三维动画渲染和制作软件。其前身是基于DOS 操作系统的3D Studio 系列软件。在Windows NT 出现以前,工业级的CG 制作被SGI 图形工作站所垄断。3D Studio Max + Windows NT 组合的出现一下子降低了CG 制作的门槛,首选开始运用在电脑游戏中的CG动画制作,后更进一步开始参与影视片的特效制作,例如X 战警日, 最后的武士等。
2. Maya 软件简介
Maya 是美国Autodesk 公司出品的世界*的三维动画软件,应用对象是专业的影视广告、角色动画、电影特技等。Maya 功能完善, 工作灵活, 易学易用,制作效率极高,渲染真实感极强,是电影级别的高端制作软件。
3 . Maya 和3DS Max 的区别与联系
Maya 和3DS Max 都是高端3D 软件,两者之间都有很多相同的功能, 像创建模型、渲染材质、动画制作等等。但就运用实际情况而言, 3DS Max 更加适合于游戏、建筑学、室内设计等等, 而Maya 可以说是专门为影视特效而生的一款软件,这也是当初Alias 设计Maya 的意图。Maya 在如角色动画、运动学模拟,以及完美的材质系统, 使得Maya 所创造出来的三维效果如此逼真, 可以说全世界的电影特效工作室都必须用到Maya。
Maya 和3DS Max 都是高端3D 软件,两者之间都有很多相同的功能, 像创建模型、渲染材质、动画制作等等。但就运用实际情况而言, 3DS Max 更加适合于游戏、建筑学、室内设计等等, 而Maya 可以说是专门为影视特效而生的一款软件,这也是当初Alias 设计Maya 的意图。Maya 在如角色动画、运动学模拟,以及完美的材质系统, 使得Maya 所创造出来的三维效果如此逼真, 可以说全世界的电影特效工作室都必须用到Maya。
3DS Max 和Maya 都是功能强大的三维制作软件, 各有很大优点, 但是, 不同的行业用不同的软件会让制作比较轻松,所以说,
3DS Max 和Maya 究竟哪个好,哪个更好用与所从事的行业有着一定联系。
3DS Max 在建筑动画效果上要强于Maya ,而Maya 在影视动画上又略胜于3DS max, 两款软件没有太大的本质性区别,只是看个人掌握哪个软件比较顺手罢了。
目前,业内已开始对软件使用做了一些细分。3DS Max 主要用来做建筑, 国内多数公司喜欢拿他做游戏, 附带着可以做影视动画, 建模比较方便。Maya 主要用来做影视动画, 在动画和动力学模块上很强大,国内不少游戏公司慢慢将3DS Max 转Maya 了。其他的栏目包装,广告等两者都可用到。最终没有谁好谁坏之分,只有用处不同,两软件都是相通的。以前国内学Maya 的不多,因为没有中文版,教程也少,现在不一样了,教程也是不少了。
3DS Max 在建筑动画效果上要强于Maya ,而Maya 在影视动画上又略胜于3DS max, 两款软件没有太大的本质性区别,只是看个人掌握哪个软件比较顺手罢了。
目前,业内已开始对软件使用做了一些细分。3DS Max 主要用来做建筑, 国内多数公司喜欢拿他做游戏, 附带着可以做影视动画, 建模比较方便。Maya 主要用来做影视动画, 在动画和动力学模块上很强大,国内不少游戏公司慢慢将3DS Max 转Maya 了。其他的栏目包装,广告等两者都可用到。最终没有谁好谁坏之分,只有用处不同,两软件都是相通的。以前国内学Maya 的不多,因为没有中文版,教程也少,现在不一样了,教程也是不少了。
3DS Max 和Maya 各有其优势, 用户可以根据自身的项目需求自主选择软件, 其最大的区别就是3DS Max可以不费力气地得到你想要的东西,Maya 可能要花很多精力但是可以做出任何你想要的东西, 灵活性较强。
17.3 对X文件的认识
在Direct3D 中,我们一般都采用X 文件来存储网格数据。.x 文件格式是微软定义的3D 模型 文件格式, 其中包括网格的纹理、动画以及用户定义对象的一些数据等。需要注意的是, X 文件 通常并未存储具体的纹理数据, 它只包含纹理贴图的文件名,所以我们常常会发现, X 文件会和一 些图片文件比如.BMP 、. DDS 一同出没,放在一起,比如本节示例程序中我们使用的初音模型, 除 了.x 文件本身miki.X 之外, 还有数十张BMP 位图纹理图片配合着miki.X。17.4 从3DS Max 中导出X 文件方法详解
由于我们是初次接触三维模型文件的载入, 当然得先学走, 也就是先学Direct3D 自带的X 模 型文件的载入和使用。X 文件是微软定义的3D 模式文件格式,也是Direct3D 的默认三维模型格 式,Direct3D 自然对其有着非常好的支持度,有大量的函数辅助着X 文件的使用。至于学到后头了,我们就可以通过一些代码的编写(或者说使用游戏引擎的时候〉, 直接在 Direct3D 中使用3ds 、.max 、.obj以及.mb 等主流三维建模软件本身的文件。不过现在我们作为初 学者, 当前掌握好X 模型文件使用的各方各面, 就能应付绝大多数场合了。 想要从3DS Max 中导出. x 文件,简单来说,有三个要素, 我们分别来详细说一说。
第一个要素, 当然是3DS Max 软件的下载和安装。作者目前安装的是3DS Max 2012 的32 位 版。大家可以百度一下, 下载并安装, 互联网上关于3DS Max 的资源非常多。
第二个要素, Panda 插件的下载和配置。这个Panda 插件就是能让3DS Max支持导出X 格式 文件的一个插件。通常我们在3DS Max 中打开一个3D 模型后,点左上角的【主菜单】→ 【导出】 之后, 在弹出的导出选项框中并没有我们想要的X 文件。所以我们需要下载并配置一下与我们3DS Max 相对应的Panda 插件,来让导出选项中有我们的X 文件,这样才能完成3DS 中的模型到X 文 件的导出。以使我们在我们的DirectX 游戏程序中通过X 文件来载入好看的三维模型。
我们在互联网上Google/百度“ 3DS Max panda ”关键字,找到自己对应3DS Max 版本的Panda 插件下载。
比如,我们使用的是3DS Max 2012 ,那么我们下载到一个对应的Panda文件。把里面的对应安装版本位数的dle 文件( 32位对应X86 字样, 64 位对应X64 字样)放到3DS Max 安装目录下的plugins 之中,然后重启3DS Max 即可。
例如作者自己的3DS Max2012 安装在D 盘的"D:\Program Files\Autodesk\"目录之下,那么对应的就在
例如作者自己的3DS Max2012 安装在D 盘的"D:\Program Files\Autodesk\"目录之下,那么对应的就在
"D"\Program Files\Autodesk\3ds Max 2012\plugins" 目录中添加dle 插件文件;
然后, 重启一下3DS Max 2012 ,我们就可以发现, 导出选项中有了X 文件导出项了。
第三个要素, 也就是导出X 文件的具体步骤了。
首先, 正所谓巧妇难为无米之炊,我们需要有一个导出的对象,也就是一个三维的模型。模型嘛, 可以自己用3DS Max现场做,也可以自己去网上下载,这里推荐一个论坛, 有大量的3D 模型资源:http://www.cgmodel.com/
最后经过3DS Max 的处理,我们就发现在我们设定的保存路径下多了X 文件以及配套的纹理了。
第三个要素, 也就是导出X 文件的具体步骤了。
首先, 正所谓巧妇难为无米之炊,我们需要有一个导出的对象,也就是一个三维的模型。模型嘛, 可以自己用3DS Max现场做,也可以自己去网上下载,这里推荐一个论坛, 有大量的3D 模型资源:http://www.cgmodel.com/
最后经过3DS Max 的处理,我们就发现在我们设定的保存路径下多了X 文件以及配套的纹理了。
如果我们想要在我们写的游戏程序中使用这个三维模型,把这两个文件一起放到我们工程中相应的地方就可以了。
另外提一点, Maya 中可以用cvXporter 插件来导出X 文件,对应于3DS Max 中的Panda 插件。
之前讲了那么多元非是在为X 文件的诞生造势,下面就开始隆重介绍如何在Direct3D 程序中载入X 文件的具体知识吧。
想利用X 文件来在游戏程序中载入三维模型的话,首先就需要将X 文件中的各种数据分别加载到内存中,而这些数据主要包括顶点数据、材质数据和纹理数据等等。首先,我们需要介绍一下与网格模型相关的一个重要的接口——ID3DXMESH 。
另外提一点, Maya 中可以用cvXporter 插件来导出X 文件,对应于3DS Max 中的Panda 插件。
之前讲了那么多元非是在为X 文件的诞生造势,下面就开始隆重介绍如何在Direct3D 程序中载入X 文件的具体知识吧。
想利用X 文件来在游戏程序中载入三维模型的话,首先就需要将X 文件中的各种数据分别加载到内存中,而这些数据主要包括顶点数据、材质数据和纹理数据等等。首先,我们需要介绍一下与网格模型相关的一个重要的接口——ID3DXMESH 。
17.5 网格模型接口ID3DXMESH
在Direct3D 中,微软为我们提供了ID3DXMesh 接口表示网格,这个接口继承自ID3DXBaseMesh 接口。网格模型接口ID3DXMesh 实际上是三维物体的顶点缓存的集合,他将为我们创建顶点缓存、定义灵活顶点格式和绘制顶点缓冲区等功能封装在一个COM 对象中,这样复杂三维物体的绘制就显得非常简便了。
其中, ID3DXMESH 接口中的D3DXCreateMesh()可用于创建一个Direct3D 网格模型对象,我们可以在MSDN 中查到该函数声明是这样的:
其中, ID3DXMESH 接口中的D3DXCreateMesh()可用于创建一个Direct3D 网格模型对象,我们可以在MSDN 中查到该函数声明是这样的:
HRESULT D3DXCreateMesh( __in DWORD NumFaces, __in DWORD NumVertices, __in DWORD Options, __in const LPD3DVERTEXELEMENT9 *pDeclaration, __in LPDIRECT3DDEVICE9 pD3DDevice, __out LPD3DXMESH *ppMesh );这个函数的参数说明如下。
- 第一个参数, DWORD 类型的NumFaces ,表示创建网格模型的多边形数目。
- 第二个参数, DWORD 类型的NumVertices ,表示创建网格的顶点数目。
- 第三个参数, DWORD 类型的Options ,表示创建网格时的附加选项,他的取值为D3DXMESH枚举体中的一个或者多个值。这个枚举体定义如下:
typedef enum D3DXMESH { D3DXMESH_32BIT = 0x001, D3DXMESH_DONOTCLIP = 0x002, D3DXMESH_POINTS = 0x004, D3DXMESH_RTPATCHES = 0x008, D3DXMESH_NPATCHES = 0x4000, D3DXMESH_VB_SYSTEMMEM = 0x010, D3DXMESH_VB_MANAGED = 0x020, D3DXMESH_VB_WRITEONLY = 0x040, D3DXMESH_VB_DYNAMIC = 0x080, D3DXMESH_VB_SOFTWAREPROCESSING = 0x8000, D3DXMESH_IB_SYSTEMMEM = 0x100, D3DXMESH_IB_MANAGED = 0x200, D3DXMESH_IB_WRITEONLY = 0x400, D3DXMESH_IB_DYNAMIC = 0x800, D3DXMESH_IB_SOFTWAREPROCESSING = 0x10000, D3DXMESH_VB_SHARE = 0x1000, D3DXMESH_USEHWONLY = 0x2000, D3DXMESH_SYSTEMMEM = 0x110, D3DXMESH_MANAGED = 0x220, D3DXMESH_WRITEONLY = 0x440, D3DXMESH_DYNAMIC = 0x880, D3DXMESH_SOFTWAREPROCESSING = 0x18000 } D3DXMESH, *LPD3DXMESH;一般情况下,我们都把这个Options 取为D3DXMESH_ SYSTEMMEM 或者D3DXMESH_MANAGED , 表示对Direct3D 顶点缓冲区和索引缓冲区使用D3DPOOL_SYSTEMMEM 或者D3DPOOL_MANAGED 内存。
- 第四个参数, const LPD3DVERTEXELEMENT9 类型的*pDeclaration , 表示顶点包含哪些信息。这个参数的作用类似于我们之前一直在用的灵活顶点格式(FVF ),表示顶点包含了哪些具体数据,但是它却高于灵活顶点格式。它的类型LPD3DVERTEXELEMENT9 表示顶点元素, 主要用于我们来没讲到的可编程渲染流水线之中,在此我们暂且不用去多做考虑。
- 第五个参数, LPDIRECT3DDEVICE9 类型的pD3DDevice,就是我们的金钥匙, Direct3D设备的指针了。
- 第六个参数,LPD3DXMESH 类型的*ppMesh,指向我们创建好的网格模型对象指针的地址,用于返回创建好的网格模型对象。可以说我们调用D3DXCreateMesh 就是为了创建并得到这个指针地址。后面关于我们创建好的网格模型的访问,都靠这个ppMesh 参数了。
介绍完网格模型接口ID3DXMESH 相关的知识,下面就来看看从X 文件载入模型的具体步骤。
17.6 文件模型载入三步曲
17.6.1 三步曲之一:通过X文件加载网格模型
上面讲到了D3DXCreateMesh 函数很少去直接应用,因为我们常常都是通过载入X 文件来生成网格模型的,用到的函数是D3DXLoadMeshFromX 。我们可以在MSDN 中查到这个函数的声明如下:
HRESULT D3DXLoadMeshFromX( __in LPCTSTR pFilename, __in DWORD Options, __in LPDIRECT3DDEVICE9 pD3DDevice, __out LPD3DXBUFFER *ppAdjacency, __out LPD3DXBUFFER *ppMaterials, __out LPD3DXBUFFER *ppEffectInstances, __out DWORD *pNumMaterials, __out LPD3DXMESH *ppMesh );
- 第一个参数, LPCTSTR 类型的pFilename,显然就是一个指向我们需要加载的X 文件的磁盘路径和文件名的字符串了。
- 第二个参数, DWORD 类型的Options ,表示创建网格时的附加选项,他的取值为D3DXMESH 枚举体中的一个或者多个值。这个参数我们刚才在讲D3DXCreateMesh 时已经讲过了,具体参看D3DXCreateMesh的第三个参数,在这里就不赘述了。
- 第三个参数,LPDIRECT3DDEVICE9 类型的pD3DDevice,也就是我们的金钥匙, Direct3D设备的指针。
- 第四个参数, LPD3DXBUFFER 类型的*ppAdjacency ,用于保存加载网格的邻接信息,也就是包含每个多边形周围的多边形信息的缓冲区的内存地址。
- 第五个参数, LPD3DXBUFFER 类型的*ppMaterials ,用于保存网格的所有子集的材质,指向用于存储模型材质和纹理文件名的缓冲区的地址,而材质的数目存在之后第七个参数pNum扣laterials 中了。
- 第六个参数,LPD3DXBUFFER 类型的*ppEffectlnstances ,用于存储网格模型的特殊效果,指向用于存储模型效果实例的缓冲区的内存地址,这个参数通常设为NULL 就可以了。
- 第七个参数,DWORD 类型的*pNumMaterials,它配合着第五个参数,用于存储所有子集材质的数目。
- 第八个参数,LPD3DXMESH 类型的*ppMesh ,指向我们从文件生成的Direct3D 网格模型指针的地址。可以说我们调用D3DXLoadM eshFrornX 就是为了从文件加载X 文件的模型信息并进行模型的创建,从而能得到这个指向创建好的模型的指针地址。后面关于我们创建好的网格模型的访问,都靠这个ppMesh 参数了。
可以发现,在上面我们的D3DXLoadMeshFromX 函数中引入了一个新的Direct3D 类型, 它就是LPD3DXBUFFER。LPD3DXBUFFER 因数据操作的方便性而诞生,我们称它为泛型数据结构。它的好处是可以存储顶点位置坐标、材质、纹理等多种类型的Direct3D 数据,而不必对每种数据都去声明一种函数接口类型。可使用接口函数ID3DXBuffer::GetBufferPointer()获取缓冲区中的数据, 使用ID3DXBuffer::GetBufferSize()获得缓冲区数据大小,这两个接口函数的声明如下:
LPVOID GetBufferPointer(); SIZE_T GetBufferSize();没错,这就是原型声明,因为这两个函数都没有参数,所以它们的身子显得非常单薄。
比如,我们要从网格模型中提取材质属性和纹理文件名,那么代码就是像这样写:
// 读取材质和纹理数据 D3DXMATERIAL *pMtrls = (D3DXMATERIAL*)pMtrlBuffer->GetBufferPointer();
17.6.2 三步曲之二:载入材质和纹理
如果之前的D3DXLoadMeshFromX 函数调用成功的话,那么参数ppMaterials 就会获得.x 文件中三维模型的材质和纹理等信息,而pNumMaterials 参数就会获得材质的数目。
X文件中的材质信息是以D3DXMATERIAL 结构类型的数组形式储存的。其中,该结构定义了D3DMATERIAL9 结构类型的成员和一个指向以NULL 结尾的字符串指针, 而该字符串用于指定与网格子集相关的纹理贴图文件名。
X文件中的材质信息是以D3DXMATERIAL 结构类型的数组形式储存的。其中,该结构定义了D3DMATERIAL9 结构类型的成员和一个指向以NULL 结尾的字符串指针, 而该字符串用于指定与网格子集相关的纹理贴图文件名。
我们可以在MSDN 中查到D3DXMATERIAL 结构体的定义如下:
typedef struct D3DXMATERIAL { D3DMATERIAL9 MatD3D; LPSTR pTextureFilename; } D3DXMATERIAL, *LPD3DXMATERIAL;当我们加载X 文件后,需要遍历整个D3DXMATERIAL 结构类型的数组,用于取出保存在ID3DXBuffer 接口对象中的材质信息。由于X 文件中并未存储具体的纹理数据, 它只包含纹理贴图的文件名,因此需要我们自己根据该文件名创建相应的纹理对象。
就像这样:
// 从X文件中加载网格数据 LPD3DXBUFFER pAdjBuffer = NULL; LPD3DXBUFFER pMtrlBuffer = NULL; D3DXLoadMeshFromX(L"miki.X", D3DXMESH_MANAGED, g_pd3dDevice, &pAdjBuffer, &pMtrlBuffer, NULL, &g_dwNumMtrls, &g_pMesh); // 读取材质和纹理数据 D3DXMATERIAL *pMtrls = (D3DXMATERIAL*)pMtrlBuffer->GetBufferPointer(); //创建一个D3DXMATERIAL结构体用于读取材质和纹理信息 g_pMaterials = new D3DMATERIAL9[g_dwNumMtrls]; g_pTextures = new LPDIRECT3DTEXTURE9[g_dwNumMtrls]; for (DWORD i=0; i<g_dwNumMtrls; i++) { //获取材质,并设置一下环境光的颜色值 g_pMaterials[i] = pMtrls[i].MatD3D; g_pMaterials[i].Ambient = g_pMaterials[i].Diffuse; //创建一下纹理对象 g_pTextures[i] = NULL; D3DXCreateTextureFromFileA(g_pd3dDevice, pMtrls[i].pTextureFilename, &g_pTextures[i]); } SAFE_RELEASE(pAdjBuffer) SAFE_RELEASE(pMtrlBuffer)
17.6.3 三步曲之三:绘制网格模型
完成前两步做好准备工作之后,也就是生成X文件网格以及材质和纹理的读取之后,接下来就是把我们准备的内容绘制出来就行了。我们依然是用ID3DXMesh 接口的DrawSubset 方法绘制网格中的每个子集的。但是由于绘制的部分比较多,对每个部分的绘制,我们都需要专门为其进行材质和纹理的设置,然后才进行绘制,所以一般我们在绘制从X 文件读取的三维模型的时候, 一般用一个for 循环来进行绘制,就像这样: g_pd3dDevice->BeginScene(); // 开始绘制
// 用一个for循环,进行网格各个部分的绘制
for (DWORD i = 0; i < g_dwNumMtrls; i++)
{
g_pd3dDevice->SetMaterial(&g_pMaterials[i]);
g_pd3dDevice->SetTexture(0, g_pTextures[i]);
g_pMesh->DrawSubset(i);
}
g_pd3dDevice->EndScene(); // 结束绘制
一些关于使用模型的小技巧:
用三维建模工具制作的三维模型通常比较复杂,多边形数量很多。而多边形数量越多,图形的渲染速度就越慢,所以在制作模型时,在不明显影响视觉效果的情况下,应尽量减少多边形的数量。
三维模型自身的尺寸在模型制作时就确定好的, 不同的模型的尺寸可能是千差万别,因此在渲染网格模型时要针对模型进行适当的缩放。当然,最好是在制作或者导出模型时,就在建模软件中进行适当的缩放调整。
渲染网格模型时,通常需要进行光照处理和纹理映射,所以需要对光照和纹理进行相关的设置。对于纹理,如果我们忘了进行相关的设直,Direct3D 也会自动使用默认设置,而Direct3D 中是没有设置默认光源的,所以如果忘记设置光源的话,通常会造成模型的显示不正确。所以大家记得养成好的习惯,在渲染前设直光源,需要长点心了。
如果程序在读取X 文件时内存报错,看看是否把所有的纹理素材都放到X 文件所在的路径下了, 如果不行。把纹理全部取NULL,即g_pTextures[i]=NULL,然后注释掉接下来的
三维模型自身的尺寸在模型制作时就确定好的, 不同的模型的尺寸可能是千差万别,因此在渲染网格模型时要针对模型进行适当的缩放。当然,最好是在制作或者导出模型时,就在建模软件中进行适当的缩放调整。
渲染网格模型时,通常需要进行光照处理和纹理映射,所以需要对光照和纹理进行相关的设置。对于纹理,如果我们忘了进行相关的设直,Direct3D 也会自动使用默认设置,而Direct3D 中是没有设置默认光源的,所以如果忘记设置光源的话,通常会造成模型的显示不正确。所以大家记得养成好的习惯,在渲染前设直光源,需要长点心了。
如果程序在读取X 文件时内存报错,看看是否把所有的纹理素材都放到X 文件所在的路径下了, 如果不行。把纹理全部取NULL,即g_pTextures[i]=NULL,然后注释掉接下来的
D3DXCreateTextureFromFileA(g_pd3dDevice, pMtrls[i].pTextureFilename, &g_pTextures[i]);再试试。
模型的旋转是绕自身坐标轴的旋转,而不是绕世界坐标系的三个坐标轴进行旋转,而且在旋转模型的时候,它的自身坐标轴也在不断改变,大家需要注意.
在从网格模型数据中提取纹理文件名为网格模型创建纹理对象的时候,需要注意的是,提取的文件名可能是纹理文件的绝对路径。这个绝对路径常常是在利用三维建模软件时指定的路径,这个路径与当前纹理文件的路径一般是不一样的。因此在创建纹理的时候,常常就因为找不到纹理文件而报内存溢出类的错误.这个时候,我们就应该对提取到的纹理文件的绝对路径进行处理,即删除路径部分,只保留纹理文件名。这样在创建纹理对象时就可以从当前的路径搜索纹理文件。示例代码如下:
模型的旋转是绕自身坐标轴的旋转,而不是绕世界坐标系的三个坐标轴进行旋转,而且在旋转模型的时候,它的自身坐标轴也在不断改变,大家需要注意.
在从网格模型数据中提取纹理文件名为网格模型创建纹理对象的时候,需要注意的是,提取的文件名可能是纹理文件的绝对路径。这个绝对路径常常是在利用三维建模软件时指定的路径,这个路径与当前纹理文件的路径一般是不一样的。因此在创建纹理的时候,常常就因为找不到纹理文件而报内存溢出类的错误.这个时候,我们就应该对提取到的纹理文件的绝对路径进行处理,即删除路径部分,只保留纹理文件名。这样在创建纹理对象时就可以从当前的路径搜索纹理文件。示例代码如下:
// Desc : 从绝对路径中提取纹理文件名 //------------------------------------------------------------------------ voi d RemovePathFromFileName(LPSTR fullPath , LPWSTR fileName ) { //先将full Path 的类型变换为LPWSTR WCHAR wszBuf [MAX_PATH] ; MultiByteToWideChar ( CP_ACP, 0, fullPath , -1, wszBuf, MAX_PATH ) ; wszBuf[MAX PATH - 1] = L'\0'; WCHAR* wszFullPath = wszBuf ; //从绝对路径中提取文件名 LPWSTR pch=wc srchr (wszFullPath, ’\\’ ); if (pch) lstrcpy (fileName, ++pch) ; else lstrcpy (fileName, wszFullPath) ; }
17.6.4 总结与升华
总结一下,从X 文件读取模型并进行绘制其实很简单,就三步工作,简明扼要12个字的三步曲:加载网格,加载材质纹理, 绘制。我们依然可以利用如下的核心代码米加强理解和记忆:
//三部曲之一:从X文件中加载网格数据 LPD3DXBUFFER pAdjBuffer = NULL; LPD3DXBUFFER pMtrlBuffer = NULL; D3DXLoadMeshFromX(L"miki.X", D3DXMESH_MANAGED, g_pd3dDevice, &pAdjBuffer, &pMtrlBuffer, NULL, &g_dwNumMtrls, &g_pMesh); //三部曲之二: 读取材质和纹理数据 D3DXMATERIAL *pMtrls = (D3DXMATERIAL*)pMtrlBuffer->GetBufferPointer(); //创建一个D3DXMATERIAL结构体用于读取材质和纹理信息 g_pMaterials = new D3DMATERIAL9[g_dwNumMtrls]; g_pTextures = new LPDIRECT3DTEXTURE9[g_dwNumMtrls]; for (DWORD i=0; i<g_dwNumMtrls; i++) { //获取材质,并设置一下环境光的颜色值 g_pMaterials[i] = pMtrls[i].MatD3D; g_pMaterials[i].Ambient = g_pMaterials[i].Diffuse; //创建一下纹理对象 g_pTextures[i] = NULL; D3DXCreateTextureFromFileA(g_pd3dDevice, pMtrls[i].pTextureFilename, &g_pTextures[i]); } SAFE_RELEASE(pAdjBuffer) SAFE_RELEASE(pMtrlBuffer)
另外, 在载入模型三步曲之二中,这句g_pMaterials[i].Ambient = g_pMaterials[i].Diffuse 是用于将材质对漫反射光的反应程度赋值给材质的环境光反应程度。这句加上和不加上渲染出来的模型会有不同的环境光效果,大家可以自己把这句注释起来重新编译运行一下。
void Direct3D_Render(HWND hwnd) { g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(150, 150, 100), 1.0f, 0); // 三部曲之三:绘制 g_pd3dDevice->BeginScene(); // 开始绘制 // 用一个for循环,进行网格各个部分的绘制 for (DWORD i = 0; i < g_dwNumMtrls; i++) { g_pd3dDevice->SetMaterial(&g_pMaterials[i]); g_pd3dDevice->SetTexture(0, g_pTextures[i]); g_pMesh->DrawSubset(i); } g_pd3dDevice->EndScene(); // 结束绘制 g_pd3dDevice->Present(NULL, NULL, NULL, NULL); // 翻转与显示 }
17.7 示例程序D3demo12
这个程序的核心代码,其实重点部分在前面的X 文件模型载入三步曲核心代码中已经贴出过,这里只需要了解它们是放在哪里的就可以了。
运行这个程序,我们便会得到如下的效果, 一个高质量的初音模型,非常精致: