第16章 起舞不落幕——与纹理映射的华丽邂逅

16.1 纹理映射的概念

江山如此多娇,风景如画,万物都有其独特的特点。真实世界中的物体, 在计算机三维世界中如果单单用各种各样的几何体以及顶点坐标,顶点颜色来模拟的话,往往缺乏生机,往往差强人意。

比如说我们要绘制出如下效果的一个贴了瓷砖的立方体:

第16章 起舞不落幕——与纹理映射的华丽邂逅_第1张图片

综合我们之前讲过的知识,绘制一个立方体是小菜一碟。但关键就来了,如何给制出像砖块那样坑坑洼洼的效果呢?难不成用成千上万的顶点坐标一个个去机械地模拟?这显然不现实。
相信正确的做法大家应该很容易想到,先绘制一个立方体,接着准备一副描绘着砖块状的2D图片,然后把这幅图片像贴画一样贴到这个立方体的六个面,这样绘制砖块立方体的效果就可以实现了。就像这样:

第16章 起舞不落幕——与纹理映射的华丽邂逅_第2张图片

其实上面我们讲到的这种方法, 就是计算机图形学中“纹理映射”技术思想的真实写照。
在计算机三维世界中,想要模拟出真实而复杂的三维物体, 让它们从表面上看起来逼真, 就需要请出“纹理映射”这套技术。
一言以蔽之, 纹理映射是一种将2D 图像映射到3D 物体上的技术。

一般来说, 纹理是表示物体表面细节的一副或者几幅二维图形,也称纹理贴图( texture )。当我们把纹理按照特定的方式映射到物体表面上的时候, 能使物体看上去更加真实。在当今流行的计算机图形系统中,纹理绘制己经成了渲染方法中的中流砾柱了。
想要理解纹理映射其实非常简单,把纹理看做是应用在物体表面上的像素颜色就可以了。
在真实世界中,纹理表示一个对象的颜色、图案或者触觉特性。但在计算机图形学中,纹理只表示对象表面的彩色图案,它不能改变对象的几何形式,换言之,就像贴在几何体表面上的贴画一样。
好了,基本概念应该讲明白了,下面我们来看看如果要使用这种纹理映射技术的话,需要了解的具体知识。

16.2 纹理映射使用四步曲

这一节里面我们将详细讲解使用纹理映射的具体细节。
1. 四步曲之一, 纹理坐标的定义
一般把纹理映射所使用的2D 图像称作纹理贴图。Direct3D 支持多种的纹理贴图,比如 有.bmp 、. dds 、. dib 、. png 以及. tga 等等。虽然说Direct3D 对纹理贴图的大小没有限制, 但是为了 提高程序使用纹理的效率,通常使用边长为2 的N 次幂的正方形图片, 比如128 × 128 、256 × 256 、 512 × 512 等等。
纹理贴图往往都通过一个二维数组存储每个点的颜色值,该颜色值被称作纹理元素, 而每个纹 理元素在纹理中都有唯一的地址。而为了将纹理贴图映射到三维图形中, Direct3D 使用了纹理坐标 确定纹理贴图上的每个纹理元素。
纹理坐标由一个二维坐标系指定,这个坐标系由沿水平方向的u 轴和沿垂直方向的v 轴构成, 它们两个合起来就是坐标对(u, v ),其实和我们习惯的( x,y )坐标对是一个概念, 只不过在纹理 贴图中我们习惯用( u, v )而已。其中u 轴正方向为水平向右, v 轴正方向为垂直向下, 且它们的取 值范围都在[0 , 1] 之间。

第16章 起舞不落幕——与纹理映射的华丽邂逅_第3张图片

纹理坐标位于纹理空间之中,是相对坐标,相对于纹理坐标系中的原点( 0,0 )。当把纹理贴到三维模型表面上时,纹理元素首先被映射到物体模型的局部坐标系中, 然后再变换到屏幕坐标系中对应像素的位置处。

另外的一点, 纹理坐标是通过纹理层和纹理联系到一起的,通常情况下只需使用一层纹理就够了。记得我们原来讲FVF 灵活顶点格式的时候讲到过, 一个灵活顶点格式中允许最多定义八组纹理坐标,而每组纹理坐标都对应一个纹理层, 这就是说每个顶点最多可以使用八层纹理。比如这样写:

struct CUSTOMVERTEX 
{
	FLOAT _x, _y, _z;               // 顶点的位置
	FLOAT _u1, _v1;                   // 第一层纹理坐标
	FLOAT _u2, _v2;                   // 第二层纹理坐标
	FLOAT _u3, _v3;                   // 第三层纹理坐标
};
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ | D3DFVF_TEX1 | D3DFVF_TEX2 |D3DFVF_TEX3)
2. 四步曲之二, 顶点的访问
在FVF 灵活顶点格式中定义好纹理坐标之后,想要把纹理坐标存到顶点缓存中,顶点缓存的访问这一步当然必不可少。
也就是根据我们在第一步里面定义的顶点格式,来像做真空题一样填充顶点数据。
下面是一段实例代码:
比如我们在第一步中定义的是这样的顶点格式:

struct CUSTOMVERTEX 
{
	FLOAT _x, _y, _z;               // 顶点的位置
	FLOAT _u, _v;                   // 纹理坐标
	CUSTOMVERTEX(FLOAT x, FLOAT y, FLOAT z, FLOAT u, FLOAT v)
		: _x(x), _y(y), _z(z), _u(u), _v(v) {}
};
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ | D3DFVF_TEX1)
接着在这一步中就要做如下填空题的操作:

	//填充顶点缓存
	CUSTOMVERTEX* pVertices;
	if( FAILED( g_pVertexBuffer->Lock( 0, 4*sizeof(CUSTOMVERTEX), (void**)&pVertices, 0 ) ) )
		return E_FAIL;

	// 正面顶点数据
	pVertices[0] = CUSTOMVERTEX(-10.0f,  10.0f, -10.0f, 0.0f, 0.0f);
	pVertices[1] = CUSTOMVERTEX( 10.0f,  10.0f, -10.0f, 1.0f, 0.0f);
	pVertices[2] = CUSTOMVERTEX( 10.0f, -10.0f, -10.0f, 1.0f, 1.0f);
	pVertices[3] = CUSTOMVERTEX(-10.0f, -10.0f, -10.0f, 0.0f, 1.0f);
	g_pVertexBuffer->Unlock();
通过上面这句pVertices[0] =CUSTOMVERTEX(-10.0f,10.0f, -10.0f, 0.0f, 0.0);的代码,我们就指定了pVertices[0]的顶点坐标为

( -10.0f, 10.0f, -10.0f) 、纹理坐标为( 0.0f, 0.0f)。另外大家也许会发现在顶点结构体中我们比之前多出了一句:

CUSTOMVERTEX(FLOAT x, FLOAT y, FLOAT z, FLOAT u, FLOAT v)
		: _x(x), _y(y), _z(z), _u(u), _v(v) {}
