Direct3D可以将一个纹理与图元的颜色进行融合来产生透明效果,也可以将多个纹理融合到一个图元上。下面我们来讨论纹理融合的实现,分以下几个部分:
在程序中使用纹理融合之前,我们要先检查所用的硬件是否支持使用纹理融合。我们可以在D3DPRIMCAPS结构的dwTextureCaps成员中找到相关的信息。要想详细了解如何查询硬件的纹理融合能力,请看IDirect3DDevice3::GetCaps和D3DDEVICEDESC。
Direct3D渲染一个图元时,总是根据图元的材质和有关的光线信息来产生图元的颜色。如果程序中纹理融合有效,Direct3D就必须将一个或多个纹理的纹理像素颜色与图元的当前颜色进行融合。Direct3D用下面的公式来决定一个图元图象中每个像素最终的颜色:
FinalColor = TexelColor * SourceBlendFactor + PixelColor * DestBlendFactor
上面的公式中,FinalColor是最后输出到目标渲染表面的像素颜色;TexelColor表示对应于当前像素的纹理像素的颜色(有关Direct3D将像素映射到纹理像素的内容见“纹理过滤”);SourceBlendFactor是一个计算得到的值,它决定将纹理像素颜色的百分之多少应用于最后的颜色;PixelColor是图元图象中当前像素的颜色;DestBlendFactor用来决定当前像素颜色的百分之多少用于最后的颜色。SourceBlendFactor和DestBlendFactor的范围从0.0到1.0,包括0.0和1.0。
从上式中我们可以看到,当SourceBlendFactor为0.0并且DestBlendFactor为1.0时,纹理是完全透明的。如果它们是其他的值,那么得到的纹理就会有不同程度的透明。
纹理中的每个纹理像素都有一个红、绿和蓝颜色值。缺省情况下,Direct3D用alpha值作为SourceBlendFactor。因此,程序可以通过设置纹理的alpha值来控制透明度。
程序可以用D3DRENDERSTATE_SRCBLEND和D3DRENDERSTATE_DESTBLEND枚举值来控制融合因子。要使用它们,就要引用IDirect3DDevice3::SetRenderState方法,并将D3DRENDERSTATE_SRCBLEND或D3DRENDERSTATE_DESTBLEND作它的第一个参数,将D3DBLEND枚举类型的一个成员作为第二个参数。
Direct3D通过将多个纹理应用到图元上可以达到许多特殊的效果。我们最常用的形式就是多通道纹理融合。它的一个最通常的用法就是将阴影应用于图元。详细内容见“纹理的光线映射”。
所有的Direct3D设备接口都支持多通道纹理融合。从IDirect3DDevice3接口开始,DirectX能够在一个通道里将多纹理应用到图元,当然要求用户的硬件支持者一功能。详细内容见“多纹理融合”。
如果用户的硬件不支持多纹理融合,那么程序可以使用多通道纹理融合来达到相同的视觉效果。但是,我们将不能继续维持在使用多纹理映射所能达到的帧速率。
通过引用IDirect3DDevice3::SetRenderState方法可以使多通道纹理融合有效,同时要将D3DRENDERSTATE_ALPHABLENDENABLE作为第一个参数,将第二个参数设为TRUE使它有效, 设为FALSE使它无效。
纹理融合有效之后,我们还要根据所要达到的效果设置相应的源和目的融合因子。程序通过调用IDirect3DDevice3::SetRenderState来控制源融合因子,并将D3DRENDERSTATE_SRCBLEND作为它的第一个参数;如果将第一个参数设为D3DRENDERSTATE_DESTBLEND,就可以控制目的融合因子了,第二个参数要设置为相应的D3DBLEND枚举类型的成员。
调用IDirect3DDevice3::SetRenderState方法,并将第一个参数设置为D3DRENDERSTATE_TEXTUREMAPBLEND可以来设置融合操作,第二个参数必须是D3DTEXTUREBLEND枚举类型的成员。
在每一个通道上,程序都要设置当前纹理。它的纹理像素颜色将会和帧缓存中现有的像素颜色进行融合。
使用IDirect3D3和IDirect3DDevice3接口,Direct3D可以在一个通道内将最多8个纹理融合到图元上。多纹理融合的使用能够很大程度的提高程序执行时的帧速。程序使用多纹理融合可以在一个通道内使用纹理、阴影、镜面光、漫射光和其他一些特殊效果。
要融合多个纹理,程序将纹理分配到当前纹理设置中,然后创建纹理融合stage。下面我们分几个部分来进行讨论:
Direct3通过使用“纹理stage”支持单通道多纹理融合。一个纹理stage包含两个变元(argument),并对他们执行融合操作,将结果传递给下一个操作或光栅操作。一个纹理stage如下图所示:
上图中,纹理stage使用指定的操作符(operator)对两个变元进行融合。通常的操作包括对变元的颜色或alpha成分进行简单的调制(modulation)或相加(addition),但是现在也支持多过24个的操作。平台的边缘可以是相关的纹理、迭代的(iterated)颜色或alpha值(在Gouraud明暗处理时进行迭代)、任意颜色和alpha或者是前一级纹理stage的结果。
纹理stage中的边缘和操作的联合实际上定义了一种简化的基于流程的融合语言。上一级stage的结果传递给另外一级stage,然后再依次传递下去。这样一级一级的传递结果,最终到达光栅进行多边形的光栅操作,我们把这一过程称为“纹理融合级联”。右图展示了单独的纹理stage如何来组成纹理融合级联:
一个设备中的每个stage都有一个基于0的索引。Direct3D接受最多8个融合stage,并且要通过检查设备的能力来决定当前的硬件支持使用几个纹理stage。第一个stage的索引值为0,第二个为1,直到7为止。
只使用我们需要使用的stage数量,缺省时,不用的融合stage是无效的。因此,如果程序仅使用了前两个stage,那么就只需要设置stage 0和1的操作和变元,而不用管其他的。
性能优化:如果在程序中根据不同的情况使用不同数量的渲染平台,那么不需要将前面使用过的所有平台都明确的声明为无效。如果使第一个没有使用的平台的颜色操作无效,那么所有它以后的平台都将无法使用。你可以将第一个纹理stage的纹理映射与颜色操作一起设置为无效。
程序将一个纹理融合stage与当前纹理设置中的每个纹理相联系。正如前面我们所讨论的那样,Direct3D依次估计每一个融合stage,以第一个纹理为开始,以第8个纹理为结束。
Direct3D将当前纹理设置中的每个纹理的信息应用到与之相关的融合stage。程序通过IDirect3DDevice3::SetTexture State方法来控制使用一个给定纹理中的信息。我们可以分别为颜色与alpha通道设置操作,每操作使用两个变元。使用D3DTSS_COLOROP stage状态来声明颜色通道操作,D3DTSS_ALPHAOP声明alpha操作,它们都使用D3DTEXTUREOP枚举类型中的值。
纹理融合变元使用D3DTEXTURE stage STATETYPE枚举类型的 D3DTSS_COLORARG1、D3DTSS_COLORARG2、D3DTSS_ALPHARG1和D3DTSS_ALPHARG2成员。使用纹理变元标志来声明相应的变元值。
注:通过将stage的颜色操作设置为D3DTOP_DISABLE,我们可以使一个纹理stage无效——包括级联中它后面的任何纹理融合stage。设置颜色操作无效同时也使alpha操作无效。
Direct3D作多可以将8个纹理依次融合到图元上。在当前纹理设置中,只能使用以纹理接口指针形式创建的纹理。
程序调用IDirect3DDevice3::SetTexture方法将纹理分配到当前纹理集中。第一个参数必需是一个从0到7的数字,第二个参数是纹理接口指针。
下面我们来看一个例子:
|
注:软件设备不支持一次将一个纹理分配到多个纹理stage。
一个纹理stage就是一系列纹理操作以及它们的变元。创建融合stage时,程序调用IDirect3DDevice3::SetTexture平台State函数。第一次调用声明要执行的操作,另两次调用定义变元。下面的代码就是创建融合stage的例子:
// Set arg1 for the texture operation. // Set arg2 for the texture operation. |
纹理中的纹理像素数据包含了颜色和alpha值。程序可以在融合stage上同时定义颜色和alpha操作。每个操作都有自己的变元。
下面这些不是Direct3D API中的一部分,但是我们仍然可以使用这些宏来创建纹理融合stage:
#define SetTextureAlpha平台( dev, i, arg1, op, arg2 ) \ |
Direct3D仍然支持纹理融合渲染状态,D3DRENDERSTATE_TEXTUREMAPBLEND,但是它所提供的融合模式不能与基于纹理融合的纹理stage混合使用。但是我们可以使用纹理stage“建立”我们自己的遗留的融合模式的等价物。
下面展示了遗留的融合模式(由D3DTEXTUREBLEND枚举类型的成员来确认),后面都跟着通过纹理stage状态设置相应的融合的简短的例子。(对于所有的例子,g_lpDev使一个指向IDirect3DDevice3接口的正确的指针):
D3DTBLEND_COPY and D3DTBLEND_DECAL D3DTBLEND_DECALALPHA D3DTBLEND_MODULATE if ( the_texture_has_an_alpha_channel ) D3DTBLEND_MODULATEALPHA D3DTBLEND_DECALMASK and D3DTBLEND_MODULATEMASK |
对于实际渲染场景的程序,必须要考虑光源的影响。尽管平面明暗处理与Gouraud明暗处理已经时非常有用的工具了,但是对于我们所需要的还不够。Direct3D支持多通道和多纹理融合。这些技术的应用可以使场景看起来比只使用明暗处理方法时更加真实。通过使用光线映射,程序可以将光照区域与阴影效果添加到图元上。
一个光线映射就是一个或一组纹理,它们包含了场景中的光线信息。我们可以将光线信息存储在光线映射的alpha值中,也可以存储在颜色值中,当然也可以同时存储在它们之中。
如果使用多通道纹理融合来执行光线映射,那么程序应该在第一个通道将光线映射渲染到图元上,使用第二个通道来渲染基本的纹理。但是在使用镜面光映射(specular light mapping)时,不能按照这样的顺序,而要首先渲染基本纹理,然后在加入光线映射。
多纹理融合可以使我们在一个通道内渲染光线映射和基本纹理。如果用户的硬件支持多纹理融合,那么在执行光线映射时,就因该充分利用硬件的能力,它会明显提高程序的性能。
使用光线映射,Direct3D程序可以在渲染一个图元时获得不同的光线效果。程序不能只在场景中映射单色和彩色光线,可以加入一些细节的东西,如镜面高光和漫射光线等。
下面我们分几个部分进行详细的讨论:
一些早期的3-D加速板不支持使用目的像素的alpha值进行纹理融合。它们也不支持多纹理融合操作。如果你的程序要使用这样的硬件,那么就只能使用多通道纹理融合来执行单色光映射。
要执行单色光映射,程序必须在光线映射纹理的alpha数据中存储光线信息。程序使用Direct3D的纹理过滤能力将图元图象的每个像素映射到光线映射中相应的纹理像素。它将源融合因子设置为相应纹理像素的alpha值。
下面的代码展示了如何使用单色光映射:
|
因为不支持目的alpha融合的显示适配器通常都不支持多纹理融合,因此,上面例程中将光线映射设置为第一个纹理,这对于所有的3-D加速卡都是适用的。例子中,纹理融合stage的颜色操作是将纹理数据与图元上的颜色相融合,然后选择第一个纹理和图元上已有的颜色作为输入数据。
使用彩色光映射可以使场景更加真实。彩色光映射使用光线映射的RGB数据作为它的光线信息。我们来看下面的例子:
|
例子中将光线映射设置为第一个纹理,然后将第一融合stage的状态设置为对输入纹理数据进行调制(modulate)。它使用第一个纹理和图元的当前颜色作为调制操作的两个变元。
执行镜面光映射时,首先将镜面光映射与图元上已有的纹理进行调制,然后加入单色或RGB光线映射,下面的代码显示了这一过程:
// Set the specular light map. // Set the RGB light map. |
当我们使用点光源照射一个不光滑的表面时,就会显示出漫射光反射。漫射光的亮度与对象离光源的距离和表面法向量与光源方向向量的夹角有关。
程序可以使用纹理光线映射来模拟漫射光的效果,将漫射光映射加入到基本纹理中,如下例所示:
// Set the base texture operation and args // Set the diffuse light map. // Set the blend 平台. |
DirectDraw提供了压缩表面的功能用于对3-D模型贴赋纹理。有关详细内容请看DirectDraw文档中的相关内容。
程序创建了一个渲染设备之后,就可以决定是否支持使用IDirect3DDevice3::EnumTextureFormats方法从纹理压缩表面执行纹理操作。这个方法要调用D3DEnumPixelFormatsCallback回调函数,它是由程序提供给每个像素格式的。如果枚举像素格式使用了DXT1、DXT2、DXT3、DXT4或DXT5特征码(FOURCCs),那么设备就可以直接从使用这些格式的压缩表面来执行纹理操作。如果这样的话,就可以调用IDirect3DDevice3::SetTexture方法来直接使用压缩纹理表面了。如果设备不支持压缩纹理表面,我们仍然可以将纹理数据存储在一个压缩格式的表面,但是在使用时必须先将它转化成支持的格式。DirectDraw的blit方法,IDirectDrawSurface4::Blt和IDirectDrawSurface4::BltFast,可以自动将压缩格式转化为标准的RGBA格式。为了尽量减少信息损失,应该为目的像素格式选取最适合的压缩格式,例如ARGB:1555就应该选择DXT1,而ARGB:4444应该选择DXT3,因为DXT3包含了四位的alpha信息。
简单来说,纹理管理就是决定在给定时间哪些纹理需要渲染,并确保这些纹理被加载到显存中的过程。通常,纹理管理要包含以下一些主要的任务:
以前,Direct3D程序要自己管理纹理。现在,在DirectX 6.0中,立即模式开始支持自动管理纹理,它能够有效的管理纹理,使纹理加载能有最佳的性能。(由Direct3D管理的纹理表面通常称为“被管理的纹理(managed textures)”。)
在创建纹理时,可以请求对它们进行自动管理。要得到一个被管理的纹理,只需要在创建纹理表面时在相应的DDSCAPS2结构的dwCaps2成员中包含DDSCAPS2_TEXTUREMANAG标志。注意,在你想要创建纹理的地方不允许进行声明——当创建一个被管理的纹理时,不能使用DDSCAPS_SYSTEMMEMORY或DDSCAPS_VIDEOMEMORY标志。创建了被管理纹理之后,可以调用IDirect3DDevice3::SetTexture方法将它设置到一个纹理stage中。
Direct3D根据需要自动将纹理加载到显存中。(系统可能将被管理的纹理安排在本地或非本地显存中,这要看非本地显存是否可用或其他因素而定。被管理的纹理被安排在哪里不会通知给程序,并且这在使用自动纹理管理时也是不需要知道的。)如果程序使用的纹理比显存空间所能容纳的多,那么Direct3D就会将老的纹理逐出显存,为新纹理腾出空间。如果你要再次使用被逐出的纹理,系统会使用最初的系统内存表面重新将纹理加载到显存cache中。重新加载纹理对于性能只有很小的影响。
我们可以对纹理表面进行blitting或锁定(locking)操作,这样可以动态的修改纹理的原始系统内存拷贝。当系统检查到一个“脏的”表面——执行完一个blit操作,或者当表面解锁(unlock)后——纹理管理会自动刷新纹理的显存拷贝。这种方法对性能的损耗与重新加载被逐出的纹理一样。
当游戏进入新的一关时,程序可能会将所有被管理的纹理都从显存中冲刷掉。这时可以调用IDirect3D3::EvictManagedTextures方法逐出所有的被管理纹理。在调用这个方法时,Direct3D会将所有的本地与非本地显存纹理销毁掉,只留下原始系统内存拷贝。
注:调用IDirect3DTexture2::GetHandle方法不能得到一个被管理纹理的纹理句柄。
我们使用的硬件没有必要执行所有的Direct3D接口提供的功能。所以在程序中,我们要检查正在使用的硬件,并调整渲染方法。
许多3-D加速卡都不支持将漫射迭代值(diffuse iterated value)作为融合单元的变元。但是,在执行纹理融合时,程序却可以引入迭代的颜色数据。
有些3-D硬件没有纹理stage能与第一个纹理相联系。对于这些适配器来说,程序需要在当前纹理设置中的第二和第三纹理stage上来执行融合操作。
由于许多硬件的限制,现在很少有硬件能够执行由IDirect3DDevice3通过多纹理融合接口提供的三线性mipmap内插运算。也很少有硬件能将D3DTSS_MIPFILTER纹理stage状态设置为D3DTFP_LINEAR。当然,我们可以使用多通道纹理融合来得到同样的效果,或者退一步使用D3DTFP_POINT mipmap过滤模式。