纹理过滤

来源:互联网 发布:symbian源码 编辑:程序博客网 时间:2024/04/27 17:21

纹理过滤

 


Microsoft® Direct3D®在渲染图元时,要把三维图元映射到二维屏幕上。如果图元贴有纹理,那么Direct3D必须用该纹理给图元在二维屏幕上对应的每个像素产生一个颜色。对于每个像素,Direct3D必须从纹理获得一个颜色值,这个过程被称为纹理过滤。

在执行纹理过滤操作时,正在使用的纹理一般会被放大或缩小,换句话说,就是纹理被贴到比它大或比它小的图元上。纹理放大会使多个像素映射到一个texel,得到的图像可能会有马赛克。纹理缩小会使一个像素映射到多个texel,得到的图像可能会模糊不清或有锯齿。要解决这些问题,必须在计算像素的颜色时对texel的颜色进行一些混合操作。

Direct3D把复杂的纹理过滤过程简化了,它给应用程序提供了三种类型的纹理过滤——线性过滤、各向异性过滤和mipmap过滤。如果应用程序不选择以上三种纹理过滤,那么Direct3D使用一种被称为最近点取样的技术。

每种类型的纹理过滤都各有优缺点。例如,线性纹理过滤可能会在最终图像中产生锯齿边缘或马赛克,但它是三种纹理过滤方法中计算量最小的。用mipmap纹理过滤通常可以得到最好的效果,尤其是和各向异性过滤一起使用的时候,但是在Direct3D支持的纹理过滤技术中,它对内存的需求也最大。

使用纹理接口指针的应用程序应该调用IDirect3DDevice9::SetSamplerState方法设置当前的纹理过滤方法。第一个参数为从0到7的整数,表示要设置纹理过滤方法的纹理层的索引值。第二个参数为D3DSAMP_MAGFILTERD3DSAMP_MINFILTERD3DSAMP_MIPFILTER,分别表示放大、缩小和mipmap过滤。第三个参数为D3DTEXTUREFILTERTYPE枚举类型值,为要设置的纹理过滤方法。

本节介绍了Direct3D支持的纹理过滤方法,并被划为以下主题。

  • 最近点取样
  • 线性纹理过滤
  • 各向异性纹理过滤
  • 用Mipmap进行纹理过滤

注意  虽然D3DRENDERSTATETYPE枚举类型中定义的纹理过滤渲染状态已经被纹理层状态取代,但是如果应用程序试图使用这些渲染状态的话,IDirect3DDevice9与IDirect3DDevice2不同的是,IDirect3DDevice9::SetRenderState方法不会失败。相反,系统会把这些渲染状态映射到多重纹理的第一层,也就是索引值为0的那层。应用程序不应该把老的渲染状态和它们对应的纹理层状态混在一起,这样可能会产生无法预知的结果。


最近点取样

 


应用程序不一定要使用纹理过滤。应用程序可以让Microsoft® Direct3D®先计算纹理地址(通常都不是整数),然后使用离该值最近的整数地址处的texel的颜色,这个过程被称为最近点取样。如果纹理的大小与图元在屏幕上的大小相近的话,那么这将是快速且有效的纹理处理方法,但如果不是这样的话,得到的图像可能会有马赛克、锯齿或模糊不清。

C++应用程序可以调用IDirect3DDevice9::SetSamplerState方法选择最近点取样,只需把第三个参数设置为D3DTEXF_POINT即可。

应用程序在使用最近点取样时应该小心,因为当在两个texel间的边界处进行纹理取样时,这种方法有时会产生图形残留物。当使用最近点取样时,系统要么取样一个texel,要么取样另一个,这样当纹理地址从一个texel移向下一个texel时,取样得到的texel会突然改变。这种效果会导致在最终显示的纹理中出现不希望的图形残留物。当使用线性过滤时,取样得到的texel是根据所有邻近的texel计算得到的,当纹理地址在邻近的texel间移动时,线性过滤会根据当前纹理地址与邻近texel间的位置关系,把相邻texel混合。

当把非常小的纹理贴到非常大的多边形表面时可以看到这种效果:这个操作通常被称为纹理放大magnification)。例如,当使用的纹理看起来像西洋跳棋的棋盘时,最近点取样会得到一个非常大的棋盘,格子之间有清晰的边缘,相比之下,线性纹理过滤得到的图像中棋盘的颜色会沿着多边形逐渐改变。

大多数情况下,为了得到最好的效果,应用程序应该尽量避免使用最近点取样。当今的大多数硬件都为线性过滤做了优化,所以应用程序不必担心因此导致的性能下降。如果应用程序想要的效果一定要用最近点取样——比如用纹理显示可读的文本——那么应用程序应该极度小心,避免在texel边界取样,因为那样会导致不想得到的效果。下图显示了这些图形残留物可能的样子。

