【Visual C++】游戏开发笔记四十三 浅墨DirectX教程十一 为三维世界添彩:纹理映射技术(二)


 

本系列文章由zhmxy555(毛星云)编写,转载请注明出处。  

文章链接: http://blog.csdn.net/zhmxy555/article/details/8567848

作者:毛星云(浅墨)    邮箱: [email protected]   

   


本篇文章里,我们首先对Direct3D之中固定功能流水线中的纹理映射相关的知识进行了详尽的剖析,然后我们将DirectInput这套游戏输入控制领域一手遮天的API用一个C++类进行了抽象和封装,这样之后我们用起来会显得非常便捷。希望在学习抽象和封装这套API的时候,大家能领悟到那些只能意会不可言传的宝贵编程思想。

 

文章末尾依旧是提供文章配套详细注释的demo源代码的欣赏,并在文章末尾给出了源代码的下载地址。

 

 

 

一、纹理映射知识回顾



 因为这是纹理映射技术讲解的下半部分文章,且中间隔了一次在讲DirectInput,所以我们有必要把纹理映射知识再回顾一下。

首先我们来概括回顾一下《【Visual C++】游戏开发笔记四十一 浅墨DirectX教程之九 为三维世界添彩:纹理映射技术(一)》中讲过的知识点。

纹理是所有游戏中永恒的核心部分,无论是2D还是3D游戏。一言以蔽之,纹理映射是一种将2D图像映射到3D物体上的技术,可以让贴上纹理的表面展现出化腐朽为神奇的逼真显示效果。比如一块用砖块砌成的墙,更合适的做法是用简单的平面形状表面贴上砖块的纹理图像,而不是傻傻地用大量复杂的几何图形来模拟砖块坑洼的表面形态。纹理在计算机图形学中存在的意义就是模拟出复杂的表面图形和形状,而这种近乎逼真的模拟效果是单单用几何图形无法比拟的。通过这幅图可以非常容易地理解纹理映射的概念:

【Visual C++】游戏开发笔记四十三 浅墨DirectX教程十一 为三维世界添彩:纹理映射技术(二)_第1张图片

 

接着我们来复习一下【Visual C++】游戏开发笔记四十一 浅墨DirectX教程之九 为三维世界添彩:纹理映射技术(一)中讲到的核心知识点,使用纹理的四步曲:


 纹理映射使用四步曲,简明扼要20个字:

顶点的定义,顶点的访问,纹理的创建,纹理的启用。

 

四步曲之一、顶点的定义。

FVF顶点格式的定义,最多八层纹理


四步曲之二、顶点的访问。

访问顶点缓存内容,纹理坐标的填充


四步曲之三、纹理的创建。

一个对象:IDIRECT3DTEXTURE9接口对象,

一个方法:D3DXCreateTextureFromFile方法


四步曲之四、纹理的启用。

一个方法:SetTexture方法


 

复习完前置知识,下面我们就来看看本篇文章中的故事会有怎样的美好。





二、四大纹理过滤方式精讲



我们知道,当Direct3D渲染一个图元或者三维图形时,必须将它们通过坐标变换映射到二维屏幕之上。而当我们使用纹理来进行辅助渲染的时候,Direct3D就必须使用该纹理为二维图像上的每个像素进行着色。这里的每个像素都包含一个来自纹理的颜色值,而从纹理中为每个像素获取颜色的过程,就是所谓的纹理过滤(texture filtering)了。

 

大多数情况下,屏幕显示的图形大小与纹理贴图大小是不同的。换句话说的话,这个纹理将被映射到一个比它大或者小的图元的图像上。这样纹理常常会被放大或者缩小。对纹理的放大会造成许多像素被映射到同一个纹理元素上,这样图形渲染的结果就会有色块的感觉。缩小一个纹理意味着一个像素被映射到许多纹理元素上,图形看上去会闪烁或者失真抑或是有锯齿。而为了解决这些问题,可以将相关纹理元素的颜色融合到一个像素之上,而如何将多个纹理元素的颜色融合到一个像素就取决于纹理过滤的方式了。

 

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

 

1.最近点采样过滤(nearst-pointsampling)

 

2.线性纹理过滤(lineartexture filtering)

 

3.各项异性过滤(anisotropictexture filtering)

 

4.多级渐进过滤(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;


这个枚举体中的D3DSAMP_MAGFILTER、  D3DSAMP_MINFILTER 、  D3DSAMP_MIPFILTER、D3DSAMP_MIPMAPLODBIAS、  D3DSAMP_MAXMIPLEVEL、D3DSAMP_MAXANISOTROPY用于控制纹理过滤的方式,具体用法我们下面会讲到的。

 

■ 第三个参数,DWORD类型的Value,这个参数和第二个参数Type联系很紧密,就是对第二个参数指定的属性进行值的设置的。

 

 

 

Ⅰ、最近点采样过滤


最近采样点过滤可谓是四种纹理过滤方式中速度最快但是效果最差的过滤方式了。Direct3D计算得到的纹理过滤元素通常都是一个浮点值,当使用最近点采样时,Direct3D会复制与这个浮点值地址最接近的整数地址的纹理元素的颜色。

设置最近点采样的具体方法其实很简单,就是调用我们上面提到的IDirect3DDevice9::SetSamplerState函数,通过第二个参数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);

     

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

 

 

Ⅱ、线性纹理过滤


线性纹理过滤是目前使用最广泛的纹理过滤方式。与最近点采用方式相比,能有效地提高图像的显示质量,且对系统性能的影响不大。线性纹理过滤取得与计算得到的纹理元素的浮点地址最接近的上下左右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);


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

 

Ⅲ. 各向异性纹理过滤


首先需要知道一个常识,在很多时候,三维物体的表面不可能是完全平面的,当三维物体的表面和投影平面不平行时,它在屏幕上的投影会有拉长和扭曲的现象,这种现象称为各向异性(Anisotropic)。当一个各项异性的图元映射到纹理元素上时,它的形状就会发生扭曲。而Direct3D会根据屏幕像素反向转换到纹理元素的延长度,来决定各项异性的程度。

关于使用方法,依然是用SetSamplerState,与最近点采样过滤和线性纹理过滤不同的地方,除了第三个参数设为我们各向异性纹理过滤的专属D3DTEXF_ANISOTROPIC外,另外还需专门设置一下最大各项异性程度值。还是用这个货SetSamplerState,第一个参数取需要进行设置的纹理层数,第二个参数设为D3DSAMP_MAXANISOTROPY,第三个参数设为大于一的任意值就行了。

下面是一个实例,对第1层纹理,设置最大各项异性值为3的各向异性纹理过滤方式:

 

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

 


Ⅳ、多级渐进纹理过滤


首先需要知道的是,这个多级渐进纹理过滤通常不是独当一面的纹理过滤方式,他需要找个伴,也就是需要和我们上面讲到的四大纹理过滤方式其他三种之一结合使用。

多级渐进纹理就是由一组分辨率逐渐降低的纹理序列组成的,每一级纹理的宽度和高度都是上一级纹理的宽度和高度的一半。需要注意,纹理的宽度和高度并不一定要相等。也就是说,这些图形并不一定要是正方形。

Direct3D在纹理映射时,自动选择一幅与物体大小最接近的纹理进行渲染。当物体离投影面很远时,Direct3D会选择一张尺寸较小、分辨率较低的纹理进行渲染:当物体离投影面较近时,Direct3D则会选择一张尺寸较大,分辨率较高的纹理进行渲染。就像这样:

 【Visual C++】游戏开发笔记四十三 浅墨DirectX教程十一 为三维世界添彩:纹理映射技术(二)_第2张图片【Visual C++】游戏开发笔记四十三 浅墨DirectX教程十一 为三维世界添彩:纹理映射技术(二)_第3张图片【Visual C++】游戏开发笔记四十三 浅墨DirectX教程十一 为三维世界添彩:纹理映射技术(二)_第4张图片

