Direct3D基礎——光照的組成、材質、頂點法線、光源
為了增強所繪製的場景的真實感,我們會為場景增加光照,使用光照來描物體的立體感。使用光照的時候,我們就無需描述頂點的顏色,Direct3D會將頂點送入光照計算引擎,然後引擎根據光源型別,材質以及物體表面和相對於光源的朝向等等,計算出內個頂點的顏色值。基於某種光照模型然後計算出頂點的顏色,效果更nice。
光照的組成
環境光
這種型別的光經過其他物體表面反射到達所觀察物體,並且照亮場景。比如:某一個物體的一部分被照亮,但是該物體並沒有處在光源的直接照射下,之所以被照亮,是因為有其他物體反射光源的光,然後到達自己身上,再被照亮的。這類光叫做環境光。
漫射光
這種型別的光沿著特定的方向傳播。當漫射光到達一定表面的時候,沿著物體表面的所有方向均勻反射,所以無論從哪一個方向觀察,表面的亮度都是相同的,所以採用漫射光的模型的時候,不需要考慮觀察者的位置。所以漫射光方程中只需要考慮光的傳播方向以及表面的朝向。從一個光源發出的光,大多就是這種型別的光。
鏡面光
這類光沿著特定的方向傳播,當光線到達表面之後,然後嚴格的按照另外一個方向進行,從而形成只能在一定角度才能觀察到的高亮度照射,所以在鏡面光方程中我們不僅要光照的入射方向和物體的表面朝向,還要考慮觀察者的方向,鏡面光可以模擬物體中的高光點,例如:當光線照射到物體表面所形成的的比較亮的照射
鏡面光的計算量比環境光和漫射光的計算量要大得多,所以Direct3D專門為其設定了開關選項,預設情況下,Direct3D是不計算鏡面光的反射的。
使用狀態機將有關狀態設定為true,就會將鏡面光照開啟:
Device->SetRenderState(D3DRS_SOECULARENABLE,true);
材質
在現實世界中,物體的顏色是由其反射的光的顏色決定的。一個物體反射所有的紅色光,吸收所有的非紅色光,那麼這個物體就呈現於紅色光。Direct3D中允許我們通過定義物體的材質來模擬同樣的現象。材質允許我們定義物體的表面對各種光的反射比例,在程式碼中使用D3DMATERIAL9來表示:
typedef struct D3DMATERIAL9 { D3DCOLORVALUE Diffuse; //指定材質對漫射光的反射率 D3DCOLORVALUE Ambient; //指定材質對環境光的反射率 D3DCOLORVALUE Specular; //指定材質對鏡面光的反射率 D3DCOLORVALUE Emissive; //該分量用於增強物體的亮度,使之看起來可以自己發光 float Power; //指定鏡面高光的銳度,該值越大,高光點的銳度越大 }D3DMATERIAL9,*LPD3DMATERIAL9;
假如現在有一個紅色的球體,我們想讓他只去反射紅色的光,應該向下面這樣定義:
D3DMATERIAL9 red;
::ZeroMemory(&red, sizeof(red));
red.Diffuse = D3DXCOLOR(1.0f, 0.0f, 0.0f, 1.0f);
red.Ambient = D3DXCOLOR(1.0f, 0.0f, 0.0f, 1.0f);
red.Specular = D3DXCOLOR(1.0f, 0.0f, 0.0f, 1.0f);
red.Emissive = D3DXCOLOR(0.0f, 0.0f, 0.0f, 0.0f);//用於增強物體亮度的分量
red.Power = 5.0f; //指物體高光點的銳度
上面這個例子中,我們將綠色和藍色光的反射率設定為0,意思就是:對綠色光和藍色光全部吸收,對紅色光的反射率為100%。也就是說我們可以控制材質在各種型別光的照射下,那種顏色的光會被反射。
注意的一點:如果使用一個藍色光去照射一個只反射紅色的球體的話,因為藍色光被全部吸收,紅色光為0,沒有反射出來的紅色光,所以球體是不能被照亮的。但一個球體吸收全部的顏色的時候成:黑色。當一個物體可以百分之百的反射紅色光、綠色光、藍色光的時候,物體成白色。
為了減輕我們初始化的工作,我們可以再d3dUtility.h/cpp中新增如下使用函式以及全域性材質質量:
const D3DXCOLOR WHITE(D3DCOLOR_XRGB(255, 255, 255));
const D3DXCOLOR BLACK(D3DCOLOR_XRGB(0,0,0));
const D3DXCOLOR RED(D3DCOLOR_XRGB(255, 0, 0));
const D3DXCOLOR GREEN(D3DCOLOR_XRGB(0,255,0));
const D3DXCOLOR BLUE(D3DCOLOR_XRGB(0,0,255));
const D3DXCOLOR YELLOW(D3DCOLOR_XRGB(255,255,0));
const D3DXCOLOR CYAN(D3DCOLOR_XRGB(0,255,255));
const D3DXCOLOR MAGENTA(D3DCOLOR_XRGB(255,0,255));
D3DMATERIAL9 d3d::IniMtrl(D3DCOLOR a, D3DCOLOR d, D3DCOLOR s, D3DCOLOR e, float p)
{
D3DMATERIAL9 mtrl;
mtrl.Ambient = a;
mtrl.Diffuse = d;
mtrl.Specular = s;
mtrl.Emissive = e;
mtrl.Power = p;
return mtrl;
}
//下面的五種材質都是物體亮度和鏡面高光點的銳度為0的材質
//白色材質,因為對環境光,漫反射光,鏡面光的反射率都是反射白色光
const D3DMATERIAL9 WHITE_MTRL = InitMtrl(WHITE, WHITE, WHITE, BLACK,8.0f);
const D3DMATERIAL9 RED_MTRL = InitMtrl(RED, RED, RED, BLACK, 8.0f);
const D3DMATERIAL9 GREEN_MTRL = InitMtrl(GREEN, GREEN, GREEN, BLACK, 8.0f);
const D3DMATERIAL9 BLUE_MTRL = InitMtrl(BLUE, BLUE, BLUE, BLACK, 8.0f);
const D3DMATERIAL9 YELLOW_MTRL = InitMtrl(YELLOW, YELLOW, YELLOW, BLACK, 8.0f);
因為頂點的結構中不包含材質的屬性,所以我們必須要對當前繪製的圖元的材質進行設定:
//定義一個藍色材質和一個紅色材質
D3DMATERIAL9 blueMaterial, redMaterial;
//省略一些舒適化材質內容的程式碼
//這裡省去的就是在材質結構體裡面填充他的鏡面光,漫反射光、環境光之中的反射率
.
.
.
//設定材質
Device->SetMaterial(&blueMaterial);
//畫一個球,現在畫出來的就是藍色球
drawsphere();
//設定材質
Device->SetMaterial(&redMaterial);
//畫一個球,現在畫出來的就是紅色球
drawsphere();
頂點法線
面法線是描述一個多邊形朝向的向量。頂點法線是描述構成多邊形頂點的法線。
Direct3D需要知道每個頂點的朝向,也就是每個頂點的頂點法向量,來計算光照達到表面的入射角。
一個多邊形的頂點法線和麵法線不一定就是相同的,下面就是例子:
為了描述一個帶頂點法線的頂點,我們的頂點結構就必須要改變:
struct Vertex{
float x,y,z;
float _nx,_ny,_nz;
static const DWORD FVF;
};
const DWORD Vertex::FVF = D3DFVF_XYZ | D3DFVF_NORMAL
在結構裡面,我們去掉了頂點的顏色資訊,因為我們可以通過光照來計算顏色的資訊。
對於簡單的物體,比如立方體和球體,我們可以直接的觀察得到頂點的法線,而對於比較複雜物體,我們就需要通過一種方法來計算。
假定有一個三角形,由頂點p0,p1,p2構成,現在我們來計算每一個頂點的法線:n0,n1,n2。
最簡單的方法就是三角形的面法線,然後將這個面法線當做每個頂點的頂點法線。
首先我們來計算三角形內部的兩個向量:
p1 - p0 = u;
p2 - p0 = v;
則面法線就是:
n = u × v;
由於每一個頂點的發向量都相等,那麼n0 = n1 = n2 = n;
下面是一個C語言寫的計算三個頂點的所構成的面的面法向量。該函式中假設指定的頂點的順序是順時針繞序,如果是逆時針繞序的話,計算出來的法向量就是相反方向的。
void ComputeNormal (D3DVECTOR3 * p0,D3DVECTOR3 * p1,D3DVECTOR3 * p2,D3DVECTOR3 * pOut)
{
D3DVECTOR3 u = *p1 - *p0;
D3DVECTOR3 v = *p2 - *p0;
//求叉積
D3DVec3Cross(out,&u,&v);
//返回3D向量的規格化向量
D3DVec3Normal(out,out);
}
但是當使用三角形表面逼近表示曲面的時候,將單個的面法向量當做頂點的法向量不可能產生一個平滑的向量。現在我們的改進方法就是求出所有使用這個點的那些面的法向量,然後進行求均值。例如:為了求出點v的頂點法向量Vn,我們需要求出所有共享頂點V的三角形的面法向量,然後對這些面法向量求均值。
Vn = 1/3(n0 + n1 + n2)
再變換完成之後,可能有一些頂點法向量已經不是規範化的了,所以做好的方法就是改變繪製狀態來使得所有的向量重新規範化。
Device->SetRenderState(D3DRS_NORMALIZENORMALS, true);
光源
Direct3D支援三種光源的形式。
點光源:該光源在世界座標系中的位置固定,並均勻的向所有方向發射光線。
方向光:該光源沒有位置,發射的光線互相平行,沿著某一特定方向傳播
聚光燈:這種光源和手電筒類似,,發出的光程錐形,沿著特定的方向傳播,錐形有兩個角度φ和θ,θ描述內部角度,φ描述外部角度
程式碼中的光源結構使用結構體:D3DLIGHT9表示。
typedef struct D3DLIGHT9 {
D3DLIGHTTYPE Type; //光源型別:D3DLIGHT_POINT、D3DLIGHT_SPOT(聚光燈)、 D3DLIGHT_DIRECTIONAL
D3DCOLORVALUE Diffuse; //光源發出的漫反射光的顏色
D3DCOLORVALUE Specular; //光源發出的鏡面光的顏色
D3DCOLORVALUE Ambient; //光源發出的環境光的顏色
D3DVECTOR Position; //光源在世界座標中的位置,對於方向光該引數無意義,
D3DVECTOR Direction; //描述光在世界座標中的傳播方向的向量,對於點光源該引數無意義
float Range; //光線消亡之前,所能達到的最大的光程,對於方向光,該引數無意義
float Falloff; //該值僅用於聚光燈,該引數定義了光強從內錐形到外錐形的衰減,一般取:1.0f
float Attenuation0; //光線的常量衰減係數
float Attenuation1; //光線的線性衰減係數
float Attenuation2; //光線的2次距離衰減係數
float Theta; //僅用於聚光燈。指定了內部錐形的圓錐角,單位是:弧度
float Phi; //僅用於聚光燈。指定了外部錐形的圓錐角,單位是:弧度
}D3DLIGHT9, *LPD3DLIGHT;
與材質的結構體:D3DMATERIAL9的結構體初始化類似,當您需要一個比較簡單的光源的時候,初始化是很麻煩的一件事,我們就可以在d3dUtility.h/.cpp中加入初始化的函式:
namespace d3d
{
D3DLIGHT9 InitDirectjonaLight(D3DXVECTOR3* direction, D3DXCOLOR* color);
D3DLIGHT9 InitPointLight(D3DXVECTOR3* position, D3DXCOLOR* color);
D3DLIGHT9 InitSpotLight(D3DXVECTOR3* position, D3DXVECTOR3* direction, D3DCOLOR* color);
}
//簡單實現一下方向光:
D3DLIGHT9 d3d::InitDirectionLight(D3DVECTOR3* direction, D3DXCOLOR* color)
{
D3DLIGHT9 Light;
::ZeroMemory(&Light, sizeof(Light));
Light.Type = D3DLIGHT_DIRECTIONAL;
Light.Ambient = *color * 0.4f; //光源發出的環境光的顏色
Light.Diffuse = *color; //光源發出的漫反射光的顏色
Light.Specular = *color * 0.6f; //光源發出的鏡面光的顏色
Light.Direction = *direction; //描述光源在世界座標系中傳播方向的向量
return Light;
}
建立一個平行於x軸的方向光:
D3DXVECTOR3 dir(1.0f, 0.0f, 0.0f);
D3DXCOLOR c = d3d::WHITE;
D3DLIGHT9 dirLight = d3d::InitDirectionalLight(&dir, &c);
當我們對D3DLIGHT9的物件例項化完畢之後,我們需要在Direct3D所維護的一個光源內部列表中對所有我們要使用的光源進行註冊。
//對使用的光源進行註冊
Device->SetLight(
0, //要設定的燈光列表中的元素
&light //要設定的燈光,剛才已經初始化好之後的
)
//一旦光源註冊成功,我們就可以對其進行開關狀態進行控制
Device->LightEnable(
0, //燈光列表中的元素啟用或者禁用
true //true = 啟用,false = 禁用
)