注意这组图片中右上角的两个方块与其余的不同,可以在它们的对角线上看到明显的偏移。要避免此类图形残留物,开发人员必须熟悉Direct3D在最近点取样时使用的纹理取样规则。Direct3D把闭区间[0.0, 1.0]范围内的浮点纹理坐标映射到texel空间中的整数地址[–0.5, n – 0.5]范围内,这里n为给定纹理的大小。得到的纹理地址被舍入到最近的整数,这种映射方法在texel边界会产生取样误差。

举个简单的例子,设想应用程序用D3DTADDRESS_WRAP纹理寻址模式渲染多边形。根据Direct3D使用的映射方法,对于宽度为4个texel的纹理,纹理在u方向上的映射如下。

注意这张图中的纹理坐标0.0和1.0,正好在texel之间的边界。根据Direct3D的映射方法,纹理坐标的范围为[–0.5, 4 0.5],这里4为纹理的宽度。在这个例子中,纹理坐标为1.0处取样得到的texel是texel 0。但是,如果纹理坐标只比1.0稍微小一点,那么取样得到的就会是texel n而不是texel 0。

这就意味着用正好等于0.0和1.0的纹理坐标去放大较小的纹理,并把它贴到整齐排列在屏幕上的三角形时,如果过滤方法为最近点取样方法,那么得到的像素可能会在texel之间的边界进行取样。在纹理坐标计算过程中的任何误差,无论多小,都可能在渲染得到的图像上与texel边界对应的部分出现图形残留物。

要完全精确地执行从浮点纹理坐标到整数texel地址的映射是很难的,也很耗时,并且通常是不必要的。大多数硬件实现在给三角形内的每个像素计算纹理坐标时使用迭代法。迭代法趋向于隐藏误差,因为误差在迭代过程中被均匀地累积起来。

Direct3D参考光栅化器在给每个像素计算纹理地址时使用直接赋值法。直接赋值法与迭代法的不同之处在于这种方法产生的误差分布更随机。因为参考光栅化器不进行完全精确的计算,所以这种方法会导致在边界处的取样误差更容易被察觉。

最好的方法是只在必要的时候使用最近点取样。如果应用程序必须使用这种方法,那么最好把纹理坐标稍微偏移一些使之离开边界位置,这样就可以避免残留物的产生。


线性纹理过滤

 


Microsoft® Direct3D®使用一种被称为双线性过滤的线性纹理过滤方法。和最近点取样一样,双线性过滤首先计算一个texel地址,这通常都不会是整数,然后找到离该地址最近的整数地址。另外,Direct3D渲染模块还会根据最近取样点上、下、左、右的texel计算它们的加权平均值。

可以调用IDirect3DDevice9::SetSamplerState方法选择双线性过滤,只需把第三个参数设为D3DTEXF_LINEAR即可。


各向异性纹理过滤

 


因为三维物体表面与屏幕间的夹角而造成的纹理扭曲被称为各向异性。当把一个各向异性的图元所对应的像素映射到texel时,像素的形状会被扭曲。Microsoft® Direct3D®根据像素被反向映射到纹理空间中的伸长率——也就是长度除以宽度——计量屏幕上像素的各向异性属性。

为了提高渲染质量,应用程序可以把各向异性过滤与线性纹理过滤或mipmap纹理过滤结合在一起使用。应用程序可以调用IDirect3DDevice9::SetSamplerState方法启用各向异性纹理过滤,只需把第三个参数设为D3DTEXF_ANISOTROPIC即可。

应用程序必须同时把degree of anisotropy设为大于一的值。可以调用IDirect3DDevice9::SetSamplerState方法设置这个值。第一个参数为0到7的纹理层索引值,把第二个参数设为D3DSAMP_MAXANISOTROPY,第三个参数为要设置的degree of anisotropy(译注:原文为degree of isotropy)的值。

应用程序只需把degree of anisotropy(译注:原文为degree of isotropy)设为一即可禁用各向异性过滤。要确定degree of anisotropy的允许范围,可以检查D3DCAPS9结构的MaxAnisotropy成员。


Mipmap进行纹理过滤

 


Mipmap是一系列纹理,每一张纹理都表示同一幅图像,但是分辨率逐渐变低。Mipmap中的每张图像,或每一级,都比前一级小一半。Mipmap不必是正方形的。

较高分辨率的mipmap图像用于离用户近的物体,而较低分辨率的图像则用于远处的物体。使用Mipmap在消耗更多内存的情况下,提高了渲染得到的纹理的质量。

Microsoft® Direct3D®用一连串从属表面表示mipmap。分辨率最高的纹理在链的最前端,下一级mipmap是它的从属表面。依次,每一级mipmap的从属表面是它在mipmap中的下一级,一直到mipmap中分辨率最低的那级。

下图显示了这样的例子。该纹理表示在一个三维第一人称游戏中的一个容器上的标记。创建mipmap时,最高分辨率的纹理是mipmap中的第一个,mipmap中每个随后的纹理的宽度和高度都是原来的一半,在这个例子中,最高分辨率的mipmap为256x256。下一级纹理为128x128,最后一级纹理为64x64。

