纹理过滤

1277 篇文章 12 订阅
1010 篇文章 11 订阅

  Direct3D渲染一个图元时,会将三维图元映射到二维屏幕上。如果图元有纹理,Direct3D就必须用纹理来产生图元的二维渲染图象上每个像素的颜色。对于图元在二维屏幕上图象的每个像素来说,都必须从纹理中获得一个颜色值。我们把这一过程称为纹理过滤(texture filteringtexture filtering

  进行纹理过滤时,正在使用的纹理通常也正在被进行放大或缩小。换句话说,这个纹理将被映射到一个比它大或小的图元的图象上。纹理的放大会导致许多像素被映射到同一个纹理像素上。那么结果看起来就会使矮矮胖胖的。纹理的缩小会导致一个像素被映射到许多纹理像素上。其结果将会变得模糊或发生变化。要解决这些问题,我们可以将一些纹理像素颜色融合到一个像素颜色上。

 

  Direct3D提供了一些方法来简化纹理过滤的过程。它提供了三种类型的纹理过滤:线性过滤(linear filtering)、各向异性过滤(anisotropic filtering)和mipmap过滤(mipmap filtering)。如果不选择纹理过滤,Direct3D还会使用一种叫做最近点采样(nearest point sampling)的技术。

  每种类型的纹理过滤都有各自的优缺点。例如,线性过滤会产生锯齿状的边缘和矮胖的效果。但是,它对系统的消耗却是最小的。另一方面,

mipmap过滤的效果通常是最好的,特别是和各项异性过滤混合使用时。但是它却需要很大的内存消耗。

  如果程序使用纹理句柄,可以调用

IDirect3DDevice3::SetRenderState方法来设置当前的纹理过滤方法,同时要将第一个参数设置为D3DRENDERSTATE_TEXTUREMAGD3DRENDERSTATE_TEXTUREMIN,第二个参数要设置为D3DTEXTUREFILTER枚举类型的一个成员。

  程序也可以使用纹理接口指针来设置纹理过滤方法,这是要调用

IDirect3DDevice3::SetTexture平台State方法,并将第一个参数设置为要进行纹理过滤的那个纹理的整数索引号(0-7),将第二个参数设置为D3DTSS_MAGFILTERD3DTSS_MINFILTERD3DTSS_MIPFILTER,将第三个参数设置为D3DTEXTUREMAGFILTERD3DTEXTUREMINFILTERD3DTEXTUREMIPFILTER枚举类型的一个成员。

  下面我们来分别讨论几种纹理过滤方法:

 

 

 

 

4.1 最近点采样

  在程序中,我们并不是必须要使用纹理过滤。

Direct3D可以被设置来计算纹理像素的地址,这个地址通常不是一个整数值,这时,可以简单的取一个与它最接近的整数地址来代替原地址,并使用这个整数地址上的纹理像素颜色。我们把这一过程称为最近点采样(nearest point sampling)。当纹理的大小与图元图象的大小差不多时,这种方法非常有效和快捷。如果大小不同,纹理就需要进行放大或缩小,这样,结果就会变得矮胖、变形或模糊。

  调用

IDirect3DDevice3::SetTexture平台State方法可以来选择最近点采样方法,将它的第一个参数设置为纹理的整数平台索引号(0-7),第二个参数设置为D3DTEXTUREMAGFILTERD3DTEXTUREMIPFILTERD3DTEXTUREMIPFILTER,将第三个参数设置为D3DTFG_POINT(设置放大过滤时)、D3DTFN_POINT(缩小时)或D3DTFP_POINTmipmap时)。

  使用最近点采样是要特别小心,因为在两个纹理像素的分界线上进行采样时,可能产生图象失真(

graphic artifacts)。在进行采样时,系统会选择这个采样纹理像素或者是那个采样纹理像素,在穿越分界线时,得到的结果将会产生很大的变化,也许会得到我们不想要的结果。(在使用线性过滤时,当纹理索引穿越两个纹理像素的边界时,最终的纹理像素将会是这两个纹理像素的融合结果。)

  当我们将一个很小的纹理映射到一个很大的多边形上——这被称为“放大(

magnification)”——时,我们会看到这种效果。例如,当我们使用一个看起来象西洋跳棋盘一样的纹理时,最近顶采样会得到一个更大的西洋跳棋盘,并带有明显的边界;而采用线性过滤时,最后在多边形上,跳棋格颜色会平滑的进行过渡,不会有明显的边界出现。

  大多数时候,不使用最近点采样往往能得到最好的结果。并且在现在,大部分的硬件都是针对线性过滤进行优化的。如果你确实想得到最近顶采样那样的效果——比如要清晰的显示一些文本特征——那么就要尽量避免在纹理像素分界处进行采样,否则,就会产生图象的失真。下图中向我们展示了一种图象的失真:

 

