雾化效果是计算机图形学中应用最广的效果之一,它不仅能显著地增加视觉效果的真实感,并可以提供一定的深度感。在实时图形程序,特别是游戏设计程序中,为了确保图形系统的运行速度,图形开发人员往往在位于观察点远处的场景使用较为简单的三维模型,甚至不绘制物体,而在近处使用复杂模型,这样就可能造成物体变形、突然出现或突然消失等失真现象,雾化效果可以有效地避免这种失真现象。
雾化效果实现原理
在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;
}
下载示例工程