D3D基礎 – 光照,材質與着色

初接觸d3d時,相信許多初學者和我一樣,雖然對3D實時渲染的原理有所瞭解,但是卻對整體的管線結構有所困惑,包括可能許多做3d遊戲的程序員,由於常常採用封裝好的引擎做上層邏輯開發,因此對底層的架構也未必瞭解得十分透徹。

長久以來,對3d圖形學的底層技術,我一直也是隻瞭解個只鱗半爪而已,最近一段時間,在工作中不斷的接觸到這方面的知識,纔有了比較系統的思考與總結,本文記敘了我學習dx管線的一些思考,由於水平所限以及尚未對最新的DX11架構有所關注,所以本文不涵蓋DX11的最新架構,只涉及DX9及以前的知識結構,雖然如此,但是相信如果系統的掌握了這塊知識,對了解後續的新技術也是有所幫助的。

本文主要是針對對3D渲染知識有一定了解,然而對D3D的結構卻不是很清楚的同學,把DX8/9的管線結構做了一番說明,將固定管線的光照、紋理混合與可編程管線加以對比,期望對這一塊知識有一個比較完整的總結。

 

目錄

1. 硬件加速的3D渲染

2. 材質與光照

3. 變換,光照與頂點着色器

4. 紋理與像素着色器

5. Alpha測試,深度測試與Alpha混合

6. 更多話題

1. 硬件加速的3D渲染

硬件加速的3D渲染,早些時候只是被稱爲Hardware Transform & Lighting的加速技術,這些技術是相對於早期時候,圖形硬件尚未普及,軟件實現的實時3d遊戲中常用的軟件變換與光照而言的。因此,最早的顯卡最主要的目標也就是通過硬件來加速頂點變換,和頂點光照(如果嚴格來說,紋理採樣應該也要算在內),從DX的固定管線接口中可以看出這一點,在固定管線中,通過SetTransform來設置變換矩陣,通過SetMaterial來設置材質屬性,SetLight來設置光源參數,最後一個DrawPirmitive把三角形畫到表面上去。

這幾個步驟分別都是什麼意思?接下來我會一一分說。

對於3D圖形學有所瞭解的同學應當知道,繪製3D畫面時,我們需要幾樣東西:

1) 所要繪製的目標:一個Mesh網格(包含了若干頂點,若干索引,實際構成爲一個個三角形)

2) 物體的世界變換矩陣:通常我們表示一個3d物體(比如一個Mesh)的時候,都是用Local座標系進行表示,實際渲染時,再乘上變換矩陣得到其在世界中的位置。這種做法有什麼好處?或者是否非如此不可?讀者可以想一想,其實原因非常簡單。 : )

3) 攝像機與視口:我們如何去看待三維世界中的物體,攝像機定義了我們觀察物體的位置,角度,以及遠近變換的程度

上述三樣東西是必須的,有了相機和繪製目標,以及繪製目標的世界矩陣,我們就可以進行3D繪製了。但是想要獲得更加具有真實感的圖像,我們需要給繪製目標加上光照(注意,光照不等同於陰影,儘管當我們要實施陰影的時候,也往往是按照光照時設置的光源來繪製陰影的)此處所說的光照是指頂點光照,根據光源與頂點的相對位置,頂點的法線朝向,以及物體的受光材質決定最終的頂點顏色。一個簡單的光照函數類似下面這樣:

DiffuseColor = Clamp(DotProduct(VertexNormal, VertexToLightDir), 0, 1) * LightColor * MaterialColor + Ambient

Clamp函數保證點積的結果在[0.0, 1.0]區間內

上面的這個函數沒有考慮光源衰減,燈光範圍,不考慮SpotLight的衰減,另外只涉及漫反射光照,高光則需另外計算。但是從原理上已經可以說明(頂點)光照是怎麼一回事了。這裏強調頂點光照,是相對於像素光照來說的,頂點光照與像素光照後文會詳述。

公式中提到的VertexToLightDir, LightColor屬於光源的屬性,在D3D9中則是由D3DLIGHT9來描述的,通過D3DDevice::SetLight接口進行設置。而MaterialColor屬於材質屬性,描述結構爲D3DMATERIAL9,通過SetMaterial設置。

這樣我們得到了繪製3D物體的另兩樣參數,分別爲:

4) 光源:決定燈光的強度,顏色,範圍,衰減等