这是我们为顶点结构体定义的一个默认构造函数, 方便了后面我们的赋值操作。


3 . 四步曲之三,纹理的创建
顶点这边的内容经过以上两步已经大功告成,接下来就是创建一个纹理对象,从文件中读取一副纹理并保存在这个对象中。

可以这样说, 在Direct3D 中,纹理是以COM 对象的形式存在的,也就是IDirect3DTexture9这个接口。我们要对物体表面进行纹理映射的话, 首先要创建纹理对象,创建时需要指定纹理的宽度、高度、格式等属性,然后还需要将图形文件加载到纹理对象中。我们可以用D3DX 库中的D3DXCreateTexture 函数创建一个纹理对象。不过这个函数比较冷门,平常用得少, 还是先来简单介绍一下这个函数的用法:

 HRESULT  D3DXCreateTexture(
  __in   LPDIRECT3DDEVICE9 pDevice,
  __in   UINT Width,
  __in   UINT Height,
  __in   UINT MipLevels,
  __in   DWORD Usage,
  __in   D3DFORMAT Format,
  __in   D3DPOOL Pool,
  __out  LPDIRECT3DTEXTURE9 *ppTexture
);

  •  第一个参数,LPDIRECT3DDEVICE9 类型的pDevice,这就是我们的绘制金钥匙一一Direct3D 设备对象了。
  •  第二个参数, UINT 类型的Width , 表示我们创建的纹理对象的宽度。
  •  第三个参数, UINT 类型的Height ,表示我们创建的纹理对象的高度。
  •  第四个参数, UINT 类型的MipLevels , 表示我们创建的纹理的渐进级别,通常取默认值D3DX_DEFAULT 就可以了, 表示创建一个完整阳的MIP贴图链。其实取0 也无妨,和取默认值D3DX_DEFAULT 效果是一样的。
  •  第五个参数, DWORD 类型的Usage ,指定了我们纹理的使用方式,取值在0 、D3DUSAGE_RENDERTARGET 、
  • D3DUSAGE_DYMANIC 中三取一。
  •  第六个参数, D3DFORMAT 类型的Format,用于指定纹理中每个保存每个颜色成分所使用的位数, 在D3DFORMAT 枚举体中取值, 这个参数也可以设为0,表示使用默认值。
  •  第七个参数, D3DPOOL 类型的Pool ,指定了纹理对象停驻的内存的类别, 在D3DPOOL枚举体中取值, 不过我们经常是在这个枚举体其中的两个成员D3DPOOL_DEFAULT 和D3DPOOL_MANAGED 之间取一个。
  •  第八个参数, LPDIRECT3DTEXTURE9 类型的*ppTexture ,指针的指针,也就是指向IDirect3DTexture9 接口的地址, 显然我们调用D3DXCreateTexture,就是把最终创建的纹理交给这个参数保管了,后面如果要使用我们创建的这个纹理的话, 通过这个参数就可以了。

但更多情况下是从文件中读取纹理图形的,我们可以任意读取我们喜欢的图片文件到Direct3D 中,这就用到了D3DXCreateTextureFromFile 函数:

 HRESULT  D3DXCreateTextureFromFile(
  __in   LPDIRECT3DDEVICE9 pDevice,
  __in   LPCTSTR pSrcFile,
  __out  LPDIRECT3DTEXTURE9 *ppTexture
);

  •  第一个参数, LPDIRECT3DDEVICE9 类型的pDevice,这就是我们的绘制金钥匙——Direct3D 设备对象了。
  •  第二个参数, LPCTSTR 类型的pSrcFile , 指向了用于创建纹理的图标文件名字的字符串,也就是我们要使用的纹理图片的文件地址了。支持的图片格式多种多样, 有.bmp 、.dds 、.dib 、.png以及.tga 等等。
  •  第三个参数, LPDlRECT3DTEXTURE9 类型的*ppTexture ,指针的指针,也就是指向LDirect3DTexture9 接口的地址,显然我们调用D3DXCreateTexture , 就是把最终创建的纹理交给这个参数保管了,后面如果要使用我们创建的这个纹理的话,通过这个参数就可以了。