pic80.gif (3829 bytes)

 

  注意上面的两个靠右的方格,它们就产生了失真。要避免这样的失真,我们首先要了解

 

Direct3D 最近点采样的规则。 Direct3D 是将一个浮点纹理坐标,范围为 [0.0, 1.0] (包括它们),映射到一个整数的纹理像素空间,范围为 [ 0.5, n 0.5] n

是一个给定大小的纹理中纹理像素的数量。最终的纹理索引是最近的整数。这样的映射会导致在纹理像素分界处进行采样。

  下面我们来看一个例子,我们使用

 

D3DTADDRESS_WRAP 纹理寻址模式来对多边形进行渲染,使用 Direct3D 提供的映射对一个有 4 个纹理像素宽度的纹理进行映射, u 纹理索引如下:

pic81.gif (4017 bytes)

 

  注意图中的纹理坐标

 

0.01.0,它们正好在两个纹理像素的分界处。更具最近点采样法的规则,纹理坐标的范围为[0.5, 40.5]4是纹理的宽度。这样,被采样的纹理像素就是第0个纹理像素,它的纹理索引值就为1.0。但是,如果纹理坐标只比1.0小一点,那么被采样的纹理像素就应该是第n个而不是第0

个。

  这也就表示在将一个小的纹理使用最近点采样方法放大映射到屏幕空间时,会产生在纹理像素分界处采样的情况,这样就会产生失真。

  要将浮点纹理坐标映射到整数纹理像素上是很困难的,并且通常也没有必要这样做。大多数硬件在执行时使用一种迭代的方法来计算每个像素位置上的纹理坐标。这种方法往往会隐藏这些错误,因为它们在迭代过程中会逐渐积累起来。

 

  Direct3D参考光栅使用直接估计(direct-evaluation)的方法来计算每个像素位置上的纹理索引。与迭代法不同,直接估计过程中的一些错误会比较自由的表现出来。这样,在使用最近点采样时出现的错误就会比较明显。

  我们最好只在必需时再使用最近点采样法。在使用它时,最好能使纹理坐标明显的偏离纹理像素的分界处。

 

 

 

 

4.2 线性纹理过滤

 

 

  Direct3D使用的线性过滤方法是双线性过滤(bilinear filtering)。和最近点采样一样,双线性过滤首先要计算一个纹理像素的地址,这个地址通常不是整数地址。然后,找到一个地址最接近的整数地址纹理像素。另外,Direct3D渲染模块还要计算与最近采样的点相邻的四个纹理像素的加权平均(weighted average

)。

  可以调用

 

IDirect3DDevice3::SetTexture平台State方法来选择双线性纹理过滤,并将第一个参数设置为纹理的整数索引号(0-7),将第二个参数设置为D3DTEXTUREMAGFILTERD3DTEXTUREMIPFILTERD3DTEXTUREMIPFILTER,将第三个参数设置为D3DTFG_LINEAR(放大)、D3DTFN_LINEAR(缩小)或D3DTFP_LINEARmipmap)。

 

 

4.3 各向异性纹理过滤

4.3 各向异性纹理过滤

  各向异性是对一个三维物体纹理像素的可见的变形,这个物体的表面朝向屏幕平面,并与之有一定的角度。各向异性图元的像素在映射到纹理像素时,它的形状会发生变形。

Direct3D用反映射到纹理空间的屏幕像素的延伸率(长度/宽度)来度量一个像素的各向异性(anisotropy)。

  各向异性纹理过滤可以和线性过滤或

mipmap过滤联合使用。调用IDirect3DDevice3::SetTexture平台State方法可以使各项异性过滤有效,同时要将第一个参数设置为纹理的整数索引值(0-7),将第二个参数设置为D3DTEXTUREMAGFILTERD3DTEXTUREMINFILTER,将第三个参数设置为D3DTFG_ANISOTROPIC(放大)或D3DTFN_ANISOTROPIC(缩小)。

  程序还要调用

IDirect3DDevice3::SetTexture平台State方法将各向异性度(degree of anisotropy)设置在01之间(不包含它们),同时将诶一个参数设置为纹理的整数索引值(0-7),将第二个参数设置为D3DTSS_MAXANISOTROPY,最后一个参数就是各向同性度(degree of isotropy)。

  将各向同性度(

degree of isotropy)设置为1将使各向同性过滤无效(设置为任何比1大的值将会使它有效)。检查D3DPRIMCAPS结构中的D3DPRASTERCAPS_ANISOTROPY标志,以确定各向异性度的可能的范围。

 

 

 

 

 

 

