霧化效果是計算機圖形學中應用最廣的效果之一,它不僅能顯著地增加視覺效果的真實感,并可以提供一定的深度感。在實時圖形程序,特別是游戲設計程序中,為了確保圖形系統的運行速度,圖形開發人員往往在位于觀察點遠處的場景使用較為簡單的三維模型,甚至不繪制物體,而在近處使用復雜模型,這樣就可能造成物體變形、突然出現或突然消失等失真現象,霧化效果可以有效地避免這種失真現象。
霧化效果實現原理
在Direct3D圖形系統中,霧化是通過將景物顏色與霧的顏色,以隨物體到觀察點距離增加而衰減的混合因子混合而實現的。距離觀察點越近,混合因子越大,場景內的物體顏色越大,霧的顏色越小,景物就越清晰;隨著觀察點拉遠,混合因子逐漸變小,場景中物體的顏色變小,而物的顏色變大,景物越來越模糊。Direct3D計算霧化的方法如下:
color = f * colorscene + (1-f) * colorfog
其中,color表示最終經過霧化處理的顏色,colorscene表示物體原來的顏色,colorfog表示應用程序中定義的霧的顏色,f表示霧化混合因子。
霧化混合因子計算方法
從上面的霧化計算方法可以看出,影響霧化效果的因素有兩個:一個是霧化混合因子,另一個是霧的顏色。通常指定霧的顏色為白色,當然也可以指定其他任何顏色以實現特殊效果。所以大多數情況下考慮的是霧化混合因子對于霧化效果的影響。
通過指定Direct3D霧化計算公式,可以定義Direct3D圖形程序中霧化效果隨距離增加的趨勢,霧化公式計算的結果就是霧化混合因子。霧化效果是物體可見程度的反映,霧化混合因子越小,物體的可見度越低。枚舉常量D3DFOGMODE定義了三種霧化公式:
Defines constants that describe the fog mode.
typedef enum D3DFOGMODE
{
D3DFOG_NONE = 0,
D3DFOG_EXP = 1,
D3DFOG_EXP2 = 2,
D3DFOG_LINEAR = 3,
D3DFOG_FORCE_DWORD = 0x7fffffff,
} D3DFOGMODE, *LPD3DFOGMODE;

