Direct3D提供了一套强有力的工具来帮助我们提高三维场景的逼真度。下面我们将讨论一些Direct3D产生的通用的特殊效果。但是,可能出现的效果并不只限于这里所讨论的内容。下面我们分成几个主要的部分:
1. 雾化
下面我们介绍有关雾化的内容以及不同雾化特性的使用:
1.1 雾化介绍
在一个三维场景中使用雾化效果可以增加真实感、产生一定的气氛或者设置一种情绪,也可以减少由于远处的几何体靠近观察者所产生的失真。Direct3D立即模式支持两种雾化模型,像素雾化和顶点雾化,它们都有各自的特点和编程接口。
从本质上来说,雾化就是将场景中对象的颜色与根据对象深度或者离观察的距离而选择的一个雾的颜色进行混合而得到的。当对象的距离不断增加时,它原始的颜色也就更多的融合到雾的颜色中,这样就好像对象在逐渐的模糊。下面的插图中的一幅没有使用雾化,而另一幅使用了雾化。
上图中,左边的场景有清晰的地平线,场景中所有的风景都是可见的。右边的场景使用了雾化效果,雾的颜色预备将的颜色一样,这样就使物体逐渐的消失在背景中。如果结合使用分立的(discrete)雾化效果,那么就可以在场景中添加一定的情绪,使场景中对象的颜色变的柔和。
Direct3D提供了两种产生雾化的方法,像素雾化和顶点雾化,它们是根据产生雾的方式来命名的。
1.2 雾化公式
通过改变Direct3D计算雾化的方式,我们可以控制如何对对象进行雾化处理。D3DFOGMODE枚举类型中包含了用来确定三种雾化公式的成员。所有计算雾化因子的公式都是距离的函数。如何计算距离也要根据投影矩阵或者Range-Based雾化是否有效而定。
在线性公式中,start是雾化效果开始的距离,end是雾化结束的距离,d表示深度(或到观察点的距离)。d值的增加就表示对象在不断远离。像素雾化和顶点雾化都支持线性公式,但是目前只有像素雾化才支持指数公式:
f =1 / ed×density
上面的两个公式中,e表示自然对数的低(它的值约为2.71828),density表示任意的雾化密度,范围从0.0到1.0,d表示深度(或离观察点的距离)。
注:系统将雾化因子存储在顶点镜面颜色的alpha乘分钟。如果程序执行自己的变换和光线处理,可以手控的插入雾化因子值。
下图中描绘出了上述公式的曲线图:
Direct3D计算雾化效果时,就会使用上述公式中的一个来计算雾化因子,再应用到下面的公式中:
C=f·Ci+(1-f)·Cf
公式中,使用雾化因子f对当前多边形的颜色Ci进行缩放,然后再和雾的颜色Cf与(1-f)的乘积相加。得到的颜色值就是雾的颜色与原始颜色的混合值。这个公式适用于DirectX 6.0中支持的所有设备。对于遗留的ramp设备,雾化因子对漫射和镜面颜色成分进行缩放,范围也在0.0到1.0之间(包括它们)。通常,近处平面的雾化因子为1.0,远处的为0.0。
1.3 雾化融合
雾化融合操作是指将雾化因子应用到雾和对象的颜色,从而产生最终表现在场景中的颜色。D3DRENDERSTATE_FOGENABLE渲染状态控制雾化融合。将它设置为TRUE表示雾化融合有效,缺省时为FALSE。
|
1.4 雾化颜色
像素雾化和顶点雾化的雾化颜色都通过D3DRENDERSTATE_FOGCOLOR渲染状态来设置。这个值可以是任意的RGB颜色,它以RGBA方式来声明,alpha成分被忽略了。
下面例子中将雾化颜色设置为白色:
HRESULT hr; |
1.5 基于范围的雾化
有时,使用雾化可能会产生图形失真,会使对象以非直观的方式来对雾化颜色进行渲染。例如,一个场景中有两个可见的对象,其中一个的距离可以使用雾化效果,而另一个则距离较近,不受雾化的影响。这时,如果观察区域发生了旋转,那么所表现出来的雾化效果就会改变,即使场景中的对象并没有移动。下图向我们描绘出了这样的情况:
基于范围的雾化是又一种决定雾化效果的方法,并且它更加精确。在这种方式中,Direct3D使用视点到顶点的真实距离来计算雾化效果,并随着两点间距离的增加,逐渐增加雾化效果,而不是根据顶点在场景中的深度变化,这样就能避免旋转产生的失真。
如果当前设备支持基于范围的雾化,那么可以调用IDirect3DDevice3::GetCaps方法在D3DPRIMCAPS结构的dwRasterCaps成员中设置D3DPRASTERCAPS_FOGRANGE能力标志。要使基于范围的雾化有效,要将D3DRENDERSTATE_RANGEFOGENABLE渲染状态设置为TRUE。
Direct3D在变换和光线处理时计算基于范围的雾化。程序不使用Direct3D的变化和光线处理引擎时,也可以执行自己的顶点雾化计算。这时,要为每个顶点的镜面成分的alpha成分提供基于范围的雾化因子。
注:目前,还没有硬件支持每像素的基于范围的雾化(per-pixel range-based fog)。因此,只有在使用Direct3D的变换和光线处理引擎进行顶点雾化时,才能使用基于范围的雾化。如果你的程序执行自己的变换和光线处理,那么也必须执行自己的雾化计算。
1.6 像素雾化
这一部分介绍像素雾化的内容:
1.6.1 关于像素雾化
像素雾化的名称来源于它的处理方式,它是通过计算每个像素来得到雾化效果的。(这与顶点雾化不同,Direct3D在执行变化和光线处理时使用顶点雾化方式。)像素雾化有时也被称为“表格雾化”,因为一些设备要使用一个预先计算好的查询表格来决定雾化因子(使用每个像素的深度)。像素雾化可以使用任何D3DFOGMODE枚举类型的成员声明的雾化公式。像素雾化公式的指向是根据驱动器而定的,如果驱动器不支持复杂的雾化公式,那它就只能使用较简单的公式来进行计算了。
注:我们在“基于范围的雾化”中讨论过,像素雾化不支持基于范围的雾化运算。
1.6.2 目相关对基于Z深度
为了减少由于深度缓冲中z-值的不均匀分布引起的有关雾化的图形失真,大多数硬件设备都使用了目相关(eye-relative)深度来代替基于z的深度值。目相关深度从本质上来说是一个同质坐标系统中的w成分。(Direct3D通过一个设备空间坐标设置的RHW成分的倒数来产生真正的w。)如果设备支持目相关雾化,那么当你调用IDirect3DDevice3::GetCaps方法时,就要在D3DPRIMCAPS的dwRasterCaps成员中设置D3DPRASTERCAPS_WFOG标志。(传递给GetCaps的D3DDEVICEDESC结构包含了多个D3DPRIMCAPS结构来描述对于不同类型图元所要使用的能力。)
注:除了参考光栅之外,软件设备总是使用z来计算像素雾化效果。
当支持目相关雾化时,如果提供的投影矩阵在世界空间中产生的z值与设备空间中的w值等价,那么系统会自动使用目相关深度来代替基于z的深度。(调用IDirect3DDevice3::SetTransform方法时,使用D3DTRANSFORMSTATE_PROJECTION值并传递一个D3DMATRIX结构来代表需要的矩阵,这样来设置投影矩阵。)如果投影矩阵不满足需要,雾化效果将会不正确。有关产生一个合适的矩阵的内容,请参看“一个W-Friendly投影矩阵”部分。(“什么是投影变换?”部分中提供的透视投影矩阵是一个合适的投影矩阵。)
使用注意事项:要使用目相关雾化,透视修正纹理映射必须有效。将D3DRENDERSTATE_TEXTUREPERSPECTIVE渲染状态设置为TRUE可以使透视修正纹理映射有效。对于DirectX 6.0及以后的版本,这一设置是默认的。
Direct3D在基于w(w-based)深度运算中要使用正确设置的投影矩阵。这样,程序就必须设置一个合适的透视矩阵才能得到基于w特性,即使它们不使用Direct3D变换管道。
Direct3D检查投影矩阵的第4列,如果系数为[0,0,0,1](对于一个仿射投影affine projection),那么系统会在雾化时使用基于z的深度。在这种情况下,也要在设备空间中对线性雾化声明开始和结束距离,范围从最近点的0.0到最远点的1.0。
1.6.3 像素雾化参数
像素雾化的所有参数都通过设备渲染状态来进行控制。它支持所有“雾化公式”中介绍的公式。将D3DRENDERSTATE_FOGTABLEMODE设置为D3DFOGMODE枚举类型中相应的成员,就可以得到我们想要使用的公式。
使用线性雾化公式时,通过D3DRENDERSTATE_FOGTABLESTART和D3DRENDERSTATE_FOGTABLEEND渲染状态来设置开始和结束距离。这些距离都使用世界空间中的单位,但是当使用软件设备时除外,这时距离是在设备空间中。
使用指数雾化公式时,D3DRENDERSTATE_FOGTABLEDENSITY渲染状态控制雾化密度雾化密度也是一个很重要的因子,范围从0.0到1.0(包括它们),它对能指数中的距离值进行缩放。
D3DRENDERSTATE_FOGCOLOR渲染状态用来控制在雾化融合时使用的颜色。
1.6.4 使用像素雾化
我们按下述步骤使用像素雾化:
下面我们来看一个例子:
void SetupPixelFog(DWORD dwColor, DWORD dwMode) |
注:一些雾化参数需要是浮点值,但是IDirect3DDevice3::SetRenderState方法的第二个参数只接受DWORD值。上面例子中没有进行数据变化就将一些浮点值提供给了SetRenderState,它是将浮点变量的地址赋予DWORD指针,然后再使用它们。
1.7 顶点雾化
下面我们讨论有关顶点雾化的内容:
1.7.1 关于顶点雾化
当系统执行顶点雾化时,它在多边形的每个顶点处进行雾化运算,然后在进行光栅处理时在多边形面内对结果进行内插。与像素雾化不同,顶点雾化只支持线性雾化公式(D3DFOG_LINEAR)。由于顶点雾化效果要通过Direct3D的光线处理和变换引擎来计算,因此它的大部分参数都要通过IDirect3DDevice3::SetLightState方法在设备的光线状态中来进行设置。
如果你的程序不使用Direct3D来进行变换和光线处理,那么就必须执行你自己的雾化运算。这时,你的程序可以将计算的雾化因子放置在每个顶点的镜面颜色的alpha成分中。你可以使用任意的公式——基于范围的或其它的。Direct3D使用提供的雾化因子在每个多边形面内进行内插。不使用Direct3D的变化和光线处理的程序不需要通过光线状态来设置顶点雾化参数,但是仍然要使雾化有效,并通过相关的渲染状态设置雾化颜色。
1.7.2 顶点雾化参数
我们通过IDirect3DDevice3::SetLightState方法设置相应的设备光线状态来控制顶点雾化参数。目前,顶点雾化值支持线性雾化公式,它要通过将D3DLIGHTSTATE_FOGMODE灯光状态设置为D3DFOG_LINEAR来进行选择。通过D3DLIGHTSTATE_FOGSTART和D3DLIGHTSTATE_FOGEND灯光状态来设置线性雾化的开始和结束距离。所有的距离都在世界空间中。
注:尽管目前顶点雾化不支持指数公式,但是SetLightState方法所使用的D3DLIGHTSTATETYPE枚举类型中仍然包含了一个D3DLIGHTSTATE_FOGDENSITY成员。这个成员目前是没有用的,它是为将来的能力扩展而设置的。
雾化融合使用颜色通过D3DRENDERSTATE_FOGCOLOR设备渲染状态来进行控制。
执行自己的变换和光线处理的程序也必须执行自己的顶点雾化运算,并且也不能再使用灯光状态了。这样一来,这样的程序就只需要使雾化融合有效和通过相应的渲染状态设置雾化颜色了。
1.7.3 使用顶点雾化
按下面的步骤来使用顶点雾化:
我们来看一个例子:
|
注:一些雾化参数需要是浮点值,但是IDirect3DDevice3::SetRenderState和IDirect3DDevice3::SetLightState方法的第二个参数只接受DWORD值。上面例子中没有进行数据变化就将一些浮点值提供给了这些方法,它是将浮点变量的地址赋予DWORD指针,然后再使用它们。
2. 公告板技术
当我们创建一个三维场景时,我们可以通过对一些看起来具有三维效果的二维对象进行渲染的方法来提高性能。公告板技术正是出于这种思想来考虑的。
通常,公告板就是指路边的一些告示牌。Direct3D程序中,可以通过定义一个固定的矩形并对它使用纹理的方法来创建和渲染这样一种公告板。三维图形学中的公告板就是这种公告板的一种扩展。它的目的就是使二维对象能够看起来具有三维效果。这项技术将一幅包含了对象图象的纹理应用到一个矩形图元上。这个图元随着观察者不断旋转,使它总能朝向观察者。我们不用考虑对象的图象是否是矩形。公告板上的某些地方可以是透明的,这样就能使我们不想见到的公告板图象的某些部分变得不可见。
许多游戏都对它们的动画精灵使用公告板技术。例如,当我们在一个三维迷宫中行走时,我们可能会看到一些武器或者其他一些奖品,并且能够拾起它们。这些物品通常都是将一些二维纹理图象应用到一个矩形图元上生成的。我们还经常使用公告板技术来渲染场景中的树木和灌木丛等的图象。
当我们要将一个图象应用到一个公告板时,我们首先得将矩形图元进行旋转,使图象能够朝向观察者的方向。然后,程序将它平移到所需的位置。接着,就可以将纹理应用到图元上了。
公告板技术最好能用于对称的物体,特别是沿垂直方向对称的物体。它还要求观察者的水平高度不能太高,否则就有可能很明显的感觉到对象是二维的,而不是三维的。
3. 云、烟与蒸汽效果
云、烟和蒸汽效果都可以通过公告板技术来实现。通过沿两个轴对公告板进行旋转,我们就可以从任意角度看到公告板的内容。通常,我们的程序都是沿着水平和垂直两个轴来进行旋转。
要制造一个简单的云的效果,我们可以将一个矩形图元沿一个或两个轴进行渲染,使它总能朝向观察者方向。然后,将一幅云的纹理应用到图元上,并使用透明处理。我们也可以根据时间的变化改变云的图象来产生动画效果。
我们也可以使用一组图元来产生更加复杂的云的效果。云的每一部分都是一个矩形图元。这些图元可以随时间独立的移动位置,从而产生出动态效果。下图展示了这种方法:
烟的产生方法与云相似。通常,烟需要多个公告板,就和复杂的云一样。一般情况下,烟需要有翻滚和上升的效果,这样就要求组成烟的公告板能够进行相应的移动。随着烟的上升与扩散,需要的公告板数量也会增加。
蒸汽效果实际上就是一些不会上升的烟。但是与烟一样,它也会逐渐扩散。下面的插图展示了使用公告板来模拟蒸汽效果的技术:
4. 纹理融合技术
这一部分我们介绍使用纹理融合技术来产生一些特殊效果或增加真实感等的内容。我们分以下几个部分来进行讨论:
4.1 凹凸映射
纹理融合技术可以为一个图元创建具有复杂纹理的表面。特别是对于光滑的表面,效果明显。例如,我们可以将一幅经过抛光的平滑的木谷仓的纹理应用到场景中的一个桌面上。
但是,如果只使用纹理融合技术就不能满足粗糙表面的要求了,比如一些粗糙的树皮等。但幸运的是,Direct3D中有一种被称为凹凸映射(Bump Mapping)可以解决这一问题。一个凹凸映射实际上就是一幅包含了深度信息的纹理。也就是说,它储存了一些用来表示表面上高低位置的值。
程序使用融合stage将凹凸映射应用到纹理上。将包含凹凸映射的融合stage的纹理融合操作设置为D3DTOP_BUMPENVMAP或D3DTOP_BUMPENVMAPLUMINANCE。详细内容见D3DTEXTUREOP和“创建融合stage”部分。
4.2 细节纹理映射
通过多纹理或多通道融合,Direct3D使程序可以细节纹理应用到图元上。使用细节纹理,可以在表面上产生磨损的痕迹、凹凸的表面以及其他一些表面属性。我们也可以对使用细节纹理应用mipmap。详细内容,清参看“Mipmap纹理过滤”部分。
细节纹理还可以被用作深度提示(depth cue)。例如,我们现在要模拟一架正在着陆的直升机。当直升机接近地面时,由于放大,地面纹理会变得模糊不清。这时,我们要区分到地面的距离就会有一定困难。如果我们对地面使用了类似于沙砾等物体的细节纹理,我们就可以获得足够的深度提示来正确的操纵降落的直升机。
如果观察者远离图元,我们大概不会想得到很多的细节展示。但要注意,当程序不使用细节纹理时,图元可能会看起来更加明亮一些。这时,我们可以通过使用一个光线映射纹理使图元变暗来进行补偿。
5. 火焰、闪光、爆炸效果等
我们可以使用Direct3D来模拟自然界中能量释放的各种现象。例如,我们可以将一些火焰一样的纹理应用到公告板上来产生火的效果。我们还可以利用一系列的纹理来产生动画效果,并可以控制公告板变化的速度来提高真实感。如果我们要看到三维的火焰效果,可以将公告板分层放置,然后再对它们使用纹理。
闪光效果可以通过将连续的明亮光线映射到场景中所有的图元上来进行模拟。但是这种方法的系统开销很大,因此只能允许在某些区域内来使用。也就是说,场景中闪光出现的地方可以先使用这种方法。
还有一种技术是将公告板放置在视口的前面,这样就挡住了整个视口。然后,我们将一些连续的白色纹理应用到公告板上,并根据时间逐渐减少透明度,这样,整个场景就会逐渐的消退成白色。这是一种系统开销较小的模拟闪光的方法。但是,这种方法很难模拟从一个点光源产生的闪光。
使用上面所说的模拟火焰、闪光效果的方法,我们也可以模拟爆炸效果。我们可以用一个公告板来模拟爆炸的冲击波产生的一缕烟雾,同时,使用一系列的公告板来模拟火焰,另外还可以在视口前放置一个单独的公告板来模拟闪光效果。
能量柱(beam)可以使用公告板来进行模拟。也可以使用用直线列表和直线带定义的图元来进行模拟。
程序可以使用公告板或者是由三角形列表定义的图元来创建force fields。使用图元时,要在三角形列表中定义一些分离的三角形,它们均匀的覆盖住force field所占的区域。三角形之间可以有缝隙,这样就能看到三角形后面的场景了。然后,将纹理应用到三角形上。
6. 运动模糊
如果我们要在一个三维场景体现出物体的速度感,我们可以将这个物体模糊化来进行处理,同时可以在物体后面留下一条模糊的运动痕迹。Direct3D程序可以通过在每一帧中对物体进行多次渲染的方法来实现之一效果。
我们知道,Direct3D程序通常是将场景渲染到一个离屏缓冲器中。当程序调用IDirectDrawSurface4::Flip方法时,在将缓冲器的内容显示到屏幕上。这样,我们就可以在显示之前对每一帧中的对象进行多次渲染。
因此,程序需要多次调用DrawPrimitive的方法,重复的传递同一个三维物体。在每次调用之前,物体的位置要有轻微的变化,这样就能制造出一系列模糊的物体图象。如果物体有一个或多个纹理,我们可以将它所有的纹理进行透明处理,然后渲染出来作为运动模糊的第一幅图象。以后每次渲染这物体时,将它的透明度逐渐减小。当在最后的位置上渲染物体时,物体的纹理不能透明。但是如果我们需要纹理透明效果时,可以将最后位置的物体纹理处理为透明。在任何情况下,帧中物体的原始图象应该是最透明的,最后一幅图象应该是最不透明的。
当我们渲染完一系列物体图象以及场景中其他部分之后,就可以调用IDirectDrawSurface4::Flip方法来显示这一帧了。
如果我们要模拟观察者高速移动的效果,可以对整个场景进行模糊处理。这样,程序就需要在每一帧中对整个场景进行多次渲染。每一次渲染场景时,还必须移动观察点的位置。如果场景十分复杂,那么当移动速度增加时,效果就会有所降低,这是因为每一帧中需要渲染的东西增加的缘故。