8.3實例程序:平面陰影
在場景中被燈光照射的地方會產生陰影,這將使場景變的更真實。在這一部分我們將演示怎樣實現平面陰影,即在平面上的陰影(如圖8.5)。

使用這種陰影只是一種權宜之計,雖然它增強了場景的真實效果,但是這并不是現實中的陰影。
為了實現平面陰影,我們首先必須找到物體投射到平面上的陰影并進行幾何建模以便我們能夠渲染它,用一些3D數學就能很容易的實現它,然后我們用50%透明度的黑色材質來渲染描述陰影的多邊形。渲染陰影時可能出現“雙倍混合”,我們將用一小部分進行解釋,并使用模板緩存來防止雙倍混合發生。
8.3.1平行光陰影
圖8.6顯示了物體在平行光照射下得到的陰影。光線是從平行光源放射出的,它的方向是L,通過頂點p得到r(t)
= p + tL。光線r(t)和平面n
* p + d = 0 相交得到 s
。

An
intersection point s is easily found with a ray/plane
intersection test:

8.3.2點光源陰影
圖8.7顯示了物體在點光源照射下得到的陰影。點光源的位置是L。光線通過頂點p,則得到
r(t) = p + t ( p
– L )。光線r(t)和平面n
* p + d = 0 相交得到 s
。用8.3.1同樣的方法我們可以得到s。
注意:在點光源和平行光中的L是不同的。對于點光源,我們用L來表示點光源的位置。而對于平行光,我們則是用L來表示平行光的照射方向。

8.3.3陰影矩陣
注意圖8.6中所示的平行光,影子本質上是把物體按照燈光照射方向平行地投射到平面n*p+d=0之上。同樣的,圖8.7中所示的點光源,影子本質上是把物體按照透視畫法從光源投射到平面n*p+d=0之上。
我們能夠使用一個矩陣來表示從一個頂點p變換到平面n*p=d=0上的s的變化。而且,我們能夠用同一個矩陣來表現正交投影和透視投影。
我們用一個4D向量(nx,
ny, nz, d)來表示將要用于投射陰影平面的平面等式中的各個系數。讓4D向量L=(Lx,
Ly, Lz, Lw)來表示平行光的照射方向或點光源的位置。我們用w來區別:
1.假如w=0,那么L表示平行光的照射方向。
2.假如w=1
,那么L表示點光源的位置。
假定平面的法向量已經單位化,我們讓k=(nx,
ny, nz, d)*(Lx,
Ly, Lz, Lw)= nxLx+nyLy+nzLz+dLw
那么我們就可得到表示點p到點s的變換矩陣,即陰影矩陣:

因為在其他地方已經被推導出來了,對于我們來說推導它并沒有重大的意義,在這里我們就不再演示推導怎樣得到這個矩陣的過程了。但是對與感興趣的讀者可以自己到網上查找相應的信息。
D3DX庫中已經給我們提供了一個建立陰影矩陣的函數。其中當w=0時表示平行光,當w=1時表示點光源:
Builds a matrix that flattens geometry into a plane.
D3DXMATRIX * D3DXMatrixShadow(
D3DXMATRIX * pOut,
CONST D3DXVECTOR4 * pLight,
CONST D3DXPLANE * pPlane
);
Parameters
- pOut
- [in, out] Pointer to the D3DXMATRIX structure that
is the result of the operation.
- pLight
- [in] Pointer to a D3DXVECTOR4 structure describing
the light's position.
- pPlane
- [in] Pointer to the source D3DXPLANE structure.
Return Values
Pointer to a D3DXMATRIX structure that flattens
geometry into a plane.
Remarks
The D3DXMatrixShadow function flattens geometry
into a plane, as if casting a shadow from a light.
The return value for this function is the same value
returned in the pOut parameter. In this way, the D3DXMatrixShadow
function can be used as a parameter for another function.
This function uses the following formula to compute the
returned matrix.
P = normalize(Plane);
L = Light;
d = dot(P, L)
P.a * L.x + d P.a * L.y P.a * L.z P.a * L.w
P.b * L.x P.b * L.y + d P.b * L.z P.b * L.w
P.c * L.x P.c * L.y P.c * L.z + d P.c * L.w
P.d * L.x P.d * L.y P.d * L.z P.d * L.w + d
If the light's w-component is 0, the ray from the
origin to the light represents a directional light. If it is 1, the light is a
point light.
8.3.4用模板緩存防止雙倍混合
幾何學上,當我們將一個物體投影到一個平面上時,很可能會有兩個或者更多的投影三角形被重疊到一起。若我們就這樣渲染,那么有重疊三角形的地方就會被多次混合以至這些地方將會變得更黑。圖8.8就是這種情況。