这个标记有一个最远可见距离。如果用户离标记很远,那么游戏就用mipmap链中最小的纹理显示,在这个例子中就是64x64的纹理。

随着用户移动视点并离标记越来越近,游戏会逐渐使用mipmap链中更高分辨率的纹理。下图中纹理的分辨率为128x128。

当视点离标记的距离为所允许的最近距离时,游戏就使用最高分辨率的纹理。

对于纹理而言,这是一种更有效的模拟透视的方法,与在不同的分辨率下用单张纹理进行渲染相比,在不同分辨率下使用多张纹理会更快。

Direct3D可以确定mipmap链中哪一级纹理的分辨率与当前需要的最为接近,并把像素映射到那一级纹理的texel空间。如果最终图像需要的分辨率位于mipmap链中两级纹理的分辨率之间,Direct3D会取得这两级mipmap中的texel并把它们的颜色值混合在一起。

要使用mipmap,应用程序必须创建一个mipmap链。应用程序只需把mipmap链设为当前纹理可以使用mipmap。更多信息,请参阅纹理混合

下一步,应用程序必须设置Direct3D用于取样texel的纹理过滤方法。Mipmap过滤最快的方法就是让Direct3D选择最近的texel,D3DTEXF_POINT枚举类型值就是用来选择这种方法的。如果应用程序使用D3DTEXF_LINEAR枚举类型值,那么Direct3D可以产生更好的过滤效果,这会使Direct3D选择最近的那级mipmap,然后根据当前像素在那一级mipmap中所映射的texel及其附近的texel计算加权平均值。

Mipmap纹理用于减少渲染三维场景所需的时间,同时提高了场景的真实感。但是,mipmap通常需要大量的内存。

创建mipmap

以下示例代码显示了应用程序如何调用IDirect3DDevice9::CreateTexture方法创建一条五级mipmap链:256x256, 128x128, 64x64, 32x32, 及16x16。

// 本例假设变量d3dDevice为指向IDirect3DDevice9接口的有效指针。
 
IDirect3DTexture9 * pMipMap;
d3dDevice->CreateTexture(256, 256, 5, 0, D3DFMT_R8G8B8, D3DPOOL_MANAGED, &pMipMap);

IDirect3DDevice9::CreateTexture的前两个参数为最高一级的纹理的宽和高。第三个参数为mipmap纹理的级数,如果应用程序把它设为零,那么Direct3D会创建一系列表面,每个都是前一个的一半,直到大小为1x1为止。第四个参数指定该资源的用途,本例中,零表示不为该资源指定特殊的用途。第五个参数指定纹理的表面格式,该参数为D3DFORMAT枚举类型值。第六个参数为D3DPOOL枚举类型值,表示在把创建的资源放在哪种类型的内存中,除非应用程序使用动态纹理,否则建议使用D3DPOOL_MANAGED。最后一个参数为指向IDirect3DTexture9接口指针的地址。

注意    Mipmap链中的每个表面的大小都是前一个表面的一半。如果最高一级mipmap的大小为256x128,那么第二级的mipmap就是128x64,第三级为64x32,依次类推,直到1x1(译注:最后几级分别为:4x2,2x11x1)。应用程序在Levels中要求的mipmap级不能使链中的任何mipmap的宽和高小于1。举个简单的例子,如果最高一级的mipmap表面为4x2,那么Levels的最大允许值就是三,第三层的大小为1x1。如果Levels的值大于3,那么会导致第二级mipmap的高度值出现小数,而这是不允许的。(译注:原文表述不够准确,应该是log2 (max (width, height) ) < 0

选择并显示mipmap

可以调用IDirect3DDevice9::SetTexture方法把mipmap纹理设置为当前纹理中的第一个纹理,更多信息请参阅纹理混合

应用程序在选择mipmap纹理后,必须用D3DTEXTUREFILTERTYPE枚举类型值设置D3DSAMP_MIPFILTER取样器状态。之后Direct3D就可以自动执行mipmap纹理过滤。以下示例代码显了如何启用mipmap纹理过滤。

d3dDevice->SetTexture(0, pMipMap);
d3dDevice->SetTextureStageState(0, D3DSAMP_MIPFILTER, D3DTEXF_POINT);

应用程序也可以手工遍历mipmap链,只需调用IDirect3DTexture9::GetSurfaceLevel方法,并指定要取得的mipmap级即可。以下示例代码从mipmap链的最高一级遍历到最低一级。

IDirect3DSurface9 * pSurfaceLevel;
for (int iLevel = 0; iLevel < pMipMap->GetLevelCount(); iLevel++)
{
    pMipMap->GetSurfaceLevel(iLevel, &pSurfaceLevel);
    //Process this level.
    pSurfaceLevel->Release();
}

为了把位图数据载入mipmap链中的每个表面,应用程序需要手工遍历mipmap链,一般来说这是遍历mipmap链的唯一原因。应用程序可以通过调用IDirect3DBaseTexture9::GetLevelCount取得mipmap的级数。

原创粉丝点击