5) 材質:決定物體受光的顏色,比如是綠色的物體,或紅色的物體等。

有了光源和材質,繪製的3D物體則有了明暗和顏色,這對於提升渲染的真實感是有很大幫助的。

在此做一下小節:我們剛纔回顧了繪製3D畫面時,所必要做的事情,我們想象一下,如果沒有3D硬件,或者不利用D3D,要實現上述功能,得由我們自己去完成哪些事?

首先要做軟件的頂點變換,對每一個頂點,使用世界矩陣把它乘到世界座標系中,用相機參數做必要的可見性剔除,利用相機朝向與面法線朝向做隱藏面消除,用光源與材質參數對世界空間中的頂點位置做頂點光照,將世界空間中的三角形變換到屏幕空間中,利用光照得到的頂點顏色,對三角形做Gouraud插值着色,最終得到繪製在屏幕上的圖像。(有一本書叫做《3D遊戲編程大師技巧》,對上述過程作了非常詳盡的描述)

好了,有了圖形硬件與D3D之後,上述工作都省掉了,我們通過SetTransform來設置世界矩陣、觀察矩陣和投影矩陣,通過SetMaterial/SetLight來設置材質和光源參數,然後通過DrawPrimitive來繪製三角面。圖形硬件和D3D接口極大的簡化了我們編寫3D程序的難度,諸多繁雜的事情都被交給硬件去完成了。

 

2. 材質與光照

前一節主要是簡單介紹了一下D3D硬件都爲我們做了哪些事——事實上現如今的圖形硬件所完成的功能已經遠遠不止上述那些了。接下來一節,我準備總結一下材質與光照的細節和原理。

上一節簡單介紹了下材質與光照,但是沒有深入系統的做總結,本節準備將這個過程做一個比較系統的總結。

關於幾種基本光源,其公式,原理,實現,可以通過MiloYip大牛的這篇文章來了解。該文中的範例是基於光線追蹤渲染器實現的,因此光和影的效果直接都有了,甚至在最後一個Demo中運用多光源模擬了軟陰影的繪製。然而在光柵化渲染器中,光照與材質只能解決物體的顏色以及明暗問題,不能解決影子問題。要在光柵化渲染器中實現陰影的繪製,則是另外一番話題了。

接下來還是藉助D3D的接口來說材質。

typedef struct _D3DMATERIAL9 {
    D3DCOLORVALUE   Diffuse;        /* Diffuse color RGBA */
    D3DCOLORVALUE   Ambient;        /* Ambient color RGB */
    D3DCOLORVALUE   Specular;       /* Specular 'shininess' */
    D3DCOLORVALUE   Emissive;       /* Emissive color RGB */
    float           Power;          /* Sharpness if specular highlight */
} D3DMATERIAL9;

材質描述了物體是如何對光作出響應的。雖然在可編程管線中,材質、光照都有自定義的實現方法,然而在固定管線中,光照採用的都是D3D所固有的一套機制,D3D中的最終光照結果由下式構成:

Global Illumination = Ambient Light + Diffuse Light + Specular Light + Emissive Light

環境光(Ambient Light)的計算方法爲:Ambient Lighting =

其中Ca爲材質的Ambient分量,Ga爲全局環境光顏色(通過ID3DDevice9::SetRenderState(D3DRS_AMBIENT, COLOR)設置全局環境光顏色),求和的部分是所有被激活的光源的環境光進行求和,光源的衰減,聚光燈的Factor(如果是聚光燈)會被考慮在內。

漫反射光(Diffuse Light)的計算方法:Diffuse Lighting =

解釋一下這個方程,其中Cd是材質的DiffuseColor,Ld是光的DiffuseColor,N是頂點法線,Ldir是從頂點到光源的方向向量(N與Ldir都是單位向量),再乘以光源的衰減參數,及SpotLight的因子。這裏的N.Ldir正是計算漫反射光強弱的關鍵,一個頂點是否能被光源照亮,取決於該點法線與光方向的夾角,如果兩者重合,該點受光程度達到最大,如果夾角大於等於90度,則不受光。

高光(Specular Light)的計算方法:Specular Lighting =

 

 

Cs是材質的Specular值,p是材質中的Power,Ls是燈光的Specular值,N是頂點法線,H是一個被稱爲Halfway vector的單位方向,該向量的含義爲頂點到攝像機的向量與頂點到光源的向量的中間向量,計算方式爲(H = norm(norm(Cp – Vp) + Ldir))