使用模板緩存來解決這個問題,設置模板測試為允許像素第一次被渲染。即,當把影子像素渲染到后臺緩存時,我們同時在模板緩存中做好標記。然后,如果試圖把像素向一個已經渲染過的地方寫,那么模板測試將會失敗。這樣,我們就防止了重復寫像素也就是防止了二次融合的發生。
8.3.5代碼和解釋
下面的代碼就是講解影子例子。本例的相關代碼都在RenderShadow函數中。注意我們假設模板緩存都已經被清除為0了。
首先設置模板渲染狀態。將模板比較運算設為D3DCMP_EQUAL且將D3DRS_STENCILREF渲染狀態設置為0x0,因此假如在模板緩存中相應的值為0x0,那么就指定渲染陰影到后臺緩存中。
因為模板緩存是被清除為0x0的,所以我們第一次將影子像素寫入的時候總是正確的;不過因為我們設置D3DRS_STENCILPASS為D3DSTENCILOP_INCR,假如你試圖將已經寫過的像素寫入的話,這個測試將會失敗。在第一次寫入的時候模板像素已經被寫成了0x1,因此假如你再一次寫入,模板測試將會失敗。因此,我們避免了重復寫像素,也避免了二次融合。
void
RenderShadow()
{
Device->SetRenderState(D3DRS_STENCILENABLE,
true);
Device->SetRenderState(D3DRS_STENCILFUNC, D3DCMP_EQUAL);
Device->SetRenderState(D3DRS_STENCILREF, 0x0);
Device->SetRenderState(D3DRS_STENCILMASK, 0xffffffff);
Device->SetRenderState(D3DRS_STENCILWRITEMASK, 0xffffffff);
Device->SetRenderState(D3DRS_STENCILZFAIL, D3DSTENCILOP_KEEP);
Device->SetRenderState(D3DRS_STENCILFAIL, D3DSTENCILOP_KEEP);
Device->SetRenderState(D3DRS_STENCILPASS, D3DSTENCILOP_INCR);
|
下一步,我們計算陰影變換并將它放置到場景中適當的位置。
// compute the transformation to flatten the
teapot into a shadow.
D3DXVECTOR4
lightDirection(0.707f, -0.707f, 0.707f, 0.0f);
D3DXPLANE
groundPlane(0.0f, -1.0f, 0.0f, 0.0f);
D3DXMATRIX S;
D3DXMatrixShadow(&S,
&lightDirection, &groundPlane);
D3DXMATRIX T;
D3DXMatrixTranslation(&T, TeapotPosition.x, TeapotPosition.y,
TeapotPosition.z);
D3DXMATRIX W = T *
S;
Device->SetTransform(D3DTS_WORLD, &W);
|
最后,我們設置一個50%透明度的黑色材質,關閉深度測試,渲染陰影,然后開啟深度緩存同時關閉alpha混合和模板測試。我們關閉深度緩存來防止z-fighting,它是當兩個不同的表面在深度緩存中有同樣的深度值時出現的現象;深度緩存不知道那一個是在前面,此時就會產生討厭的閃動。因為陰影和地板是在同一個平面上,z-fighting很可能就會出現。通過先渲染地板然后禁用深度測試并繪制陰影,這樣我們就能夠保證陰影將繪制在地面只之上。
Device->SetRenderState(D3DRS_ALPHABLENDENABLE,
true);
Device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
Device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
D3DMATERIAL9 mtrl =
d3d::InitMtrl(d3d::BLACK, d3d::BLACK,
d3d::BLACK,
d3d::BLACK, 0.0f);
mtrl.Diffuse.a =
0.5f; // 50% transparency.
// Disable depth buffer so that z-fighting
doesn't occur when we
// render the shadow on top of the floor.
Device->SetRenderState(D3DRS_ZENABLE, false);
Device->SetMaterial(&mtrl);
Device->SetTexture(0, 0);
Teapot->DrawSubset(0);
Device->SetRenderState(D3DRS_ZENABLE, true);
Device->SetRenderState(D3DRS_ALPHABLENDENABLE,
false);
Device->SetRenderState(D3DRS_STENCILENABLE,
false);
}//end
RenderShadow()
|
主程序:
/**************************************************************************************
Demonstrates shadows with stencils.
Use the arrow keys and the 'A' and 'S' key to navigate the scene and translate the teapot.
**************************************************************************************/
#include "d3dUtility.h"
#pragma warning(disable : 4100)
class cTextureVertex
{
public:
float _x, _y, _z;
float _nx, _ny, _nz;
float _u, _v;
cTextureVertex() { }
cTextureVertex(float x, float y, float z,
float nx, float ny, float nz,
float u, float v)
{
_x = x; _y = y; _z = z;
_nx = nx; _ny = ny; _nz = nz;
_u = u; _v = v;
}
};
const DWORD TEXTURE_VERTEX_FVF = D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX1;
////////////////////////////////////////////////////////////////////////////////////////////////////
const int WIDTH = 640;
const int HEIGHT = 480;
IDirect3DDevice9* g_d3d_device;
IDirect3DVertexBuffer9* g_vertex_buffer;
IDirect3DTexture9* g_floor_texture;
IDirect3DTexture9* g_wall_texture;
IDirect3DTexture9* g_mirror_texture;
D3DMATERIAL9 g_floor_material = WHITE_MATERIAL;
D3DMATERIAL9 g_wall_material = WHITE_MATERIAL;
D3DMATERIAL9 g_mirror_material = WHITE_MATERIAL;
ID3DXMesh* g_teapot_mesh;
D3DXVECTOR3 g_teapot_pos(0.0f, 3.0f, -7.5f);
D3DMATERIAL9 g_teapot_material = YELLOW_MATERIAL;
void render_scene();
void render_shadow();
////////////////////////////////////////////////////////////////////////////////////////////////////
bool setup()
{
// make walls have low specular reflectance - 20%
g_wall_material.Specular = WHITE * 0.2f;
D3DXCreateTeapot(g_d3d_device, &g_teapot_mesh, NULL);
// Create and specify geometry. For this sample we draw a floor and a wall with a mirror on it.
// We put the floor, wall, and mirror geometry in one vertex buffer.
//
// |----|----|----|
// |Wall|Mirr|Wall|
// | | or | |
// /--------------/
// / Floor /
// /--------------/
g_d3d_device->CreateVertexBuffer(24 * sizeof(cTextureVertex), 0, TEXTURE_VERTEX_FVF, D3DPOOL_MANAGED,
&g_vertex_buffer, NULL);
cTextureVertex* v;
g_vertex_buffer->Lock(0, 0, (void**)&v, 0);
// floor
v[0] = cTextureVertex(-7.5f, 0.0f, -10.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f);
v[1] = cTextureVertex(-7.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f);
v[2] = cTextureVertex( 7.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f);
v[3] = cTextureVertex(-7.5f, 0.0f, -10.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f);
v[4] = cTextureVertex( 7.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f);
v[5] = cTextureVertex( 7.5f, 0.0f, -10.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f);
// wall
v[6] = cTextureVertex(-7.5f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f);
v[7] = cTextureVertex(-7.5f, 5.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f);
v[8] = cTextureVertex(-2.5f, 5.0f, 0.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f);
v[9] = cTextureVertex(-7.5f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f);
v[10] = cTextureVertex(-2.5f, 5.0f, 0.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f);
v[11] = cTextureVertex(-2.5f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f);
// Note: We leave gap in middle of walls for mirror
v[12] = cTextureVertex(2.5f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f);
v[13] = cTextureVertex(2.5f, 5.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f);
v[14] = cTextureVertex(7.5f, 5.0f, 0.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f);
v[15] = cTextureVertex(2.5f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f);
v[16] = cTextureVertex(7.5f, 5.0f, 0.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f);
v[17] = cTextureVertex(7.5f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f);
// mirror
v[18] = cTextureVertex(-2.5f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f);
v[19] = cTextureVertex(-2.5f, 5.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f);
v[20] = cTextureVertex( 2.5f, 5.0f, 0.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f);
v[21] = cTextureVertex(-2.5f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f);
v[22] = cTextureVertex( 2.5f, 5.0f, 0.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f);
v[23] = cTextureVertex( 2.5f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f);
g_vertex_buffer->Unlock();
// create the texture and set filters
D3DXCreateTextureFromFile(g_d3d_device, "checker.jpg", &g_floor_texture);
D3DXCreateTextureFromFile(g_d3d_device, "brick0.jpg", &g_wall_texture);
D3DXCreateTextureFromFile(g_d3d_device, "ice.bmp", &g_mirror_texture);
g_d3d_device->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
g_d3d_device->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
g_d3d_device->SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_POINT);
// lights
D3DXVECTOR3 light_dir(0.707f, -0.707f, 0.707f);
D3DXCOLOR color(1.0f, 1.0f, 1.0f, 1.0f);
D3DLIGHT9 light = init_directional_light(&light_dir, &color);
g_d3d_device->SetLight(0, &light);
g_d3d_device->LightEnable(0, TRUE);
g_d3d_device->SetRenderState(D3DRS_NORMALIZENORMALS, TRUE);
g_d3d_device->SetRenderState(D3DRS_SPECULARENABLE, TRUE);
// set the projection matrix
D3DXMATRIX proj;
D3DXMatrixPerspectiveFovLH(&proj, D3DX_PI/4.0f, (float)WIDTH/HEIGHT, 1.0f, 1000.0f);
g_d3d_device->SetTransform(D3DTS_PROJECTION, &proj);
return true;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////
void cleanup()
{
safe_release<IDirect3DVertexBuffer9*>(g_vertex_buffer);
safe_release<IDirect3DTexture9*>(g_floor_texture);
safe_release<IDirect3DTexture9*>(g_wall_texture);
safe_release<IDirect3DTexture9*>(g_mirror_texture);
safe_release<ID3DXMesh*>(g_teapot_mesh);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////
bool display(float time_delta)
{
// update the scene
if(GetAsyncKeyState(VK_LEFT) & 0x80000f)
g_teapot_pos.x -= 3.0f * time_delta;
if(GetAsyncKeyState(VK_RIGHT) & 0x80000f)
g_teapot_pos.x += 3.0f * time_delta;
static float radius = 20.0f;
if(GetAsyncKeyState(VK_UP) & 0x80000f)
radius -= 2.0f * time_delta;
if(GetAsyncKeyState(VK_DOWN) & 0x80000f)
radius += 2.0f * time_delta;
static float angle = (3.0f * D3DX_PI) / 2.0f;
if(GetAsyncKeyState('A') & 0x80000f)
angle -= 0.5f * time_delta;
if(GetAsyncKeyState('S') & 0x80000f)
angle += 0.5f * time_delta;
D3DXVECTOR3 position(cosf(angle) * radius, 3.0f, sinf(angle) * radius);
D3DXVECTOR3 target(0.0f, 0.0f, 0.0f);
D3DXVECTOR3 up(0.0f, 1.0f, 0.0f);
D3DXMATRIX view_matrix;
D3DXMatrixLookAtLH(&view_matrix, &position, &target, &up);
g_d3d_device->SetTransform(D3DTS_VIEW, &view_matrix);
// render now
g_d3d_device->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER | D3DCLEAR_STENCIL, 0xff000000, 1.0f, 0);
g_d3d_device->BeginScene();
render_scene();
render_shadow();
g_d3d_device->EndScene();
g_d3d_device->Present(NULL, NULL, NULL, NULL);
return true;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////
void render_scene()
{
D3DXMATRIX identity_matrix;
D3DXMatrixIdentity(&identity_matrix);
g_d3d_device->SetTransform(D3DTS_WORLD, &identity_matrix);
g_d3d_device->SetStreamSource(0, g_vertex_buffer, 0, sizeof(cTextureVertex));
g_d3d_device->SetFVF(TEXTURE_VERTEX_FVF);
// draw the floor
g_d3d_device->SetMaterial(&g_floor_material);
g_d3d_device->SetTexture(0, g_floor_texture);
g_d3d_device->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 2);
// draw the walls
g_d3d_device->SetMaterial(&g_wall_material);
g_d3d_device->SetTexture(0, g_wall_texture);
g_d3d_device->DrawPrimitive(D3DPT_TRIANGLELIST, 6, 4);
// draw the mirror
g_d3d_device->SetMaterial(&g_mirror_material);
g_d3d_device->SetTexture(0, g_mirror_texture);
g_d3d_device->DrawPrimitive(D3DPT_TRIANGLELIST, 18, 2);
// draw teapot
g_d3d_device->SetMaterial(&g_teapot_material);
g_d3d_device->SetTexture(0, NULL);
D3DXMATRIX world_matrix;
D3DXMatrixTranslation(&world_matrix, g_teapot_pos.x, g_teapot_pos.y, g_teapot_pos.z);
g_d3d_device->SetTransform(D3DTS_WORLD, &world_matrix);
g_teapot_mesh->DrawSubset(0);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////
void render_shadow()
{
g_d3d_device->SetRenderState(D3DRS_STENCILENABLE, TRUE);
g_d3d_device->SetRenderState(D3DRS_STENCILFUNC, D3DCMP_EQUAL);
g_d3d_device->SetRenderState(D3DRS_STENCILREF, 0x0);
g_d3d_device->SetRenderState(D3DRS_STENCILMASK, 0xffffffff);
g_d3d_device->SetRenderState(D3DRS_STENCILWRITEMASK, 0xffffffff);
g_d3d_device->SetRenderState(D3DRS_STENCILZFAIL, D3DSTENCILOP_KEEP);
g_d3d_device->SetRenderState(D3DRS_STENCILFAIL, D3DSTENCILOP_KEEP);
g_d3d_device->SetRenderState(D3DRS_STENCILPASS, D3DSTENCILOP_INCR); // increment to 1
// position shadow
D3DXVECTOR4 light_dir(0.707f, -0.707f, 0.707f, 0.0f);
D3DXPLANE ground_plane(0.0f, -1.0f, 0.0f, 0.0f); // xz plane
D3DXMATRIX shadow_matrix;
D3DXMatrixShadow(&shadow_matrix, &light_dir, &ground_plane);
D3DXMATRIX tran_matrix;
D3DXMatrixTranslation(&tran_matrix, g_teapot_pos.x, g_teapot_pos.y, g_teapot_pos.z);
D3DXMATRIX world_matrix = tran_matrix * shadow_matrix;
g_d3d_device->SetTransform(D3DTS_WORLD, &world_matrix);
// alpha blend the shadow
g_d3d_device->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
g_d3d_device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
g_d3d_device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
D3DMATERIAL9 material = init_material(BLACK, BLACK, BLACK, BLACK, 0.0f);
material.Diffuse.a = 0.5f; // 50% transparancy
// disable depth buffer so that z-fighting doesn't occur when we render the shadow
// on top of the floor.
g_d3d_device->SetRenderState(D3DRS_ZENABLE, FALSE);
g_d3d_device->SetMaterial(&material);
g_d3d_device->SetTexture(0, NULL);
g_teapot_mesh->DrawSubset(0);
// restore render states
g_d3d_device->SetRenderState(D3DRS_ZENABLE, TRUE);
g_d3d_device->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
g_d3d_device->SetRenderState(D3DRS_STENCILENABLE, FALSE);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////
LRESULT CALLBACK wnd_proc(HWND hwnd, UINT msg, WPARAM word_param, LPARAM long_param)
{
switch(msg)
{
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_KEYDOWN:
if(word_param == VK_ESCAPE)
DestroyWindow(hwnd);
break;
}
return DefWindowProc(hwnd, msg, word_param, long_param);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////
int WINAPI WinMain(HINSTANCE inst, HINSTANCE, PSTR cmd_line, int cmd_show)
{
if(! init_d3d(inst, WIDTH, HEIGHT, true, D3DDEVTYPE_HAL, &g_d3d_device))
{
MessageBox(NULL, "init_d3d() - failed.", 0, MB_OK);
return 0;
}
if(! setup())
{
MessageBox(NULL, "Steup() - failed.", 0, MB_OK);
return 0;
}
enter_msg_loop(display);
cleanup();
g_d3d_device->Release();
return 0;
}
下載源程序