DirectX 9.0c遊戲開發手記之RPG編程自學日誌之14: Drawing with DirectX Graphics (用DirectX圖形繪圖)(第7節)

        本文由哈利_蜘蛛俠原創,轉載請註明出處!有問題請聯繫[email protected]

 

        這一次我們繼續來講述Jim Adams老哥的RPG編程書籍第二版第二章的第7節:Lighting,也就是光照。這一節的內容有點多,不過還是爭取一次性講完吧!

 

        我們先將這一節的各小節的標題列在下面,以供大家參考:

1、 Using Point Lights (使用點光源)

2、 UsingSpotlights (使用聚光燈)

3、 Using Directional Lights (使用方向光)

4、 Ambient Light (環境光)

5、 Setting the Light (設置光照)

6、 Using Normals (使用法向量)

7、 Let There Be Light! (要有光!)

 

        注意:原書並沒有單獨的這一節,而是我從Alpha Blending這一節後半部分抽取出來的——這樣顯得更加合理。


        原文翻譯:

 

===============================================================================

 

2.7 Lighting (光照)

 

        高級圖形技術列表中的下一項是光照的使用。與在現實生活中不同,大多數遊戲完全照亮整個場景,這雖然讓圖形看上去很清晰(look sharp),但是卻不真實。爲了得到一個更加逼真的場景,爲了給你的圖形增加遊戲玩家會go ga-ga over(不知道怎麼個翻譯)的微妙的光照效果,你需要使用Direct3D的光照功能。你在Direct3D中可以使用四種類型的光:環境光、點光源、聚光燈以及方向光。

        環境光(ambient light)是一個不變的光源,它用同樣的光強照亮場景中的所有物體。因爲它是設備組件的一部分,環境光是唯一一種不受光照引擎操控的光照成分。

        其他三種光(如圖2.18所示)具有獨特的性質。點光源(pointlight)將它周圍的所有物體照亮(就像電燈泡那樣子)。聚光燈(spotlight)位於一個特定的方向(實際上,應該是位置)併發出一個圓錐形光。位於錐體內部的物體會被照亮,而位於錐體外部的則不會被照亮。方向光(directional light)(簡化的聚光燈)只朝着一個方向投射光線。

(實際上,上面的說法有一點不科學:聚光燈與方向光並不是特殊與一般的關係;聚光燈有一個光源,而方向光沒有光源。)




        光就像其他的3-D物體那樣被放置在場景中——通過使用x-, y-和z-座標。有一些光,例如聚光燈,還有一個方向向量來決定它們指向哪裏。每一個光具有一個強度值、範圍、衰減因子以及顏色。對的,甚至有色光源在Direct3D中都是可能的!

        除了環境光以外,每種光使用一個D3DLIGHT9數據結構來儲存其特有的信息。這個結構體定義如下:

typedef struct_D3DLIGHT9 {
  D3DLIGHTTYPE    Type;        // Type of light
  D3DCOLORVALUE   Diffuse;     // Diffuse color
  D3DCOLORVALUE   Specular;    // Specular color
  D3DCOLORVALUE   Ambient;     // Ambient color
  D3DVECTOR      Position;     //Position of light
  D3DVECTOR      Direction;    //Direction light is pointing
  float          Range;        //Range of light
  float          Falloff;      //Falloff of spotlight
  float          Attenuation0; // Light attenuation 0
  float          Attenuation1; // Light attenuation 1
  float          Attenuation2; // Light attenuation 2
  float          Theta;        // Angle of inner cone
  float          Phi;          // Angle of outer cone
} D3DLIGHT9;

        哇哦!這是一個big puppypuppy是小狗的意思;這句話不知道怎麼翻譯。),但是它包含了你需要用來描述一個光的所有信息。雖然光並不一定要到D3DLIGHT9中的所有變量,但是所有的光都共享一些成員。

        你設置的第一個變量是Type,這是你要使用的光源的類型。這可以是D3DLIGHT_POINT(對於點光源)、D3DLIGHT_SPOT(對於聚光燈)或者D3DLIGHT_DIRECTIONAL(對於方向光)。

        下一行是光的顏色。你會最常使用Diffuse成員;它決定了光發出來的顏色。注意這些顏色成員是以D3DCOLORVALUE 的形式出現的,它是一個看上去如下的結構體:

 