關於Halfway vector的含義,可以參考下圖:

halfwayvector

仔細思考一下我們就能明白,相比將入射光向各個方向均勻反射的漫反射而言(對方向不敏感),高光對視線與反射光線方向是敏感的,也就是說,從眼睛到頂點與反射光方向的夾角越小就會越亮,日常生活中我們往往可以在光滑表面看到高光現象,比如汽車表面的噴漆或者玻璃。

最後一個是自發光,自發光很簡單,就直接等於材質參數中的Emissive分量:Emissive Lighting = Ce

上述這些就是D3D固定管線中材質與光照的工作原理。其實所有的公式都可以在DXSDK的文檔中獲取到,這裏不過是簡單總結了一下。

前面所描述的這些材質與光照,依賴於正確的輸入,比如頂點法線:漫反射和高光的計算都要依賴於頂點法線。我們都知道D3D有自定義的頂點格式,被稱爲FVF的東西。那麼也就是說,如果想要正確的使用D3D頂點光照,我們採用的頂點格式,應當至少包含法線(D3DFVF_NORMAL)。

我們也常見到不包含法線,而是包含漫反射顏色(D3DFVF_DIFFUSE)和高光色彩(D3DFVF_SPECULAR)的頂點格式,採用這種頂點格式往往意味着我們準備自行爲頂點填充顏色,而不採用D3D光照。

 

3. 變換,光照與頂點着色器

要說頂點着色器,則不得不從固定管線與頂點格式說起。D3D中有所謂的Flexible Vertex Format,也就是大名鼎鼎的FVF常量。我們常見的D3DFVF_DIFFUSE,D3DFVF_XYZ,D3DFVF_NORMAL都是定義好的常量,在需要用的時候,我們把這些常量“或”在一起,構成一個頂點格式,傳遞給D3D使用。D3D固定管線通過該FVF格式對我們傳入的VertexBuffer進行解釋(每份頂點數據的長度,以及每個成員的偏移)。

起初學習D3D的時候,我對FVF格式有個疑惑,百思而不得其解。我們都知道要寫一個普通的具有位置,法線和UV的頂點格式聲明應該怎麼寫:

#define MY_VERTEX_FVF D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX0

struct VERTEX
{
    float x, y, z;
    float nx, ny, nz;
    float u, v;
};

我覺得很奇怪,D3D怎麼知道我把x,y,z放在前面還是把nx,ny,nz放在前面,難道是根據我定義FVF時的先後順序?明顯講不通啊:因爲或運算不可能給出任何先後順序的信息來的。

直到後來我才明白,原來D3D固定管線的頂點格式也是有固定順序的,也就是說如果要寫一個既帶有位置,又帶有法線的頂點數據結構,那麼x,y,z一定要放在nx,ny,nz之前,或者說,D3D是根據一個固定的偏移去解釋這個頂點數據結構的,首先一定是位置,然後纔是法線,其他更復雜包含更多成員的數據格式也是與此同理。什麼意思呢?

還是以上面的例子來解釋,你把頂點數據結構寫成下面這樣(區別在於,nx,ny,nz與x,y,z調換了順序):

#define MY_VERTEX_FVF D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX0

struct VERTEX
{
    float nx, ny, nz;
    float x, y, z;
    float u, v;
};

D3D會說,不好意思,我不知道你怎麼命名的,也不在乎你把它叫nx還是x,反正我都把你你頭三個浮點數就當作是位置,接下來三個浮點數當成是法線,只要你給我的FVF是有D3DFVF_XYZ的,那頭三個浮點就是位置沒跑了!

從上面的例子中我們可以看到,實際上固定管線的結構是很死板的,缺乏給程序員自由發揮的空間,它規定好了一系列的頂點數據的用途,然後你必須按照它指定的方式去做,否則結果就不對。

當然了,這也只是後來的可編程管線替代固定管線的諸多原因之一。如今我們見到的市面上的所有顯卡基本上已經沒有不支持可編程管線的了,而且DX也已經更新換代到了DX11,新架構與本文中所介紹的基於DX9的結構又有了很多變化。

繼續前面所述的話題,D3DFVF格式不僅決定了頂點數據結構如何定義,也說明了頂點要如何進行座標系變換,比如:

D3DFVF_XYZ說明了頂點需要進行全套的世界、觀察、投影變換;

