纹理映射的概念
在三维里,如果我们绘制的图形都是单一颜色的图形,那么这个世界看起来会缺乏生机。比如我们之前用顶点绘制的正方体,他只有一种色。而我们平时看到的正方体都是看起来很有活力的,比如说一个箱子,他虽然也是一个正方体,但是他上面的图案,看起来却让人觉得很有生机。那么要怎么样才能绘制这样的正方体呢。这就是纹理映射要做的的事了。
怎么去理解纹理映射,其实很简单。我们可以把他看做是应用在物体表面的的像素颜色。
基本概念也讲完了,我们学习Direct3D是怎么运用纹理映射的。
纹理的坐标
纹理坐标由一个二维坐标系指定,这个坐标系由沿水平方向的u轴和沿垂直方向的v轴构成,他们两个合起来就是坐标对(u,v),其实和我们习惯的(x,y)坐标对是一个概念,只不过在纹理贴图中我们习惯用(u,v)而已。其中u轴正方向为水平向右,v轴正方向为垂直向下,且他们的取值范围都在[0,1]之间。
纹理坐标位于纹理空间之中,是相对坐标,相对于纹理坐标系中的原点(0,0)。当把纹理贴到三维模型表面上时,纹理元素首先被映射到物体模型的局部坐标系中,然后再变换到屏幕坐标系中对应像素的位置处。
另外的一点,纹理坐标是通过纹理层和纹理联系到一起的,通常情况下只需使用一层纹理就够了。记得我们原来讲FVF灵活顶点格式的时候讲到过,一个灵活顶点格式中允许最多定义八组纹理坐标,而每组纹理坐标都对应一个纹理层,这就是说每个顶点最多可以使用八层纹理。
比如这样写:
struct MyVecter
{
float _x,_y,_z;
float _u1,_v2;//第一层纹理
float _u2,_v2;//第二层纹理
};
#define MYVECTER_FVF (D3DFVF_XYZ|D3DFVF_TEX1|D3DFVF_TEX2)
本节中我们定义只定义一层纹理顶点定义如下:
struct MyVecter
{
float _x,_y,_z;
float _u,_v;
MyVecter(float x,float y,floatz,float u,float v)
:_x(x),_y(y),_z(z),_u(u),_v(v) {}
};
#define MYVECTER_FVF (D3DFVF_XYZ|D3DFVF_TEX)
这个顶点结构定义多了个构造函数,是为了我们方便定义顶点结构各个值。
具体写法:
g_pVertexBuffer->Lock(NULL,sizeof(MyVecter),(void**)&pVertices,0);
// 正面顶点数据
pVertices[0] = MyVecter(-10.0f, 10.0f, -10.0f, 0.0f, 0.0f);
pVertices[1] = MyVecter( 10.0f, 10.0f, -10.0f, 1.0f, 0.0f);
pVertices[2] = MyVecter( 10.0f, -10.0f, -10.0f,1.0f, 1.0f;
pVertices[3] = MyVecter(-10.0f, -10.0f,-10.0f, 0.0f, 1.0f);
讲解一下这段代码的赋值操作。
pVertices[0]= MyVecter(-10.0f, 10.0f, -10.0f, 0.0f, 0.0f);表示一个点的结构的各个值,前三个表示x,y,z。这次的重点是后两个,(0.0f,0.0f)表示这个点所对应的纹理坐标。这样说是不是不太好理解。我们这样说吧,现在这里4个顶点,表示一个正方形的4个点和对应的纹理,而他们对应的纹理坐标是(0,0)(1,0)(1,1)(0,1)这样直接写出来是不是觉得很像一个小正方形的4个顶点,其实就是一个大正方形上面再盖着一个和他一样大的正方形,只不过相对坐标不同而已。分别对应着这个大正方形的左上角,右上角,右下角,左下角。还有因为纹理的取值为 [0,1]之间所以这里显示一个图案,如果纹理坐标设为(0,0)(2,0)(2,2)(0,2)那么这次的大正方形上面盖着就是一个背劈成田子的正方形了,而且纹理图片显示在左上角第一个格子。如下图
纹理坐标为(0,0)(1,0)(1,1)(0,1)
纹理坐标为(0,0)(2,0)(2,2)(0,2)
顶点创建好了,我们要开始创建纹理图了。
在Direct3D中,纹理是以COM对象的形式存在的,也就是IDirect3DTexture9这个接口。我们要对物体表面进行纹理映射的话,首先要创建纹理对象,创建时需要指定纹理的宽度、高度、格式等属性,然后还需要将图形文件加载到纹理对象中。我们可以用D3DX库中的D3DXCreateTexture函数创建一个纹理对象。不过这个方函数比较冷门,平常用得少,还是先来简单介绍一下这个函数的用法:
HRESULT D3DXCreateTexture(
__in LPDIRECT3DDEVICE9pDevice,
__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 LPDIRECT3DDEVICE9pDevice,
__in LPCTSTR pSrcFile,
__out LPDIRECT3DTEXTURE9*ppTexture
);
第一个参数,LPDIRECT3DDEVICE9类型的pDevice,无需多言,这就是我们的绘制金钥匙——Direct3D设备对象了。
第二个参数,LPCTSTR类型的pSrcFile,指向了用于创建纹理的图标文件名字的字符串,也就是我们要使用的纹理图片的文件地址了。支持的图片格式多种多样,有.bmp、.dds、.dib、.png以及.tga等等。
第三个参数,LPDIRECT3DTEXTURE9类型的*ppTexture,指针的指针,也就是指向IDirect3DTexture9接口的地址,显然我们调用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"darksider.jpg",&g_pTexture); // 创建纹理
注意:D3DXCreateTextureFromFile和D3DXCreateTextureFromFileEx主要用于是否要用多级渐进纹理过滤,后面会讲到纹理的过滤。
直接就是我们的纹理创建了,要不然看不到我们的纹理就功亏一篑了。
很简单加载完纹理后,就可以调用IDirect3DDevice9接口的SetTexture方法,设置我们当前需要启用的纹理。我们可以在MSDN中查到SetTexture方法有如下原型:
HRESULT SetTexture(
[in] DWORD Sampler,
[in] IDirect3DBaseTexture9*pTexture
);
第一个参数,DWORD类型的Sampler,指定了应用的纹理是哪一层。我们知道Direct3D中最多可以设置8层纹理,所以这个参数取值就在0~7之间了。
第二个参数,IDirect3DBaseTexture9类型的*pTexture,表示我们将要启用的纹理的IDirect3DBaseTexture9接口对象,比如我们填上面第二步末尾实例中定义过的g_pTexture参数,注意要加取地址符号,也就是这样填&g_pTexture,则表示我们现在绘图时用的就是“pal5q.jpg”这张图了。
另外,如果场景中绘制每个物体模型所使用的纹理不相同,那么在绘制每个物体模型之前都需要调用该方法设置对应的纹理。也就是说当前在用的纹理就一种,就像上场打比赛的就一名队员,其他的都是替补,要换纹理的话,就用SetTexture申请换人。
下面我们依旧来个实例:
g_pd3dDevice->SetTexture(0,&g_pTexture1); //设置第一个物体的纹理
g_pMesh1->DrawSubset(0); //进行第一个物体的绘制
g_pd3dDevice->SetTexture(0,&g_pTexture2); //设置第一个物体的纹理
g_pMesh2->DrawSubset(0); //进行第二个物体的绘制
多个依次类推
前面总体来讲就是,设定顶点结构和顶点格式,然后设置顶点值,然后创建纹理,最后开启纹理。
别以为纹理创建就只是这样,目前用到这些只是一个普通的纹理创建。
我们都知道当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中进行取值
第三个参数,DWORD类型的Value,这个参数和第二个参数Type联系很紧密,就是对第二个参数指定的属性进行值的设置的
最近点采样过滤
最近采样点过滤可谓是四种纹理过滤方式中速度最快但是效果最差的过滤方式了。Direct3D计算得到的纹理过滤元素通常都是一个浮点值,当使用最近点采样时,Direct3D会复制与这个浮点值地址最接近的整数地址的纹理元素的颜色。
g_pD3DDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_POINT);
g_pD3DDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_POINT);
如果纹理大小和屏幕图元的实际大小相近,那么采用最近点采样过滤对图像质量的影响不大。但是,如果纹理大小和屏幕图元的实际大小相差太多,则图像的精度就会受到影响,从而图像质量就会差强人意,出现闪烁或者失真的现象。
线性纹理过滤
线性纹理过滤是目前使用最广泛的纹理过滤方式。与最近点采用方式相比,能有效地提高图像的显示质量,且对系统性能的影响不大。线性纹理过滤取得与计算得到的纹理元素的浮点地址最接近的上下左右4个纹理元素,对这四个纹理元素进行加权平均,从而得到最终显示得颜色值。因为是在单一纹理层上的线性过滤,而且是从x,y方向上的线性过滤,所以我们也称通常的线性纹理过滤为双线性纹理过滤。
g_pD3DDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
g_pD3DDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
目前市面上的大多数显卡都为线性纹理过滤进行了针对性优化,所以使用这种纹理过滤方式能获得比较好的图形质量,而且也不吃资源。线性纹理过滤是目前使用最广泛的纹理过滤方式。
各向异性纹理过滤
首先需要知道一个常识,在很多时候,三维物体的表面不可能是完全平面的,当三维物体的表面和投影平面不平行时,它在屏幕上的投影会有拉长和扭曲的现象,这种现象称为各向异性(Anisotropic)。当一个各项异性的图元映射到纹理元素上时,它的形状就会发生扭曲。而Direct3D会根据屏幕像素反向转换到纹理元素的延长度,来决定各项异性的程度。
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将多级渐进纹理描绘成一系列相互联系的表面,当创建一个渐进纹理时,分辨率最高的纹理位于开始处,并与下一级纹理相互联系,直到分辨率最低的一级纹理。
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"cat.JPG",0,0,6,0,D3DFMT_X8R8G8B8,
D3DPOOL_MANAGED,D3DX_DEFAULT,D3DX_DEFAULT,0xFF000000,0,0,&g_pTexture);
多级渐进纹理一般都要与前面提到的三个过滤方式配合使用
例如:
//首先开线性纹理过滤
g_pd3dDevice->SetSamplerState(0,D3DSAMP_MIPFILTER,D3DTEXF_LINEAR);
//然后设置多级渐进纹理过滤
g_pd3dDevice->SetSamplerState(0,D3DSAMP_MIPFILTER,D3DTEXF_LINEAR);
g_pd3dDevice->SetSamplerState(0,D3DSAMP_MAXMIPLEVEL,15);
寻址方式:
我们创建了一个正方形图元,而指定4个顶点的纹理坐标分别为(0.0,0.0),(0.0,2.0),(2.0,2.0),(2.0,0.0),因为重复寻址为Direct3D中的默认寻址模式,所以我们不用额外设置,那么Direct3D就会在u,v方向各复制两遍原始纹理,效果就像这样:
用法:
g_pD3DDevice->SetSamplerState(0, D3DSAMP_ADDRESSU,D3DTADDRESS_WRAP);
g_pD3DDevice->SetSamplerState(0, D3DSAMP_ADDRESSV,D3DTADDRESS_WRAP);
镜像寻址模式
使用镜像纹理寻址模式是,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);
夹取寻址模式
夹取纹理寻址模式将纹理坐标夹取在[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);
边框颜色寻址
边框颜色寻址模式就是在[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();来设置一下就可以了
源码下载地址:http://pan.baidu.com/s/1c0AfF9I