Copyright © MikeFeng QQ: 76848502
D3D支持三種光:環境光(Ambient Light),漫反射(Diffuse Light)和鏡面反射(Specular Light)。鏡面反射需要更多計算,因此D3D默認關閉鏡面反射。可以通過給SetRenderState傳D3DRS_AMBIENT來設置環境光。
在現實世界中,我們看到一個紅色的物體是因爲其他顏色的光線被這個物體吸收了,我們看到的是這個物體反射的紅色光。D3D引入材質這個概念來模擬現實。
Typpdef struct D3DMATERIAL9 {
D3DCOLORVALUE Diffuse, Ambient, Specular, Emissive;
Float Power;
} D3DMATERIAL9;
D3DCOLORVALUE是一個由r, g,b,a四個元素組成的色彩結構。
Diffuse, Ambient, Specular分別代表這三種光的反射量,而Emissive讓物體使物體更亮,看上去就像本身在發光一樣。Power描述鏡面反射高亮的程度,值越高越亮。注意如果一個物體吸收所有顏色的光,那麼它就是黑色的。可以通過SetMaterial函數來設定材質。
D3DMATERIAL9 blueMaterial;
…
Device->SetMaterial(&blueMaterial)
D3D引入頂點法線和麪法線來解決光照問題。面法線描述了一個多邊形的朝向,頂點法線的朝向可以自己定義。D3D必須知道頂點法線的方向以便計算它的反射光。
在定義頂點的時候可以加入頂點法線的朝向。
struct Vertex
{
float _x, _y, _z;
float _nx, _ny, _nz;
static const DWORD FVF;
}
const DWORD Vertex::FVF = D3DFVF_XYZ | D3DFVF_NORMAL;
頂點顏色的信息被法線信息替代了,因爲我們需要根據材質和法線信息自己計算反射光。簡單的圖形可以由觀察得到頂點法線的信息,但是複雜的圖形需要通過計算得到。一種計算方法是將頂點法線等同於面法線。對於一個三角形來說,只要叉積的方式求出面發現即可。另一種更科學的方法是法線平均法。例如三棱錐的情況,最上面那個頂點的法線Vn是由包含着個定點的三個側面的法線V1, V2, V3平均數決定的。即Vn=(V1+V2+V3)/3。前面學過由於矩陣轉換可能會導致法線不偏移的現象,我們可以通過
Device->SetRenderState(D3DRS_NORMALIZENORMALS, true);
來重新歸一(Renormalize)法線。
D3D中的光源有3種類型:點光源(向四周發射的光源),平行光源,錐形光源(類似於電筒光)。在錐形光源中有兩個角度來描述其大小,一個描述內錐,一個描述外錐。D3D中描述光源的結構是D3DLIGHT9:
typedef struct _D3DLIGHT9 {
D3DLIGHTTYPE Type;
D3DCOLORVALUE Diffuse;
D3DCOLORVALUE Specular;
D3DCOLORVALUE Ambient;
D3DVECTOR Position;
D3DVECTOR Direction;
float Range;
float Falloff;
float Attenuation0;
float Attenuation1;
float Attenuation2;
float Theta;
float Phi;
} D3DLIGHT9;
可以通過SetLight函數來設定光源。可以通過LightEnable函數來打開關閉光源。
給一個物體添加光照分爲以下幾步:
① 啓用光照
② 爲每個物體創建材質,並在渲染前爲這些物體配上材質
③ 創建一個或多個光源,設置並啓用它們
④ 啓用附加的光照狀態,例如鏡面高光。
設定光源最好不要放在OnCreateDevice中,因爲切換全屏/窗口或者改變窗口大小時會丟失光源信息。應放在OnResetDevice中。
Example 1 Cube Without Lighting
前面有個旋轉平面的例子,現在爲了要使用光照,立體的東西效果好一點。下面是一個立體方塊的例子,和平面的類似,不過頂點定義中多了頂點法線。
// cube.h
#ifndef __cubeH__
#define __cubeH__
#include "dxstdafx.h"
structVertex
{
Vertex(){}
Vertex(
floatx, float y, float z,
floatnx, float ny, float nz,
floatu, float v)
{
_x = x; _y = y; _z = z;
_nx = nx; _ny = ny; _nz = nz;
_u = u; _v = v;
}
float_x, _y, _z;
float_nx, _ny, _nz;
float_u, _v; // texture coordinates
};
#define FVF_VERTEX (D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX1)
class Cube
{
public:
Cube(IDirect3DDevice9* device);
~Cube();
HRESULTOnResetDevice();
VOIDOnLostDevice();
bool OnFrameMove( double fTime ) ;
bool OnFrameRender( D3DMATERIAL9* mtrl, IDirect3DTexture9* tex );
private:
HRESULTCreate();
D3DMATERIAL9 m_Mtrl;
IDirect3DDevice9* m_device;
IDirect3DVertexBuffer9* m_vb;
IDirect3DIndexBuffer9* m_ib;
};
#endif//__cubeH__
//其他函數和plain.h中類似,因此只列出Create函數。其餘函數請參照前面的Plain類
HRESULTCube::Create()
{
m_device->CreateVertexBuffer(
24 * sizeof(Vertex),
D3DUSAGE_WRITEONLY,
FVF_VERTEX,
D3DPOOL_MANAGED,
&m_vb,
NULL);
Vertex* v;
m_vb->Lock(0, 0, (void**)&v, 0);
// build box
// x, y, z, nx, ny, nz, tu, tv
// fill in the front face vertex data
v[0] = Vertex(-1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f);
v[1] = Vertex(-1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f);
v[2] = Vertex( 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f);
v[3] = Vertex( 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f);
// fill in the back face vertex data
v[4] = Vertex(-1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f);
v[5] = Vertex( 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f);
v[6] = Vertex( 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f);
v[7] = Vertex(-1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f);
// fill in the top face vertex data
v[8] = Vertex(-1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f);
v[9] = Vertex(-1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f);
v[10] = Vertex( 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f);
v[11] = Vertex( 1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f);
// fill in the bottom face vertex data
v[12] = Vertex(-1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f);
v[13] = Vertex( 1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f);
v[14] = Vertex( 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f);
v[15] = Vertex(-1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f);
// fill in the left face vertex data
v[16] = Vertex(-1.0f, -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f);
v[17] = Vertex(-1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f);
v[18] = Vertex(-1.0f, 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f);
v[19] = Vertex(-1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f);
// fill in the right face vertex data
v[20] = Vertex( 1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f);
v[21] = Vertex( 1.0f, 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f);
v[22] = Vertex( 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f);
v[23] = Vertex( 1.0f, -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f);
m_vb->Unlock();
m_device->CreateIndexBuffer(
36 * sizeof(WORD),
D3DUSAGE_WRITEONLY,
D3DFMT_INDEX16,
D3DPOOL_MANAGED,
&m_ib,
0);
WORD* i = 0;
m_ib->Lock(0, 0, (void**)&i, 0);
// fill in the front face index data
i[0] = 0; i[1] = 1; i[2] = 2;
i[3] = 0; i[4] = 2; i[5] = 3;
// fill in the back face index data
i[6] = 4; i[7] = 5; i[8] = 6;
i[9] = 4; i[10] = 6; i[11] = 7;
// fill in the top face index data
i[12] = 8; i[13] = 9; i[14] = 10;
i[15] = 8; i[16] = 10; i[17] = 11;
// fill in the bottom face index data
i[18] = 12; i[19] = 13; i[20] = 14;
i[21] = 12; i[22] = 14; i[23] = 15;
// fill in the left face index data
i[24] = 16; i[25] = 17; i[26] = 18;
i[27] = 16; i[28] = 18; i[29] = 19;
// fill in the right face index data
i[30] = 20; i[31] = 21; i[32] = 22;
i[33] = 20; i[34] = 22; i[35] = 23;
m_ib->Unlock();
returnS_OK;
}
主函數和旋轉平面基本相同,只是聲明瞭一個不同的全局變量:
Cube* g_pCube = NULL;
在未啓用光照模式[SetRenderState( D3DRS_LIGHTING, FALSE) ]的情況下效果如圖:
Example 2 Cube With Material & Lighting
Step 1 啓用光照( D3D中默認啓用 )
pd3dDevice->SetRenderState( D3DRS_LIGHTING, TRUE );
Step 2 爲每個物體創建材質,並在渲染前爲這些物體配上材質
在主回調函數OnCreateDevice中定義光源
在上例Cube的構造函數添加材質代碼,使其如下
Cube::Cube(IDirect3DDevice9* device)
{
// save a ptr to the device
m_device = device;
m_Mtrl.Ambient = D3DXCOLOR(1,0,0,0);
m_Mtrl.Diffuse = D3DXCOLOR(0,0,0,0);
m_Mtrl.Specular = D3DXCOLOR(1,1,1,0);
m_Mtrl.Emissive = D3DXCOLOR(0,1,0,0);
m_Mtrl.Power = 5.0f;
}
其中m_Mtrl是cube.h中定義的成員變量D3DMATERIAL9,Emissive對於材質來說很重要,因爲它決定了我們看見的紋理顏色範圍,這裏定義的是隻顯示綠色。
在Cube::OnFrameRender中添加如下代碼
bool Cube::OnFrameRender( D3DMATERIAL9* mtrl, IDirect3DTexture9* tex)
{
if( mtrl )
m_device->SetMaterial(mtrl);
else if (&m_Mtrl)
m_device->SetMaterial(&m_Mtrl);
if( tex )
m_device->SetTexture(0, tex);
m_device->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
m_device->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
m_device->SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR);
m_device->SetStreamSource(0, m_vb, 0, sizeof(Vertex));
m_device->SetIndices(m_ib);
m_device->SetFVF(FVF_VERTEX);
m_device->DrawIndexedPrimitive(
D3DPT_TRIANGLELIST,
0,
0,
24,
0,
12);
return true;
}
如果主回調函數中未給此函數設置材質,那麼它將使用自己的成員變量m_Mtrl作爲材質。
Step 3 創建一個或多個光源,設置並啓用它們
在主回調函數OnCreateDevice中添加如下代碼
D3DLIGHT9dir;
::ZeroMemory(&dir, sizeof(dir));
dir.Type = D3DLIGHT_DIRECTIONAL;
dir.Diffuse = D3DXCOLOR(1,1,0,1);
dir.Specular = D3DXCOLOR(0,0,11);
dir.Ambient = D3DXCOLOR(1,0,0,1);
dir.Direction = D3DXVECTOR3(1.0f, 0.0f, 0.0f);
pd3dDevice->SetLight(0, &dir);
pd3dDevice->LightEnable(0, true);
上面的參數只允許光源產生藍色鏡面反射光,以及紅色+綠色環境光。該光源是平行光源。
在主回調函數OnFrameRender中添加如下代碼,以重新歸一法線和啓用鏡面反射
pd3dDevice->SetRenderState(D3DRS_NORMALIZENORMALS, true);
pd3dDevice->SetRenderState(D3DRS_SPECULARENABLE, true);
注意不要忘記在適當時間禁用鏡面反射,以提高效率。
pd3dDevice->SetRenderState(D3DRS_SPECULARENABLE, false);
Step 4 啓用附加的光照狀態(未設置)
藍色部分爲鏡面反射,綠色部分爲材質本身反射。因爲光源由x正方向照射過來,因此鏡面反射只有在右側纔有效果。最終效果如下