D3DFVF_XYZRHW則說明該頂點座標已經是屏幕座標,x,y表示窗口中的座標,z值表示z-buffer中的值,從近到遠爲0.0~1.0,採用這種格式的頂點聲明不能與D3DFVF_XYZ或者D3DFVF_NORMAL混用,並且不經過頂點處理單元;

D3DFVF_XYZW也是表示已經變換過後的頂點,但是這種頂點格式會經過頂點處理單元,也就是vertexshader的階段;

D3DFVF_XYZBn (n=1..4) 表示該頂點在變換階段需要進行混合,啥意思?也就是說在做變換的時候,不是一個世界矩陣對其起作用,而是有n個矩陣,根據Blend的參數共同對該頂點起作用。如下例(摘自DXSDK):

#define D3DFVF_BLENDVERTEX (D3DFVF_XYZB3|D3DFVF_NORMAL|D3DFVF_TEX1)

struct BLENDVERTEX
{
    D3DXVECTOR3 v;       // Referenced as v0 in the vertex shader
    FLOAT       blend1;  // Referenced as v1.x in the vertex shader
    FLOAT       blend2;  // Referenced as v1.y in the vertex shader
    FLOAT       blend3;  // Referenced as v1.z in the vertex shader
    // v1.w = 1.0 - (v1.x + v1.y + v1.z)
    D3DXVECTOR3 n;       // Referenced as v3 in the vertex shader
    FLOAT       tu, tv;  // Referenced as v7 in the vertex shader
};

也就是說v在被變換的時候,會被4個矩陣依次乘上,並將其結果使用(blend1,blend2,blend3,1-(blend1 + blend2 + blend3))四個參數作爲係數進行混合。注意一下SetTransform函數,它有兩個參數,第一個參數的說明裏有這麼一段:

 

State 

 

[in] Device-state variable that is being modified. This parameter can be any member of the D3DTRANSFORMSTATETYPE enumerated type, or the D3DTS_WORLDMATRIX macro. 其中的D3DTS_WORLDMATRIX是一個宏,可以像這麼用D3DTS_WORLDMATRIX(0),D3DTS_WORLDMATRIX(1),這樣一來就可以傳遞多個矩陣到固定管線中了(等到可編程管線就沒這麼麻煩了,可以直接通過頂點着色器常量傳遞矩陣)。

 

最後簡單說一下D3DFVF_XYZBn這種頂點格式的用途:這種頂點格式往往被用在骨骼動畫的頂點裏,因爲骨骼動畫需要一個頂點受多個骨骼的影響。

定義好了FVF與頂點數據結構之後,我們需要把它設置到設備上去。我們知道,早些年的DX8也好,DX9也好,其Device都有一個SetVertexShader接口,用於設置頂點着色器程序。而在DX8中,這個接口不僅僅是可編程管線的vertex shader通過它來設置,實際上如果採用的是固定管線的話,頂點的FVF格式也是走這個接口設置的。DX8中的Vertex shader創建出來之後是一個32位的DWORD類型的handle,從而得以與FVF常量共用這同一個接口。而在DX9中,SetVertexShader的接受參數變成一個IDirect3DVertexShader9*了,而固定管線的FVF被挪走,需要通過接口SetFVF來進行設置了。

另外,由於在DX9中,IDirect3DVertexShader9在創建的時候,並沒有包含頂點聲明信息,因此DX9增加了一個接口IDirect3DVertexDeclaration9專門用於爲可編程管線的VertexShader提供頂點聲明信息。爲什麼一定要有頂點聲明信息呢?寫過HLSL的同學都知道,我們在vertex shader裏是必須要定義vs input的數據結構的,頂點聲明信息必須與我們的VS_INPUT數據結構保持一致。那麼這個頂點聲明信息在什麼時候用呢?仔細思考一下就會明白,vs是一段運行於GPU上的程序,它按照我們編寫的HLSL代碼接受輸入,經過計算後輸出,輸入的數據是在頂點緩衝中準備好的,在DrawPrimitive之前,通過SetVertexBuffer設置,此後這些輸入數據通過驅動程序被提供給顯卡,然而底層如何知曉應把多長一截的數據作爲一份輸入的頂點數據呢?以及應該把頂點數據中的哪一部分送到頂點寄存器裏,哪些送到紋理採樣單元裏去?到這裏,就不得不依賴頂點聲明提供的信息了。