上面我们刚讲到了D3DXCreateTextureFromFile 函数, 这个函数有一个孪生的哥哥,叫D3DXCreateTextureFromFileEx 。我们可以看到两兄弟从名字上来说唯一的区别就是哥哥在尾巴上多了一个Ex ,表示“额外”的意思, 表示是弟弟D3DXCreateTextureFromFile 的加强版。(可以结合CreateWindow 和CreateWindowEx 一起理解记忆, 算是微软对它们写的API 函数一套惯用的命名规则吧。〉此函数不仅包含了它的孪生弟弟D3DXCreateTexturefromFile 的全部功能( 从磁盘上的图形文件上加载文件并创建纹理对象),而且还能指定创建的纹理对象的宽度、高度以及格式等等。这里我们贴出它的原型:

HRESULT  D3DXCreateTextureFromFileEx(
  __in     LPDIRECT3DDEVICE9 pDevice,
  __in     LPCTSTR pSrcFile,
  __in     UINT Width,
  __in     UINT Height,
  __in     UINT MipLevels,
  __in     DWORD Usage,
  __in     D3DFORMAT Format,
  __in     D3DPOOL Pool,
  __in     DWORD Filter,
  __in     DWORD MipFilter,
  __in     D3DCOLOR ColorKey,
  __inout  D3DXIMAGE_INFO *pSrcInfo,
  __out    PALETTEENTRY *pPalette,
  __out    LPDIRECT3DTEXTURE9 *ppTexture
);
给这步一个实例吧, 其实就是简单定义一个纹理接口对象,然后从本地图片文件读取图片数据再存到这个纹理接口对象中。
LPDIRECT3DTEXTURE9	g_pTexture   = NULL;    // 纹理接口对象
D3DXCreateTextureFromFile(g_pd3dDevice, L"pal5q.jpg", &g_pTexture);

4 四步曲之四,纹理的启用
加载完纹理后, 就可以调用lDirect3DDevice9 接口的SetTexture 方法, 设置当前需要启用的纹理。我们可以在MSDN 中查到SetTexture 方法有如下原型:

HRESULT SetTexture(
  [in]  DWORD Sampler,
  [in]  IDirect3DBaseTexture9 *pTexture
);

  • 第一个参数, DWORD 类型的Sampler,指定了应用的纹理是哪一层。我们知道Direct3D中最多可以设置8 层纹理,所以这个参数取值就在0~7 之间了。
  • 第二个参数, IDirect3DBaseTexture9 类型的*pTexture , 表示将要启用的纹理的IDirect3DBaseTexture接口对象,比如我们填上面第二步末尾实例中定义过的g_pTexture 参数,注意要加取地址符号, 也就是这样填&g_pTexture,则表示我们现在绘图时用的就是“ pal5q.jpg ”这张图了。

想要正确地显示出纹理,可以通过g_pd3dDevice->SetRenderState (D3DRS_LIGHTING, FALSE) 这句代码来关掉Direct3D 中需要通过光的反射才能看到物体的自然界现象。|
如果不关掉这种自然现象的话,那么还是需要对应的光照分量配合上对应的材质,才能显示出物体的纹理来。

另外,如果场景中绘制每个物体模型所使用的纹理不相同,那么在绘制每个物体模型之前都需要调用该方法设置对应的纹理。也就是说当前在用的纹理就一种,就像上场打比赛的就一名队员,其他的都是替补,要换纹理的话,就用SetTexture 申请换人。
下面我们依旧来个实例:

g_pd3dDevice->SetTexture (0,g_pTexturel); // 设置第一个物体的纹理
g_pMeshl->DrawSubset(0) ;                 // 进行第一个物体的绘制
g_pd3dDevice->SetTexture(0,g_pTexture2); // 设置第二个物体的纹理
g_pMesh2->DrawSubset(0);                 // 进行第二个物体的绘制

16.3 总结与升华

上面使用方法讲解中夹杂着各种概念讲解,讲得比较散,为了方便想快速上手掌握并使用纹理映射技术的朋友们,下面我们来做个知识整理, 突出一下重点。

1 . 四步曲的大纲
首先是这四步曲的大纲,纹理映射使用四步曲,简明扼要20 个字: 顶点的定义,顶点的访问,纹理的创建, 纹理的启用。
2 . 知识点的关键字总结
接着我们以关键字的形式来总结一下这四步的知识点:
四步曲之一,顶点的定义。
FVF 顶点格式的定义, 最多八层纹理。
四步曲之二,顶点的访问。
访问顶点缓存内容,纹理坐标的填充。
四步曲之三,纹理的创建。
一个对象: IDIRECT3DTEXTURE9 接口对象:
一个方法: D3DXCreateTextureFromFile 方法。
四步曲之四,纹理的启用。
一个方法: SetTexture 方法。
3 . 以代码为载体记忆
下面我们给一段代码, 并按照这四步做了注释,大家若是要快速掌握Direct3D 中绘制纹理方法,消化这段代码就可以了。

	//--------------------------------------------------------------------------------------
	// 【纹理绘制四步曲之一】:顶点的定义	
	//--------------------------------------------------------------------------------------
	struct CUSTOMVERTEX 
	{
		FLOAT _x, _y, _z;               // 顶点的位置
		FLOAT _u, _v;                   // 纹理坐标
		CUSTOMVERTEX(FLOAT x, FLOAT y, FLOAT z, FLOAT u, FLOAT v)
			: _x(x), _y(y), _z(z), _u(u), _v(v) {}
	};
	#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ | D3DFVF_TEX1)

	//--------------------------------------------------------------------------------------
	// 【纹理绘制四步曲之二】:顶点的访问	
	//--------------------------------------------------------------------------------------
	//填充顶点缓存
	CUSTOMVERTEX* pVertices;
	if( FAILED( g_pVertexBuffer->Lock( 0, 24*sizeof(CUSTOMVERTEX), (void**)&pVertices, 0 ) ) )
		return E_FAIL;

	// 正面顶点数据
	pVertices[0] = CUSTOMVERTEX(-10.0f,  10.0f, -10.0f, 0.0f, 0.0f);
	pVertices[1] = CUSTOMVERTEX( 10.0f,  10.0f, -10.0f, 1.0f, 0.0f);
	pVertices[2] = CUSTOMVERTEX( 10.0f, -10.0f, -10.0f, 1.0f, 1.0f);
	pVertices[3] = CUSTOMVERTEX(-10.0f, -10.0f, -10.0f, 0.0f, 1.0f);
	g_pVertexBuffer->Unlock();
	
	//--------------------------------------------------------------------------------------
	// 【纹理绘制四步曲之三】:纹理的创建
	//--------------------------------------------------------------------------------------
	LPDIRECT3DTEXTURE9	g_pTexture   = NULL;    // 纹理接口对象
	D3DXCreateTextureFromFile(g_pd3dDevice, L"pal5q.jpg", &g_pTexture);
	
	//【纹理绘制四步曲之四】:纹理的启用
	g_pd3dDevice->BeginScene();                     // 开始绘制
	g_pd3dDevice->SetTexture(0, g_pTexture);  //启用纹理
	g_pd3dDevice->EndScene();                       // 结束绘制
需要注意一点,第四步纹理的启用, 必须在绘制五步曲的BeginScene 和EndScene 这对好兄弟之间进行,因为在BeginScene 和EndScene 之间就是来写绘制图形的相关代码的, 在绘制过程如果要用到纹理的话就需要启用,启用后再调用相关绘制函数进行图形的绘制,这是完全合情合理的。

16.4 示例程序D3Ddemo10

通过上面的学习,我们了解了纹理的一般绘制方法,接下来看一个关于纹理绘制的demo ,就 像本章引言里说的那样,先绘制一个立方体,然后把贴画贴到这个立方体的各个面上。

这个程序还可以这样理解,把前面我们的变形金刚的绘制换成了一个利用顶点缓存、索引缓存配合纹理映射绘制出的立方体盒子。

具体代码可在网上下载:

运行上述demo ,我们可以看到如下的效果, 可以用鼠标和键盘调整视角,观察被纹理映射后的立方体。

第16章 起舞不落幕——与纹理映射的华丽邂逅_第4张图片

刚在上一章中看到了载入帅气的“擎天柱” 的示例程序,这一章忽然弄出一个好像没有什么技术含量的立方体来,是不是落差有些大, 其实我们在载入帅气的“擎天柱”的时候,就用到了纹理映射技术, 不然擎天柱看起来就只有一些网格了,而不是表面看起来有些质感的样子了(当然, 擎天柱本身的材质也做出了适当的贡献)。

16.5 四大纹理过滤方式精讲

我们知道,当Direct3D 渲染一个图元或者三维图形时,必须将它们通过坐标变换映射到二维屏幕之上。而当我们使用纹理来进行辅助渲染的时候, Direct3D 就必须使用该纹理为二维图像上的每个像素进行着色。这里的每个像素都包含一个来自纹理的颜色值,而从纹理中为每个像素获取颜色的过程, 就是所谓的纹理过滤( texture filtering )了。
大多数情况下, 屏幕显示的图形大小与纹型贴图大小是不同的。换句话说,这个纹理将被映射到一个比它大或者小的图元的图像上。这样纹理常常会被放大或者缩小。对纹理的放大会造成许多像素被映射到同一个纹理元素上,这样图形渲染的结果就会有色块的感觉。缩小一个纹理意味着一个像素被映射到许多纹理元素上, 图形看上去会闪烁或者失真抑或是有锯齿。而为了解决这些问题,可以将相关纹理元素的颜色融合到一个像素之上, 而如何将多个纹理元素的颜色融合到一个像素就取决于纹理过滤的方式了。

Direct3D 中,有四种纹理过滤方式, 它们分别是:

  •  最近点采样过滤( nearst-pointsarnpling )
  •  线性纹理过滤( lineartexture fi ltering)
  •  各项异性过滤( anisotropictexture filtering )
  •  多级渐进过滤( texturefiltering with mipmaps )
这纹理过滤的“四大天王”各有优缺点。比如, 线性过滤生成的图像较糙,但是计算量小。多级渐进纹理过滤的效果通常最好,特别是和各向异性过滤联合使用的时候效果更佳。但是它占有的资源最多,计算量也最大。
设置纹理过滤方式通常都是用IDirect3DDevice9 : :SetSamplerState 函数,这个函数用于设置纹理的各种采样属性,下面我们来讲一下这个函数:

HRESULT SetSamplerState(
  [in]  DWORD Sampler,
  [in]  D3DSAMPLERSTATETYPE Type,
  [in]  DWORD Value
);

  •  第一个参数, DWORD 类型的Sampler ,指定为哪一层纹理设置采样状态,在Direct3D 中支持最多8 层纹理, 所以这个值取值显然就是0~7 了。如果只使用单层纹理进行渲染的话, 这个值设为0 就行了。
  •  第二个参数, D3DSAMPLERSTATETYPE 类型的Type,用来指定对哪种纹理采样属性来进行操作。从参数类型来看我们就知道需要在枚举体D3DSAMPLERSTATETYPE 中进行取值,下面我们就来看看这个枚举体的定义:
typedef enum D3DSAMPLERSTATETYPE {
  D3DSAMP_ADDRESSU        = 1,
  D3DSAMP_ADDRESSV        = 2,
  D3DSAMP_ADDRESSW        = 3,
  D3DSAMP_BORDERCOLOR     = 4,
  D3DSAMP_MAGFILTER       = 5,
  D3DSAMP_MINFILTER       = 6,
  D3DSAMP_MIPFILTER       = 7,
  D3DSAMP_MIPMAPLODBIAS   = 8,
  D3DSAMP_MAXMIPLEVEL     = 9,
  D3DSAMP_MAXANISOTROPY   = 10,
  D3DSAMP_SRGBTEXTURE     = 11,
  D3DSAMP_ELEMENTINDEX    = 12,
  D3DSAMP_DMAPOFFSET      = 13,
  D3DSAMP_FORCE_DWORD     = 0x7fffffff 
} D3DSAMPLERSTATETYPE, *LPD3DSAMPLERSTATETYPE;

  •  第三个参数,DWORD 类型的Value,这个参数和第二个参数Type联系很紧密,就是对第二个参数指定的属性进行值的设置。
1 . 最近点采样过滤
最近采样点过滤可谓是四种纹理过滤方式中速度最快但是效果最差的过滤方式了。Direct3D 计算得到的纹理过滤元素通常都是一个浮点值,当使用最近点采样时, Direct3D 会复制与这个浮点值地址最接近的整数地址的纹理元素的颜色。
设置最近点采样的具体方法其实很简单,就是调用我们上面提到的IDirect3DDevice9 : :SetSarnplerState函数, 通过第二个参数Type 的不同取值,分别设置纹理过滤的放大过滤器和缩小过滤器就可以了。其中,将第一个参数设置为与我们相关联的纹理层的序号

(0~7 之间取值) 。第二个参数Type 取D3DSAMP_MAGFILTER 表示设置放大过滤器,取D3DSAMP_MINFILTER 表示设置缩小过滤器。
而对应于第二个参数的取值,第三个参数就直接设为表示最近点采样的枚举常量D3DTEXT_POINT 就行了。
下面就是一个调用实例,把纹理层2 的过滤方式设置为最近点来样:

// 最近点采样过滤
g_pd3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_POINT);
g_pd3dDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_POINT);
对于使用最近点采样过滤后图像质量相关的常识,我们提一下:如果纹理大小和屏幕图元的实际大小相近,那么采用最近点采样过滤对图像质量的影响不大。但是,如果纹理大小和屏幕图元的实际大小相差太多,则图像的精度就会受到影响,从而图像质量就会差强人意,出现闪烁或者失真的现象。