上面这四张图从左到右分别是512x512,256x256,128x128,64x64分辨率的纹理链,为了排版美观,浅墨在这里对图片整体进行了适当的缩小。

好了,下面继续讲。

Direct3D将多级渐进纹理描绘成一系列相互联系的表面,当创建一个渐进纹理时,分辨率最高的纹理位于开始处,并与下一级纹理相互联系,直到分辨率最低的一级纹理。

Direct3D能够估计出多级渐进纹理链中哪幅纹理的分辨率最接近想要的输出结果,然后能将像素映射到纹理空间。而且当最终显示的图形大小介于任意两级纹理图形之间时,Direct3D能智能地取得两级纹理的相应元素进行混合后显示出来。

多级渐进纹理过滤能够有效地提高图形渲染速度,当物体离投影平面较远的时候,Direct3D会选择一张尺寸较小的纹理进行渲染,而无需经过复杂的诸如各项异性纹理过滤。并且由于这时纹理需要的显存比不使用多级渐进纹理要小,因此还能有效地减小纹理载入显存的时间。

对于多级渐进纹理的生成,使用我们之前讲创建纹理的时候提到过的一个方法,就是D3DXCreateTextureFromFileEx方法:

 

HRESULT D3DXCreateTextureFromFileEx(
 _In_     LPDIRECT3DDEVICE9pDevice,   //D3D设备接口对象
 _In_     LPCTSTR pSrcFile,   //纹理贴图的文件地址
 _In_     UINT Width,        //纹理宽度,为0的话表示使用贴图的宽度
 _In_     UINT Height,       //纹理高度,为0的话表示使用贴图的高度
 _In_     UINT MipLevels,         //生成的渐进纹理的级数
 _In_     DWORD Usage,          //用法的标志,通常设为0
 _In_     D3DFORMAT Format, //纹理贴图的格式
 _In_     D3DPOOL Pool,         //保存纹理的方式
 _In_     DWORD Filter,        //纹理过滤方式
 _In_     DWORD MipFilter,     //生成纹理序列的过滤方式
  _In_     D3DCOLOR ColorKey, //替换Alpha值得颜色值
 _Inout_  D3DXIMAGE_INFO *pSrcInfo,  //通常设为NULL就可以了
 _Out_    PALETTEENTRY *pPalette,   //调色板的地址,通常设为NULL即可
 _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);


下面讲一下多级渐进的使用方法。我们在这小节开头一开始就提到过,多级渐进纹理过滤通常不是独当一面的纹理过滤方式,他需要找个伴,也就是最好和我们上面讲到的四大纹理过滤方式其他三种之一结合使用。首先对于多级渐近纹理过滤,还是用SetSamplerState函数,第一个参数依旧是设置纹理层数的序号,第二个参数设为D3DSAMP_MIPFILTER,第三个参数设置为在相邻纹理级之间的过滤方式,取枚举类型D3DTEXTUREFILTERTYPE的任意值。比如下面这个段代码就把相邻纹理级之间的过滤方式设置为线性过滤。

 

   //       g_pd3dDevice->SetSamplerState(0,D3DSAMP_MIPFILTER, D3DTEXF_LINEAR);

   

如果把上面第三个参数设为D3DTEXF_NONE的话,就表示一直使用最高一级的纹理。而设为D3DTEXF_POINT则Direct3D就会只使用与图元大小最匹配的一级纹理;而设为D3DTEXF_LINEAR的话,Direct3D就会与图元的大小最匹配的两级纹理以线性方式混合。

另外,我们还需要设置一下实际渲染时纹理过滤的最大级数,需要将第二个参数设为D3DSAMP_MAXMIPLEVEL,而第三个参数设为实际渲染时纹理过滤的最大级数。下面这句代码就是设置了纹理层5的最大多级纹理过滤级数为8:


 //       g_pd3dDevice->SetSamplerState(0,D3DSAMP_MAXMIPLEVEL, 8);

另外,还可以通过IDirect3DDevice9:: SetSamplerState的第二个参数设为D3DSAMP_MIPLODBIAS,来设置多级纹理映射级数的偏移值。

好了,关于线性纹理过滤相关的讲了这么多,我们来串联一下,给一个综合起来的实例,线性纹理过滤配合多级渐进纹理过滤的实例:

 

 //首先开线性纹理过滤
            	g_pd3dDevice->SetSamplerState(0,D3DSAMP_MIPFILTER, D3DTEXF_LINEAR);
//然后设置多级渐进纹理过滤
                 g_pd3dDevice->SetSamplerState(0,D3DSAMP_MIPFILTER, D3DTEXF_LINEAR);
                 g_pd3dDevice->SetSamplerState(0,D3DSAMP_MAXMIPLEVEL, 15);


 

 接着我们来看看四大纹理寻址方式。





三、四大纹理寻址方式精讲



Direct3D应用程序可以为任何图元的任何顶点指定纹理坐标。通常使用的u、v纹理坐标的取值范围是[0.0,1.0]。但是,这显然不能满足我们多变的实际运用。

这时候就需要用到纹理寻址模式的设置,来控制Direct3D如何控制[0.0,1.0]坐标范围之外的纹理坐标值了。

Direct3D中有四种纹理模式供我们选择,来处理超出[0.0,1.0]范围之外的纹理映射情况。他们分别是重复寻址模式(wrap texture address mode),镜像纹理寻址模式(mirrortexture address mode)、夹取寻址模式(clamp textrue address mode)和边框颜色纹理寻址(border color textrue address mode)。

我们来分别进行介绍。

 

 

Ⅰ、重复寻址模式


首先需要注意,重复寻址是Direct3D中的默认寻址模式,这种寻址模式允许在每个整数连接点出重复上一个整数的纹理。

比如,我们创建了一个正方形图元,而指定4个顶点的纹理坐标分别为(0.0,0.0),(0.0,2.0),(2.0,2.0),(2.0,0.0),因为重复寻址为Direct3D中的默认寻址模式,所以我们不用额外设置,那么Direct3D就会在u,v方向各复制两遍原始纹理,效果就像这样:

【Visual C++】游戏开发笔记四十三 浅墨DirectX教程十一 为三维世界添彩:纹理映射技术(二)_第5张图片

 

如果我们硬是要手动启用这种重复寻址模式,或者用了其他的寻址模式想换回来的话,还是用那个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;

 


Ⅱ、镜像寻址模式



使用镜像纹理寻址模式是,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);
      }

那么得到的效果就是如下:

【Visual C++】游戏开发笔记四十三 浅墨DirectX教程十一 为三维世界添彩:纹理映射技术(二)_第6张图片


 


Ⅲ、夹取寻址模式



夹取纹理寻址模式将纹理坐标夹取在[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);

然后得到的效果就是如下图:

 【Visual C++】游戏开发笔记四十三 浅墨DirectX教程十一 为三维世界添彩:纹理映射技术(二)_第7张图片



Ⅳ、边框颜色寻址



边框颜色寻址模式就是在[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);
 

然后得到的效果就是如下图:

【Visual C++】游戏开发笔记四十三 浅墨DirectX教程十一 为三维世界添彩:纹理映射技术(二)_第8张图片

 

好了,纹理映射的讲解总算是告一段落了,另外还有一个纹理包装没有讲到,暂且没有大碍,等到后面有用到的时候再提一下吧。

 

 



四、纹理映射知识总结



本着把后书读薄的精神,纹理映射相关知识也讲了两万字了,其实总结起来,我们也就讲了三方面的内容,总结如下:

 

1.纹理映射四步曲

 

 纹理映射使用四步曲,简明扼要20个字:

顶点的定义,顶点的访问,纹理的创建,纹理的启用。

 

四步曲之一、顶点的定义。

FVF顶点格式的定义,最多八层纹理


四步曲之二、顶点的访问。

访问顶点缓存内容,纹理坐标的填充


四步曲之三、纹理的创建。