前面在材質與光照一節,我們簡單介紹了一下D3D固定管線光照中,材質是如何與燈光一起起作用,並作用於物體的色澤,明暗,高光的。現在當我們手握Shader這樣的利器,就擁有了繞開D3D固定管線光照的自由了(譬如如今已經很普及的基於像素的光照技術,就是把光照從頂點着色階段推遲至像素着色器中)。我們還可以利用頂點着色器的常量傳遞變換矩陣,從而擁有自定義頂點變換,或者在頂點變換階段獲取一些我們想要的中間結果的能力。

4. 紋理與像素着色器

前面說了關於頂點變換與頂點光照相關的東西。接下來要說的是紋理混合,也就是SetTextureStageState函數乾的事情。我們都知道DX支持紋理混合,我們可以把多個紋理按照Sampler的索引(DX9中是Sampler,而在DX8中則是Stage),通過SetTexture函數設置到設備上。

 

所謂Stage是什麼一個概念呢?參考下圖(來源於DX9SDK):

dx_texture_stage

在每一個Stage上設有一張紋理(如果沒有設置紋理),在固定管線中,我們通過SetTextureStageState設置在當前的Stage中,紋理如何與“當前像素”混合並輸出到下一個Stage,“當前像素”來源於上一個Stage的輸出,0Stage的當前像素的Color及Alpha是頂點Color/Alpha插值得來的。在紋理階段比較常見的就是DiffuseTexture,SpecularTexture,分別用於顏色與高光。在每一個Stage中,紋理顏色與上一階段顏色採用下式進行混合:

FinalColor = TexelColor × SourceBlendFactor + PixelColor × DestBlendFactor

這裏需要注意的是,紋理混合與最終的AlphaBlending是兩個概念。紋理混合階段,根據頂點顏色以及紋理決定最終要輸出的像素的顏色和透明度,而到了AlphaBlending階段,則是決定該像素與已經繪製到BackBuffer上的像素如何去混合。此外,更復雜的功能如NormalMapping,通常則需要像素着色器參與。按照我的理解,如果想要實現像素光照,則必須把光照階段從頂點着色器中推遲到像素着色器中才可行,NormalMap提供了這一階段每個像素的法線,從而使像素光照得以實現。(D3D9在固定管線中提供了一套BumpMap,但是老實說,D3D9文檔裏對此的說明看得我雲裏霧裏的,並沒有搞清楚其原理究竟是什麼。)

像素着色器最初的就是爲了替代固定管線的紋理混合操作而誕生的。也就是說,如果採用了PixelShader對像素進行着色,那麼SetTextureStageState裏面的那些顏色,Alpha混合操作就不再起作用了。

我們以一個常見的例子來解釋紋理混合的用法,美術可能要求實現一種正片疊底的紋理混合效果,對於程序實現而言,實現方案就是找兩張貼圖,用乘法進行紋理混合。如果採用SetTextureStageState實現:

 

pD3DDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1);
pD3DDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);

pD3DDevice->SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_MODULATE);
pD3DDevice->SetTextureStageState(1, D3DTSS_COLORARG1, D3DTA_TEXTURE);
pD3DDevice->SetTextureStageState(1, D3DTSS_COLORARG2, D3DTA_CURRENT);

 

解釋一下這裏的兩個StageState的設置:階段0,我們對Color採用SELECTARG1操作,同時把COLORARG1,設置爲D3DTA_TEXTURE,也就是說,完全以SetTexture(0, …)設置的紋理顏色作爲這一階段的輸出;在階段1,COLOROP爲MODULATE,也就是將COLORARG1與COLORARG2相乘作爲輸出,公式:

 

其中COLORARG1設置爲D3DTA_TEXTURE,也就是SetTexture(1, …)中設置的紋理,而COLORARG2爲D3DTA_CURRENT,表示的是上一階段的輸出。這樣經過這兩個Stage之後,我們獲得的輸出就是兩張貼圖相乘的結果了。

接下來再用Ps2.0(需要DX9,如果採用DX8則需用ps1.4實現)實現一遍該效果:

 

texture g_Tex0;
texture g_Tex1;
sampler g_Sampler0 = sampler_state {Texture = g_Tex0; MipFilter = LINEAR; MinFilter = LINEAR; MagFilter = LINEAR;}
sampler g_Sampler1 = sampler_state {Texture = g_Tex1; MipFilter = LINEAR; MinFilter = LINEAR; MagFilter = LINEAR;}

