在自然界中的很多表面象鏡子一樣允許我們通過它的反射來看物體,我們來看看怎樣用3D應用程序來模擬鏡子,爲了簡單起見我們只模擬平面鏡。舉個例子,一輛擦亮的小汽車能夠反射;然而車的車身是光滑的圓的,不是一個平面。我們渲染的是那些光滑的大理石地板、掛在牆上的鏡子的反射,換句話說就是在一個平面的鏡子。
實現鏡子的程序需要我們解決兩個問題。第一,我們必須學習沿着一個面怎樣反射一個物體以便能夠正確地繪製反射結果。第二,我們必須只能在一個鏡子範圍內顯示反射結果。即,我們必須標記一個表面作爲鏡子,且只渲染那些在鏡子裏物體。圖8.1就是說的這個內容。
第一個問題只需要用一些幾何向量就可以簡單解決,我們能夠利用模板緩存解決第二個問題。下兩小節分別介紹怎樣解決這兩個問題。第三小節把它們柔和在一起並且介紹一下本章的第一個應用程序實例代碼——鏡子。
8.2.1反射數學
我們現在演示怎樣計算點V=(Vx, Vy, Vz)被平面n*p+d=0反射的點V’=(V’x, V’y, V’z),請參閱圖8.2:
根據Part I中的“平面”部分,我們能夠知道q=v-kn,這裏k是有符號的從v到平面的距離。下面是v相對與平面(n,d)的反射推導:
在D3DX庫中用下面的函數來創建反射矩陣R。
D3DXMATRIX *D3DXMatrixReflect( D3DXMATRIX *pOut, // The resulting reflection matrix. CONST D3DXPLANE *pPlane // The plane to reflect about. ); |
一旦我們說到反射變換的話題,就讓我們看看其他3種特殊的反射變換。它們是關於三個座標平面的反射—yz平面,xz平面,和xy平面—它們分別通過下面三個矩陣來表現:
通過yz平面反射一個點,我們只需要簡單的將x分量取反就可以了。同樣的,通過xz平面反射一個點,我們只需要簡單的將y分量取反。通過xy平面反射一個點,我們只需要簡單的將z分量取反。這種反射是非常容易理解的。
8.2.2鏡面實現流程
當實現一個鏡面,一個物體假如在一面鏡子前那麼它就會被反射。然而,我們不想測試空間一個物體是否在一面鏡子前,要做它是非常複雜的。因此,爲了簡化事情,我們總是反射物體並且無限制地渲染它。但是這樣就有一個象本章開頭的圖8.1一樣的問題。即,物體反射被渲染到了沒有鏡子的表面。我們能夠用模板緩存來解決這個問題,因爲模板緩存允許我們阻止渲染在後緩存中的特定區域。因此,我們使用模板緩存來阻止渲染被反射的不在鏡子裏的茶壺。下面的步驟簡要的說明了怎樣實現:
1、正常渲染所有的場景——地板,牆,鏡子和茶壺——不包含反射的茶壺。注意這一步沒有修改模板緩存。
2、清除模板緩存爲0。圖8.3顯示了後臺緩存和模板緩存。
3、渲染只有鏡子部分的圖元到模板緩存中。設置模板測試總是成功,並且假如測試成功就指定模板緩存入口爲1。我們僅僅渲染鏡子,在模板緩存中的所有像素都將爲0,除了鏡子部分爲1以外。圖8.4顯示了更新以後的模板緩存。也就是說,我們在模板緩存中對鏡子像素做了標記。
4、現在我們渲染被反射的茶壺到後臺緩存和模板緩存中。但是假如模板測試通過,我們就只渲染後臺緩存。假如在模板緩存中的值爲1,那麼我們設置模板測試通過。這樣,茶壺就僅僅被渲染到模板緩存爲1的地方了。因爲只有鏡子對應的模板緩存值爲1,所以反射的茶壺就只能被渲染到鏡子裏。
8.2.3代碼和解釋
這個例子的相關代碼在RenderMirror函數中,它首先渲染鏡子圖元到模板緩存,然後渲染那些能被渲染到鏡子裏的反射茶壺。我們現在一行一行的分析RenderMirror函數的代碼,並解釋爲什麼要這麼做。
假如你想使用8.2.2部分的步驟實現代碼,注意我們從第3步開始,因爲對模板緩存來說1和2步已經沒有什麼事做了。同樣我們通過這個解釋來討論通過鏡子渲染的信息。
注意我們將分成幾個部分來討論它。
8.2.3.1第一部分
我們通過允許模板緩存和設置渲染狀態來開始:
void RenderMirror() { Device->SetRenderState(D3DRS_STENCILENABLE, true); Device->SetRenderState(D3DRS_STENCILFUNC, D3DCMP_ALWAYS); Device->SetRenderState(D3DRS_STENCILREF, 0x1); 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_REPLACE); |
這是非常容易理解的。我們設置模板比較運算爲D3DCMP_ALWAYS,這就是說讓所有模板測試都通過。
假如深度測試失敗了,我們指定D3DSTENCILOP_KEEP,它表明不更新模板緩存入口。即,我們保存當前值。這樣做的原因是假如深度測試失敗了,那麼就意味着像素被“遮擋”了。我們不想渲染被“遮擋”的反射像素。
同樣假如模板測試失敗了,我們也指定D3DSTENCILOP_KEEP。但是在這裏這樣做不是必須的,因爲我們指定的是D3DCMP_ALWAYS,當然這樣的測試也就永遠不會失敗。然而,我們只改變比較運算的一位,那麼設置模板失敗渲染狀態是必須的。我們現在就這樣做。
假如深度測試和模板測試都通過了,我們就指定D3DSTENCILOP_REPLACE,更新模板緩存入口,設置模板參考值爲0x1。
8.2.3.2第二部分
這下一步阻止渲染鏡子代碼,除了模板緩存。我們通過設置D3DRS_ZWRITEENABLE並指定爲false來阻止寫深度緩存。我們能夠防止更新後臺緩存,混合和設置源混合要素爲D3DBLEND_ZERO目的混合要素爲D3DBLEND_ONE。將這些混合要素代入混合等式,我們得到後臺緩存是不會改變的:
// disable writes to the depth and back buffers Device->SetRenderState(D3DRS_ZWRITEENABLE, false); Device->SetRenderState(D3DRS_ALPHABLENDENABLE, true); Device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ZERO); Device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ONE); // draw the mirror to the stencil buffer Device->SetStreamSource(0, VB, 0, sizeof(Vertex)); Device->SetFVF(Vertex::FVF); Device->SetMaterial(&MirrorMtrl); Device->SetTexture(0, MirrorTex); D3DXMATRIX I; D3DXMatrixIdentity(&I); Device->SetTransform(D3DTS_WORLD, &I); Device->DrawPrimitive(D3DPT_TRIANGLELIST, 18, 2); // re-enable depth writes Device->SetRenderState(D3DRS_ZWRITEENABLE, true); |
8.2.3.3第三部分
在模板緩存中,符合鏡子可視像素的爲0x1,因此對已經渲染的鏡子區域做記號。我們現在準備渲染被反射的茶壺。回憶一下,我們僅僅想渲染鏡子範圍內的反射像素。我們現在可以很容易的做到了,因爲在模板緩存中這些像素已經被做了記號。
我們設置下面的渲染狀態:
Device->SetRenderState(D3DRS_STENCILFUNC, D3DCMP_EQUAL); Device->SetRenderState(D3DRS_STENCILPASS, D3DSTENCILOP_KEEP); |
用一個新的比較運算設置,我們進行下面的模板測試:
(ref & mask == (value & mask) (0x1 & 0xffffffff) == (value & 0xffffffff) (0x1)== (value & 0xffffffff) |
這說明了只有當value=0x1時模板測試才成功。因爲在模板緩存中只有鏡子相應位置的值纔是0x1,若我們渲染這些地方那麼測試將會成功。因此,被反射的茶壺只會在鏡子裏繪製而不會在鏡子以外的表面上繪製。
注意我們已經將渲染狀態由D3DRS_STENCILPASS變爲了D3DSTENCILOP_KEEP,簡單的說就是假如測試通過那麼就保存模板緩存的值。因此,在下一步的渲染中,我們不改變模板緩存的值。我們僅僅使用模板緩存來對鏡子相應位置的像素做標記。
8.2.3.4第四部分
RenderMirror函數的下一部分就是計算在場景中反射位置的矩陣:
// position reflection D3DXMATRIX W, T, R; D3DXPLANE plane(0.0f, 0.0f, 1.0f, 0.0f); // xy plane D3DXMatrixReflect(&R, &plane); D3DXMatrixTranslation(&T, TeapotPosition.x, TeapotPosition.y, TeapotPosition.z); W = T * R; |
注意我們首先確定沒有反射的茶壺位置,然後就通過xy平面來反射。這種變換規則是通過矩陣相乘來指定的。
8.2.3.5第五部分
我們已經爲渲染反射茶壺做好了準備。然而,假如我們現在就渲染它,它是不會被顯示的。爲什麼呢?因爲被反射的茶壺的深度比鏡子的深度大,因此鏡子的圖元將把被反射茶壺的圖元弄模糊。爲了避免這種情況,我們清除深度緩存:
Device->Clear(0, 0, D3DCLEAR_ZBUFFER, 0, 1.0f, 0); |
並不是所有問題都解決了。假如我們簡單的清除深度緩存,被反射的茶壺會被繪製到鏡子的前面,物體看起來就不對了。我們想做的是清除深度緩存並且要混合被反射的茶壺和鏡子。這樣,被反射的茶壺看起來就象在鏡子裏了。我們能夠通過下面的混合等式來混合被反射的茶壺和鏡子:
因爲原像素(sourcePixel)來自被反射的茶壺,目的像素(DestPixel)來自鏡子,我們能夠通過這個等式明白它們是怎麼被混合到一起的。我們有如下的代碼:
Device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_DESTCOLOR); Device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ZERO); |
最後,我們準備繪製被反射的茶壺:
Device->SetTransform(D3DTS_WORLD, &W); Device->SetMaterial(&TeapotMtrl); Device->SetTexture(0, 0); Device->SetRenderState(D3DRS_CULLMODE, D3DCULL_CW); Teapot->DrawSubset(0); |
回顧一下8.2.3.4部分的W,它能夠正確的將被反射的茶壺變換到場景中恰當的位置。同樣,我們也要改變背面揀選模式。必須這樣做的原因是當一個物體被反射以後,它的正面和背面將會被交換。因此爲了改變這種情況,我們必須改變背面揀選模式。
Device->SetRenderState(D3DRS_ALPHABLENDENABLE, false); Device->SetRenderState( D3DRS_STENCILENABLE, false); Device->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW); } // end RenderMirror() |
運行截圖:
主程序:
Demonstrates mirrors 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_mirror();
////////////////////////////////////////////////////////////////////////////////////////////////////
bool setup()
{
g_wall_material.Specular = WHITE * 0.2f; // make walls have low specular reflectance - 20%
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_LINEAR);
// 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_mirror();
g_d3d_device->EndScene();
g_d3d_device->Present(NULL, NULL, NULL, NULL);
return true;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////
void render_scene()
{
// 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);
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);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////
void render_mirror()
{
// Draw Mirror quad to stencil buffer ONLY. In this way only the stencil bits that
// correspond to the mirror will be on. Therefore, the reflected teapot can only be
// rendered where the stencil bits are turned on, and thus on the mirror only.
g_d3d_device->SetRenderState(D3DRS_STENCILENABLE, TRUE);
g_d3d_device->SetRenderState(D3DRS_STENCILFUNC, D3DCMP_ALWAYS);
g_d3d_device->SetRenderState(D3DRS_STENCILREF, 0x1);
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_REPLACE);
//// Disable writes to the depth and back buffers,
//// enable alpha blending so mirror will overwrite dest areas.
//g_d3d_device->SetRenderState(D3DRS_ZWRITEENABLE, FALSE);
//g_d3d_device->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
//g_d3d_device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ZERO);
//g_d3d_device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ONE);
//// draw the mirror to the stencil buffer
//g_d3d_device->SetStreamSource(0, g_vertex_buffer, 0, sizeof(cTextureVertex));
//g_d3d_device->SetFVF(TEXTURE_VERTEX_FVF);
//g_d3d_device->SetMaterial(&g_mirror_material);
//g_d3d_device->SetTexture(0, g_mirror_texture);
D3DXMATRIX identity_matrix;
D3DXMatrixIdentity(&identity_matrix);
g_d3d_device->SetTransform(D3DTS_WORLD, &identity_matrix);
g_d3d_device->DrawPrimitive(D3DPT_TRIANGLELIST, 18, 2);
// re-enalbe depth writes
g_d3d_device->SetRenderState(D3DRS_ZWRITEENABLE, TRUE);
// only draw reflected teapot to the pixels where the mirror was drawn to
g_d3d_device->SetRenderState(D3DRS_STENCILFUNC, D3DCMP_EQUAL);
g_d3d_device->SetRenderState(D3DRS_STENCILPASS, D3DSTENCILOP_KEEP);
// clear depth buffer and blend the reflected teapot with the mirror
g_d3d_device->Clear(0, NULL, D3DCLEAR_ZBUFFER, 0, 1.0f, 0);
g_d3d_device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_DESTCOLOR);
g_d3d_device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ZERO);
// position reflection
D3DXMATRIX world_matrix, translation_matrix, reflect_matrix;
D3DXPLANE plane(0.0f, 0.0f, 1.0f, 0.0f); // xy plane
D3DXMatrixReflect(&reflect_matrix, &plane);
D3DXMatrixTranslation(&translation_matrix, g_teapot_pos.x, g_teapot_pos.y, g_teapot_pos.z);
world_matrix = translation_matrix * reflect_matrix;
// Finally, draw the reflected teapot.
g_d3d_device->SetTransform(D3DTS_WORLD, &world_matrix);
g_d3d_device->SetMaterial(&g_teapot_material);
g_d3d_device->SetTexture(0, NULL);
g_d3d_device->SetRenderState(D3DRS_CULLMODE, D3DCULL_CW);
g_teapot_mesh->DrawSubset(0);
// restore render states
g_d3d_device->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
g_d3d_device->SetRenderState(D3DRS_STENCILENABLE, FALSE);
g_d3d_device->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////
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;
}