一个对象:IDIRECT3DTEXTURE9接口对象,

一个方法:D3DXCreateTextureFromFile方法


四步曲之四、纹理的启用。

一个方法:SetTexture方法


相关代码:

//-------------------------------------------------------------------------------------- 
//【纹理绘制四步曲之一】:顶点的定义               
//-------------------------------------------------------------------------------------- 
  
struct CUSTOMVERTEX  
{ 
      FLOAT_x, _y, _z;               // 顶点的位置 
      FLOAT_u, _v;                   // 纹理坐标 
      CUSTOMVERTEX(FLOATx, 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, sizeof(CUSTOMVERTEX),(void**)&pVertices, 0 ) )) 
                     returnE_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); 
  
/*纹理设置完之后,就开始绘制,用DrawIndexedPrimitive,DrawSubset 之类的函数*/ 
g_pd3dDevice->EndScene();                       // 结束绘制 


 

2.四大纹理过滤模式



以代码为载体,咱们就贴启用各种模式的代码了。

1.最近点采样过滤(nearst-pointsampling)

// 最近点采样过滤
      //       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.四大纹理寻址模式


1.重复寻址模式(wraptexture address mode)

// 设置重复纹理寻址模式
           g_pd3dDevice->SetSamplerState(0,D3DSAMP_ADDRESSU, D3DTADDRESS_WRAP);
           g_pd3dDevice->SetSamplerState(0,D3DSAMP_ADDRESSV, D3DTADDRESS_WRAP);


2.镜像纹理寻址模式(mirrortextureaddress mode)

g_pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_MIRROR);
		g_pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_MIRROR);
	}


 

3.夹取寻址模式(clamptextrue 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);


 

 


五、对DirectInput的抽象和封装


Ⅰ、对DirectInput的抽象和封装于类中的实现


首先,浅墨希望大家在学习抽象和封装这套API的过程中,在这区区一百来行代码的字里行间里,能领悟到那些只能意会不可言传的宝贵编程思想。

 

上篇文章中我们系统讲解了DirectInput这套API,鉴于下面的讲解不会仔细重复讲解已经讲过的内容,如果大家对DirectInput的使用还是有些生疏的话,请移步《【Visual C++】游戏开发笔记四十二 浅墨DirectX教程之十 游戏输入控制利器:DirectInput专场》再温习一下吧。

对某某内容进行抽象和封装这种思想大家在正式的游戏开发中会经常碰到。其实说白了,游戏引擎就是在做这类工作,把很多内容很多接口都封装在类中,我们要使用游戏引擎的话,直接用已经封装好的类就可以了。

而我们在游戏开发过程中,经常也是把某些相似度很高或者功能性很明确的内容封装在类中,方便后续使用的调用,也把功能都模块化了,显得步步为营,有章可循。比如后面我们还会介绍把一个摄像机封装在类中,把地形系统封装在一个类中,把天空盒系统封装在一个类中,把粒子系统封装在一个类中,把处理骨骼动画的代码封装在一个类中等等。

 

好了,下面我们开始介绍封装的实现细节吧。浅墨每次在写一个类的时候,都是先按照这里类的属性先定义好成员变量,然后围绕着这些成员变量定义一些函数,最后再检查一下除了围绕着成员变量的函数之外,还有没有额外需要定义的函数,这样函数大体的框架就打好了。最后补充一下构造函数和析构函数中的代码,实现一下具体函数的代码,增增减减相关代码。通过这个四步曲流程,一个类就写好了。

 

而对于本次需要写的DInputClass类,我们经过一番思考,可以写出轮廓如下:

 

 

//DInputClass类定义开始
class DInputClass
{
private:
      IDirectInput8                         * m_pDirectInput;   //IDirectInput8接口对象
      IDirectInputDevice8         * m_KeyboardDevice;  //键盘设备接口对象
      char                                            m_keyBuffer[256];       //用于键盘键值存储的数组
 
      IDirectInputDevice8         *m_MouseDevice;      //鼠标设备接口对象
      DIMOUSESTATE                    m_MouseState;                //用于鼠标键值存储的一个结构体
 
public:
      HRESULT         Init( HWND hWnd,HINSTANCEhInstance,DWORD keyboardCoopFlags, DWORD mouseCoopFlags ); //初始化DirectInput键盘及鼠标输入设备
      void                      GetInput();   //用于获取输入信息的函数
      bool                     IsKeyDown(int iKey);   //判断键盘上某键是否按下
     
      bool                     IsMouseButtonDown(intbutton);  //判断鼠标按键是否被按下
      float                     MouseDX();   //返回鼠标的X轴坐标值
      float                     MouseDY();     //返回鼠标的Y轴坐标值
      float                     MouseDZ();     //返回鼠标的Z轴坐标值
 
 
public:
      DInputClass(void);      //构造函数
      ~DInputClass(void);    //析构函数
};


对于这个DInputClass类,首先肯定要一个指向IDirectInput8接口的指针m_pDirectInput,然后是键盘设备对象m_KeyboardDevice,存储键盘内容的字符数组m_keyBuffer[256],鼠标设备对象m_MouseDevice,最后是用于存储鼠标坐标和键值相关内容的结构体m_MouseState。

而围绕着这五个成员变量,我们来看看需要哪些成员函数。

首先肯定是有构造函数和析构函数的。养成一个好的习惯,我们在构造函数中对成员变量都附一个初值,而析构函数中记得COM对象和指针,该释放的都要释放掉。

构造函数中代码如下:

//-----------------------------------------------------------------------------
// Desc: 构造函数
//-----------------------------------------------------------------------------
DInputClass::DInputClass()
{
      m_pDirectInput= NULL;
      m_KeyboardDevice= NULL;
      ZeroMemory(m_keyBuffer,sizeof(char)*256);
      m_MouseDevice=NULL;
      ZeroMemory(&m_MouseState,sizeof(m_MouseState));
}


然后,我们定义了一个Init函数,用于完成DirectInput初始化的四步曲,在里面我们完成了键盘和鼠标的双双初始化。注意这里我们需要把WinMain函数中的HWND和HINSTANCE这两个参数传进来,因为在DirectInput初始化中需要用到这两个参数的,这样才能确保我们的DirectInput与主窗口的关联。然后我们还设置了两个参数keyboardCoopFlags,和mouseCoopFlags,这样我们可以在初始化时在外部自定义鼠标和键盘的协作级别了。Init函数的实现代码就是如下:

//-----------------------------------------------------------------------------
// Name:DInputClass::Init()
// Desc: 初始化DirectInput键盘及鼠标输入设备
//-----------------------------------------------------------------------------
HRESULT DInputClass::Init( HWNDhWnd,HINSTANCE hInstance,DWORD keyboardCoopFlags, DWORD mouseCoopFlags )
{
      HRESULThr;
      //初始化一个IDirectInput8接口对象
      HR(DirectInput8Create(hInstance, DIRECTINPUT_VERSION,
           IID_IDirectInput8,(void**)&m_pDirectInput,NULL));
 
      //进行键盘设备的初始化
      HR(m_pDirectInput->CreateDevice( GUID_SysKeyboard, &m_KeyboardDevice, NULL));
      HR(m_KeyboardDevice->SetCooperativeLevel( hWnd, keyboardCoopFlags));
      HR(m_KeyboardDevice->SetDataFormat( &c_dfDIKeyboard ));
      HR(m_KeyboardDevice->Acquire( ));
      HR(m_KeyboardDevice->Poll( ));
 
      //进行鼠标设备的初始化
      HR(m_pDirectInput->CreateDevice( GUID_SysMouse, &m_MouseDevice, NULL ));
      HR(m_MouseDevice->SetCooperativeLevel( hWnd,mouseCoopFlags));
      HR(m_MouseDevice->SetDataFormat( &c_dfDIMouse ));
      HR(m_MouseDevice->Acquire( ));
      HR(m_KeyboardDevice->Poll( ));
 
      returnS_OK;
}