struct VS_OUTPUT
{
    float4 Position   : POSITION;   // vertex position
    float2 TexCoord   : TEXCOORD0;  // vertex texture coords
};

float4 PS_Main_2_0( VS_OUTPUT Input ) : COLOR0
{
    float4 color0 = tex2D( g_Sampler0, Input.TexCoord );
    float4 color1 = tex2D( g_Sampler1, Input.TexCoord );
    return float4(color0 * color1);
}

 

使用ps的話,所需要做的工作非常直觀,用預先定義好的Sampler以及uv座標對紋理採樣,並把顏色相乘輸出即可。

這只是一個最簡單的例子,實際上ps的用途非常廣泛(實現ShaderMap,像素光照,諸多後期處理效果都需要採用ps實現),而更多的內容就不可能涵蓋在本文的範圍之內了,感興趣的讀者可以讀一讀GPUGems系列,ShaderX系列等經典書籍。

5. Alpha測試,深度測試,以及Alpha混合

走完了像素着色的過程之後,就到了最終的繪製步驟了。這裏有幾個因素決定我們是否,以及如何往BackBuffer/ZBuffer/StencilBuffer上繪製像素:AlphaTest,StencilTest,ZTest,Alpha混合模式。

關於上述測試的順序和流程,由於OpenGl與D3D在上述測試的順序上達成了一致,因此可以參考下圖(圖摘自OpenGL2.0 spec Page 199):

Pre-FragmentOperations

如果開啓了AlphaTest,則首先做AlphaTest,根據像素的Alpha值與一個指定的D3DRS_ALPHAREF值比較的結果,將D3DRS_ALPHAFUNC的判斷未能通過的像素直接砍掉。(不會進行後續的z-test以及z-write)

如果開啓了StencilTest,則繼續做StencilTest,如果不通過StencilTest,則也不會進行下一步ZTest,但是可能會修改StencilBuffer。

如果開啓了ZTest,根據當前像素Z值,與DepthBuffer上該位置已有Z值做比較,如果D3DRS_ZFUNC的判斷未能通過,則將該像素砍掉,但是根據D3DRS_STENCILZFAIL的狀態,有可能會修改StencilBuffer。通過了ZBuffer的像素,如果開啓了ZWrite,則該像素的Z值會寫入DepthBuffer,否則(ZWrite關閉)該像素不會影響DepthBuffer(也就是說不會遮擋後續繪製的像素,儘管可能該像素離觀察者比將來要繪製的像素更近)

最後根據D3DRS_ALPHABLENDENABLE決定是否開啓Alpha混合。如果Alpha混合沒有開啓:則直接把像素的顏色透明度寫入BackBuffer,如果Alpha混合開啓了,則根據D3DRS_SRCBLEND以及D3DRS_DESTBLEND的選項,決定新寫入像素如何與BackBuffer已有像素進行混合。

上述流程是標準流程,然而在較新的幾代顯卡里,針對StencilTest與ZTest又有了許多新的技術,比如EarlyZ優化等,由於這裏牽扯到的顯卡型號,各廠商可能都有不一致的地方,我就沒有做更進一步的詳細研究。下面有幾個可以供參考的鏈接,感興趣的同學可以自己瞭解一下:

amddeveloper的一篇文章在這裏:http://developer.amd.com/media/gpu_assets/Depth_in-depth.pdf

beyond3d上的一個討論帖看這裏:http://forum.beyond3d.com/showthread.php?t=51025

 

6. 更多話題

本文介紹了D3D流水線的一般過程,對比了固定管線與可編程管線的差別(當然有很多細節未能兼顧到),對D3D渲染流程的知識結構做了一個一般性的總結。

事實上,隨着圖形技術的發展,如今的3D圖形技術已遠走出很長一段路了,比如在DX9架構下就已經發展出很多成果的ShadowMap,NormalMap,Realtime Global Illumination等。再比如到DX10新增的Geometry Shader以及DX11架構下新增的Compute Shader,Tessellation等。

有時候學得越多,才越知道自己所知甚少。在撰寫本文的過程中,我也把此前有些混淆的概念做了整理,自己也有不少收穫。如果讀者對本文所涉的話題有更多理解,或者發現了錯漏之處,還望不吝告知。 : )

本文轉自:http://www.windameister.org/blog/2010/09/18/d3dbasic-lighting-material-shading/

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