2. 线性纹理过滤
线性纹理过滤是目前使用最广泛的纹理过滤方式。与最近点采用方式相比,能有效地提高图像的显示质量,且对系统性能的影响不大。线性纹理过滤取得与计算得到的纹理元素的浮点地址最接近的上下左右4 个纹理元素,对这四个纹理元素进行加权平均,从而得到最终显示的颜色值。因为是在单一纹理层上的线性过滤,而且是从x, y 方向上的线性过滤,所以我们也称通常的线性纹理过滤为双线性纹理过滤。
关于使用方法,与上面刚讲到的最近点采样方法当然类似,就是用SetSamplerState 函数,第二个参数还是D3DSAMP_MAGFILTER 与D3DSAMP_MINFILTER , 有不同的地方就是第三个参数, 改为线性纹理过滤专属的D3DTEXF_LINEAR 就可以了。下面依旧是一个调用实例,将第0层纹理的放大和缩小过滤器设为线性纹理过滤:

	//线性纹理过滤
	g_pd3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
	g_pd3dDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
目前市面上的大多数显卡都为线性纹理过滤进行了针对性优化,所以使用这种纹理过滤方式能获得比较好的图形质量,而且也不吃资源。线性纹理过滤是目前使用最广泛的纹理过滤方式。


3. 各向异性纹理过滤
首先需要知道一个常识,在很多时候,三维物体的表面不可能是完全平面的,当三维物体的表面和投影平面不平行时,它在屏幕上的投影会有拉长和扭曲的现象,这种现象称为各向异性( Anisotropic )。当一个各项异性的图元映射到纹理元素上时, 它的形状就会发生扭曲。而Direct3D会根据屏幕像素反向转换到纹理元素的延长度,来决定各项异性的程度。
关于使用方法, 依然是用SetSamplerState ,与最近点采样过滤和线性纹理过滤不同的地方,除了第三个参数设为我们各向异性纹理过滤的专属D3DTEXF_ANISOTROPIC 外, 另外还需专门设置一下最大各项异性的程度值。还是用这个SetSamplerState , 第一个参数取需要进行设置的纹理层数,第二个参数设为D3DSAMP_MAXANISOTROPY ,第三个参数设为大于1 的任意值就行了。
下面是一个实例,对第0 层纹理,设置最大各项异性值为3 的各向异性纹理过滤方式:

	//各向异性过滤
	g_pd3dDevice->SetSamplerState(0, D3DSAMP_MAXANISOTROPY, 3);
	g_pd3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_ANISOTROPIC);
	g_pd3dDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_ANISOTROPIC);


