為了提高場景的真實性,我們可以為其加入燈光。燈光也能幫助表現(xiàn)物體的立體感以及物體的實體形狀。當(dāng)使用燈光時,我們不再自己指定頂點的顏色;Direct3D中每個頂點都通過燈光引擎來計算頂點顏色,該計算是基于定義的燈光資源,材質(zhì)以及燈光資源關(guān)心的表面方向。通過燈光模型計算頂點顏色會得到更真實的場景。
在Direct3D燈光模型中,燈光是通過燈光資源的三個成員之一來照射的,即有三種燈光。
環(huán)境光(Ambient
Light)——這種類型的燈光將被其他所有表面反射且被用在照亮整個場景。例如,物體的各部分都被照亮,對于一個角度,甚至穿過不在光源直接照射的地方他們都能被照亮。環(huán)境光的使用是粗略的,便宜的,它模仿反射光。
漫反射(Diffuse
Reflection)——這種燈光按照特殊方向傳播。當(dāng)它照射到一個表面,它將在所有方向上均勻的反射。因為漫射光在所有方向上都均勻的反射,被反射的光線將到達眼睛而與觀察點無關(guān),因此我們不必為觀察者考慮。因而,漫射光僅僅需要考慮燈光方向和表面的朝向。這種燈光將成為你的資源中照射的普通燈光。
鏡面反射(Specular
Reflection)——這種燈光按照特殊方向傳播。當(dāng)它照射到一個表面時,它嚴(yán)格地按照一個方向反射。這將產(chǎn)生一個明亮的光澤,它能在某角度被看見。因為這種燈光在一個方向反射。明顯的觀察點,必須考慮燈光的方向和表面朝向,且必須按照鏡面燈光等式來考慮。鏡面燈光被用在物體上產(chǎn)生高光的地方,這種光澤只有在燈光照射在磨光的表面上才會產(chǎn)生。
鏡面光比其他燈光類型要求更多的計算;因此,Direct3D提供了一個開關(guān)選擇。實際上,它默認(rèn)是被關(guān)閉的;要使用鏡面光你必須設(shè)置D3DRS_SPECULARENABLE渲染狀態(tài)。
Device->SetRenderState(D3DRS_SPECULARENABLE,
true);
|
每一種燈光都是通過D3DCOLORVALUE結(jié)構(gòu)或者描述燈光顏色的D3DXCOLOR來描繪的。這里有幾個燈光顏色的例子:
D3DXCOLOR redAmbient(1.0f,
0.0f, 0.0f, 1.0f);
D3DXCOLOR blueDiffuse(0.0f,
0.0f, 1.0f, 1.0f);
D3DXCOLOR
whiteSpecular(1.0f, 1.0f, 1.0f, 1.0f);
|
注意:在D3DXCOLOR類中的alpha值用在描述燈光顏色時是被忽略的。
5.2材質(zhì)
在現(xiàn)實世界中我們看到的物體顏色將由物體反射回來的燈光顏色來決定。比如,一個紅色的球是紅色的,因為它吸收所有的燈光顏色除了紅色光。紅色光是被球反射回來進入我們眼睛的,因此我們看到的球是紅色的。Direct3D通過我們定義的物體材質(zhì)來模擬這些所有的現(xiàn)象。材質(zhì)允許我們定義表面反射燈光的百分比。在代碼中通過D3DMATERIAL9結(jié)構(gòu)描述一個材質(zhì)。
typedef
struct _D3DMATERIAL9 {
D3DCOLORVALUE
Diffuse, Ambient, Specular, Emissive;
float Power;
} D3DMATERIAL9;
|
Diffuse——指定此表面反射的漫射光數(shù)量。
Ambient——指定此表面反射的環(huán)境光數(shù)量。
Specular——指定此表面反射的鏡面光數(shù)量
Emissive——這個是被用來給表面添加顏色,它使得物體看起來就象是它自己發(fā)出的光一樣。
Power——指定銳利的鏡面高光;它的值是高光的銳利值。
舉例,想得到一個紅色的球。我們將定義球的材質(zhì)來只反射紅光吸收其他顏色的所有光:
D3DMATERIAL9 red;
::ZeroMemory(&red,
sizeof(red));
red.Diffuse =
D3DXCOLOR(1.0f, 0.0f, 0.0f, 1.0f); // red
red.Ambient =
D3DXCOLOR(1.0f, 0.0f, 0.0f, 1.0f); // red
red.Specular =
D3DXCOLOR(1.0f, 0.0f, 0.0f, 1.0f); // red
red.Emissive =
D3DXCOLOR(0.0f, 0.0f, 0.0f, 1.0f); // no
emission
red.Power = 5.0f;
|
這里我們設(shè)置綠色和藍(lán)色的值為0,這表明材質(zhì)反射0%此顏色的光。我們設(shè)置紅色為1,表示材質(zhì)反射100%的紅光。注意,我們能夠控制每種燈光反射的顏色(環(huán)境、漫射和鏡面光)。
同樣假如我們定義一個只發(fā)出藍(lán)色光的光源,對球的光照將失敗因為藍(lán)色光將被全部吸收而沒有紅光被反射。當(dāng)物體吸收了所有光以后,物體看起來就為黑色。同樣的,當(dāng)物體反射100%的紅、綠和藍(lán)光,物體就將呈現(xiàn)為白色。
因為手工填充一個材質(zhì)結(jié)構(gòu)將是乏味的工作,我們添加下列有用的函數(shù)和全局材質(zhì)常數(shù)到d3dUtility.h/cpp文件中:
// lights
D3DLIGHT9 init_directional_light(D3DXVECTOR3* direction, D3DXCOLOR* color);
D3DLIGHT9 init_point_light(D3DXVECTOR3* position, D3DXCOLOR* color);
D3DLIGHT9 init_spot_light(D3DXVECTOR3* position, D3DXVECTOR3* direction, D3DXCOLOR* color);
// materials
D3DMATERIAL9 init_material(D3DXCOLOR ambient, D3DXCOLOR diffuse, D3DXCOLOR specular,
D3DXCOLOR emissive, float power);
const D3DMATERIAL9 WHITE_MATERIAL = init_material(WHITE, WHITE, WHITE, BLACK, 2.0f);
const D3DMATERIAL9 RED_MATERIAL = init_material(RED, RED, RED, BLACK, 2.0f);
const D3DMATERIAL9 GREEN_MATERIAL = init_material(GREEN, GREEN, GREEN, BLACK, 2.0f);
const D3DMATERIAL9 BLUE_MATERIAL = init_material(BLUE, BLUE, BLUE, BLACK, 2.0f);
const D3DMATERIAL9 YELLOW_MATERIAL = init_material(YELLOW, YELLOW, YELLOW, BLACK, 2.0f);
D3DLIGHT9 init_directional_light(D3DXVECTOR3* direction, D3DXCOLOR* color)
{
D3DLIGHT9 light;
ZeroMemory(&light, sizeof(light));
light.Type = D3DLIGHT_DIRECTIONAL;
light.Ambient = *color * 0.6f;
light.Diffuse = *color;
light.Specular = *color * 0.6f;
light.Direction = *direction;
return light;
}
D3DLIGHT9 init_point_light(D3DXVECTOR3* position, D3DXCOLOR* color)
{
D3DLIGHT9 light;
ZeroMemory(&light, sizeof(light));
light.Type = D3DLIGHT_POINT;
light.Ambient = *color * 0.6f;
light.Diffuse = *color;
light.Specular = *color * 0.6f;
light.Position = *position;
light.Range = 1000.0f;
light.Falloff = 1.0f;
light.Attenuation0 = 1.0f;
light.Attenuation1 = 0.0f;
light.Attenuation2 = 0.0f;
return light;
}
D3DLIGHT9 init_spot_light(D3DXVECTOR3* position, D3DXVECTOR3* direction, D3DXCOLOR* color)
{
D3DLIGHT9 light;
ZeroMemory(&light, sizeof(light));
light.Type = D3DLIGHT_SPOT;
light.Ambient = *color * 0.6f;
light.Diffuse = *color;
light.Specular = *color * 0.6f;
light.Position = *position;
light.Direction = *direction;
light.Range = 1000.0f;
light.Falloff = 1.0f;
light.Attenuation0 = 1.0f;
light.Attenuation1 = 0.0f;
light.Attenuation2 = 0.0f;
light.Theta = 0.4f;
light.Phi = 0.9f;
return light;
}
D3DMATERIAL9 init_material(D3DXCOLOR ambient, D3DXCOLOR diffuse, D3DXCOLOR specular,
D3DXCOLOR emissive, float power)
{
D3DMATERIAL9 material;
material.Ambient = ambient;
material.Diffuse = diffuse;
material.Specular = specular;
material.Emissive = emissive;
material.Power = power;
return material;
}
頂點結(jié)構(gòu)沒有材質(zhì)屬性;一個通用的材質(zhì)必須被設(shè)置。設(shè)置它我們使用IDirect3DDevice9::SetMaterial(CONST
D3DMATERIAL9*pMaterial)方法。
假設(shè)我們想渲染幾個不同材質(zhì)的物體;我們將按照如下的寫法去做:
D3DMATERIAL9 blueMaterial,
redMaterial;
// set up
material structures
Device->SetMaterial(&blueMaterial);
drawSphere();
// blue sphere
Device->SetMaterial(&redMaterial);
drawSphere();
// red sphere
|
面法線(face
normal)是描述多邊形表面方向的一個向量(如圖5.1)。