4.4 Mipmap

纹理过滤

纹理过滤

 

 

  Mipmap

纹理技术用来降低场景渲染的时间消耗。同时也提高了场景的真实感。但它的缺点是要占用大量的内存空间。

  下面我们来讨论

 

mipmap纹理技术的有关内容:

什么是

  一个

mipmap就是一系列的纹理,每一幅纹理都与前一幅是相同的图样,但是分辨率都要比前一幅有所降低。mipmap中的每一幅或者每一级图象的高和宽都比前一级小二分之一。Mipmap并不一定必须是正方形。

  高分辨率的

mipmap图象用于接近观察者的物体。当物体逐渐远离观察者时,使用低分辨率的图象。Mipmap可以提高场景渲染的质量,但是它的内存消耗却很大。

 

  Direct3Dmipmap描绘成一系列相互联系的表面。高分辨率的纹理位于开始处,并与下一级纹理相互联系。以此类推,纹理相互联系,逐渐排列到分辨率最小的一级。

  下面这套插图显示了这样的一个例子。这套纹理是一个三维场景中一个集装箱的标签。当我们创建了一个

mipmap时,分辨率最高的一幅纹理就是这一套纹理的第一个。这套mipmap中的每一个纹理宽高都是前一个纹理宽高的二分之一。这样,最大分辨率的纹理是256x256,接下来的纹理就是128x128,最后一个纹理就是64x64

  我们有一个能看到这个标签的最大距离。如果观察者从远处向标签走近,那么场景中首先会显示最小的一幅纹理,它的大小是

64x64的。

pic82.gif (3304 bytes)

  当观察者走进标签时,我们就使用更高分辨率一幅纹理:

 

pic83.gif (8944 bytes)

 

 

 

 

 

 

 

 

  当观察者走到允许的最近距离时,我们使用分辨率最高的那幅纹理:

pic84.gif (19673 bytes)

  这是方法能够模拟纹理的透视效果并能够减少处理时的计算量。与将一幅纹理用于不同的分辨率相比,这种方法更加快速。

 

 

 

   Direct3D 能够访问 mipmap 中与我们想要输出的分辨率最接近的那个纹理设置,并将像素映射到它的纹理像素空间中。如果最终图象的分辨率在 mipmap 纹理的分辨率的中间,那么 Direct3D

会对两幅纹理中的纹理像素进行检查,并将它们的颜色值进行融合。

  如果要使用

 

mipmap ,首先要创建一套 mipmap 。详细内容见“创建一系列 mipmap ”。如果程序使用的使纹理句柄,那么就必须将 mipmap 选择作为当前纹理。如果使用纹理接口指针,那么就要将 mipmap 选择作为当前纹理设置中的第一个纹理。详细内容见“多纹理融合”。

接下来,程序要设置用来对纹理像素采样的过滤方法。

 

Mipmap过滤最快的方法就是让Direct3D选择最近的纹理像素。我们用D3DTFP_POINT枚举值来选择这一方法。如果程序使用D3DTFP_LINEAR枚举值,可以得到更好的过滤效果。它会选择最近的mipmap

,然后找到当前像素映射在纹理中的位置,计算这个位置周围纹理像素的加权平均。

 

 

 

4.4.2 创建一系列Mipmap

  要创建一个表示

 

mipmap中某一级的表面,需要在DDSURFACEDESC结构中声明DDSCAPS_MIPMAPDDSCAPS_COMPLEX标志,并将这个结构传递给IDirectDraw4::CreateSurface方法。由于所有的mipmap也是纹理,因此也要声明DDSCAPS_TEXTURE

标志。

  我们也可以自己来创建

 

mipmap中的每一级,然后使用IDirectDrawSurface4::AddAttachedSurface方法将它们链接在一起。但是我们并不推荐使用这样的方法。许多3-D硬件都对IDirectDraw4::CreateSurface方法优化了它们的驱动程序。因此,通过调用IDirectDrawSurface4::AddAttachedSurface来建立mipmap链接的程序可能会发现mipmapping

没有想象中的那么快捷。

  下面的例子向我们展示了如何使用

 

IDirectDraw4::CreateSurface方法来创建一个mipmap链接,这个mipmap分为5级,大小分别为256×256128×12864×6432×3216×16

 


// This code fragment assumes that the variable lpDD is a
// valid pointer to a DirectDraw interface.