初始化完成,下面就是读取鼠标和键盘消息了。对应上篇文章中使用的全局函数Read_Device(),本次我们定义了GetInput()函数,其内部实现思想和代码和Read_Device()基本一致,也就是不断对设备进行获取,设备丢失就重新获取:

//-----------------------------------------------------------------------------
// Name:DInputClass::GetInput()
// Desc: 用于获取输入信息的函数
//-----------------------------------------------------------------------------
void DInputClass::GetInput()
{
      HRESULThr = m_KeyboardDevice->GetDeviceState(sizeof(m_keyBuffer),(void**)&m_keyBuffer);
      //获取键盘输入消息
      if(hr)
      {
           m_KeyboardDevice->Acquire(); 
           m_KeyboardDevice->GetDeviceState(sizeof(m_keyBuffer),(LPVOID)m_keyBuffer );
      }
 
      hr= m_MouseDevice->GetDeviceState(sizeof(DIMOUSESTATE),(void**)&m_MouseState);
      //获取鼠标输入消息
      if(hr)
      {
           m_MouseDevice->Acquire();
           m_MouseDevice->GetDeviceState(sizeof(DIMOUSESTATE), (void**)&m_MouseState);
      }
}


剩下的几个函数就好说了,就是对键盘值进行判断,对鼠标按键进行判断,以及返回鼠标指针的当前坐标,实现代码如下:

//-----------------------------------------------------------------------------
// Name:DInputClass::IsKeyDown()
// Desc: 判断键盘上某个键是否按下
//-----------------------------------------------------------------------------
bool DInputClass::IsKeyDown(int iKey)
{
      if(m_keyBuffer[iKey]& 0x80)
           returntrue;
      else
           returnfalse;
}
 
 
//-----------------------------------------------------------------------------
// Name:DInputClass::IsMouseButtonDown()
// Desc: 判断鼠标上某键是否按下
//-----------------------------------------------------------------------------
bool DInputClass::IsMouseButtonDown(intbutton)
{
      return(m_MouseState.rgbButtons[button] & 0x80) != 0;
}
 
//-----------------------------------------------------------------------------
// Name:DInputClass::MouseDX
// Desc: 返回鼠标指针的X轴坐标值
//-----------------------------------------------------------------------------
float DInputClass::MouseDX()
{
      return(float)m_MouseState.lX;
}
 
//-----------------------------------------------------------------------------
// Name:DInputClass::MouseDY
// Desc: 返回鼠标指针的Y轴坐标值
//-----------------------------------------------------------------------------
float DInputClass::MouseDY()
{
      return(float)m_MouseState.lY;
}
 
//-----------------------------------------------------------------------------
// Name:DInputClass::MouseDZ
// Desc: 返回鼠标指针的Z轴坐标值(滚轮)
//-----------------------------------------------------------------------------
float DInputClass::MouseDZ()
{
      return(float)m_MouseState.lZ;
}


 

当然,不能忘了我们析构函数中需要释放掉COM接口对象和指针:

//-----------------------------------------------------------------------------
// Desc: 析构函数
//-----------------------------------------------------------------------------
DInputClass::~DInputClass(void)
{
      if(m_KeyboardDevice!= NULL)
           m_KeyboardDevice->Unacquire();
      if(m_MouseDevice!= NULL)
           m_MouseDevice->Unacquire();
      SAFE_RELEASE(m_KeyboardDevice);
      SAFE_RELEASE(m_MouseDevice);
      SAFE_RELEASE(m_pDirectInput);
}

 

这样,DirectInput就被我们封装在了一个类中了。

 


Ⅱ.对于抽象好的DirectInput类的使用



之后我们要使用的话,就显得非常方便。

首先定义一个类对象:

DInputClass*                                          g_pDInput = NULL;         //一个DInputClass类的指针


然后在适当的地方进行初始化:

 

//进行DirectInput类的初始化
      g_pDInput= new DInputClass();
      g_pDInput->Init(hwnd,hInstance,DISCL_FOREGROUND| DISCL_NONEXCLUSIVE,DISCL_FOREGROUND | DISCL_NONEXCLUSIVE);

 

接着在把进行输入数据读取的函数Init放在适当的地方(Update函数中):

 

  //使用DirectInput类读取数据
      g_pDInput->GetInput();

  

最后针对要判断的按键,进行一通IsKeyDown,IsMouseButtonDown,MouseDX,MouseDY, MouseDZ的调用就OK了。比如:

if (g_pDInput->IsKeyDown(DIK_2)) //键盘上2键被按下
	{
		// 设置镜像纹理寻址模式
		g_pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_MIRROR);
		g_pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_MIRROR);
	}


另外再提一点,如果准备把DirectInput封装在类中,且协作级别需要取到前台模式或者独占模式的话,则DirectInput的初始化最好放在UpdateWindow(hwnd)之后进行。不然因为前台模式或者独占模式的霸道特性,可能引起hWnd的访问出问题,从而照成DirectInput甚至Direct3D初始化的失败。




 

六、详细注释的配套源代码欣赏



首先友情提醒,源代码的下载链接在文章末尾处。

 本篇文章配套的demo,大家给大家准备的是纹理寻址模式相关的内容,通过键盘上1,2,3,4个键的按下,在四大纹理寻址模式之间进行切换。(因为考虑到纹理过滤模式显示效果不太明显,而纹理寻址模式的显示效果显著)

从本篇文章的配套源代码开始,因为开始对某些功能用类进行封装了,所以之后的源代码就不单单是一个面向过程的main.cpp文件了,而是配套着有了对应类的头文件的源文件,以及我们另外写的一个D3DUtil.h文件。这个D3DUtil.h主要用于公共辅助宏的定义。首先我们来看一下它的实现代码:

 

 

1.D3DUtil.h

 

//*****************************************************************************************
// Desc: D3DUtil.h头文件,用于公共辅助宏的定义
// 2013年 2月03日  Create by 浅墨 
//*****************************************************************************************

#pragma once


#ifndef HR
#define HR(x)    { hr = x; if( FAILED(hr) ) { return hr; } }         //自定义一个HR宏,方便执行错误的返回
#endif

#ifndef SAFE_DELETE					
#define SAFE_DELETE(p)       { if(p) { delete (p);     (p)=NULL; } }       //自定义一个SAFE_RELEASE()宏,便于指针资源的释放
#endif    

#ifndef SAFE_RELEASE			
#define SAFE_RELEASE(p)      { if(p) { (p)->Release(); (p)=NULL; } }     //自定义一个SAFE_RELEASE()宏,便于COM资源的释放
#endif



 

然后是封装了DirectInput的类DInputClass的头文件:

2.DirectInputClass.h

//=============================================================================
// Name: DirectInputClass.h
//	Des: 封装了DirectInput键盘输入处理类的头文件
// 2013年 2月03日  Create by 浅墨 
//=============================================================================
#pragma once
#include "D3DUtil.h"
#define DIRECTINPUT_VERSION 0x0800
#include <dinput.h>

//DInputClass类定义开始
class DInputClass
{
private:
	IDirectInput8					* m_pDirectInput;   //IDirectInput8接口对象
	IDirectInputDevice8		* m_KeyboardDevice;  //键盘设备接口对象
	char								m_keyBuffer[256];       //用于键盘键值存储的数组

	IDirectInputDevice8		*m_MouseDevice;      //鼠标设备接口对象
	DIMOUSESTATE				m_MouseState;			//用于鼠标键值存储的一个结构体

public:
	HRESULT		Init( HWND hWnd,HINSTANCE hInstance,DWORD keyboardCoopFlags, DWORD mouseCoopFlags ); //初始化DirectInput键盘及鼠标输入设备
	void				GetInput();   //用于获取输入信息的函数
	bool				IsKeyDown(int iKey);   //判断键盘上某键是否按下
	
