比如说我们要绘制出如下效果的一个贴了瓷砖的立方体:
综合我们之前讲过的知识,绘制一个立方体是小菜一碟。但关键就来了,如何给制出像砖块那样坑坑洼洼的效果呢?难不成用成千上万的顶点坐标一个个去机械地模拟?这显然不现实。
相信正确的做法大家应该很容易想到,先绘制一个立方体,接着准备一副描绘着砖块状的2D图片,然后把这幅图片像贴画一样贴到这个立方体的六个面,这样绘制砖块立方体的效果就可以实现了。就像这样:
其实上面我们讲到的这种方法, 就是计算机图形学中“纹理映射”技术思想的真实写照。
在计算机三维世界中,想要模拟出真实而复杂的三维物体, 让它们从表面上看起来逼真, 就需要请出“纹理映射”这套技术。
一言以蔽之, 纹理映射是一种将2D 图像映射到3D 物体上的技术。
一般来说, 纹理是表示物体表面细节的一副或者几幅二维图形,也称纹理贴图( texture )。当我们把纹理按照特定的方式映射到物体表面上的时候, 能使物体看上去更加真实。在当今流行的计算机图形系统中,纹理绘制己经成了渲染方法中的中流砾柱了。
想要理解纹理映射其实非常简单,把纹理看做是应用在物体表面上的像素颜色就可以了。
在真实世界中,纹理表示一个对象的颜色、图案或者触觉特性。但在计算机图形学中,纹理只表示对象表面的彩色图案,它不能改变对象的几何形式,换言之,就像贴在几何体表面上的贴画一样。
好了,基本概念应该讲明白了,下面我们来看看如果要使用这种纹理映射技术的话,需要了解的具体知识。
纹理坐标位于纹理空间之中,是相对坐标,相对于纹理坐标系中的原点( 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. 四步曲之二, 顶点的访问
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
);
但更多情况下是从文件中读取纹理图形的,我们可以任意读取我们喜欢的图片文件到Direct3D 中,这就用到了D3DXCreateTextureFromFile 函数:
HRESULT D3DXCreateTextureFromFile(
__in LPDIRECT3DDEVICE9 pDevice,
__in LPCTSTR pSrcFile,
__out LPDIRECT3DTEXTURE9 *ppTexture
);
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);
HRESULT SetTexture(
[in] DWORD Sampler,
[in] IDirect3DBaseTexture9 *pTexture
);
想要正确地显示出纹理,可以通过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); // 进行第二个物体的绘制
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 之间就是来写绘制图形的相关代码的, 在绘制过程如果要用到纹理的话就需要启用,启用后再调用相关绘制函数进行图形的绘制,这是完全合情合理的。
这个程序还可以这样理解,把前面我们的变形金刚的绘制换成了一个利用顶点缓存、索引缓存配合纹理映射绘制出的立方体盒子。
具体代码可在网上下载:
运行上述demo ,我们可以看到如下的效果, 可以用鼠标和键盘调整视角,观察被纹理映射后的立方体。
刚在上一章中看到了载入帅气的“擎天柱” 的示例程序,这一章忽然弄出一个好像没有什么技术含量的立方体来,是不是落差有些大, 其实我们在载入帅气的“擎天柱”的时候,就用到了纹理映射技术, 不然擎天柱看起来就只有一些网格了,而不是表面看起来有些质感的样子了(当然, 擎天柱本身的材质也做出了适当的贡献)。
我们知道,当Direct3D 渲染一个图元或者三维图形时,必须将它们通过坐标变换映射到二维屏幕之上。而当我们使用纹理来进行辅助渲染的时候, Direct3D 就必须使用该纹理为二维图像上的每个像素进行着色。这里的每个像素都包含一个来自纹理的颜色值,而从纹理中为每个像素获取颜色的过程, 就是所谓的纹理过滤( texture filtering )了。
大多数情况下, 屏幕显示的图形大小与纹型贴图大小是不同的。换句话说,这个纹理将被映射到一个比它大或者小的图元的图像上。这样纹理常常会被放大或者缩小。对纹理的放大会造成许多像素被映射到同一个纹理元素上,这样图形渲染的结果就会有色块的感觉。缩小一个纹理意味着一个像素被映射到许多纹理元素上, 图形看上去会闪烁或者失真抑或是有锯齿。而为了解决这些问题,可以将相关纹理元素的颜色融合到一个像素之上, 而如何将多个纹理元素的颜色融合到一个像素就取决于纹理过滤的方式了。
Direct3D 中,有四种纹理过滤方式, 它们分别是:
HRESULT SetSamplerState(
[in] DWORD Sampler,
[in] D3DSAMPLERSTATETYPE Type,
[in] DWORD Value
);
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;
(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);
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);
接着我们来看看四大纹理寻址方式。
// 设置重复纹理寻址模式
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. 镜像寻址模式( 2.0, 0.0 ),设置为镜像寻址模式,也就是在绘制之前写如下两句代码:
// 设置镜像纹理寻址模式
g_pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_MIRROR);
g_pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_MIRROR);
那么得到的效果就是如下:
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);
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);
然后得到的效果就是如下图:
本着把厚书读薄的精神,纹理映射相关知识也讲了这么多了, 其实总结起来,我们也就讲了三方面的内容,总结如下:
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 . 四大纹理过滤模式
// 最近点采样过滤
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);
大家如若需要选取相应的纹理过滤模式以及纹理寻址模式,可以快速地在这里查阅,在相应的地方敲上代码即可。
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( )函数】--------------------------------
// 描述:不是即时渲染代码但是需要即时调用的,如按键后的坐标的更改,都放在这里
//--------------------------------------------------------------------------------------------------
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();
}
接着看一下运行截图,我们可以拖动鼠标, 按键盘上的一些按键, 以不同的角度观察不同模式的寻址方式:
学完了这章纹理映射的内容,可以选取自己喜爱的人物或者风景图片作为纹理素材, 在学习过程中给自己一个好心情。