Remarks
The values in this enumerated type are used by the D3DRS_FOGTABLEMODE and
D3DRS_FOGVERTEXMODE render states.
Fog can be considered a measure of visibility—the lower the fog value
produced by a fog equation, the less visible an object is.
其中d表示當前計算點到觀察點的距離,density表示霧的濃度。霧化混合因子f隨距離d而呈線性、指數、指數平方變化。由霧化公式計算得到的霧化混合因子f將用于Direct3D霧化計算公式,計算最終顏色。從上面的三種霧化混合因子f計算公式和霧化計算公式可以看出,物體距離觀察點越遠,f越小,物體原色越小,霧化顏色越多,物體越模糊。
頂點霧化與像素霧化
Direct3D采用了兩種方法進行霧化處理,頂點霧化和像素霧化。頂點霧化是在Direct3D頂點坐標和光照流水線階段實現,它根據物體多邊形每個頂點到觀察點的距離計算每個頂點的霧化程度,然后在多邊形面上根據計算結果進行插值,得到每個像素點的值。而像素霧化在Direct3D像素繪制階段實現,它根據每個像素相對于觀察點的深度計算霧化效果值,與頂點霧化相比,像素霧化計算更為精確,但同時也耗費更多的系統資源。
頂點霧化與基于范圍的霧化
有時候,使用霧化會導致圖像失真,即物體顏色和霧化顏色以意想不到的方式混合。對于頂點霧化,在默認情況下,霧化混合因子計算公式中的距離d為物體在觀察坐標系中的深度值,這在某些情況下會導致失真。例如,在一個場景中有A、B兩個物體,它們與觀察點的距離相同,從理論上說,它們應具有相同的霧化效果,然而Direct3D默認以它們在觀察坐標系中的深度值(即z值)作為d計算霧化效果,因此會導致物體A和B的霧化效果明顯不同。
為了解決上面的問題,Direct3D為頂點霧化提供了基于范圍的霧化處理(range-based
fog),在基于范圍的霧化處理中,使用物體和觀察點之間的實際距離作為參數d進行霧化混合因子的計算,因為霧化效果更精確,代價是計算量大。
要使用基于范圍的霧化,首先需要檢查當前設備是否支持基于范圍的霧化,檢查的示例代碼如下:
// check if hardware supports range fog
D3DCAPS9 caps;
g_device->GetDeviceCaps(&caps);
if(! (caps.RasterCaps & D3DPRASTERCAPS_FOGRANGE))
return false;
-
- D3DPRASTERCAPS_FOGRANGE
- Device supports range-based fog. In
range-based fog, the distance of an object from the viewer is used to
compute fog effects, not the depth of the object (that is, the
z-coordinate) in the scene.
如果當前設備支持基于范圍的霧化處理,則可以通過下面的代碼激活它:
g_device->SetRenderState(D3DRS_RANGEFOGENABLE, TRUE);
像素霧化和與眼相關深度霧化
頂點霧化得到了Direct3D硬件廣泛支持,而且這種類型的霧化處理在大多數情況下可以正常工作,但當使用較大的多邊形時也會出現問題。如果某個頂點與虛擬攝像機太近,而另一個頂點在霧化區域的某個位置,那么頂點霧化就會在這兩個頂點之間進行線性插值,得到各個像素的霧化效果,這樣計算的結果與霧化方程式對每個像素應用所得到的結果區別較大。在這種情況下,為了提供更準確的霧化效果,Direct3D支持像素霧化處理功能。Direct3D中的像素霧化處理針對每個像素進行計算,像素霧化處理也稱為表格霧化,因為某些設備利用預先計算好的查找表格決定霧化因子。每個像素的深度將用于計算霧化因子,利用D3DFOGMODE類型的成員D3DFOG_LINEAR、D3DFOG_EXP、D3DFOG_EXP2定義的霧化公式都可以用于像素霧化。根據設備的不同,可以不同方式實現像素霧化公式。如果設備不支持所需使用的霧化公式,可以編寫代碼實現較為簡單的公式,必要的話使用線性霧化公式。注意:像素霧化功能不支持基于范圍的霧化計算。
為了減輕z值在深度緩沖區內不均勻分布所造成的與霧化相關的圖像失真,大多數硬件設備使用與眼相關深度(eye-relative
depth)代替基于z坐標的深度值進行像素霧化,Direct3D使用設備空間中頂點的RHW元素來生成真正的與眼相關的深度值。如果一個設備支持與眼相關深度霧化,則當調用函數IDirect3DDevice9::GetDeviceCaps()時,結構體D3DCAPS9的成員RasterCaps將被設置為D3DPRASTERCAPS_WFOG。參考光柵器(reference
rasterizer)是個例外,軟件設備總是使用頂點的z坐標值來計算像素霧化效果。
-
- D3DPRASTERCAPS_WFOG
- Device supports w-based fog. W-based fog is
used when a perspective projection matrix is specified, but affine
projections still use z-based fog. The system considers a projection
matrix that contains a nonzero value in the [3][4] element to be a
perspective projection matrix.
如果當前設備設置的投影矩陣是一個兼容矩陣(即w友好矩陣),并且當前設備支持與眼相關深度的霧化效果,則系統會自動使用與眼相關深度值來代替頂點的z坐標進行像素霧化效果計算。如果設置的投影矩陣不是兼容矩陣,則像素霧化效果就不能正確實現。Direct3D檢查投影變換矩陣的第4列,如果該列是[0,
0, 0, 1](用作仿射投影變換),則系統將使用基于z的深度值進行霧化計算。這時如果使用線性霧化因子計算公式,就必須在設備空間中指定線性霧化效果的開始和結束距離,該距離的取值范圍是[0.0,
1.0]。
為場景添加霧化效果
添加頂點霧化效果需要對Direct3D渲染設備進行3個方面的設置:
1、激活霧化效果
Direct3D在默認情況下禁用霧化效果,所以為了給場景添加霧化效果,首先需要激活霧化處理,示例代碼如下:
g_device->SetRenderState(D3DRS_FOGENABLE, TRUE);
2、設置霧化混合因子計算公式
采用函數IDirect3DDevice9::SetRenderState()設置霧化混合因子計算公式。如果想使用頂點霧化,將第一個參數設置為D3DRS_FOGVERTEXMODE渲染狀態,第二個參數設置為枚舉類型D3DFOGMODE中的一個成員來設置相應的霧化公式,示例代碼如下:
case WM_KEYDOWN:
switch(wParam)
{
case 48: // press key "0", disable fog.
g_device->SetRenderState(D3DRS_FOGENABLE, FALSE);
break;
case 49: // press key "1", enable linear fog.
g_device->SetRenderState(D3DRS_FOGENABLE, TRUE);
g_device->SetRenderState(D3DRS_FOGVERTEXMODE, D3DFOG_LINEAR);
g_device->SetRenderState(D3DRS_FOGSTART, *(DWORD*)&fog_start);
g_device->SetRenderState(D3DRS_FOGEND, *(DWORD*)&fog_end);
break;
case 50: // press key "2", enable exp fog.
g_device->SetRenderState(D3DRS_FOGENABLE, TRUE);
g_device->SetRenderState(D3DRS_FOGVERTEXMODE, D3DFOG_EXP);
g_device->SetRenderState(D3DRS_FOGDENSITY, *(DWORD*)&fog_density);
break;
case 51: // press key "3", enable exp2 fog.
g_device->SetRenderState(D3DRS_FOGENABLE, TRUE);
g_device->SetRenderState(D3DRS_FOGVERTEXMODE, D3DFOG_EXP2);
g_device->SetRenderState(D3DRS_FOGDENSITY, *(DWORD*)&fog_density);
break;
case VK_ESCAPE:
DestroyWindow(hwnd);
break;
}
break;
3、設置霧化參數
霧化參數包括霧的顏色和用于計算霧化混合因子f的相關參數。
霧的顏色通過渲染狀態D3DRS_FOGCOLOR設置,示例代碼如下:
g_device->SetRenderState(D3DRS_FOGCOLOR, 0xFFAA8888);
為了算出霧化混合因子f,需要根據選擇的霧化計算公式在相關的渲染狀態中設置相關的霧化參數,包括為線性霧化設置開始和結束距離以及為指數霧化和指數平方霧化設置霧的濃度。
渲染狀態D3DRS_FOGSTART和D3DRS_FOGEND用于設置線性霧化的開始點和結束點到觀察點的距離。因為IDirect3DDevice9::SetRenderState()第二個參數只接受32位整數值,所以設置霧的開始距離和結束距離時需要把它們轉換為DWORD類型,示例代碼如下:
static float fog_start = 50;
static float fog_end = 300;
g_device->SetRenderState(D3DRS_FOGENABLE, TRUE);
g_device->SetRenderState(D3DRS_FOGVERTEXMODE, D3DFOG_LINEAR);
g_device->SetRenderState(D3DRS_FOGSTART, *(DWORD*)&fog_start);
g_device->SetRenderState(D3DRS_FOGEND, *(DWORD*)&fog_end);
渲染狀態D3DRS_FOGDENSITY用于設置呈指數霧化或指數平方霧化的濃度,濃度為浮點值,取值范圍為0.0
~ 1.0,示例代碼如下:
static float fog_density = 0.01f;
g_device->SetRenderState(D3DRS_FOGDENSITY, *(DWORD*)&fog_density);
示例程序:

