关于纹理坐标
本文译自微软DirectX 6.1帮助文件,英文版权属MicroSoft™公司所有,中文版权(如果有的话:-))属程连冀、刘长松所有。
Direct3D支持广泛的纹理特性设置,使程序员可以轻松的访问提供高级纹理技术。下面我们来讨论Direct3D中的纹理的一些内容:
这一部分中讲述了Direct3D中有关纹理的一些概念,我们分以下几个部分来分别讨论:
早期的计算机生成的3-D图象,它们的表面看起来就象是一个发亮的塑料表面。它们总是缺少一些能使物体看起来更加真实的东西,如表面的磨损、裂纹、人手的印记或是一些污点等等。近几年来,纹理的使用使得计算机三维图象具有了更好的真实感。
一个纹理实际上就是一个位图。从这个意义上来讲,当纹理一词被用于计算机图形学时,它就有了一个明确的定义。从语义学角度来讲,纹理一词既是指一个物体上颜色的模式,又是指物体表面是粗糙的还是光滑的。Direct3D的纹理不会使物体表面真的变得“崎岖不平”,而只是使它的表面看起来是“崎岖不平”的。
由于Direct3D的纹理就是简单的位图,因此任何纹理否可以被用在Direct3D图元上。例如,我们可以创建一些具有木头和谷物图案的对象;也可以将一些青草、泥土和岩石用于一些三维图元,并将它们堆成一座小山,这样就有了一个山坡的背景;或者搞一些路标、悬崖等等。
另外,Direct3D还支持一些高级的纹理技术,如纹理融合(透明或不透明)、光线映射(light mapping)等。
如果程序创建了一个HAL设备、MMX设备或RGB设备,它就可以使用8-、16-、24-或32-bit纹理。使用单色设备的程序可以使用8-bit纹理。
纹理实际上是一个二维数组,它的元素是一些颜色值。单个的颜色值被称为纹理元素(texture elements)或纹理像素(texel)。每一个纹理像素在纹理中都有一个唯一的地址。这个地址可以被认为是一个列(column)和行(row)的值,它们分别由U和V来表示。
纹理坐标位于纹理空间中。也就是说,它们和纹理中的(0,0)位置相对应。当我们将一个纹理应用于一个图元时,它的纹理像素地址必须要映射到对象坐标系中。然后再被平移到屏幕坐标系或像素位置上。
Direct3D直接将纹理空间中的纹理像素映射到屏幕空间中的像素,跳过了中间过程,从而提高了效率。这一映射过程实际上是一个相反的(inverse)映射。也就是说,对于屏幕空间中的每一个像素,我们来计算相应的纹理空间中的纹理像素位置。对那一点上的或那一点周围的纹理颜色进行采样(sample)。采样过程被称为纹理过滤(texture filtering)。
纹理中的每一个纹理像素可以通过它的坐标来声明。但是,为了将纹理像素映射到图元上,对于所有纹理上的所有纹理像素,Direct3D需要有一个统一的地址范围。因此,它使用了一个通用的地址方案,在这个方案中所有纹理像素地址的范围都在0.0到1.0之间,包括0.0和1.0。Direct3D程序用U、V的值来声明纹理坐标,它和用x、y坐标来声明二维迪卡尔坐标系一样。
在这种情况下,不同纹理中的同一纹理地址又可能被映射为不同的纹理像素坐标。如左面插图中的纹理地址(0.0,0.5)。由于纹理的大小有所不同,因此纹理地址将会映射为不同的纹理像素。左边的纹理1,大小为5x5,纹理地址(0.0,0.5)映射到纹理像素(0,2);右边的纹理2,大小为7x7,纹理地址(0.0,0.5)映射到纹理像素(0,3)。
左图展示了一个简单的纹理像素映射的过程:
我们要给图中左边的像素确定一定的颜色。像素四个角的地址被映射到对象空间中的图元上,这时,像素的形状会有一些变形,这是由图元的形状和观察的角度造成的。然后,与像素角相对应的图元表面上的几个角被映射到纹理空间中。这一映射过程再次使像素的形状产生变形。像素最终的颜色值就由该像素映射到的区域中的纹理像素计算而得。在设置纹理过滤方法时,要决定Direct3D使用什么方法来得到像素的颜色。
程序可以直接将纹理坐标分配给顶点。这一能力使我们能够控制将一个纹理的哪些部分映射到一个图元上。现在假定我们要创建一个矩形图元,它的大小恰好与下图中纹理的大小一样。我们要将整个纹理都映射到一整堵墙上,那么分配给图元顶点的纹理坐标就应该是(0.0,0.0)、(1.0,0.0)、(1.0,1.0)和(0.0,1.0)。
现在我们要将墙的高度缩小一半。我们可以将纹理变形以适应墙的变化,也可以在分配纹理坐标时只使用纹理的下面一半。
如果使用纹理变形或缩放的方法,那么所使用的纹理过滤方法就会对图象的质量产生影响。详细内容见“纹理过滤”。
如果采用分配纹理坐标的方法,那么分配给图元顶点的纹理坐标就应该是(0.0,0.0)、(1.0,0.0)、(1.0,0.5)和(0.0,0.5)。Direct3D会将纹理的下面一半应用到墙上。
有时,一个顶点的纹理坐标可能比1.0大。当分配给顶点的纹理坐标不在0.0到1.0(包括它们)范围内时,就要设置纹理寻址模式了。
下面我们来讨论Direct3D纹理寻址模式。
- 1.3.1 什么是纹理寻址模式?
- 1.3.2 Wrap纹理寻址模式
- 1.3.3 镜像纹理寻址模式
- 1.3.4 钳位(clamp)纹理寻址模式
- 1.3.5 边界颜色纹理寻址模式
- 1.3.6 设置并恢复纹理寻址模式
- 1.3.7 纹理寻址模式和纹理Wrapping
Direct3D程序可以将纹理坐标分配给任何图元的顶点。一般来说,分配的U、V纹理坐标值都在0.0到1.0范围内(包括它们)。但是,如果我们分配了超出这个范围的纹理坐标,可能会得到一些特别的纹理效果。
通过设置纹理寻址模式,我们就可以在纹理坐标超出范围时进行控制。详细的内容请看下面的相关讨论:
- Wrapping纹理寻址模式
- 镜像纹理寻址模式
- 钳位纹理寻址模式
- 边界颜色纹理寻址模式
“Wrapping”纹理寻址模式由D3DTEXTUREADDRESS枚举类型的D3DTADDRESS_WRAP成员来确定,它使Direct3D在每一个整数结点(integer junction)对纹理进行重复。假设我们要创建一个正方形图元,并将纹理坐标声明为(0.0,0.0)、(0.0,3.0)、(3.0,3.0)和(3.0,0.0)。这时,如果我们设置了纹理寻址模式,就可以使纹理在U、V方向都重复三次。,如下图所示:
这种纹理寻址模式的效果与“镜像”模式比较相似,但在本质上是不同的。
“镜像”纹理寻址模式由D3DTEXTUREADDRESS枚举类型的D3DTADDRESS_MIRROR成员来确定,它使Direct3D在每个整数边界处(integer boundary)对纹理进行镜像处理。想在我们创建一个正方形图元,为坐标为(0.0,0.0)、(0.0,3.0)、(3.0,3.0)和(3.0,0.0)。我们设置镜像纹理寻址模式,纹理在U、V方向都重复了三次,并且每一行、每一列都与相邻的行和列成镜像关系。如下图所示:
镜像纹理寻址模式的效果与Wrapping寻址模式比较相似,但本质上是不同的。
“钳位”纹理寻址模式由D3DTEXTUREADDRESS枚举类型的D3DTADDRESS_CLAMP成员确定,它使Direct3D将纹理坐标钳制在[0.0, 1.0]范围内。也就是说,它只使用一次纹理,然后将边缘像素的颜色抹去。我们创建一个正方形图元,纹理地址分配为(0.0,0.0)、(0.0,3.0)、(3.0,3.0)和(3.0,0.0)。这时,设置钳位纹理寻址模式,纹理将只使用一次,并且最顶一行和最后一列上的像素颜色会一直延伸到图元的最顶端和最右段,如下图所示:
“边缘颜色”纹理寻址模式由D3DTEXTUREADDRESS枚举类型的D3DTADDRESS_BORDER成员确定,它使Direct3D可以在纹理坐标超过范围的地方使用一个任意的颜色,也就是边界颜色。
下图中展示了一个使用了纹理的图元,它使用了红色的边界色:
程序如何来设置边界色依赖于使用什么版本的Direct3D设备接口。如果程序使用IDirect3DDevice3接口,可以调用IDirect3DDevice3::SetTexture平台State来设置边界颜色。将第一个参数设为要调用的纹理stage标识符,第二个参数设置为D3DTSS_BORDERCOLOR平台状态值,第三个参数为RGBA边界颜色。
如果使用IDirect3DDevice2接口,可以调用IDirect3DDevice2::SetRenderState方法来设置边界颜色,将D3DRENDERSTATE_BORDERCOLOR渲染状态值和RGBA颜色值作为它的参数。
注:IDirect3DDevice3::SetRenderState方法仍然认可D3DRENDERSTATE_BORDERCOLOR渲染状态。IDirect3DDevice3不会放弃这种方法,它会将这一渲染状态的效果映射到stage 0上。程序不应将这种遗留下来的渲染状态与相应的纹理stage状态相互混淆,否则结果将难以预料。
程序如何设置和获得纹理寻址模式只要依赖于使用什么版本的Direct3D设备接口。在使用IDirect3DDevice3进行多纹理融合时,可以调用IDirect3DDevice3::SetTexture平台State方法来为每个纹理stage设置纹理寻址模式。将所需的纹理stage标识符作为第一个参数。将第二个参数设置为D3DTSS_ADDRESS,同时改变U、V纹理寻址模式;也可以设置为D3DTSS_ADDRESSU或D3DTSS_ADDRESSV,分别来改变U、V寻址模式。第三个参数决定设置哪种纹理寻址模式;它可以使D3DTEXTUREADDRESS枚举类型的任何一个成员。要得到一个给定纹理stage的当前的纹理寻址模式,可以调用IDirect3DDevice3::GetTexture平台State,并使用D3DTEXTURE平台STATETYPE的D3DTSS_ADDRESS、D3DTSS_ADDRESSU或D3DTSS_ADDRESSV成员以决定要了解哪种寻址模式的信息。
如果程序使用IDirect3DDevice2接口,可以调用IDirect3DDevice2::SetRenderState方法来设置纹理寻址模式,同时用D3DRENDERSTATE_TEXTUREADDRESS渲染状态来设置U、V纹理寻址。也可以使用D3DRENDERSTATE_TEXTUREADDRESSU或D3DRENDERSTATE_TEXTUREADDRESSV来分别设置U、V寻址。和SetTexture平台State方法一样,它也使用D3DTEXTUREADDRESS枚举类型的值。
注:IDirect3DDevice3::SetRenderState仍认可D3DRENDERSTATE_TEXTUREADDRESS、D3DRENDERSTATE_TEXTUREADDRESSU、D3DRENDERSTATE_TEXTUREADDRESSV渲染状态。IDirect3DDevice3会将它们的效果映射到stage 0上。我们不能将遗留的渲染状态与相应的纹理stage状态相互混淆,否则结果将难以预料。
Direct3D允许程序执行纹理Wrapping。但是,将纹理寻址模式设置为D3DTADDRESS_WRAP与执行纹理Wrapping使不同的。设置Wrapping纹理寻址模式只会在纹理应用使产生多个拷贝,而纹理Wrapping则会改变系统对纹理多边形的光栅方式。详细内容见“纹理Wrapping”。
纹理Wrapping有效时,超出范围的纹理坐标将是不正确的,并且对这样的纹理坐标进行光栅操作也没有进行定义。同时,在使用纹理Wrapping时,不能使用纹理寻址模式。
Direct3D提供了两种操作和控制纹理的方法,纹理句柄和纹理接口。其中纹理句柄是老方法。IDirect3D和IDirect3D2接口使用这种方法。
在IDirect3D3接口中,我们通过纹理接口指针来创建和使用纹理。调用IUnknown::QueryInterface方法对IDirect3DTexture2接口查询DirectDrawSurface对象,可以得到一个纹理接口指针。
使用纹理接口,我们可以得到一些新的纹理特性。它支持对一个图元同时使用最多8个纹理的融合,也加入了一些新的纹理融合操作。详细内容见“纹理接口”。
Direct3D设备支持从带调色板的纹理表面进行纹理操作。这种纹理类型有时被称为“调色板纹理(palettized textures)”。一个调色板纹理是一个DirectDrawSurface对象,在创建时使用了DDSCAPS_TEXTURE能力,它使用一种DDPF_PALETTEINDEXEDn像素格式(n为1、2、4或8)。这些能力包含在用来创建纹理的DDSURFACEDESC2结构中。和所有的调色板表面(palettized surfaces)一样,每个像素都是相应的DirectDrawPalette对象中的一个索引值,而不是一个颜色值。在检查与纹理相关的设备能力时,一定要调用IDirect3DDevice3::EnumTextureFormats方法来检验所支持的纹理像素格式。
准备一个调色板纹理,需要以下步骤:
- 检查DirectDraw和Direct3D能力。
- 创建一个包含DDSCAPS_TEXTURE能力的适当大小的表面,并使用一种DDPF_PALETTEINDEXEDn像素格式。
- 创建并初始化一个DirectDrawPalette对象。(要使用alpha-only的调色板纹理,在创建调色板时就要包含DDPCAPS_ALPHA能力。)
- 对纹理表面调用IDirectDrawSurface4::SetPalette,将调色板配属于这个表面。
注:如果创建一个调色板纹理表面,但是却忽略了配属一个调色板,在进行渲染时会导致错误的访问。
调色板纹理使用的调色板不仅仅只限于使用颜色数据。有时,也可以使用alpha信息。如果这样的话,在调用IDirectDraw4::GetCaps方法时,DirectDraw会显示使用了DDPCAPS_ALPHA调色板能力标志——这个标志在相应的DDCAPS结构的dwPalCaps成员中。如果渲染设备能够执行具有alpha能力的调色板纹理,那么在调用IDirect3DDevice3::GetCaps方法时,就会看到D3DPTEXTURECAPS_ALPHAPALETTE能力标志。(D3DPTEXTURECAPS_ALPHAPALETTE在D3DDEVICEDESC结构包含的两个D3DPRIMCAPS结构中。)
使用纹理句柄时,我们应该使用IDirect3D和IDirect3D2接口。一个Direct3D纹理就是一个DirectDraw表面。通过调用IDirectDrawSurface4::QueryInterface方法来得到一个IDirect3DTexture2接口,这样我们就能将一个DirectDraw表面作为一个纹理映射来使用了。使用IDirect3DTexture2接口来加载纹理,得到句柄,并跟踪调色板的变化。
由IDirect3DTexture2接口创建的一个纹理映射必须与一个3-D设备相对应。一个纹理句柄就反映出了一个纹理映射与一个设备之间的相互对应关系。一个给定的纹理可以与一个以上的设备相对应当程序调用IDirect3DTexture2::GetHandle方法将一个纹理与一个设备相互联系起来时,Direct3D对它进行进一步的确认,以确保所选设备可以支持声明的纹理格式和纹理大小。如果调用成功,GetHandle方法会返回一个纹理句柄。程序可以使用这个纹理句柄来作为渲染状态的参数。
程序可以交替的将纹理句柄用于给定的设备。
IDirect3DTexture2接口中去掉了一些原先在IDirect3DTexture接口中存在的方法。
下面我们来讨论有关纹理句柄的应用问题:
- 2.1 创建一个纹理句柄
- 2.2 使用纹理句柄进行渲染
下面的例子展示了如何来创建一个IDirect3DTexture2接口。同时展示了调用IDirect3DTexture2::GetHandle方法来获得纹理句柄的过程。然后,程序还使用IDirect3DTexture2::Load方法加载了纹理。(使用这种方法加载纹理时,需要在创建纹理时使用DDCAPS_ALLOCONLOAD标志。)注意查询的DirectDraw表面必须包含DDSCAPS_TEXTURE能力,这样才能支持一个Direct3D纹理。
// This code fragment assumes that lpDDS is a valid pointer to
// a DirectDraw surface, and that lpD3DDevice is a valid pointer to
// an IDirect3DDevice3 interface.
LPDIRECT3DTEXTURE2 lpD3DTexture2;
D3DTEXTUREHANDLE d3dhTextureHandle;
// Get the texture interface pointer.
lpDDS->QueryInterface( IID_IDirect3DTexture2, &lpD3DTexture2);
// Associate the texture with a device.
lpD3DTexture2->GetHandle( lpD3DDevice,d3dhTextureHandle);// Load the texture.
lpD3DTexture2->Load(lpD3DTexture2);
注:用IDirect3DTexture2::Load方法加载纹理时,需要为纹理分配一定的内存。这样,程序就能将纹理从一个资源或文件中加载到表面上。Direct3D使用设备无关位图(DIBs)来作为纹理位图。
创建了一个纹理句柄并将位图加载到纹理表面之后,我们就可以使用纹理来进行渲染了。不管程序使用执行缓冲方法还是DrawPrimitive方法,纹理句柄都可以用来设置与纹理相关的渲染状态。如果使用的是DrawPrimitive方法,那么所有的渲染状态设置都应该调用IDirect3DDevice3::SetRenderState方法。我们将与纹理相关的渲染状态作为第一个参数,将纹理句柄作为第二个参数。
当程序将一个纹理设置位当前纹理时,Direct3D就会将它用于所有使用DrawPrimitive方法渲染的图元上。详细内容见“当前纹理”。
通过设置纹理寻址渲染状态,程序控制纹理坐标映射到屏幕坐标的过程。详细内容见“纹理寻址状态”。
对于某些3-D硬件,程序可以进行透视修正处理。许多3-D硬件能够无条件的执行透视修正。缺省情况下,Direct3D的透视修正是有效的。详细内容见“纹理透视状态”。
Direct3D还具有很强的纹理过滤能力。详细内容见“纹理过滤”和“纹理过滤状态”。
当我们将一个纹理应用到一个图元表面上时,程序可以将图元表面的颜色与纹理的纹理像素颜色进行融合。再使用纹理句柄时,我们一次只能融合一幅纹理。详细内容见“纹理融合状态”。
使用执行缓冲的程序也可以使用纹理句柄。在使用时,要引用IDirect3DDevice3::SetRenderState方法,并将D3DRENDERSTATE_TEXTUREHANDLE渲染状态(它是D3DRENDERSTATETYPE枚举类型的一部分)作为第一个参数,将纹理句柄作为第二个参数。
创建了一个IDirect3D3 接口的程序可以使用Direct3D中的一些新的纹理特性,并将使用IDirect3DTexture2接口指针,而不是纹理句柄。现在,如果硬件支持的话,Direct3D可以执行多纹理操作。详细内容见“多纹理融合”。
- 3.1 获得一个纹理接口指针
- 3.2 用纹理接口指针进行渲染
IDirect3D3接口下的纹理也是DirectDraw表面。因此,获得纹理接口指针的第一步就是要创建一个带有DDSCAPS_TEXTURE能力设置的DirectDraw表面。
创建了表面之后,程序就能够来得到一个指向它的纹理接口的指针了。接下来要做的就是对IDirect3DTexture2接口来查询这个表面。(如果创建纹理时使用了DDSCAPS_ALLOCONLOAD标志,那么程序就必须调用IDirect3DTexture2::Load方法来加载纹理。)
下面的代码展示了从一个已经存在的DirectDrawSurface对象表面来创建纹理的过程,以及如何来加在一个纹理:
// This code fragment assumes that lpDDS is a valid pointer to
// a DirectDraw surface that was created with the DDSCAPS_TEXTURE
// surface capability, and that lpD3D3 is a valid pointer to
// an IDirect3D3 interface.
LPDIRECT3DTEXTURE2 lpD3DTexture2;
// Get the texture interface.
lpDDS->QueryInterface(IID_IDirect3DTexture2, (void**)&lpD3DTexture2);
// Load the texture.
// (This only applies to textures that were
// created with the DDSCAPS_ALLOCONLOAD flag.
lpD3DTexture2->Load(lpD3DTexture2);
注:用IDirect3DTexture2::Load方法加载纹理要为纹理分配一定的内存。然后,使用DirectDraw的blitting方法将一个位图从资源或文件中加载到表面上。(如果不支持blitting操作,可以使用DirectDraw IDirectDrawSurface4::Lock和IDirectDrawSurface4::Unlock方法直接访问表面内存。)
Direct3D支持纹理stage(texture 平台)技术,它是IDirect3DDevice3接口中多纹理操作的一部分。每个纹理stage包含着一个纹理及相应的操作。纹理stage中的纹理组成了当前纹理的设置。详细内容见“多纹理融合”。每一个纹理的状态都封装在它的纹理stage中。因此,必须使用IDirect3DDevice3::SetTexture平台State方法来设置每一个纹理的状态,同时将平台号(0-7)作为它的第一个参数,将D3DTEXTURE平台STATETYPE枚举类型的成员作为第二个参数,最后一个参数就是纹理stage的状态值。
使用纹理接口指针,程序可以对最多8个纹理进行融合。通过引用IDirect3DDevice3::SetTexture方法来设置当前纹理。在渲染时,Direct3D会将将所有的当前纹理融合到图元表面上。
调用IDirect3DDevice3::SetRenderState方法,可以来设置当前纹理的纹理Wrapping状态,同时要将从D3DRENDERSTATE_WRAP0到D3DRENDERSTATE_WRAP7的值作为它的第一个参数,并使用D3DWRAP_U或D3DWRAP_V标志的组合使纹理Wrapping在u或v方向上有效。
程序还可以设置纹理透视和纹理过滤状态。详细内容见“纹理透视状态”、“纹理过滤”和“纹理过滤状态”等内容。