typedef struct _D3DCOLORVALUE {
float r; // Red value (0.0 to 1.0)
float g; // Green value (0.0 to 1.0)
float b; // Blue value (0.0 to 1.0)
float a; // Alpha value (Unused)
} D3DCOLORVALUE;


        你可將結構體中的顏色成分設爲0.0(表示關閉)到1.0(表示滿值)之間的範圍。紅光是r=1.0, g=0.0, b=0.0,而白光是r=1.0, g=1.0, b=1.0。因爲你在處理光,所以這裏不會用到alpha值。D3DLIGHT9結構體中的Specular和Ambient光成員分別決定了高光顏色和環境光顏色。你可以安全地將兩個成員的各個顏色成分設爲1.0(除了Specular,如果你不想使用高光的話,那麼你可以將其每個顏色成分都設爲0.0)。

 

建議

===============================================================================

        除了使用光的顏色等級來照亮一個物體以外,你還可以用之來弄暗一個物體。不要使用正的顏色值,而是使用負的顏色值,然後觀察結果吧!

===============================================================================


        正如我前面提到的那樣,每一個光都可以通過使用xyz-座標(世界座標)而被放置在3-D場景中,而這個座標是儲存在向量Position中。Direction也是一個向量,但是是用來讓光指向特定方向的。你可以在“Using Spotlights”這一節中發現更多關於使用方向向量的內容。

        Range變量決定光在飛行多遠之後完全衰減(而falloff是用於決定光線從內錐體到外錐體衰減的速度的值。一般來說,將falloff值設爲1.0可以產生很平滑的轉換。)在此距離之外的物體不會被此光照亮。

        三個衰減成員決定了光是如何隨着距離進行衰減的——這三個衰減成員一般都設爲0(其實,不能夠把它們都設爲0的。)。而你是否要使用其餘的變量取決於你正在使用的光源類型。

 