	bool				IsMouseButtonDown(int button);  //判断鼠标按键是否被按下
	float				MouseDX();   //返回鼠标的X轴坐标值
	float				MouseDY();	//返回鼠标的Y轴坐标值
	float				MouseDZ();	//返回鼠标的Z轴坐标值


public:
	DInputClass(void);		//构造函数
	~DInputClass(void);	//析构函数
};



 

接着是封装了DirectInput的类DInputClass的源文件:


3.DirectInputClass.cpp


//=============================================================================
// Desc: DirectInput键盘输入处理类源文件
// 2013年 2月03日  Create by 浅墨 
//=============================================================================
#include "DirectInputClass.h"


//-----------------------------------------------------------------------------
// Desc: 构造函数
//-----------------------------------------------------------------------------
DInputClass::DInputClass()
{
	m_pDirectInput = NULL;
	m_KeyboardDevice = NULL;
	ZeroMemory(m_keyBuffer,sizeof(char)*256);
	m_MouseDevice= NULL;
	ZeroMemory(&m_MouseState, sizeof(m_MouseState));
}


//-----------------------------------------------------------------------------
// Name:DInputClass::Init()
// Desc: 初始化DirectInput键盘及鼠标输入设备
//-----------------------------------------------------------------------------
HRESULT DInputClass::Init( HWND hWnd,HINSTANCE hInstance,DWORD keyboardCoopFlags, DWORD mouseCoopFlags )
{
	HRESULT hr;
	//初始化一个IDirectInput8接口对象
	HR(DirectInput8Create( hInstance, DIRECTINPUT_VERSION, 
		IID_IDirectInput8,(void**)&m_pDirectInput,NULL ));

	//进行键盘设备的初始化
	HR( m_pDirectInput->CreateDevice( GUID_SysKeyboard, &m_KeyboardDevice, NULL ));
	HR( m_KeyboardDevice->SetCooperativeLevel( hWnd, keyboardCoopFlags));
	HR( m_KeyboardDevice->SetDataFormat( &c_dfDIKeyboard ));
	HR( m_KeyboardDevice->Acquire( ));
	HR( m_KeyboardDevice->Poll( ));

	//进行鼠标设备的初始化
	HR( m_pDirectInput->CreateDevice( GUID_SysMouse, &m_MouseDevice, NULL ));
	HR( m_MouseDevice->SetCooperativeLevel( hWnd,mouseCoopFlags));
	HR( m_MouseDevice->SetDataFormat( &c_dfDIMouse ));
	HR( m_MouseDevice->Acquire( ));
	HR( m_KeyboardDevice->Poll( ));

	return S_OK;
}


//-----------------------------------------------------------------------------
// Name:DInputClass::GetInput()
// Desc: 用于获取输入信息的函数
//-----------------------------------------------------------------------------
void DInputClass::GetInput()
{
	HRESULT hr = m_KeyboardDevice->GetDeviceState(sizeof(m_keyBuffer), (void**)&m_keyBuffer); 
	//获取键盘输入消息
	if(hr)
	{
		m_KeyboardDevice->Acquire();  
		m_KeyboardDevice->GetDeviceState( sizeof(m_keyBuffer),(LPVOID)m_keyBuffer );
	}

	hr = m_MouseDevice->GetDeviceState(sizeof(DIMOUSESTATE), (void**)&m_MouseState); 
	//获取鼠标输入消息
	if(hr)
	{
		m_MouseDevice->Acquire();
		m_MouseDevice->GetDeviceState( sizeof(DIMOUSESTATE), (void**)&m_MouseState);
	}
}

//-----------------------------------------------------------------------------
// Name:DInputClass::IsKeyDown()
// Desc: 判断键盘上某个键是否按下
//-----------------------------------------------------------------------------
bool DInputClass::IsKeyDown(int iKey)
{
	if(m_keyBuffer[iKey] & 0x80)
		return true;
	else
		return false;
}


//-----------------------------------------------------------------------------
// Name:DInputClass::IsMouseButtonDown()
// Desc: 判断鼠标上某键是否按下
//-----------------------------------------------------------------------------
bool DInputClass::IsMouseButtonDown(int button)
{
	return (m_MouseState.rgbButtons[button] & 0x80) != 0;
}

//-----------------------------------------------------------------------------
// Name:DInputClass::MouseDX
// Desc: 返回鼠标指针的X轴坐标值
//-----------------------------------------------------------------------------
float DInputClass::MouseDX()
{
	return (float)m_MouseState.lX;
}

//-----------------------------------------------------------------------------
// Name:DInputClass::MouseDY
// Desc: 返回鼠标指针的Y轴坐标值
//-----------------------------------------------------------------------------
float DInputClass::MouseDY()
{
	return (float)m_MouseState.lY;
}

//-----------------------------------------------------------------------------
// Name:DInputClass::MouseDZ
// Desc: 返回鼠标指针的Z轴坐标值(滚轮)
//-----------------------------------------------------------------------------
float DInputClass::MouseDZ()
{
	return (float)m_MouseState.lZ;
}




//-----------------------------------------------------------------------------
// Desc: 析构函数
//-----------------------------------------------------------------------------
DInputClass::~DInputClass(void)
{
	if(m_KeyboardDevice != NULL)
		m_KeyboardDevice->Unacquire();
	if(m_MouseDevice != NULL)
		m_MouseDevice->Unacquire();
	SAFE_RELEASE(m_KeyboardDevice);
	SAFE_RELEASE(m_MouseDevice);
	SAFE_RELEASE(m_pDirectInput);
}


 

 

最后才是主要的代码文件:


4.main.cpp

//*****************************************************************************************
//
//【Visual C++】游戏开发笔记系列配套源码四十三 浅墨DirectX教程十一 为三维世界添彩:纹理映射技术(二)
//		 VS2010版
// 2013年 2月03日  Create by 浅墨 
//图标及图片素材: 《仙剑奇侠传五前传》 夏侯瑾轩
//源码配套博文链接:  http://blog.csdn.net/zhmxy555/article/details/8523341
//更多内容请访问我的博客: http://blog.csdn.net/zhmxy555 
//此刻心情:如果你看到了前面的黑暗,不要担心,那是因为你的背后有阳光。  
//
//***************************************************************************************** 


//*****************************************************************************************
// Desc: 宏定义部分   
//*****************************************************************************************
#define SCREEN_WIDTH	800						//为窗口宽度定义的宏,以方便在此处修改窗口宽度
#define SCREEN_HEIGHT	600							//为窗口高度定义的宏,以方便在此处修改窗口高度
#define WINDOW_TITLE	_T("【Visual C++】游戏开发笔记系列配套源码四十三 浅墨DirectX教程十一 为三维世界添彩:纹理映射技术(二)") //为窗口标题定义的宏



//*****************************************************************************************
// Desc: 头文件定义部分  
//*****************************************************************************************                                                                                       
#include <d3d9.h>
#include <d3dx9.h>
#include <tchar.h>
#include <time.h> 
#include "DirectInputClass.h"



//*****************************************************************************************
// Desc: 库文件定义部分  
//***************************************************************************************** 
#pragma comment(lib,"d3d9.lib")
#pragma comment(lib,"d3dx9.lib")
#pragma comment(lib, "dinput8.lib")     // 使用DirectInput必须包含的头文件,注意这里有8
#pragma comment(lib,"dxguid.lib")
#pragma comment(lib, "winmm.lib") 



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)