頂點法線(Vertex
normals)也是基于同樣的概念,但是我們與其指定每個多邊形的法線,還不如為每個頂點指定(如圖5.2)。

Direct3D需要知道頂點法線以便它能夠確定燈光照射到物體表面的角度,并且一旦計算了每個頂點的燈光,Direct3D需要知道每個頂點的表面方向。注意頂點法線不一定和面法線相同。球體/環(huán)形物就是很好的實物例子,它們的頂點法線和三角形法線是不相同的(如圖5.3)。

為了描述頂點的頂點法線,我們必須更新原來的頂點結(jié)構(gòu)::
class cLightVertex
{
public:
float m_x, m_y, m_z;
float m_nx, m_ny, m_nz;
cLightVertex() {}
cLightVertex(float x, float y, float z, float nx, float ny, float nz)
{
m_x = x; m_y = y; m_z = z;
m_nx = nx; m_ny = ny; m_nz = nz;
}
};
const DWORD LIGHT_VERTEX_FVF = D3DFVF_XYZ | D3DFVF_NORMAL;
作為一個簡單的物體比如立方體和球體,我們能夠通過觀察看見頂點法線。對于更多復(fù)雜的網(wǎng)格,我們需要一個更多的機械方法。假設(shè)一個由p0,p1,p2構(gòu)成的三角形,我們需要計算每個頂點的法線n0,n1,n2。
簡單的步驟,我們列舉它是為了找到由三個點構(gòu)成的三角形的面法線,同時使用面法線作為頂點法線。首先計算三角形上的兩個向量:
Then the face normal is:
Since each vertex normal is the same as the face normal:
下面是一個C函數(shù),它通過三角形的三個頂點計算三角形的面法線。注意這個函數(shù)的三個頂點是按照順時針方向指定的。假如不是這樣,那么法線方向?qū)⑹窍喾吹摹?/p>
void
ComputeNormal(D3DXVECTOR3* p0, D3DXVECTOR3* p1, D3DXVECTOR3* p2,
D3DXVECTOR3* out)
{
D3DXVECTOR3 u = *p1
- *p0;
D3DXVECTOR3 v = *p2
- *p0;
D3DXVec3Cross(out,
&u, &v);
D3DXVec3Normalize(out, out);
}
|
當(dāng)用三角形近似表示曲面時,使用面法線作為頂點法線不能表現(xiàn)一個平滑的結(jié)果。一個更好的方法是找到頂點法線的平均法線。為了找到頂點v的頂點法線vn,我們找到網(wǎng)格模型中所有三角形的面法線記為頂點v。vn是通過計算他們的平均面法線得到的。這里有一個例子,假設(shè)有3個三角形它們的面法線分別是n0,n1,n2,指定為頂點v。那么vn的平均法線就是:
vn = (1/3) . (n0 + n1 + n2)
變換過程中把頂點法線變?yōu)閚on-normal,這是有可能的。因此最好通過D3DRS_NORMALIZENORMALS設(shè)置渲染狀態(tài),Direct3D重新單位化所有法線。
Device->SetRenderState(D3DRS_NORMALIZENORMALS,
true);
|