2.7.1 Using Point Lights (使用點光源)

        點光源是最容易使用的光源;你只需要設定它們的位置、顏色成分以及範圍。爲了建立一個點光源,實例化一個D3DLIGHT9 結構體並用需要的信息對其進行填充:(我覺得下面的代碼中還需要加上一句:PointLight.Type = D3DLIGHT_POINT;

D3DLIGHT9 PointLight;

// Clear out the light data
ZeroMemory(&PointLight, sizeof(D3DLIGHT9));

// Position the light at 0.0, 100.0, 200.0
PointLight.Position = D3DVECTOR3(0.0f, 100.0f, 200.0f);

// Set the diffuse and ambient colors to white
PointLight.Diffuse.r = PointLight.Ambient.r = 1.0f;
PointLight.Diffuse.g = PointLight.Ambient.g = 1.0f;
PointLight.Diffuse.b = PointLight.Ambient.b = 1.0f;

// Set the range to 1000 units
PointLight.Range = 1000.0f;

 

2.7.2 Using Spotlights (使用聚光燈)

 

        聚光燈運作起來與其他的光有點不一樣,因爲聚光燈從光源出發、在一個錐體內投射光線。在中心處光是最亮的,而當它靠近錐體的外部時會逐漸變暗。錐體外部的物體不會被照亮。

 

當心

===============================================================================

        聚光燈是你能夠使用的最耗費計算量的光源,所以在場景中不要使用太多的聚光燈爲好。

===============================================================================

 

        你通過在D3DLIGHT9 結構體中設定位置、方向、顏色成分、範圍、衰減值、衰減因子、內錐體半徑和外錐體半徑來定義一個聚光燈。你不需要擔心衰減值和衰減因子,但是你確實需要考慮內外錐體的半徑。

        D3DLIGHT9結構體中的Phi變量確定了外錐體的大小。Phi,以及Theta,用(以弧度爲單位的)角度來表示。光傳播得離聚光燈光源越遠,那麼投影的半徑就越大。程序員們決定要使用什麼樣的值,而你只需多嘗試幾個值,直到你找到你喜歡的爲止。

        下面的代碼建立了一個聚光燈,它設立了位置、顏色、範圍、衰減值以及內外錐體半徑:(同樣,我覺得下面的代碼中還需要加上一句:PointLight.Type = D3DLIGHT_SPOT;

D3DLIGHT9 Spotlight;

// Clear out the light data
ZeroMemory(&SpotLight, sizeof(D3DLIGHT9));

// Position the light at 0.0, 100.0, 200.0
SpotLight.Position = D3DVECTOR3(0.0f, 100.0f, 200.0f);

// Set the diffuse and ambient colors to white
SpotLight.Diffuse.r = SpotLight.Ambient.r = 1.0f;
SpotLight.Diffuse.g = SpotLight.Ambient.g = 1.0f;
SpotLight.Diffuse.b = SpotLight.Ambient.b = 1.0f;

// Set the range
SpotLight.Range = 1000.0f;

// Set the falloff
SpotLight.Falloff = 1.0f;

// Set the cone radiuses
Spotlight.Phi = 0.3488; // outer 20 degrees
Spotlight.Theta = 0.1744; // inner 10 degrees

 

        現在,你要讓聚光燈朝着一個特定的方向發光。D3DX又一次出手相助,用一對函數來幫助你設置聚光燈(還有任意其他光)的方向。一個函數是D3DXVECTOR3對象的重載的構造函數,它讓你設定三個座標。

        對於這三個座標,你使用世界空間的座標來定義到原點的距離。如果你在場景的任意位置有一個聚光燈,並且你想將它往上指向一個位於燈光上方500單位的目標,那麼你將向量對象的值設爲X=0, Y=500, Z=0(注意到這三個座標是相對於燈光的位置的)。例如,下面的代碼設定了向量的值:

D3DXVECTOR3 Direction= D3DXVECTOR3(0.0f, 500.0f, 0.0f);

        前面的Direction向量聲明的唯一問題是Direct3D喜歡這些向量被歸一化(normalized),這表示座標需要位於0和1之間。沒問題,因爲第二個D3DX函數,D3DXVec3Normalize,爲你處理這件事:

D3DXVECTOR3*D3DXVec3Normalize(
D3DXVECTOR3      *pOut, // normalized vector
CONST D3DXVECTOR3 *pV);   // source vector

        當你把原始向量(比如,前面的那個包含了座標X=0,Y=500, Z=0 的向量)和指向新向量的指針傳遞進去後,D3DXVec3Normalize 函數將座標轉換爲0到1之間的座標。這個新的向量現在包含了你可以用於賦給D3DLIGHT9結構體的光源方向成員的方向值。

        繼續前面的例子,我們通過將聚光燈的方向設爲向上,並將其歸一化,再儲存到D3DLIGHT9結構體中:

D3DXVECTOR3 Dir = D3DXVECTOR3(0.0f, 500.0f,0.0f);
D3DXVec3Normalize((D3DXVECTOR3*)&Spotlight.Direction,&Dir);

 

 

2.7.3 Using Directional Lights (使用方向光)

 

        就處理的角度來說,方向光是你能夠使用的最快的光源類型了。它們照亮每一個面朝它們的多邊形。爲了準備一個方向光以供使用,你設定D3DLIGHT9結構體中的方向以及顏色成分成員。

        如果你好奇爲何不使用位置向量,那麼答案是很符合邏輯的。將方向光想象成是跑向一個方向的無限長的一條河流。儘管河流中的物體的位置彼此不同,但是河水的流蘇保持不變;起作用的是河流的方向。將光照的情形與之類比,河水代表光線,而河流的方向代表光的角度。世界中的任何物體,不管位於何方,都會接收到光線。

        回憶一下前兩種類型的光的技術,然後看看這個例子,它建立了一個照向場景的下方的淡黃色的光:(還是一樣,我覺得下面的代碼中還需要加上一句:PointLight.Type = D3DLIGHT_DIRECTIONAL;

D3DLIGHT9 DirLight;

// Clear out the light data
ZeroMemory(&DirLight, sizeof(D3DLIGHT9));

// Set the diffuse and ambient colors to yellow
DirLight.Diffuse.r = DirLight.Ambient.r = 1.0f;
DirLight.Diffuse.g = DirLight.Ambient.g = 1.0f;
DirLight.Diffuse.b = DirLight.Ambient.b = 0.0f;

D3DXVECTOR3 Dir = D3DXVECTOR3(0.0f, 500.0f, 0.0f);
D3DXVec3Normalize((D3DXVECTOR3*)&Dirlight.Direction, &Dir);

 

當心

===============================================================================

        一個光的方向向量必須包含至少一個非零的值。也就是說,你不能夠設定一個方向爲X=0, Y=0, Z=0。

===============================================================================

 

 

 

2.7.4 Ambient Light(環境光)

 

        環境光是唯一一種Direct3D處理方式不一樣的光源類型。Direct3D將環境光運用於所有的多邊形,不管它們的角度或者光源類型,所以不會產生明暗變化。環境光是不變的光,並且就像其他類型的光(點光源、聚光燈和方向光)一樣,你可以隨意設定其顏色。

        你通過設定D3DRS_AMBIENT 渲染狀態並傳遞進一個你想要使用的D3DCOLOR(使用D3DCOLOR_COLORVALUE 宏來設定要使用的位於0.0到1.0之間的紅、綠和藍色的強度)值來設定環境光:

g_pD3DDevice->SetRenderState(D3DRS_AMBIENT,                    \
                    D3DCOLOR_COLORVALUE(0.0f, Red, Green, Blue));

 

2.7.5 Setting the Light (設置光照)

 

        在你建立了D3DLIGHT9結構體之後,你使用IDirect3DDevice9::SetLight 函數來將其傳遞進Direct3D中:

HRESULT IDirect3DDevice9::SetLight(
DWORD             Index,   // Index of light to set
CONST D3DLIGHT9 *pLight); // D3DLIGHT9 structure to use

        你知道pLight傳遞的是D3DLIGHT9結構體,但是Index成員卻是另外的東西。Direct3D允許你在一個場景中設置多個光照,所以Index是一個以0開始的、代表你想要設定的光的指標。例如,如果你想要在一個場景中使用4個光照,那麼index 0就是第一個光,index 1就是第二個,index 2是第三個,而index 4就是第四個也就是最後一個光照。

        看上去Direct3D似乎對於你在一個場景中可以使用的光照的數量沒有限制,但是我建議保持光照數在4個或以下。(實際上,是有數量限制的,這個是與顯卡相關的。)你在場景中間增加的每一個光照都會增加渲染的複雜性以及所需的時間。


 

2.7.6 Using Normals(使用法向量)

 

        爲了讓Direct3D根據你提供的光源正確地照亮多邊形面,你必須首先給多邊形中的每個頂點提供一個法向量。法向量(normal)是一個3-D向量,它定義了與一個向量相連的一個物體面朝的方向。你一般在一個確定物體從任意給定的光照中獲得多少亮度的複雜的計算中使用法向量。

        如果你近距離觀察一個多邊形面(比如圖2.19中的那個),你會看到這三個頂點有一個方向,這正是法向量。當光線擊中這些頂點時,它會基於這些法向量以一個角度反射出去。使用法向量保證所有的多邊形面被正確地照亮,也就是說它們會根據它們相對於觀察者和光線的角度而被着色。


        向你的自定義頂點信息中添加法向量就像提供紋理信息一樣簡單。你只需要以D3DVECTOR3類型插入法向量,然後重新定義靈活頂點格式(也就是要包含D3DFVF_NORMAL),如下所示:

typedef struct {
  D3DVECTOR3 Position; // Vector coordinates
  D3DVECTOR3 Normal;   // Normal
  D3DCOLOR   Color;    // Color
} sVertex;
#define VERTEXFMT (D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_NORMAL)

        你計算法向量的方法與前面“Using Spotlights”那一節中處理光照時創建方向向量的方法相同。在你開始處理3-D模型的時候,計算法向量的任務就與你無關了——因爲用於生成模型的3-D建模程序一般都已經計算好法向量了。

        下面的函數(借自DirectXSDK的示例)生成一個圓柱體,並給予每個頂點一個指向遠離圓柱體中心方向的法向量(參見圖2.20):


// g_pD3DDevice = pre-initialized 3-D device object
IDirect3DVertexBuffer9 *GenerateCylinder()
{
    IDirect3DVertexBuffer9 *pD3DVertexBuffer;
    sVertex *pVertex;
    DWORD i;
    FLOAT theta;

    // Create the vertex buffer
    if(SUCCEEDED(g_pD3DDevice->CreateVertexBuffer(               \
                    50 * 2 * sizeof(sVertex), 0, VERTEXFMT,      \
                     D3DPOOL_MANAGED, &pD3DVertexBuffer, NULL))) {

        // Fill the vertex buffer with the cylinder information
        if(SUCCEEDED(pD3DVertexBuffer->Lock(0, 0,                    \
                                             (void**)&pVertex, 0))) {
            for(i=0; i<50; i++) {
                theta = (2 * D3DX_PI * i) / (50 - 1);
                pVertex[2*i+0].Position = D3DXVECTOR3(sinf(theta),     \
                                                    -1.0f, cosf(theta));
                pVertex[2*i+0].Normal = D3DXVECTOR3(sinf(theta),       \
                                                     0.0f, cosf(theta));
                pVertex[2*i+1].Position = D3DXVECTOR3(sinf(theta),     \
                                                     1.0f, cosf(theta));
                pVertex[2*i+1].Normal = D3DXVECTOR3(sinf(theta),       \
                                                     0.0f, cosf(theta));
            }
            pD3DVertexBuffer->Unlock();

            // Return a pointer to new vertex buffer
            return pD3DVertexBuffer;
        }
    }

    // Return NULL on error
    return NULL;
}


 

2.7.7 Let There Be Light!(要有光!)

 

        現在你已經決定了要使用什麼類型的光,還建立了對應的結構體,那麼是時候激活光照管道(lighting pipeline)並打開燈光了。爲了激活光照管道,你將一個渲染狀態D3DRS_LIGHTING設爲TRUE:

// g_pD3DDevice = pre-initializing deviceobject
g_pD3DDevice->SetRenderState(D3DRS_LIGHTING,TRUE);

        爲了關閉光照管道,使用下列代碼:

// g_pD3DDevice = pre-initializing deviceobject
g_pD3DDevice->SetRenderState(D3DRS_LIGHTING,FALSE);


        在激活了光照管道之後,你通過使用IDirect3DDevice9::LightEnable 函數將獨立的燈光進行開或閉。這是LightEnable 函數的原型:

 IDirect3DDevice9::LightEnable(
  DWORD LightIndex,  // Light index, 0 – max # lights
  BOOL bEnable);     // TRUE to turn on, FALSE to turn off

        如果你已經建立了一個LightIndex 爲0的點光源,你可以用下列代碼來將之開閉:

// g_pD3DDevice = pre-initializing deviceobject
 
// Turn light on
g_pD3DDevice->LightEnable(0, TRUE);
 
// Turn light off
g_pD3DDevice->LightEnable(0, FALSE);

        以上就是使用Direct3D的光照系統的內容了!在繼續前進之前再最後警告一句:Direct3D在圖形系統中使用燈光時做的工作很得體,但是如果使用者的圖形卡不支持光照的話,那麼Direct3D不得不模擬光照效果。雖然這不是什麼壞事,但是使用光照的話,模擬會減慢渲染的速度。不過,不要讓光照模擬的威脅來阻止你,因爲在你的遊戲中使用光照效果可以大大提高你的圖形效果。

 

===============================================================================

 

        好了,這一節講完了!其實我覺得這裏的安排不太科學,因爲光照是與材質結合在一起的,二者不能夠孤立地存在;但是講述材質相關的知識卻是在第10期。其實大家應該看看“龍書”第二版的第10章,那裏講述地非常透徹!

        對了,我的“龍書”第二版修正版已經更新到了第12章了,稍後我會分享給大家的。

 

        然後是相關的代碼。這一部分的代碼示例叫做Lights,但是實際上只有一個光源,是點光源。我同樣也使用的是shader方法,因爲我覺得這樣能夠讓我們看得更清楚,而且也可以選擇實現光照的其他方法。前面說了,將光照和材質分開是不太科學的。原始的示例程序中,作者並沒有定義材質,而是在頂點結構中定義一個diffuse顏色成分。其實這樣並沒有真正地用到光照的精髓。所以我按照“龍書”第二版的方法將光照和材質都定義了出來,然後在.fx文件中實現二者的互動。並且,我還增加了鏡面反射光。不過,與“龍書”第二版的方法不同的是,我這裏使用了Direct3D內置的D3DLIGHT9結構體(但是並不是像書上那樣用的)。


        下面是程序運行時的截圖:


        最後我給出代碼,包含了原始代碼和本人更新的代碼:

Lights代碼


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章