//*****************************************************************************************
// Desc: 全局变量声明部分  
//*****************************************************************************************
LPDIRECT3DDEVICE9					g_pd3dDevice = NULL; //Direct3D设备对象
LPD3DXFONT								g_pTextFPS				=NULL;    //字体COM接口
LPD3DXFONT								g_pTextAdaperName           = NULL;  // 显卡信息的2D文本
LPD3DXFONT								g_pTextHelper          = NULL;  // 帮助信息的2D文本
LPD3DXFONT								g_pTextInfor           = NULL;  // 绘制信息的2D文本
float											g_FPS								= 0.0f;       //一个浮点型的变量,代表帧速率
wchar_t										g_strFPS[50]={0};    //包含帧速率的字符数组
wchar_t										g_strAdapterName[60]={0};    //包含显卡名称的字符数组
D3DXMATRIX							g_matWorld;   //世界矩阵
LPDIRECT3DVERTEXBUFFER9		g_pVertexBuffer = NULL;    //顶点缓存对象
LPDIRECT3DINDEXBUFFER9		g_pIndexBuffer  = NULL;    // 索引缓存对象
LPDIRECT3DTEXTURE9				g_pMipTexture= NULL;    // 纹理接口对象,用于渐进纹理的存放
DInputClass*								g_pDInput = NULL;         //一个DInputClass类的指针

//*****************************************************************************************
// Desc: 全局函数声明部分 
//***************************************************************************************** 
LRESULT CALLBACK		WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam );
HRESULT						Direct3D_Init(HWND hwnd,HINSTANCE hInstance);
HRESULT						Objects_Init();
void								Direct3D_Render( HWND hwnd);
void								Direct3D_Update( HWND hwnd);
void								Direct3D_CleanUp( );
float								Get_FPS();
void								Matrix_Set();


//*****************************************************************************************
// Name: WinMain( )
// Desc: Windows应用程序入口函数
//*****************************************************************************************
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nShowCmd)
{

	//开始设计一个完整的窗口类
	WNDCLASSEX wndClass = { 0 };				//用WINDCLASSEX定义了一个窗口类,即用wndClass实例化了WINDCLASSEX,用于之后窗口的各项初始化    
	wndClass.cbSize = sizeof( WNDCLASSEX ) ;	//设置结构体的字节数大小
	wndClass.style = CS_HREDRAW | CS_VREDRAW;	//设置窗口的样式
	wndClass.lpfnWndProc = WndProc;				//设置指向窗口过程函数的指针
	wndClass.cbClsExtra		= 0;
	wndClass.cbWndExtra		= 0;
	wndClass.hInstance = hInstance;				//指定包含窗口过程的程序的实例句柄。
	wndClass.hIcon=(HICON)::LoadImage(NULL,_T("icon.ico"),IMAGE_ICON,0,0,LR_DEFAULTSIZE|LR_LOADFROMFILE); //从全局的::LoadImage函数从本地加载自定义ico图标
	wndClass.hCursor = LoadCursor( NULL, IDC_ARROW );    //指定窗口类的光标句柄。
	wndClass.hbrBackground=(HBRUSH)GetStockObject(GRAY_BRUSH);  //为hbrBackground成员指定一个灰色画刷句柄
	wndClass.lpszMenuName = NULL;						//用一个以空终止的字符串,指定菜单资源的名字。
	wndClass.lpszClassName = _T("ForTheDreamOfGameDevelop");		//用一个以空终止的字符串,指定窗口类的名字。

	if( !RegisterClassEx( &wndClass ) )				//设计完窗口后,需要对窗口类进行注册,这样才能创建该类型的窗口
		return -1;		

	HWND hwnd = CreateWindow( _T("ForTheDreamOfGameDevelop"),WINDOW_TITLE,			//喜闻乐见的创建窗口函数CreateWindow
		WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, SCREEN_WIDTH,
		SCREEN_HEIGHT, NULL, NULL, hInstance, NULL );


	//Direct3D资源的初始化,调用失败用messagebox予以显示
	if (!(S_OK==Direct3D_Init (hwnd,hInstance)))
	{
		MessageBox(hwnd, _T("Direct3D初始化失败~!"), _T("浅墨的消息窗口"), 0); //使用MessageBox函数,创建一个消息窗口 
	}

	

	MoveWindow(hwnd,200,50,SCREEN_WIDTH,SCREEN_HEIGHT,true);   //调整窗口显示时的位置,窗口左上角位于屏幕坐标(200,50)处
	ShowWindow( hwnd, nShowCmd );    //调用Win32函数ShowWindow来显示窗口
	UpdateWindow(hwnd);  //对窗口进行更新,就像我们买了新房子要装修一样

	//进行DirectInput类的初始化
	g_pDInput = new DInputClass();
	g_pDInput->Init(hwnd,hInstance,DISCL_FOREGROUND | DISCL_NONEXCLUSIVE,DISCL_FOREGROUND | DISCL_NONEXCLUSIVE);

	//消息循环过程
	MSG msg = { 0 };  //初始化msg
	while( msg.message != WM_QUIT )			//使用while循环
	{
		if( PeekMessage( &msg, 0, 0, 0, PM_REMOVE ) )   //查看应用程序消息队列,有消息时将队列中的消息派发出去。
		{
			TranslateMessage( &msg );		//将虚拟键消息转换为字符消息
			DispatchMessage( &msg );		//该函数分发一个消息给窗口程序。
		}
		else
		{
			Direct3D_Update(hwnd);         //调用更新函数,进行画面的更新
			Direct3D_Render(hwnd);			//调用渲染函数,进行画面的渲染			
		}
	}

	UnregisterClass(_T("ForTheDreamOfGameDevelop"), wndClass.hInstance);
	return 0;  
}



//*****************************************************************************************
// Name: WndProc()
// Desc: 对窗口消息进行处理
//*****************************************************************************************
LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )   //窗口过程函数WndProc
{
	switch( message )				//switch语句开始
	{
	case WM_PAINT:					 // 客户区重绘消息
		Direct3D_Render(hwnd);          //调用Direct3D_Render函数,进行画面的绘制
		ValidateRect(hwnd, NULL);   // 更新客户区的显示
		break;									//跳出该switch语句

	case WM_KEYDOWN:                // 键盘按下消息
		if (wParam == VK_ESCAPE)    // ESC键
			DestroyWindow(hwnd);    // 销毁窗口, 并发送一条WM_DESTROY消息
		break;
	case WM_DESTROY:				//窗口销毁消息
		Direct3D_CleanUp();     //调用Direct3D_CleanUp函数,清理COM接口对象
		PostQuitMessage( 0 );		//向系统表明有个线程有终止请求。用来响应WM_DESTROY消息
		break;						//跳出该switch语句

	default:						//若上述case条件都不符合,则执行该default语句
		return DefWindowProc( hwnd, message, wParam, lParam );		//调用缺省的窗口过程来为应用程序没有处理的窗口消息提供缺省的处理。
	}

	return 0;					//正常退出
}


//*****************************************************************************************
// Name: Direct3D_Init( )
// Desc: 初始化Direct3D
// Point:【Direct3D初始化四步曲】
//		1.初始化四步曲之一,创建Direct3D接口对象
//		2.初始化四步曲之二,获取硬件设备信息
//		3.初始化四步曲之三,填充结构体
//		4.初始化四步曲之四,创建Direct3D设备接口
//*****************************************************************************************