4 . 多级渐进纹理过滤
首先需要知道的是, 这个多级渐进纹理过滤通常不是独当一面的纹理过滤方式,他需要找个伴,也就是需要和我们上面讲到的四大纹理过滤方式其他三种之一结合使用。
多级渐进纹理就是由一组分辨率逐渐降低的纹理序列组成的, 每一级纹理的宽度和高度都是上一级纹理的宽度和高度的一半。需要注意, 纹理的宽度和高度并不一定要相等。也就是说,这些图形并不一定要是正方形。
Direct3D 在纹理映射时, 自动选择一幅与物体大小最接近的纹理进行渲染。当物体离投影面很远时, Direct3D 会选择一张尺寸较小分辨率较低的纹理进行渲染: 当物体离投影面较近时,Direct3D 则会选择一张尺寸较大分辨率较高的纹理进行渲染。 就像这样:

第16章 起舞不落幕——与纹理映射的华丽邂逅_第5张图片

Direct3D 将多级渐进纹理描绘成一系列相互联系的表面,当创建一个渐进纹理时,分辨率最高的纹理位于开始处,并与下一级纹理相互联系,直到分辨率最低的一级纹理。
Direct3D 能够估计出多级渐进纹理链中哪幅纹理的分辨率最接近想要的输出结果,然后能将像素映射到纹理空间。而且当最终显示的图形大小介于任意两级纹理图形之间时, Direct3D 能智能地取得两级纹理的相应元素进行混合后显示出来。
多级渐进纹理过滤能够有效地提高图形渲染速度,当物体离投影平面较远的时候, Direct3D 会选择一张尺寸较小的纹理进行渲染,而无需经过复杂的诸如各项异性纹理过滤。并且由于这时纹理需要的显存比不使用多级渐进纹理要小, 因此还能有效地减小纹理载入显存的时间。
对于多级渐进纹理的生成,使用我们之前讲创建纹理的时候提到过的一个方法,即D3DXCreateTextureFromFileEx 方法:

HRESULT  D3DXCreateTextureFromFileEx(
  __in     LPDIRECT3DDEVICE9 pDevice,
  __in     LPCTSTR pSrcFile,
  __in     UINT Width,
  __in     UINT Height,
  __in     UINT MipLevels,
  __in     DWORD Usage,
  __in     D3DFORMAT Format,
  __in     D3DPOOL Pool,
  __in     DWORD Filter,
  __in     DWORD MipFilter,
  __in     D3DCOLOR ColorKey,
  __inout  D3DXIMAGE_INFO *pSrcInfo,
  __out    PALETTEENTRY *pPalette,
  __out    LPDIRECT3DTEXTURE9 *ppTexture
);
然后是这个函数的一个调用实例:
        // 创建纹理
	D3DXCreateTextureFromFileEx(g_pd3dDevice, L"pal5q.jpg", 0, 0, 6, 0, D3DFMT_X8R8G8B8, 
		D3DPOOL_MANAGED, D3DX_DEFAULT, D3DX_DEFAULT, 0xFF000000, 0, 0, &g_pMipTexture);
下面讲一下多级渐进的使用方法。我们在本节一开始就提到过,多级渐进纹理过滤通常不是独当一面的纹理过滤方式,它需要找个伴,也就是最好和我们上面讲到的四大纹理过滤方式的其他三种之一结合使用。首先对于多级渐近纹理过滤, 还是SetSarnplerState 函数,第一个参数依旧是设置纹理层数的序号, 第二个参数设为D3DSAMP_MIPFILTER,第三个参数设置为在相邻纹理级之间的过滤方式,取枚举类型D3DTEXTUREFILTERTYPE 的任意值。比如下面这个代码就把相邻纹理级之间的过滤方式设置为线性过滤。

g_pd3dDevice->SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR);
另外,还可以通把IDirect3DDevice9:: SetSamplerState 的第二个参数设为D3DSAPMlP_MIPLODBIAS来设置多级纹理映射级数的偏移值。
关于线性纹理过滤相关的讲了这么多,我们来串联一下,给一个综合起来的实例, 线性纹理过滤配合多级渐进纹理过滤的实例:

	// 渐进纹理过滤
	g_pd3dDevice->SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR);
	g_pd3dDevice->SetSamplerState(0, D3DSAMP_MAXMIPLEVEL, 16);
接着我们来看看四大纹理寻址方式。

16.6 四大纹理寻址方式精讲

Direct3D 应用程序可以为任何图元的任何顶点指定纹理坐标。通常使用的u 、v 纹理坐标的取值范围是[0.0, 1.0]。但是,这显然不能满足我们多变的实际运用。
这时候就需要用到纹理寻址模式的设置,来控制Direct3D 如何控制[0.0,1.0]坐标范围之外的纹理坐标值了。
Direct3D 中有四种纹理模式供我们选择,来处理超出[0.0, 1.0]范围之外的纹理映射情况。它们分别是
重复寻址模式( wrap texture address mode )、镜像纹理寻址模式( mirror texture address mode )、
夹取纹理寻址模式( clamp textrue address mode )和边框颜色纹理寻址( border color textrue address mode ) 。
我们来分别进行介绍。
1 . 重复寻址模式
首先需要注意, 重复寻址模式是Direct3D 中的默认寻址模式, 这种寻址模式允许在每个整数连接点处重复上一个整数的纹理。

比如,我们创建了一个正方形图元,而指定4 个顶点的纹理坐标分别为(0.0 ,0.0 )、( 0.0 ,2.0)、(2.0, 2.0 )和( 2.0, 0.0 ),因为重复寻址为Direct3D 中的默认寻址模式, 所以我们不用额外设置,那么Direct3D 就会在u ,v 方向各复制两遍原始纹理, 效果就像这样:
第16章 起舞不落幕——与纹理映射的华丽邂逅_第6张图片
如果我们需要手动启用这种重复寻址模式,或者用了其他的寻址模式想换回来的话, 还是用那个SetSamplerState。如下两句连用,分别对U 轴和V 轴启用重复纹理寻址模式:

// 设置重复纹理寻址模式
g_pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_WRAP);
g_pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_WRAP);
在这里方便大家理解, 贴一个用于定义四大寻址模式的类型中用于SetSamplerState 第二个参数取值的枚举体:

typedef enum D3DTEXTUREADDRESS {
  D3DTADDRESS_WRAP          = 1,    // 重复寻址模式
  D3DTADDRESS_MIRROR        = 2,    // 镜像寻址模式
  D3DTADDRESS_CLAMP         = 3,    // 夹取寻址模式
  D3DTADDRESS_BORDER        = 4,    // 边框颜色寻址模式
  D3DTADDRESS_MIRRORONCE    = 5,
  D3DTADDRESS_FORCE_DWORD   = 0x7fffffff 
} D3DTEXTUREADDRESS, *LPD3DTEXTUREADDRESS;
2. 镜像寻址模式
使用镜像纹理寻址模式时, Direct3D 会在每个整数纹理坐标连接处自动复制并翻转纹理,且为两两成对翻转。
依然是那个例子,我们创建了一个正方形图元,而指定4 个顶点的纹理坐标分别为(0.0 ,0.0 )、( 0.0 ,2.0)、(2.0, 2.0 )和