按下數字鍵"0",禁用霧化。

按下數字鍵"1",啟用頂點線性霧化。

按下數字鍵"2",啟用頂點指數霧化。

按下數字鍵"3",啟用頂點指數平方霧化。
源程序:
#include <d3dx9.h>
#pragma warning(disable : 4127)
#define CLASS_NAME "GameApp"
#define release_com(p) do { if(p) { (p)->Release(); (p) = NULL; } } while(0)
IDirect3D9* g_d3d;
IDirect3DDevice9* g_device;
ID3DXMesh* g_mesh;
D3DMATERIAL9* g_mesh_materials;
IDirect3DTexture9** g_mesh_textures;
DWORD g_num_materials;
struct sVertex
{
float x, y, z;
float u, v;
};
inline float height_field(float x, float z)
{
float y = 0.0f;
y += 10.0f * cosf(0.051f * x) * sinf(0.055f * x);
y += 10.0f * cosf(0.053f * z) * sinf(0.057f * z);
y += 2.0f * cosf(0.101f * x) * sinf(0.105f * x);
y += 2.0f * cosf(0.103f * z) * sinf(0.107f * z);
y += 2.0f * cosf(0.251f * x) * sinf(0.255f * x);
y += 2.0f * cosf(0.253f * z) * sinf(0.257f * z);
return y;
}
void setup_world_matrix()
{
D3DXMATRIX mat_world;
D3DXMatrixRotationY(&mat_world, timeGetTime() / 1000.0f);
g_device->SetTransform(D3DTS_WORLD, &mat_world);
}
void setup_view_proj_matrices()
{
// setup view matrix
D3DXVECTOR3 eye(0.0f, 30.0f, -100.0f);
D3DXVECTOR3 at(0.0f, 0.0f, 0.0f);
D3DXVECTOR3 up(0.0f, 1.0f, 0.0f);
D3DXMATRIX mat_view;
D3DXMatrixLookAtLH(&mat_view, &eye, &at, &up);
g_device->SetTransform(D3DTS_VIEW, &mat_view);
// setup projection matrix
D3DXMATRIX mat_proj;
D3DXMatrixPerspectiveFovLH(&mat_proj, D3DX_PI/4, 1.0f, 1.0f, 500.0f);
g_device->SetTransform(D3DTS_PROJECTION, &mat_proj);
}
bool init_geometry()
{
ID3DXBuffer* material_buffer;
/*
D3DXLoadMeshFromXA(
LPCSTR pFilename,
DWORD Options,
LPDIRECT3DDEVICE9 pD3DDevice,
LPD3DXBUFFER *ppAdjacency,
LPD3DXBUFFER *ppMaterials,
LPD3DXBUFFER *ppEffectInstances,
DWORD *pNumMaterials,
LPD3DXMESH *ppMesh);
*/
if(FAILED(D3DXLoadMeshFromX("seafloor.x", D3DXMESH_SYSTEMMEM, g_device, NULL, &material_buffer, NULL,
&g_num_materials, &g_mesh)))
{
MessageBox(NULL, "Could not find seafloor.x", "ERROR", MB_OK);
return false;
}
D3DXMATERIAL* xmaterials = (D3DXMATERIAL*) material_buffer->GetBufferPointer();
g_mesh_materials = new D3DMATERIAL9[g_num_materials];
g_mesh_textures = new IDirect3DTexture9*[g_num_materials];
for(DWORD i = 0; i < g_num_materials; i++)
{
g_mesh_materials[i] = xmaterials[i].MatD3D;
// set ambient reflected coefficient, because .x file do not set it.
g_mesh_materials[i].Ambient = g_mesh_materials[i].Diffuse;
g_mesh_textures[i] = NULL;
if(xmaterials[i].pTextureFilename != NULL && strlen(xmaterials[i].pTextureFilename) > 0)
D3DXCreateTextureFromFile(g_device, xmaterials[i].pTextureFilename, &g_mesh_textures[i]);
}
material_buffer->Release();
// change model's height
IDirect3DVertexBuffer9* vertex_buffer;
g_mesh->GetVertexBuffer(&vertex_buffer);
sVertex* vertices;
vertex_buffer->Lock(0, 0, (void**)&vertices, 0);
DWORD num_vertices = g_mesh->GetNumVertices();
for(DWORD i = 0; i < num_vertices; i++)
vertices[i].y = height_field(vertices[i].x, vertices[i].z);
vertex_buffer->Unlock();
vertex_buffer->Release();
return true;
}
bool init_d3d(HWND hwnd)
{
g_d3d = Direct3DCreate9(D3D_SDK_VERSION);
if(g_d3d == NULL)
return false;
D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory(&d3dpp, sizeof(d3dpp));
d3dpp.Windowed = TRUE;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;
d3dpp.EnableAutoDepthStencil = TRUE;
d3dpp.AutoDepthStencilFormat = D3DFMT_D16;
if(FAILED(g_d3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hwnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING,
&d3dpp, &g_device)))
{
return false;
}
if(! init_geometry())
return false;
setup_view_proj_matrices();
g_device->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
g_device->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1);
g_device->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
g_device->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
g_device->SetRenderState(D3DRS_FOGCOLOR, 0xFFAA8888);
g_device->SetRenderState(D3DRS_AMBIENT, 0xFFFFBB55);
return true;
}
void cleanup()
{
delete[] g_mesh_materials;
if(g_mesh_textures)
{
for(DWORD i = 0; i < g_num_materials; i++)
release_com(g_mesh_textures[i]);
delete[] g_mesh_textures;
}
release_com(g_mesh);
release_com(g_device);
release_com(g_d3d);
}
void render()
{
g_device->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(5, 5, 5), 1.0f, 0);
g_device->BeginScene();
setup_world_matrix();
for(DWORD i = 0; i < g_num_materials; i++)
{
g_device->SetMaterial(&g_mesh_materials[i]);
g_device->SetTexture(0, g_mesh_textures[i]);
g_mesh->DrawSubset(i);
}
g_device->EndScene();
g_device->Present(NULL, NULL, NULL, NULL);
}
LRESULT WINAPI WinProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
static float fog_start = 50;
static float fog_end = 300;
static float fog_density = 0.01f;
switch(msg)
{
case WM_KEYDOWN:
switch(wParam)
{
case 48: // press key "0", disable fog.
g_device->SetRenderState(D3DRS_FOGENABLE, FALSE);
break;
case 49: // press key "1", enable linear fog.
g_device->SetRenderState(D3DRS_FOGENABLE, TRUE);
g_device->SetRenderState(D3DRS_FOGVERTEXMODE, D3DFOG_LINEAR);
g_device->SetRenderState(D3DRS_FOGSTART, *(DWORD*)&fog_start);
g_device->SetRenderState(D3DRS_FOGEND, *(DWORD*)&fog_end);
break;
case 50: // press key "2", enable exp fog.
g_device->SetRenderState(D3DRS_FOGENABLE, TRUE);
g_device->SetRenderState(D3DRS_FOGVERTEXMODE, D3DFOG_EXP);
g_device->SetRenderState(D3DRS_FOGDENSITY, *(DWORD*)&fog_density);
break;
case 51: // press key "3", enable exp2 fog.
g_device->SetRenderState(D3DRS_FOGENABLE, TRUE);
g_device->SetRenderState(D3DRS_FOGVERTEXMODE, D3DFOG_EXP2);
g_device->SetRenderState(D3DRS_FOGDENSITY, *(DWORD*)&fog_density);
break;
case VK_ESCAPE:
DestroyWindow(hwnd);
break;
}
break;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
int WINAPI WinMain(HINSTANCE inst, HINSTANCE, LPSTR, INT)
{
WNDCLASSEX wc;
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_CLASSDC;
wc.lpfnWndProc = WinProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = inst;
wc.hIcon = NULL;
wc.hCursor = NULL;
wc.hbrBackground = NULL;
wc.lpszMenuName = NULL;
wc.lpszClassName = CLASS_NAME;
wc.hIconSm = NULL;
if(! RegisterClassEx(&wc))
return -1;
HWND hwnd = CreateWindow(CLASS_NAME, "Direct3D App", WS_OVERLAPPEDWINDOW, 200, 100, 640, 480,
NULL, NULL, wc.hInstance, NULL);
if(hwnd == NULL)
return -1;
if(init_d3d(hwnd))
{
ShowWindow(hwnd, SW_SHOWDEFAULT);
UpdateWindow(hwnd);
MSG msg;
ZeroMemory(&msg, sizeof(msg));
while(msg.message != WM_QUIT)
{
if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
render();
Sleep(10);
}
}
cleanup();
UnregisterClass(CLASS_NAME, wc.hInstance);
return 0;
}
下載示例工程