HRESULT Direct3D_Init(HWND hwnd,HINSTANCE hInstance)
{

	//--------------------------------------------------------------------------------------
	// 【Direct3D初始化四步曲之一,创接口】:创建Direct3D接口对象, 以便用该Direct3D对象创建Direct3D设备对象
	//--------------------------------------------------------------------------------------
	LPDIRECT3D9  pD3D = NULL; //Direct3D接口对象的创建
	if( NULL == ( pD3D = Direct3DCreate9( D3D_SDK_VERSION ) ) ) //初始化Direct3D接口对象,并进行DirectX版本协商
 			return E_FAIL;

	//--------------------------------------------------------------------------------------
	// 【Direct3D初始化四步曲之二,取信息】:获取硬件设备信息
	//--------------------------------------------------------------------------------------
	D3DCAPS9 caps; int vp = 0;
	if( FAILED( pD3D->GetDeviceCaps( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &caps ) ) )
		{
			return E_FAIL;
		}
	if( caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT )
		vp = D3DCREATE_HARDWARE_VERTEXPROCESSING;   //支持硬件顶点运算,我们就采用硬件顶点运算,妥妥的
	else
		vp = D3DCREATE_SOFTWARE_VERTEXPROCESSING; //不支持硬件顶点运算,无奈只好采用软件顶点运算

	//--------------------------------------------------------------------------------------
	// 【Direct3D初始化四步曲之三,填内容】:填充D3DPRESENT_PARAMETERS结构体
	//--------------------------------------------------------------------------------------
	D3DPRESENT_PARAMETERS d3dpp; 
	ZeroMemory(&d3dpp, sizeof(d3dpp));
	d3dpp.BackBufferWidth            = SCREEN_WIDTH;
	d3dpp.BackBufferHeight           = SCREEN_HEIGHT;
	d3dpp.BackBufferFormat           = D3DFMT_A8R8G8B8;
	d3dpp.BackBufferCount            = 2;
	d3dpp.MultiSampleType            = D3DMULTISAMPLE_NONE;
	d3dpp.MultiSampleQuality         = 0;
	d3dpp.SwapEffect                 = D3DSWAPEFFECT_DISCARD; 
	d3dpp.hDeviceWindow              = hwnd;
	d3dpp.Windowed                   = true;
	d3dpp.EnableAutoDepthStencil     = true; 
	d3dpp.AutoDepthStencilFormat     = D3DFMT_D24S8;
	d3dpp.Flags                      = 0;
	d3dpp.FullScreen_RefreshRateInHz = 0;
	d3dpp.PresentationInterval       = D3DPRESENT_INTERVAL_IMMEDIATE;

	//--------------------------------------------------------------------------------------
	// 【Direct3D初始化四步曲之四,创设备】:创建Direct3D设备接口
	//--------------------------------------------------------------------------------------
	if(FAILED(pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, 
		hwnd, vp, &d3dpp, &g_pd3dDevice)))
		return E_FAIL;


	//获取显卡信息到g_strAdapterName中,并在显卡名称之前加上“当前显卡型号:”字符串
	 wchar_t TempName[60]=L"当前显卡型号:";   //定义一个临时字符串,且方便了把"当前显卡型号:"字符串引入我们的目的字符串中
	 D3DADAPTER_IDENTIFIER9 Adapter;  //定义一个D3DADAPTER_IDENTIFIER9结构体,用于存储显卡信息
	 pD3D->GetAdapterIdentifier(0,0,&Adapter);//调用GetAdapterIdentifier,获取显卡信息
	 int len = MultiByteToWideChar(CP_ACP,0, Adapter.Description, -1, NULL, 0);//显卡名称现在已经在Adapter.Description中了,但是其为char类型,我们要将其转为wchar_t类型
	 MultiByteToWideChar(CP_ACP, 0, Adapter.Description, -1, g_strAdapterName, len);//这步操作完成后,g_strAdapterName中就为当前我们的显卡类型名的wchar_t型字符串了
	 wcscat_s(TempName,g_strAdapterName);//把当前我们的显卡名加到“当前显卡型号:”字符串后面,结果存在TempName中
	 wcscpy_s(g_strAdapterName,TempName);//把TempName中的结果拷贝到全局变量g_strAdapterName中,大功告成~

	if(!(S_OK==Objects_Init())) return E_FAIL;

	SAFE_RELEASE(pD3D) //LPDIRECT3D9接口对象的使命完成,我们将其释放掉

	return S_OK;
}


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;
}


//*****************************************************************************************
// Name:Matrix_Set()
// Desc: 设置世界矩阵
// Point:【Direct3D四大变换】
//		1.【四大变换之一】:世界变换矩阵的设置
//		2.【四大变换之二】:取景变换矩阵的设置
//		3.【四大变换之三】:投影变换矩阵的设置
//		4.【四大变换之四】:视口变换的设置
//*****************************************************************************************
void Matrix_Set()
{
	//--------------------------------------------------------------------------------------
	//【四大变换之一】:世界变换矩阵的设置
	//--------------------------------------------------------------------------------------


	//--------------------------------------------------------------------------------------
	//【四大变换之二】:取景变换矩阵的设置
	//--------------------------------------------------------------------------------------
	D3DXMATRIX matView; //定义一个矩阵
	D3DXVECTOR3 vEye(0.0f, 0.0f, -50.0f);  //摄像机的位置
	D3DXVECTOR3 vAt(0.0f, 0.0f, 0.0f); //观察点的位置
	D3DXVECTOR3 vUp(0.0f, 1.0f, 0.0f);//向上的向量
	D3DXMatrixLookAtLH(&matView, &vEye, &vAt, &vUp); //计算出取景变换矩阵
	g_pd3dDevice->SetTransform(D3DTS_VIEW, &matView); //应用取景变换矩阵

	//--------------------------------------------------------------------------------------
	//【四大变换之三】:投影变换矩阵的设置
	//--------------------------------------------------------------------------------------
	D3DXMATRIX matProj; //定义一个矩阵
	D3DXMatrixPerspectiveFovLH(&matProj, D3DX_PI / 4.0f,(float)((double)SCREEN_WIDTH/SCREEN_HEIGHT),1.0f, 1000.0f); //计算投影变换矩阵
	g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &matProj);  //设置投影变换矩阵

	//--------------------------------------------------------------------------------------
	//【四大变换之四】:视口变换的设置
	//--------------------------------------------------------------------------------------
	D3DVIEWPORT9 vp; //实例化一个D3DVIEWPORT9结构体,然后做填空题给各个参数赋值就可以了
	vp.X      = 0;		//表示视口相对于窗口的X坐标
	vp.Y      = 0;		//视口相对对窗口的Y坐标
	vp.Width  = SCREEN_WIDTH;	//视口的宽度
	vp.Height = SCREEN_HEIGHT; //视口的高度
	vp.MinZ   = 0.0f; //视口在深度缓存中的最小深度值
	vp.MaxZ   = 1.0f;	//视口在深度缓存中的最大深度值
	g_pd3dDevice->SetViewport(&vp); //视口的设置

}


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();
}



//*****************************************************************************************
// Name: Direct3D_Render()
// Desc: 进行图形的渲染操作
// Point:【Direct3D渲染五步曲】
//		1.渲染五步曲之一,清屏操作
//		2.渲染五步曲之二,开始绘制
//		3.渲染五步曲之三,正式绘制
//		4.渲染五步曲之四,结束绘制
//		5.渲染五步曲之五,翻转显示
//*****************************************************************************************

