深度探索DxFramework 10-4


请尊重原作者的工作,转载时请务必注明转载自:www.xionggf.com

#第10章 进入三维的世界 4

10.5.4.变换包围盒

变换包围盒,除了变换包围盒顶点的坐标之外,还得变换包围盒的面的法线向量。如果有一个转换矩阵M对平面的各点进行变换,那么这个平面的法线向量的正确变换矩阵应该G为对各点进行变换的矩阵的逆转置矩阵(inverse-transpose matrix)。所以有下列代码:

 1void C_BoundingBox::TransformBoundingBox(D3DXMATRIX*  p_TransformMatrix) 
 2 {
 3     D3DXMATRIX tempMatrix;
 4
 5     //取得转换矩阵的逆矩阵,然后再将其转置
 6    D3DXMatrixInverse(&tempMatrix, NULL, p_TransformMatrix);
 7     D3DXMatrixTranspose(&tempMatrix, &tempMatrix);
 8
 9     int i;
10     //变换包围盒的各个角顶点
11    for (i = 0; i < 8; i++) 
12     {
13         D3DXVec3TransformCoord(&point[i], &point[i],  p_TransformMatrix); 
14     }
15
16     //实质上就是对平面的法线进行变换
17    for (i = 0; i < 6; i++) {
18         D3DXPlaneTransform(&plane[i], &plane[i], &tempMatrix); 
19     }
20 }

10.6 观察世界的窗口,摄像机类

10.6.1 视口设置

10.6.2 雾化效果

雾化是指在三维图形程序中模拟真实世界中的云雾,水蒸汽,灰尘等大气效果。从生活经验我们可以得知,在雾气比较严重的地方,距离观察点近的地方物体比较清晰,远处的物体则随着它离观察点的距离的增加而变得越来越模糊。通过使用雾化效果将能增加三维场景的真实感。在Direct3D中,雾化效果是根据当前景物的颜色和雾的颜色,与随着物体和观察点距离增大而衰减的混合因子(fog blending factor)混合实现的。这里的术语“观察点”,就是当前摄像机在世界坐标中的位置,或者说,观察坐标系的原点在世界坐标系下的位置。当距离景物距离观察点越远的时候,混合因子就越小,场景中物体的颜色就越小,而雾的颜色就越大,景物就会变得越模糊。Direct3D渲染流水线计算雾化效果的公式如下:

COLOR = f*COLORscene + (1-f)*COLORfog

其中COLOR表示最终经过了雾化处理的颜色。COLORscene表示景物原有的颜,色。COLORfog表示雾的颜色,这颜色是由应用程序自定义的。f表示雾化的混合因子。Direct3D有两种雾化处理方法:顶点雾化(vertex fogging)和像素雾化(pixel fogging)。顶点雾化在渲染流水线中的顶点变换和光照阶段实现。基本原理是根据多边形的每个顶点与观察点的距离计算出每个顶点的雾化程度。然后根据计算结果,针对每个多边形进行插值计算。像素雾化则在像素绘制阶段实现,它通过判断每个像素对于观察点的深度值进行雾化效果计算。像素雾化与顶点雾化相比,计算结果更为精确,但也将消耗更多的系统性能。

由上面的雾化计算公式可以看到:雾化效果是物体可见程度的反映。雾化效果计算公式算出的结果值越小就表示景物的可见度就越低。而最终的计算结果 除了取决于景物颜色和雾的颜色之外,还取决于雾化混合因子的取值。Direct3D提供了一些枚举值表示几种雾化因子的计算方式。如下:

1typedef enum _D3DFOGMODE
2 {
3     D3DFOG_NONE = 0,
4     D3DFOG_EXP = 1,
5     D3DFOG_EXP = 2,
6     D3DFOG_LINEAR = 3,
7     D3DFOG_FORCE_DWORD = 0x7FFFFFFF
8 }D3DFOGMODE;  

D3DFOG_NONE表示不使用雾化效果处理。 D3DFOG_EXP:表示雾化效果随距离按指数规律增加。公式如下:

在上面的公式中,d表示当前计算的点的世界坐标Z值与观察点坐标(也就是摄像机在世界空间的坐标)Z值之差的绝对值,density表示雾的密度。 D3DFOG_EXP2:表示雾化效果随距离按指数平方规律增加。公式如下:

在上面的公式中,d和density的含义和D3DFOG_EXP公式所表示的含义相同。 D3DFOG_LINEAR:表示雾化效果随当前计算点到观察点的距离呈线性变化。公式如下:

上面的公式中,end和start表示雾化起始点和雾化最远点到观察点的距离。d表示当前计算的点的世界坐标Z值与观察点坐标(也就是摄像机在世界空间的坐标)Z值之差的绝对值。把混合因子f的计算式代入雾化效果的计算公式便能得到雾化效果。Direct3D提供了一套接口用来实现雾化效果。总的来说,是通过对Direct3D render state的操作来实现的。要使用雾化效果,首先要告诉Direct3D启动雾化效果,方法是通过设置render state的启动雾化效果标志为TRUE,如下:

1//IDirect3DDevice8* pDevice
2pDevice->SetRenderState(D3DRS_FOGENABLE , TRUE); 

缺省地Direct3D的雾化效果是关闭的,想停止雾化效果,则把参数改为FALSE便可。启用了雾化效果之后,需要指定雾的颜色,方法也是通过设置Direct3D的render state,如下:

1//IDirect3DDevice8* pDevice
2 pDevice->SetRenderState( D3DRS_FOGCOLOR , 0xFFFFFFFF ); 

函数的第二个参数则是雾的ARGB颜色值。

有时候使用雾化效果可能会产生失真,这是因为Direct3D默认的雾化效果是基于景物在观察坐标系上的Z值(上面介绍计算公式时对d的描述:d表示当前计算的点的世界坐标Z值与观察点坐标Z值之差的绝对值。)来计算的。这种计算方法在一些情况下会导致渲染效果的失真。例如下图 1119,左边before的图中,Object1和Object2的距离是相等的,那么按经验Object1和Object2的雾化效果应该是一样的才对。但是因为Direct3D默认的雾化效果是基于景物在观察坐标系的Z值,而不是物体到观察点的距离。所以这时候Object1反而不在雾化区域内。而如果观察点,或者说摄像机发生了旋转而没有移动,那么所表现出来的雾化效果也会改变,即使景物和摄像机的距离并没有移动。如右边的after中,这时候Object1又处于雾化区域了。

基于范围(range)的雾化算法将则是解决上述问题的方案。在这种算法中,Direct3D使用视点到顶点的真实距离来计算雾化效果,并随着两点间距离的增加,逐渐增加雾化效果,而不是根据顶点在场景中的深度变化,这样就能避免旋转产生的失真。目前,还没有硬件支持逐像素的基于范围的雾化(per-pixel range-based fog)。因此,只有在使用Direct3D渲染流水线的固定管道进行顶点变换和光照运算,并且采用的雾化计算模式是顶点雾化时,才能使用基于范围的雾化。如果利用可编程管道执行自己执行顶点变换和光照处理,那么也必须执行自己的雾化计算。不一定所有的显卡都能支持基于范围的雾化计算。在使用基于范围的雾化计算之前需要对硬件能力进行查询,代码如下:

1//IDirect3DDevice8* pDevice
2D3DCAPS8 caps;
3pDevice->GetDeviceCaps( &caps );
4//如果硬件支持基于范围的雾化计算的话就启动它
5if( caps.RasterCaps & D3DPRASTERCAPS_FOGRANGE )
6     pDevice->SetRenderState( D3DRS_RANGEFOGENABLE , TRUE ); 

使用可编程管道也可以执行自己的顶点雾化计算。这时,要为每个顶点的镜面反射颜色值的alpha值提供基于范围的雾化因子。 由于计算雾化效果需要判断了当前观察点与计算点的距离,而C_Viewport类又负责管理了观察点的位置坐标。那么,在C_Viewport类中实现雾化效果,也是合理的。与Direct3D相对应,dxframework也自定义了一些雾化类型的枚举变量。如下:

1enum FOG_TYPE 
2{
3     FOG_NONE,
4     FOG_EXP,
5     FOG_EXP2,
6     FOG_LINEAR
7}; 

从代码可以知道这些枚举值和Direct3D所定义的其实是一致的。下面是C_Viewport类提供的和雾化效果相关的属性和方法,如下:

 1class C_Viewport
 2 {
 3 ...
 4public:
 5     // 启动雾化效果
 6    void C_Viewport::SetFogEnable(bool fEnable){ fogEnable = fEnable; }
 7
 8     //获取当前雾化效果是否启动的布尔值
 9    bool C_Viewport::GetFogEnable(){ return fogEnable; }
10
11     //设置雾的颜色
12    void C_Viewport::SetFogColor(DWORD fColor) { fogColor = fColor; }
13
14     //获取雾的颜色
15    DWORD C_Viewport::GetFogColor(){ return fogColor; }
16
17     //设置雾化区域的起始Z坐标值
18    void C_Viewport::SetFogStartZ(float fStartZ){ fogStartZ = fStartZ; }
19
20     //获取雾化区域的起始Z坐标值
21    float C_Viewport::GetFogStartZ(){ return fogStartZ; }
22
23     //设置雾化区域的结束Z坐标值
24    void C_Viewport::SetFogEndZ(float fEndZ) { fogEndZ = fEndZ; }
25
26     //获取雾化区域的结束Z坐标值
27    float C_Viewport::GetFogEndZ(){ return fogEndZ; }
28
29     //设置雾化模式,顶点雾化或者是像素雾化
30    void C_Viewport::SetFogMode(DWORD fMode) { fogMode = fMode; }
31
32     //获取当前使用的雾化模式
33    int C_Viewport::GetFogMode(){ return fogMode; }
34
35    //设置雾的密度
36    void C_Viewport::SetFogDensity(float fDensity){ fogDensity = fDensity; }
37
38     //获取雾的密度
39    float C_Viewport::GetFogDensity(){ return fogDensity; }
40
41 private:
42     bool      fogEnable;   //是否启动雾的效果
43    DWORD     fogColor;     //雾的颜色
44    float     fogStartZ;    //雾化区域的起始Z坐标值
45    float     fogEndZ;     //雾化区域的结束Z坐标值
46    DWORD     fogMode;     //雾化效果,顶点雾化或像素雾化
47    float     fogDensity;  //雾的密度
48...
49 }; 

dxframework在C_Viewport类中封装的雾化效果就是上面的代码。在CamDemo中有使用雾化效果的示例代码。