( 2.0, 0.0 ),设置为镜像寻址模式,也就是在绘制之前写如下两句代码:

	// 设置镜像纹理寻址模式
	g_pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_MIRROR);
	g_pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_MIRROR);
那么得到的效果就是如下:

第16章 起舞不落幕——与纹理映射的华丽邂逅_第7张图片

3 . 夹取寻址模式
夹取纹理寻址模式将纹理坐标夹取在[0.0, 1.0]之间,也就是说, 在[0.0, 1.0]之间就是把纹理复制一遍,然后对于[0.0, 1.0]之外的内容,将边缘的内容沿着u 轴和v 轴进行一下延伸。

依然是那个例子,我们创建了一个正方形图元,而指定4 个顶点的纹理坐标分别为(0.0 ,0.0 )、( 0.0 ,2.0)、(2.0, 2.0 )和

( 2.0, 0.0 ),设置为夹取寻址模式,也就是在绘制之前写如下两句代码:

	// 设置夹取纹理寻址模式
	g_pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP);
	g_pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP);

然后得到的效果如下图:
第16章 起舞不落幕——与纹理映射的华丽邂逅_第8张图片

4 . 边框颜色纹理寻址
边框颜色纹理寻址模式就是在[0.0, 1.0 ]之间绘制一下纹理,然后[0.0, 1.0]之外的内容就用边框颜色填充了。

依然是那个例子,我们创建了一个正方形图元,而指定4 个顶点的纹理坐标分别为(0.0 ,0.0 )、( 0.0 ,2.0)、(2.0, 2.0 )和

( 2.0, 0.0 ), 设置为边框颜色寻址模式,也就是在绘制之前写如下两句代码:

// 设置边框纹理寻址模式
g_pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_BORDER);
g_pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_BORDER);
然后得到的效果就是如下图:

第16章 起舞不落幕——与纹理映射的华丽邂逅_第9张图片

16.7 纹理映射知识总结

本着把厚书读薄的精神,纹理映射相关知识也讲了这么多了, 其实总结起来,我们也就讲了三方面的内容,总结如下:
1. 纹理映射四步曲
纹理映射使用四步曲,简明扼要20 个字: 顶点的定义,顶点的访问,纹理的创建,纹理的启用。
四步曲之一,顶点的定义。
FVF 顶点格式的定义,最多八层纹理。


四步曲之二,顶点的访问。
访问顶点缓存内容,纹理坐标的填充。


四步曲之三,纹理的创建。
一个对象: IDIRECT3DTEXTURE9 接口对象:
一个方法: D3DXCreateT extureFromFile 方法。


四步曲之四, 纹理的启用。
一个方法: SetTexture 方法。


相关代码:

//--------------------------------------------------------------------------------------
	// 【纹理绘制四步曲之一】:顶点的定义	
	//--------------------------------------------------------------------------------------
	struct CUSTOMVERTEX 
	{
		FLOAT _x, _y, _z;               // 顶点的位置
		FLOAT _u, _v;                   // 纹理坐标
		CUSTOMVERTEX(FLOAT x, FLOAT y, FLOAT z, FLOAT u, FLOAT v)
			: _x(x), _y(y), _z(z), _u(u), _v(v) {}
	};
	#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ | D3DFVF_TEX1)

	//--------------------------------------------------------------------------------------
	// 【纹理绘制四步曲之二】:顶点的访问	
	//--------------------------------------------------------------------------------------
	//填充顶点缓存
	CUSTOMVERTEX* pVertices;
	if( FAILED( g_pVertexBuffer->Lock( 0, 24*sizeof(CUSTOMVERTEX), (void**)&pVertices, 0 ) ) )
		return E_FAIL;

	// 正面顶点数据
	pVertices[0] = CUSTOMVERTEX(-10.0f,  10.0f, -10.0f, 0.0f, 0.0f);
	pVertices[1] = CUSTOMVERTEX( 10.0f,  10.0f, -10.0f, 1.0f, 0.0f);
	pVertices[2] = CUSTOMVERTEX( 10.0f, -10.0f, -10.0f, 1.0f, 1.0f);
	pVertices[3] = CUSTOMVERTEX(-10.0f, -10.0f, -10.0f, 0.0f, 1.0f);
	g_pVertexBuffer->Unlock();
	
	//--------------------------------------------------------------------------------------
	// 【纹理绘制四步曲之三】:纹理的创建
	//--------------------------------------------------------------------------------------
	LPDIRECT3DTEXTURE9	g_pTexture   = NULL;    // 纹理接口对象
	D3DXCreateTextureFromFile(g_pd3dDevice, L"pal5q.jpg", &g_pTexture);
	
	//【纹理绘制四步曲之四】:纹理的启用
	g_pd3dDevice->BeginScene();                     // 开始绘制
	g_pd3dDevice->SetTexture(0, g_pTexture);  //启用纹理
	g_pd3dDevice->EndScene();                       // 结束绘制
2 . 四大纹理过滤模式
依然是以代码为载体, 下面启用各种模式的代码。
(1)最近点采样过滤 (nearst-point sampling )

	// 最近点采样过滤
	 g_pd3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_POINT);
	 g_pd3dDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_POINT);
(2 )线性纹理过滤( lineartexture filtering )

         //线性纹理过滤
	g_pd3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
	g_pd3dDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
(3 )各项异性过滤( anisotropictexture filtering )

	//各向异性过滤
	g_pd3dDevice->SetSamplerState(0, D3DSAMP_MAXANISOTROPY, 3);
	g_pd3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_ANISOTROPIC);
	g_pd3dDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_ANISOTROPIC);
( 4 )多级渐进过滤( texturefiltering with mipmaps ) ·

	// 渐进纹理过滤
	g_pd3dDevice->SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR);
	g_pd3dDevice->SetSamplerState(0, D3DSAMP_MAXMIPLEVEL, 16);
3 . 四大纹理寻址模式

( l )重复寻址模式( wrap texture address mode)

	// 设置重复纹理寻址模式
	g_pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_WRAP);
	g_pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_WRAP);
( 2 )镜像纹理寻址模式( mirror texture address mode)

// 设置镜像纹理寻址模式
g_pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_MIRROR);
g_pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_MIRROR);
( 3 )夹取寻址模式( clamp textrue address mode )

	// 设置夹取纹理寻址模式
	g_pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP);
	g_pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP);
( 4 )边框颜色纹理寻址( bordercolor textrue address mode)
	// 设置边框纹理寻址模式
	g_pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_BORDER);
	g_pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_BORDER);
大家如若需要选取相应的纹理过滤模式以及纹理寻址模式,可以快速地在这里查阅,在相应的地方敲上代码即可。

16.8 示例程序D3Ddemo 11