void Direct3D_Render(HWND hwnd)
{

	//--------------------------------------------------------------------------------------
	// 【Direct3D渲染五步曲之一】:清屏操作
	//--------------------------------------------------------------------------------------
	g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(100, 100, 100), 1.0f, 0);

	//定义一个矩形,用于获取主窗口矩形
	RECT formatRect;
	GetClientRect(hwnd, &formatRect);

	//--------------------------------------------------------------------------------------
	// 【Direct3D渲染五步曲之二】:开始绘制
	//--------------------------------------------------------------------------------------
	g_pd3dDevice->BeginScene();                     // 开始绘制
	
			//--------------------------------------------------------------------------------------
			// 【Direct3D渲染五步曲之三】:正式绘制,利用顶点缓存绘制图形
			//--------------------------------------------------------------------------------------

	g_pd3dDevice->SetStreamSource( 0, g_pVertexBuffer, 0, sizeof(CUSTOMVERTEX) );//把包含的几何体信息的顶点缓存和渲染流水线相关联
	g_pd3dDevice->SetFVF( D3DFVF_CUSTOMVERTEX );//指定我们使用的灵活顶点格式的宏名称
	g_pd3dDevice->SetIndices(g_pIndexBuffer);//设置索引缓存
	//--------------------------------------------------------------------------------------
	// 【纹理绘制四步曲之四】:纹理的启用
	//--------------------------------------------------------------------------------------
	g_pd3dDevice->SetTexture(0, g_pMipTexture);  //启用纹理

	g_pd3dDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, 24, 0, 12);//利用索引缓存配合顶点缓存绘制图形

			//在窗口右上角处,显示每秒帧数
			int charCount = swprintf_s(g_strFPS, 20, _T("FPS:%0.3f"), Get_FPS() );
			g_pTextFPS->DrawText(NULL, g_strFPS, charCount , &formatRect, DT_TOP | DT_RIGHT, D3DCOLOR_RGBA(0,239,136,255));

			//显示显卡类型名
			g_pTextAdaperName->DrawText(NULL,g_strAdapterName, -1, &formatRect, 
				DT_TOP | DT_LEFT, D3DXCOLOR(1.0f, 0.5f, 0.0f, 1.0f));

			// 输出绘制信息
			 formatRect.top = 30;
			static wchar_t strInfo[256] = {0};
			swprintf_s(strInfo,-1, L"模型坐标: (%.2f, %.2f, %.2f)", g_matWorld._41, g_matWorld._42, g_matWorld._43);
			g_pTextHelper->DrawText(NULL, strInfo, -1, &formatRect, DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(135,239,136,255));

			// 输出帮助信息
			formatRect.left = 0,formatRect.top = 380;
			g_pTextInfor->DrawText(NULL, L"控制说明:", -1, &formatRect, 
				DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(235,123,230,255));
			formatRect.top += 35;
			g_pTextHelper->DrawText(NULL, L"    按住鼠标左键并拖动:平移模型", -1, &formatRect, 
				DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(255,200,0,255));
			formatRect.top += 25;
			g_pTextHelper->DrawText(NULL, L"    按住鼠标右键并拖动:旋转模型", -1, &formatRect, 
				DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(255,200,0,255));
			formatRect.top += 25;
			g_pTextHelper->DrawText(NULL, L"    滑动鼠标滚轮:拉伸模型", -1, &formatRect, 
				DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(255,200,0,255));
			formatRect.top += 25;
			g_pTextHelper->DrawText(NULL, L"    W、S、A、D键:平移模型 ", -1, &formatRect, 
				DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(255,200,0,255));
			formatRect.top += 25;
			g_pTextHelper->DrawText(NULL, L"    上、下、左、右方向键:旋转模型 ", -1, &formatRect, 
				DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(255,200,0,255));
			formatRect.top += 25;
			g_pTextHelper->DrawText(NULL, L"    键盘上1,2,3,4数字键:在四种寻址模式之间切换 ", -1, &formatRect, 
				DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(255,200,0,255));
			formatRect.top += 25;
			g_pTextHelper->DrawText(NULL, L"    ESC键 : 退出程序", -1, &formatRect, 
				DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(255,200,0,255));


	//--------------------------------------------------------------------------------------
	// 【Direct3D渲染五步曲之四】:结束绘制
	//--------------------------------------------------------------------------------------
	g_pd3dDevice->EndScene();                       // 结束绘制
	//--------------------------------------------------------------------------------------
	// 【Direct3D渲染五步曲之五】:显示翻转
	//--------------------------------------------------------------------------------------
	g_pd3dDevice->Present(NULL, NULL, NULL, NULL);  // 翻转与显示
	 
}




//*****************************************************************************************
// Name:Get_FPS()函数
// Desc: 用于计算帧速率
//*****************************************************************************************
float Get_FPS()
{

	//定义四个静态变量
	static float  fps = 0; //我们需要计算的FPS值
	static int    frameCount = 0;//帧数
	static float  currentTime =0.0f;//当前时间
	static float  lastTime = 0.0f;//持续时间

	frameCount++;//每调用一次Get_FPS()函数,帧数自增1
	currentTime = timeGetTime()*0.001f;//获取系统时间,其中timeGetTime函数返回的是以毫秒为单位的系统时间,所以需要乘以0.001,得到单位为秒的时间

	//如果当前时间减去持续时间大于了1秒钟,就进行一次FPS的计算和持续时间的更新,并将帧数值清零
	if(currentTime - lastTime > 1.0f) //将时间控制在1秒钟
	{
		fps = (float)frameCount /(currentTime - lastTime);//计算这1秒钟的FPS值
		lastTime = currentTime; //将当前时间currentTime赋给持续时间lastTime,作为下一秒的基准时间
		frameCount    = 0;//将本次帧数frameCount值清零
	}

	return fps;
}



//*****************************************************************************************
// Name: Direct3D_CleanUp()
// Desc: 对Direct3D的资源进行清理,释放COM接口对象
//*****************************************************************************************
void Direct3D_CleanUp()
{
	//释放COM接口对象
	SAFE_DELETE(g_pDInput);
	SAFE_RELEASE(g_pVertexBuffer)
	SAFE_RELEASE(g_pIndexBuffer)
	SAFE_RELEASE(g_pMipTexture)
	SAFE_RELEASE(g_pTextAdaperName)
	SAFE_RELEASE(g_pTextHelper)
	SAFE_RELEASE(g_pTextInfor)
	SAFE_RELEASE(g_pTextFPS)
	SAFE_RELEASE(g_pd3dDevice)
}


 

最后依旧是运行的截图:

 1.重复寻址模式

【Visual C++】游戏开发笔记四十三 浅墨DirectX教程十一 为三维世界添彩:纹理映射技术(二)_第9张图片


Ⅱ、镜像寻址模式

【Visual C++】游戏开发笔记四十三 浅墨DirectX教程十一 为三维世界添彩:纹理映射技术(二)_第10张图片


 Ⅲ、夹取寻址模式

【Visual C++】游戏开发笔记四十三 浅墨DirectX教程十一 为三维世界添彩:纹理映射技术(二)_第11张图片


Ⅳ、边框颜色寻址

【Visual C++】游戏开发笔记四十三 浅墨DirectX教程十一 为三维世界添彩:纹理映射技术(二)_第12张图片


文章最后,依旧是放出本篇文章配套源代码的下载:

 

本节笔记配套源代码请点击这里下载:

 

 

【浅墨DirectX提高班】配套源代码之十一下载 


 其中图标素材是仙五前传中的夏侯瑾轩【Visual C++】游戏开发笔记四十三 浅墨DirectX教程十一 为三维世界添彩:纹理映射技术(二)_第13张图片

以上就是本节笔记的全部内容,更多精彩内容,且听下回分解。

 

浅墨在这里,希望喜欢游戏开发系列文章的朋友们能留下你们的评论,每次浅墨登陆博客看到大家的留言的时候都会非常开心,感觉自己正在传递一种信仰,一种精神。

 


七、【每文一语】 


文章最后,依然是【每文一语】栏目,今天是三毛的句子: 


请你,担负起对自己的责任来,不但是活着就算了,更要活得热烈而起劲,不要懦弱,更不要别人太多的指引。每一天,活得踏实,将份内的工作,做得尽自己能力之内的完美,就无愧于天地。

【Visual C++】游戏开发笔记四十三 浅墨DirectX教程十一 为三维世界添彩:纹理映射技术(二)_第14张图片



下周一,让我们离游戏开发的梦想更近一步。

下周一,游戏开发笔记,我们,不见不散。

------------------------------------------------------------------------------------------------------------------------------

浅墨历时一年为游戏编程爱好者锻造的著作《逐梦旅程:Windows游戏编程之从零开始》

如果你喜欢浅墨写的【Visual C++】游戏开发系列博客文章,那么你一定会爱上这本书。

这是浅墨专门为热爱游戏编程的朋友们写的入门级游戏编程宝典。

【Visual C++】游戏开发笔记四十三 浅墨DirectX教程十一 为三维世界添彩:纹理映射技术(二)_第15张图片

彩版样章试读下载|配套源代码下载

书本维护页面|当当网|京东商城|亚马逊

------------------------------------------------------------------------------------------------------------------------------



你可能感兴趣的:(【Visual C++】游戏开发笔记四十三 浅墨DirectX教程十一 为三维世界添彩:纹理映射技术(二))