DDSURFACEDESC ddsd;
LPDIRECTDRAWSURFACE4 lpDDMipMap;
ZeroMemory(&ddsd, sizeof(ddsd));
ddsd.dwSize = sizeof(ddsd);
ddsd.dwFlags = DDSD_CAPS | DDSD_MIPMAPCOUNT;
ddsd.dwMipMapCount = 5;
ddsd.ddsCaps.dwCaps = DDSCAPS_TEXTURE | DDSCAPS_MIPMAP | DDSCAPS_COMPLEX;
ddsd.dwWidth = 256UL;
ddsd.dwHeight = 256UL;

 

ddres = lpDD->CreateSurface(&ddsd, &lpDDMipMap);
if (FAILED(ddres))

ddres = lpDD->CreateSurface(&ddsd, &lpDDMipMap);
if (FAILED(ddres))

 …… …… 

 

 

 

  在用

 

IDirectDraw4::CreateSurface 方法创建一系列表面时,我们可以忽略 mipmap 的级数,这样每一级都是前一级大小的二分之一,直到最小的尺寸大小为止。我们也可以忽略高和宽,这样 IDirectDraw4::CreateSurface 就会创建我们所声明的级数,并将最小一级的大小设为 1×1

注:一个mipmap链接中的每一个表面的大小都是链接中前一个表面大小的二分之一。如果一mipmap中最顶端一级的大小为256

 

× 128,那么第二级的大小就是128 × 64,第三级为64 × 32,直到2 × 1为止。如果你声明了dwWidth和dwHeight成员的大小,就要注意一些限制条件。也就是要注意,在dwMipMapCount中声明的级数大小不能使任何一级mipmap的高或宽的值小于1。我们来看一个简单的最顶端一级大小为4 ×

2的mipmap表面:dwMipMapCount所允许的最大值为2。任何大于2的值都会使高或宽变成小数,这是不允许的。

  创建了

 

mipmap 表面之后,需要将表面与一个纹理相互联系起来。如果使用纹理句柄,就可以使用前面在“创建一个纹理句柄”中介绍的方法。如果使用的是纹理接口指针,请看“获得一个纹理接口指针”部分。

 

 

 

4.4.3 选择并显示Mipmap

 

 

 

 

 

  如果程序使用纹理句柄,就要将

 

mipmap

纹理的句柄指派为当前纹理。详细内容见“当前纹理”部分。

  如果程序使用纹理接口指针,就要将

 

mipmap

纹理设置作为当前纹理列表的第一个纹理。详细内容见“多纹理融合”部分。

  程序选择了

 

mipmap 纹理设置之后, it must assign values from the D3DTEXTUREFILTER enumerated type to the D3DRENDERSTATE_TEXTUREMAG and D3DRENDERSTATE_TEXTUREMIN render states. 然后, Direct3D 会自动执行 mipmap

纹理过滤。

  程序也可以自己来设置

 

mipmap 表面的链接,这是要使用 IDirectDrawSurface4::GetAttachedSurface 方法,并要在 DDSCAPS 结构中声明 DDSCAPS_MIPMAP DDSCAPS_TEXTURE 标志。下面的例子中展示了这一过程:


LPDIRECTDRAWSURFACE lpDDLevel, lpDDNextLevel;
DDSCAPS ddsCaps;
HRESULT ddres;

 

lpDDLevel = lpDDMipMap;
lpDDLevel->AddRef();
ddsCaps.dwCaps = DDSCAPS_TEXTURE | DDSCAPS_MIPMAP;
ddres = DD_OK;
while (ddres == DD_OK)
{
 
// Process this level.
 
ddres = lpDDLevel->GetAttachedSurface( &ddsCaps, &lpDDNextLevel);
 
lpDDLevel->Release();
 
lpDDLevel = lpDDNextLevel;
}

 

if ((ddres != DD_OK) && (ddres != DDERR_NOTFOUND))
{
 
// Code to handle the error goes here
}

  程序还需要自己实现一个

 

mipmap 链接来将位图数据加载到链接中的每一个表面。

 

 

   Direct3D 会明确保存一个 mipmap 链接中的级数。当程序获得一个 mipmap 的表面描述时(调用 IDirectDrawSurface4::Lock IDirectDrawSurface4::GetSurfaceDesc 方法), DDSURFACEDESC 结构的 dwMipMapCount 成员就获得了 mipmap 的级数,包括最顶端一级。对于 mipmap 中的那些不是最顶端的级来说, dwMipMapCount 成员详细说明了链接中的级数。

 

 

 

5.  纹理Wrapping

 

 

 

  注:不要将纹理

 

Wrapping 与有相似名称的纹理寻址模式相混淆。

 

 

5.1 什么是纹理Wrapping

  简单来说,纹理

 