这个示例程序为大家准备的是纹理寻址模式相关的内容,通过按键盘上1~4 在四大纹理 寻址模式之间进行切换。
因为在D3Ddemo10 的基础上,我们只是在Object_lnit()函数对顶点索引缓存的相关代码进行 了更改,以及在

Direct3D_Update()函数中加入了通过对键盘按键的侦测,来对四大纹理寻址模式进行处理的一些代码:

//-----------------------------------【Object_Init( )函数】--------------------------------------
//	描述:渲染资源初始化函数,在此函数中进行要被渲染的物体的资源的初始化
//--------------------------------------------------------------------------------------------------
HRESULT Objects_Init()
{
	//创建字体
	D3DXCreateFont(g_pd3dDevice, 36, 0, 0, 1000, false, DEFAULT_CHARSET, 
		OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, 0, _T("Calibri"), &g_pTextFPS);
	D3DXCreateFont(g_pd3dDevice, 20, 0, 1000, 0, false, DEFAULT_CHARSET, 
		OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, 0, L"华文中宋", &g_pTextAdaperName); 
	D3DXCreateFont(g_pd3dDevice, 23, 0, 1000, 0, false, DEFAULT_CHARSET, 
		OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, 0, L"微软雅黑", &g_pTextHelper); 
	D3DXCreateFont(g_pd3dDevice, 26, 0, 1000, 0, false, DEFAULT_CHARSET, 
		OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, 0, L"黑体", &g_pTextInfor); 

	//--------------------------------------------------------------------------------------
	// 【顶点缓存、索引缓存绘图四步曲之二】:创建顶点缓存和索引缓存
	//--------------------------------------------------------------------------------------
	//创建顶点缓存
	if( FAILED( g_pd3dDevice->CreateVertexBuffer( 24*sizeof(CUSTOMVERTEX),
		0, D3DFVF_CUSTOMVERTEX,
		D3DPOOL_DEFAULT, &g_pVertexBuffer, NULL ) ) )
	{
		return E_FAIL;
	}
	// 创建索引缓存
	if( FAILED( 	g_pd3dDevice->CreateIndexBuffer(36* sizeof(WORD), 0, 
		D3DFMT_INDEX16, D3DPOOL_DEFAULT, &g_pIndexBuffer, NULL)) )
	{
		return E_FAIL;

	}
	//--------------------------------------------------------------------------------------
	// 【顶点缓存、索引缓存绘图四步曲之三】:访问顶点缓存和索引缓存
	//--------------------------------------------------------------------------------------
	//--------------------------------------------------------------------------------------
	// 【纹理绘制四步曲之二】:顶点的访问	
	//--------------------------------------------------------------------------------------
	//填充顶点缓存
	CUSTOMVERTEX* pVertices;
	if( FAILED( g_pVertexBuffer->Lock( 0, sizeof(CUSTOMVERTEX), (void**)&pVertices, 0 ) ) )
		return E_FAIL;

	// 正面顶点数据
	pVertices[0] = CUSTOMVERTEX(-10.0f,  10.0f, -10.0f, 0.0f, 0.0f);
	pVertices[1] = CUSTOMVERTEX( 10.0f,  10.0f, -10.0f, 2.0f, 0.0f);
	pVertices[2] = CUSTOMVERTEX( 10.0f, -10.0f, -10.0f, 2.0f, 2.0f);
	pVertices[3] = CUSTOMVERTEX(-10.0f, -10.0f, -10.0f, 0.0f, 2.0f);

	// 背面顶点数据
	pVertices[4] = CUSTOMVERTEX( 10.0f,  10.0f, 10.0f, 0.0f, 0.0f);
	pVertices[5] = CUSTOMVERTEX(-10.0f,  10.0f, 10.0f, 2.0f, 0.0f);
	pVertices[6] = CUSTOMVERTEX(-10.0f, -10.0f, 10.0f, 2.0f, 2.0f);
	pVertices[7] = CUSTOMVERTEX( 10.0f, -10.0f, 10.0f, 0.0f, 2.0f);

	// 顶面顶点数据
	pVertices[8]  = CUSTOMVERTEX(-10.0f, 10.0f,  10.0f, 0.0f, 0.0f);
	pVertices[9]  = CUSTOMVERTEX( 10.0f, 10.0f,  10.0f, 2.0f, 0.0f);
	pVertices[10] = CUSTOMVERTEX( 10.0f, 10.0f, -10.0f, 2.0f, 2.0f);
	pVertices[11] = CUSTOMVERTEX(-10.0f, 10.0f, -10.0f, 0.0f, 2.0f);

	// 底面顶点数据
	pVertices[12] = CUSTOMVERTEX(-10.0f, -10.0f, -10.0f, 0.0f, 0.0f);
	pVertices[13] = CUSTOMVERTEX( 10.0f, -10.0f, -10.0f, 2.0f, 0.0f);
	pVertices[14] = CUSTOMVERTEX( 10.0f, -10.0f,  10.0f, 2.0f, 2.0f);
	pVertices[15] = CUSTOMVERTEX(-10.0f, -10.0f,  10.0f, 0.0f, 2.0f);

	// 左侧面顶点数据
	pVertices[16] = CUSTOMVERTEX(-10.0f,  10.0f,  10.0f, 0.0f, 0.0f);
	pVertices[17] = CUSTOMVERTEX(-10.0f,  10.0f, -10.0f, 1.0f, 0.0f);
	pVertices[18] = CUSTOMVERTEX(-10.0f, -10.0f, -10.0f, 1.0f, 1.0f);
	pVertices[19] = CUSTOMVERTEX(-10.0f, -10.0f,  10.0f, 0.0f, 1.0f);

	// 右侧面顶点数据
	pVertices[20] = CUSTOMVERTEX( 10.0f,  10.0f, -10.0f, 0.0f, 0.0f);
	pVertices[21] = CUSTOMVERTEX( 10.0f,  10.0f,  10.0f, 1.0f, 0.0f);
	pVertices[22] = CUSTOMVERTEX( 10.0f, -10.0f,  10.0f, 1.0f, 1.0f);
	pVertices[23] = CUSTOMVERTEX( 10.0f, -10.0f, -10.0f, 0.0f, 1.0f);

	g_pVertexBuffer->Unlock();

	// 填充索引数据
	WORD *pIndices = NULL;
	g_pIndexBuffer->Lock(0, 0, (void**)&pIndices, 0);

	// 正面索引数据
	pIndices[0] = 0; pIndices[1] = 1; pIndices[2] = 2;
	pIndices[3] = 0; pIndices[4] = 2; pIndices[5] = 3;

	// 背面索引数据
	pIndices[6] = 4; pIndices[7]  = 5; pIndices[8]  = 6;
	pIndices[9] = 4; pIndices[10] = 6; pIndices[11] = 7;

	// 顶面索引数据
	pIndices[12] = 8; pIndices[13] =  9; pIndices[14] = 10;
	pIndices[15] = 8; pIndices[16] = 10; pIndices[17] = 11;

	// 底面索引数据
	pIndices[18] = 12; pIndices[19] = 13; pIndices[20] = 14;
	pIndices[21] = 12; pIndices[22] = 14; pIndices[23] = 15;

	// 左侧面索引数据
	pIndices[24] = 16; pIndices[25] = 17; pIndices[26] = 18;
	pIndices[27] = 16; pIndices[28] = 18; pIndices[29] = 19;

	// 右侧面索引数据
	pIndices[30] = 20; pIndices[31] = 21; pIndices[32] = 22;
	pIndices[33] = 20; pIndices[34] = 22; pIndices[35] = 23;

	g_pIndexBuffer->Unlock();

	//--------------------------------------------------------------------------------------
	// 【纹理绘制四步曲之三】:纹理的创建
	//--------------------------------------------------------------------------------------
	// 创建纹理
	D3DXCreateTextureFromFileEx(g_pd3dDevice, L"pal5q.jpg", 0, 0, 6, 0, D3DFMT_X8R8G8B8, 
		D3DPOOL_MANAGED, D3DX_DEFAULT, D3DX_DEFAULT, 0xFF000000, 0, 0, &g_pMipTexture);

	// 设置材质
	D3DMATERIAL9 mtrl;
	::ZeroMemory(&mtrl, sizeof(mtrl));
	mtrl.Ambient  = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f);
	mtrl.Diffuse  = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f);
	mtrl.Specular = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f);
	g_pd3dDevice->SetMaterial(&mtrl);

	// 开始设置渲染状态
	g_pd3dDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW);   //开启背面消隐
	g_pd3dDevice->SetRenderState(D3DRS_AMBIENT, D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f)); //设置环境光

	//各向异性过滤
	g_pd3dDevice->SetSamplerState(0, D3DSAMP_MAXANISOTROPY, 3);
	g_pd3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_ANISOTROPIC);
	g_pd3dDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_ANISOTROPIC);

	//线性纹理过滤
	//		g_pd3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
	//	 	g_pd3dDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);

	// 最近点采样过滤
	// 		g_pd3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_POINT);
	// 		g_pd3dDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_POINT);

	// 渐进纹理过滤
	// 		g_pd3dDevice->SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR);
	// 		g_pd3dDevice->SetSamplerState(0, D3DSAMP_MAXMIPLEVEL, 16);
	return S_OK;
}
上面的代码中,我们在136~ 138 行设定纹理过滤模式为线性过滤。其他的三种过滤方式我们在136~ 146 行里依旧写了出来但是加上了注释, 大家想试试哪一种纹理过滤,就把哪一种过滤方式的注释去掉就可以了。另外观察44~78 行的代码与D3Ddemo10 的区别我们可以发现,我们只对四个面进行了多次的纹理重复填充,另外两个面依然是一张纹理独占着。
接着我们来看看Direct3D_Update() 函数改过后的代码:

//-----------------------------------【Direct3D_Update( )函数】--------------------------------
//	描述:不是即时渲染代码但是需要即时调用的,如按键后的坐标的更改,都放在这里
//--------------------------------------------------------------------------------------------------
void				Direct3D_Update( HWND hwnd)
{
	//使用DirectInput类读取数据
	g_pDInput->GetInput();


	// 根据键盘按键的按下,设置为纹理寻址方式
	if (g_pDInput->IsKeyDown(DIK_1))  //键盘上1键被按下
	{
		// 设置重复纹理寻址模式
		g_pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_WRAP);
		g_pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_WRAP);
	}
	if (g_pDInput->IsKeyDown(DIK_2)) //键盘上2键被按下
	{
		// 设置镜像纹理寻址模式
		g_pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_MIRROR);
		g_pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_MIRROR);
	}
	if (g_pDInput->IsKeyDown(DIK_3)) //键盘上3键被按下
	{
		// 设置夹取纹理寻址模式
		g_pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP);
		g_pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP);
	}
	if (g_pDInput->IsKeyDown(DIK_4)) //键盘上4键被按下
	{
		// 设置边框纹理寻址模式
		g_pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_BORDER);
		g_pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_BORDER);
	}

	// 按住鼠标左键并拖动,为平移操作
	static FLOAT fPosX = 0.0f, fPosY = 0.0f, fPosZ = 0.0f;

	if (g_pDInput->IsMouseButtonDown(0)) 
	{
		fPosX += (g_pDInput->MouseDX())*  0.08f;
		fPosY += (g_pDInput->MouseDY()) * -0.08f;
	}

	//鼠标滚轮,为观察点收缩操作
	fPosZ += (g_pDInput->MouseDZ())* 0.02f;

	// 平移物体
	if (g_pDInput->IsKeyDown(DIK_A)) fPosX -= 0.005f;
	if (g_pDInput->IsKeyDown(DIK_D)) fPosX += 0.005f;
	if (g_pDInput->IsKeyDown(DIK_W)) fPosY += 0.005f;
	if (g_pDInput->IsKeyDown(DIK_S)) fPosY -= 0.005f;


	D3DXMatrixTranslation(&g_matWorld, fPosX, fPosY, fPosZ);


	// 按住鼠标右键并拖动,为旋转操作
	static float fAngleX = D3DX_PI/6, fAngleY =D3DX_PI/6 ;

	if (g_pDInput->IsMouseButtonDown(1)) 
	{
		fAngleX += (g_pDInput->MouseDY())* -0.01f;
		fAngleY += (g_pDInput->MouseDX()) * -0.01f;
	}
	// 旋转物体
	if (g_pDInput->IsKeyDown(DIK_UP)) fAngleX += 0.005f;
	if (g_pDInput->IsKeyDown(DIK_DOWN)) fAngleX -= 0.005f;
	if (g_pDInput->IsKeyDown(DIK_LEFT)) fAngleY -= 0.005f;
	if (g_pDInput->IsKeyDown(DIK_RIGHT)) fAngleY += 0.005f;


	D3DXMATRIX Rx, Ry;
	D3DXMatrixRotationX(&Rx, fAngleX);
	D3DXMatrixRotationY(&Ry, fAngleY);

	g_matWorld = Rx * Ry * g_matWorld; //算出最终的世界矩阵
	g_pd3dDevice->SetTransform(D3DTS_WORLD, &g_matWorld);//设置世界矩阵
	Matrix_Set();
}
接着看一下运行截图,我们可以拖动鼠标, 按键盘上的一些按键, 以不同的角度观察不同模式的寻址方式:
第16章 起舞不落幕——与纹理映射的华丽邂逅_第10张图片   第16章 起舞不落幕——与纹理映射的华丽邂逅_第11张图片

第16章 起舞不落幕——与纹理映射的华丽邂逅_第12张图片  第16章 起舞不落幕——与纹理映射的华丽邂逅_第13张图片

16.9 章节小憩

学完了这章纹理映射的内容,可以选取自己喜爱的人物或者风景图片作为纹理素材, 在学习过程中给自己一个好心情。









你可能感兴趣的:(Windows游戏开发)