Wrapping 就是要改变 Direct3D 光栅使用纹理坐标对有纹理的多边形进行光栅操作的基本方式。我们对一个多边形进行光栅操作时,系统在每一个多边形顶点的纹理坐标之间进行内插运算,这样来决定在多边形的每个像素上所使用的纹理像素。通常,系统将纹理看作一个二维平面,在这个平面内 A B 两点间的连线上进行内插。如果点 A U V 坐标为 (0.8, 0.3) ,点 B (0.1,.9)

,那么进行内插的连线就如下图所示:

pic85.gif (2608 bytes)

  注意,上图中

 

A B 两点的最短连线穿过了纹理的中间部分。 U V 纹理 Wrapping 的使用会影响 Direct3D U V 方向上对纹理坐标间最短连线的选取。现在我们假定 0.0 1.0 重合,那么通过定义,纹理 Wrapping 就会导致光栅在纹理坐标设置之间来选择最短距离。我们可以认为一个方向上的纹理 Wrapping 就是让系统认为将一个纹理包裹在了一个圆筒上,就象下图中那样:

pic86.gif (3890 bytes)

 

  上图中我们在

 

U 方向上进行了 Wrapping ,它影响了系统对纹理坐标进行的内插操作。我们使用同样的两个点 A B ,可以看到,它们之间最短的连线不再通过纹理的中间部分;它现在穿越了 0.0 1.0 所在的交界线。沿 V 方向的 Wrapping 与它相似,只不过纹理所包裹的圆筒横躺在地上。 U V 方向上同时进行 Wrapping

比较复杂,这时我们可以将纹理想象成一个园环面或者是面包圈的形状。

  纹理

 

Wrapping 最实际的应用就是执行环境映射( environment mapping )。通常,使用了纹理环境映射的对象看起来具有反射性,也就是能够表现出场景中这个对象周围环境的镜像图象。下面我们来看一个有四堵墙的空间,墙上分别绘制了一个字母: R G B Y ,以及相应的颜色:红、绿、蓝和黄,如下图所示:

. pic87.gif (7471 bytes)

  我们可以想象这个空间的天花板由一个有四个面的反射的柱子支撑着。将环境映射纹理映射到柱子上比较简单,但是要将纹理中的字母和颜色映射到墙上却并不很容易。下图展示了一个柱子的线框,并将所用的纹理坐标列在了柱子顶上(图中的虚线表示纹理的边缘):

 

 

pic88.gif (4291 bytes)

  我们在U方向上进行Wrapping,并假定U坐标的0.01.0的位置相互重叠,那么我们的得到的图象如下所示:

pic89.gif (11317 bytes)

  如果不进行纹理

 

Wrapping ,光栅就不会进行内插,也就不能产生出一个可信的、反射的图象。而且,柱子前面的区域的纹理在 U 坐标 0.175 0.875 之间进行了压缩,因为它们包括了纹理的中间部分。

 

 

 

 

 

5.2 使用纹理Wrapping

5.2 使用纹理Wrapping

  使纹理

 

Wrapping的过程根据使用的Direct3D设备接口不同而不同。如果程序使用IDirect3DDevice3接口,就要对多纹理层叠中的每个纹理分别进行纹理Wrapping。这样,就要调用IDirect3DDevice3::SetRenderState方法来使纹理Wrapping有效,将D3DRENDERSTATE_WRAP0D3DRENDERSTATE_WRAP7枚举值作为第一个参数来确定要设置哪个平台的Wrapping状态,在第二个参数中声明D3DWRAP_UD3DWRAP_V标志使Wrapping在相应的方向上有效,也可以将两个方向混合使用。要使一个纹理stage的纹理Wrapping无效,可以将相应的渲染状态值设置为0

  如果程序使用

 

IDirect3DDevice2IDirect3DDevice接口,可以调用SetRenderState方法使Wrapping有效;但是,这些接口不支持D3DRENDERSTATE_WRAP0D3DRENDERSTATE_WRAP7。相反,它们使用D3DRENDERSTATE_WRAPUD3DRENDERSTATE_WRAPV作为第一个参数。将第二个参数设置为TRUE使Wrapping生效,如果设置为FALSE则使Wrapping

无效。

  注:IDirect3DDevice3接口不接受遗留的D3DRENDERSTATE_WRAPU和D3DRENDERSTATE_WRAPV渲染状态,它们被D3DRENDERSTATE_WRAP0到D3DRENDERSTATE_WRAP7所取代。这些老的渲染状态在被传递给SetRenderState的IDirect3DDevice3时,会影响stage 0的U、V纹理Wrapping。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值