關(guān)鍵字 Direct3D,Shader,HLSL
寫(xiě)過(guò)Direct3D程序的朋友們可能還記得,在以往,大家常為如何表現(xiàn)更多真實(shí)的材質(zhì)(如玻璃、金屬等)而發(fā)愁。這種情況在DirectX8.0問(wèn)世后有所改善了,我們可以編寫(xiě)Shader來(lái)完成。最新的Direct3D中,HLSL把程序員從復(fù)雜的Shader指令集中解放出來(lái),著力于更重要的算法。
HLSL(High-Level Shader Language)本文將從如下幾個(gè)部分介紹
準(zhǔn)備工作
HLSL基礎(chǔ)
實(shí)例分析首先,什么是
Shader?什么是
HLSL?
簡(jiǎn)要地說(shuō),Shader就是一種腳本程序,相對(duì)獨(dú)立于D3D主程序,并且被編譯成顯卡的GPU指令序列在顯示芯片上跑。(你肯定想知道更多,比如這種程序用什么來(lái)寫(xiě),都要寫(xiě)什么,怎么讓GPU跑這種程序等等,別著急慢慢來(lái)),這里有必要先了解一下:
AGP顯卡的渲染流程:
首先來(lái)根據(jù)下面這張圖粗略說(shuō)明一下當(dāng)前最普遍流行的AGP顯卡的渲染流程,甭管是nVidia還是ATI哪一邊的。
每次渲染過(guò)程(例如,在一幀畫(huà)面中畫(huà)一個(gè)饅頭的過(guò)程)都包括頂點(diǎn)處理(Vertex Processing)和像素處理(Pixel Processing)兩個(gè)主要功能模塊的執(zhí)行。首先,顯卡從AGP總線(xiàn)接收這個(gè)饅頭的頂點(diǎn)數(shù)據(jù)。這些數(shù)據(jù)包括位置、法線(xiàn)、貼圖坐標(biāo)(如果是面包可能更需要貼圖,也就是說(shuō)貼圖坐標(biāo)不是必需的)等等,這些都是未經(jīng)過(guò)任何變換,也就是在物體本地空間(Object Space)下的原始坐標(biāo)。每個(gè)頂點(diǎn)依次被送入頂點(diǎn)處理單元,在這里進(jìn)行坐標(biāo)變換、光照計(jì)算(如果是每頂點(diǎn)光照)等工作,變換的結(jié)果是把每個(gè)三角形變換置屏幕空間(Screen Space)下直接可用。這里用到的變換矩陣、燈光等信息都是處理每一批頂點(diǎn)時(shí)一次性傳給顯卡的,作為顯卡的資源。頂點(diǎn)處理圈定了三角形的范圍,接下來(lái)就要逐像素地填充這個(gè)三角形了。填充哪些像素是靠對(duì)頂點(diǎn)屏幕坐標(biāo)的線(xiàn)性插值來(lái)決定的。像素的其他一些必要參數(shù),如顏色,貼圖坐標(biāo)等也是通過(guò)對(duì)上一步計(jì)算出來(lái)的頂點(diǎn)的這些屬性進(jìn)行插值得到的。另外每個(gè)像素還要通過(guò)深度檢測(cè)和模板檢測(cè)決定最終是否繪制。需要繪制的像素被送進(jìn)像素處理模塊,進(jìn)行貼圖像素取值,貼圖混合等工作,必要的話(huà)每像素光照也在這里完成。這里貼圖等信息也是作為顯卡的資源。像素最終的處理結(jié)果被放進(jìn)后備緩沖。

以往顯卡在頂點(diǎn)處理和像素處理過(guò)程中執(zhí)行的是一套布在硬件上的固定的程序,D3D程序員只能設(shè)置一些參數(shù),實(shí)際上就是你調(diào)用IDirect3DDevice::SetRenderState()時(shí)做的事,而這樣的程序在IDirect3DDevice::DrawPrimitive()中自動(dòng)執(zhí)行。那么有些事情就很難辦到,如渲染一個(gè)水晶饅頭。應(yīng)為參數(shù)再多,其渲染所用到的光照公式也跑不出石膏這種東西。現(xiàn)在的顯卡,確切的說(shuō)是現(xiàn)在的Direct3D允許你寫(xiě)這么一段程序替代固定的頂點(diǎn)處理過(guò)程和像素處理過(guò)程(記住,只是這兩個(gè)過(guò)程,跟插值什么的沒(méi)關(guān)系)。其中替換頂點(diǎn)處理的就叫Vertex Shader(暫時(shí)還沒(méi)有確切的中文名),替換像素處理的就叫Pixel Shader。就是開(kāi)篇所說(shuō)的Shader。
這樣你應(yīng)該想到Shader中大概應(yīng)該寫(xiě)些什么了。如果還不行的話(huà)建議復(fù)習(xí)一下D3D。用什么來(lái)寫(xiě)呢?三。GPU自有GPU的指令集,以往的Shader就是用這種匯編式的指令集組成,例如:
vs_1_1
dcl_position v0
dcl_normal v1,r0, v0.x, c0
mad r2, v0.y, c1, r0就如同匯編用多了必然出現(xiàn)C一樣,自Direct3D9.0后,一種叫HLSL(High Level Shading Language,高級(jí)渲染語(yǔ)言)的面向過(guò)程的Shader語(yǔ)言應(yīng)運(yùn)而生,本篇將詳細(xì)介紹的即為此。
HLSL基礎(chǔ)就像每一本編程語(yǔ)言的教材一樣,介紹一門(mén)語(yǔ)言,首先從它的數(shù)據(jù)類(lèi)型,表達(dá)式,控制流這些東西說(shuō)起。HLSL的這些基本語(yǔ)法很像C/C++,不再贅述。有些常見(jiàn)問(wèn)題還是要說(shuō)明一下,是為了讓你不會(huì)被這些牽制了全面了解Shader的腳步。
數(shù)據(jù)類(lèi)型與CPU不同,在顯卡芯片中,最小的數(shù)據(jù)吞吐單元是一個(gè)由32位浮點(diǎn)數(shù)組成的四元組。這一點(diǎn)很有道理不是,想想你在渲染過(guò)程中所有涉及到的數(shù)據(jù),最復(fù)雜的不外乎四維坐標(biāo)(x,y,z,w)或顏色(r,g,b,a),這樣GPU可以一次性處理一個(gè)四元組。而整數(shù)什么的在顯卡中被放到四元組的一個(gè)分量里使用,而很多顯卡中,整數(shù)、布爾值都不被直接支持,而是轉(zhuǎn)為浮點(diǎn)數(shù)使用。至于矩陣,通常用4個(gè)四元組表示一個(gè)4x4矩陣(默認(rèn)情況一個(gè)四元組存儲(chǔ)一行,也可以指定按列存儲(chǔ),屬于細(xì)節(jié)問(wèn)題,goto:
細(xì)節(jié)問(wèn)題)其他尺寸的以此類(lèi)推。
反映到程序上,一個(gè)四維向量就被聲明為float4,4維方矩陣被聲明為float4x4等等。當(dāng)然,你也可以使用任意不超過(guò)4的維度的向量或矩陣,如int3,float3x3,double1。這個(gè)double1實(shí)際上就是標(biāo)量了,1可以省略不寫(xiě)。想知道更多,就goto:
細(xì)節(jié)問(wèn)題紋理(Texture)&取樣器(Sampler)這倆東西可以看作特殊類(lèi)型變量。紋理就是Shader中用到的貼圖資源,這我想沒(méi)什么好說(shuō)的。來(lái)解釋一下取樣器:實(shí)際上每張貼圖在使用的時(shí)候都要用一個(gè)取樣器。取樣器相當(dāng)于這樣一個(gè)結(jié)構(gòu),除了保存貼圖本身數(shù)據(jù)之外,還包括過(guò)濾參數(shù)等取樣信息。通常,讀取貼圖這樣的指令接收的都是取樣器類(lèi)型的參數(shù)而并非直接接收紋理貼圖。聲明及使用紋理或取樣器跟使用普通變量一樣。這里有一些初始化取樣器的方法,還是等到后面的實(shí)例中講述吧。更多內(nèi)容請(qǐng)goto:
細(xì)節(jié)問(wèn)題Semantic & Annotation任何類(lèi)型的變量(包括紋理和取樣器),我們都可以用Semantic或Annotation修飾來(lái)起到一些特殊作用。Semantic暫時(shí)翻譯成語(yǔ)義;Annotation暫時(shí)翻譯成注解,這是HLSL中獨(dú)特的東西。下面這兩行中,第一個(gè)變量冒號(hào)后面的POSITION就是Semantic,第二個(gè)變量后面用一對(duì)尖括號(hào)<>圈起來(lái)的表達(dá)式就是Annotation,一組<>中可以有很多個(gè)表達(dá)式。
float3 OmniPos : POSITION;
texture TexMap < string name = "test.dds"; >;一般來(lái)講,Semantic是告訴應(yīng)用程序或D3D這個(gè)被修飾的變量是做什么用的,Annotation是告訴程序這個(gè)變量怎么用。很云山霧罩是嗎,是這樣,在應(yīng)用程序代碼中,是可以調(diào)用D3D的API認(rèn)出Semantic和Annotation的。例如上面這兩行,程序的邏輯就可能是這樣:首先寫(xiě)主程序的甲和寫(xiě)Shader的乙約定好POSITION標(biāo)識(shí)該變量代表燈泡A的位置,甲在程序里寫(xiě):{燈泡A.位置 = XXX; 找到Shader中帶POSITION的變量; 給該變量賦值為燈泡A.位置; return;} 那么甲可以不知道乙在Shader中給這個(gè)要用燈泡A位置的變量起了什么名,而且乙可以在好幾個(gè)Shader中給用這個(gè)數(shù)據(jù)的變量起不同的名。然后,甲和乙再約定遇到Annotation中的“name”就將后面的字符串作為文件名建立貼圖。于是甲的程序就從Shader中讀出了一個(gè)文件名,建立了一個(gè)貼圖以供這個(gè)texture變量使用。Semantic和Annotation大概就這么用,首先要約定好各個(gè)Semantic和Annotation都是什么意思,這是up to you的,然后就是通過(guò)它們的標(biāo)識(shí)來(lái)給變量賦值或作其他輔助性工作了。
既然都是做輔助說(shuō)明的為什么還要分成Semantic和Annotation,我的想法是Semantic簡(jiǎn)單方便而Annotation能干的事更多。不說(shuō)這個(gè)了,無(wú)關(guān)大局。要說(shuō)的是,D3D也跟我們約定了一套Semantic,它們大體上都能顧名思義,詳細(xì)信息在后面。
控制流控制流,就是if…else,for,while什么的。在CPU中,這些控制流造成的實(shí)際上是指令跳轉(zhuǎn)。但在GPU中指令跳轉(zhuǎn)并不被廣泛的支持,以往的大部分顯卡只懂得按順序一句一句執(zhí)行指令。因此HLSL的編譯器可能會(huì)做出諸如展開(kāi)循環(huán)、遍歷分支等等莽撞的事來(lái)適應(yīng)顯卡。所以使用時(shí)要特別小心,而且不是所有情況的控制流語(yǔ)句都被支持。具體的很多規(guī)則還是在
細(xì)節(jié)問(wèn)題里。
函數(shù)HLSL中提供了很多函數(shù)可供調(diào)用,在Direct3D 文檔 -> DirectX Graphics -> Reference -> HLSL Shader Reference -> HLSL Intrinsic Functions中有這些函數(shù)的詳細(xì)列表。也可自己寫(xiě)函數(shù)用,但是在較早的Shader版本中,就像內(nèi)聯(lián)函數(shù)一樣編譯時(shí)最終要將函數(shù)展開(kāi)插入到函數(shù)調(diào)用處。還有一點(diǎn)我想你一定會(huì)想到的就是主函數(shù)會(huì)是什么。Vertex Shader和Pixel Shader各自需要一個(gè)主函數(shù),由程序員來(lái)指定!沒(méi)錯(cuò),程序員在Shader外部指定。具體方法將在下篇講述(注意不是細(xì)節(jié)問(wèn)題)。
// Declare a global uniform variable
float4 UniformGlobal;
float4 main( uniform float4 UniformParam ) : POSITION
{
return UniformGlobal * UniformParam;
} 細(xì)節(jié)問(wèn)題你會(huì)覺(jué)得前面說(shuō)的太過(guò)粗略,還有很多問(wèn)題沒(méi)有敘述,但相對(duì)來(lái)講這些都算是細(xì)枝末節(jié)了。例如HLSL中保留關(guān)鍵字有哪些;變量的作用域;數(shù)據(jù)類(lèi)型的詳細(xì)信息;四元組分量的使用法則等等,這些在Direct3D文檔 -> DirectX Graphics -> Programming Guide -> The Programmable Pipeline -> Programmable HLSL Shaders -> HLSL Language Basics中講得比我清楚,我也不再多余翻譯了。
實(shí)例分析抑或你覺(jué)得看到前面的介紹后有些激動(dòng),躍躍欲試想接觸實(shí)際代碼,了解Shader的全貌了。那好我們就分析一個(gè)例子開(kāi)始。
我們通過(guò)兩個(gè)典型的Shader來(lái)看看怎么用它來(lái)實(shí)現(xiàn)我們想要的效果。最好先回顧一下前面的渲染流程,再次熟悉一下從Object空間下的頂點(diǎn)數(shù)據(jù)流到顯示器像素的途徑。然后不要著急理解下面代碼的每一句,僅僅瀏覽一遍,稍后我會(huì)帶你分析的。
先是一個(gè)Vertex Shader:
/////////////////////////////////////////////////////////////////////////////
// Copyright (c) FrontFree_Studio. All rights reserved.
/////////////////////////////////////////////////////////////////////////////
/**************** GLOABLE VARIABLES ****************/
float4 MtrlSpec = { 1.0f, 1.0f, 1.0f, 1.0f }; // material’s specular color, white
float SpecPow = 8;
float3 OmniPos : POSITION = { 0.577, 0.577, -0.577 }; // lights (world space)
float4 OmniColor = { 1.0f, 1.0 f, 1.0f, 1.0f }; // light’s color, white
float4 AmbLight = { 0.9f, 0.9f, 0.9f, 0.9f }; // ambient light color;
float3 CameraPos : CAMERAPOSITION = { 0.0f, 3.0f, 5.0f };
// camera (world space)
float4x3 matWorld : WORLD;
float4x4 matViewProj : VIEWPROJ;
/***** vertex shader output structure *********/
struct VS_OUTPUT
{
float4 Pos : POSITION;
float4 Intensity : COLOR0;
float4 Spec : COLOR1;
float3 Texcoord : TEXCOORD;
};
/**************** VERTEX SHADER ****************/
VS_OUTPUT VS( float3 InPos : POSITION, float3 InNormal : NORMAL, float2 InTexcoord : TEXCOORD )
{
VS_OUTPUT Out = (VS_OUTPUT)0;
//Calculate the output color by per-pixel lighting
// first, calculate the diffuse component
float3 P_World = mul(float4(InPos, 1), matWorld); // position to world space
float3 ToLight = normalize( P_World – OmniPos );
float3 Normal = normalize( InNormal );
float4 Diff = dot( ToLight, Normal ) * OmniColor;
Out.Intensity = Diff + AmbLight;
// then, the specular component
float3 Reflection = reflection( -ToLight, Normal );
float3 ToEye = normalize( CameraPos – P_World );
float4 Spec = pow( dot( Reflection, ToEye ), SpecPow ) * MtrlSpec * OmniColor;
Out.Spec = Spec;
// Output the position, texture coordination
Out.Pos = mul(float4(P_World, 1), matViewProj);
Out.Texcoord = InTexcoord;
return Out;
}
下面是個(gè)Pixel Shader:
/////////////////////////////////////////////////////////////////////////////
// Copyright (c) FrontFree_Studio. All rights reserved.
/////////////////////////////////////////////////////////////////////////////
/**************** TEXTURES ****************/
texture TexMap < string name = "test.dds"; >;
sampler2D TexSampler = sampler_state
{
Texture = <EnviMap>;
MipFilter = LINEAR;
MinFilter = LINEAR;
MagFilter = LINEAR;
};
/**************** PIXEL SHADER ****************/
float4 PS ( VS_OUTPUT In ) : COLOR
{
float4 Tex = texture2D( TexSampler, In.Texcoord );
return Tex * In.Intensity + In.Spec;
}
這是一個(gè)簡(jiǎn)單且無(wú)聊的例子,說(shuō)它無(wú)聊是因?yàn)闆](méi)有任何特效,僅僅是有高光,外加一重紋理貼圖。如果不用Shader實(shí)現(xiàn),僅需在D3D默認(rèn)效果上多加一行代碼。這個(gè)例子只是為了告訴你Shader里都需要干什么。首先是在Vertex Shader中將物體坐標(biāo)轉(zhuǎn)換至屏幕坐標(biāo);同時(shí)進(jìn)行光照計(jì)算出頂點(diǎn)的亮度,這里用的是Phone模型。然后在Pixel Shader中進(jìn)行紋理取樣,再混合上亮度得到最終結(jié)果。高光是在Vertex Shader中計(jì)算出來(lái)并在Pixel Shader中加上去的。
光照模型 說(shuō)通俗一點(diǎn),光照模型就是指采用何種方式來(lái)根據(jù)光源方向、頂點(diǎn)(或像素)位置、法線(xiàn)等信息計(jì)算該頂點(diǎn)(或像素) 明暗信息。上面的Shader中采用的是最常見(jiàn)的每頂點(diǎn)Phone模型。即每個(gè)頂點(diǎn)的明暗信息由環(huán)境光(Ambient)+漫反射光 (Diffuse)+高光(Specular)組成。通常,燈光打在物體上是通過(guò)漫反射進(jìn)入眼睛,因此不管你從哪個(gè)方向看一個(gè)饅頭, 上面的大部分明暗只取決于背光還是向光。數(shù)學(xué)上就是取決于該頂點(diǎn)向光的方向(圖中的向量L)和其法線(xiàn)之間的夾角。線(xiàn)數(shù)中兩根標(biāo)準(zhǔn)化的 向量點(diǎn)乘結(jié)果就是其夾角的最好度量。因此程序里也就這么算。高光(準(zhǔn)確講該叫鏡面反射光)就不一樣了。你應(yīng)該見(jiàn)過(guò)這樣的情節(jié) :張某疾步?jīng)_進(jìn)綠柳莊,突然眼前金光一閃,“哦,倚天劍”……實(shí)際上倚天劍和陽(yáng)光的位置并沒(méi)有改變,張某是否看見(jiàn)高光 還要取決于他視線(xiàn)方向和光線(xiàn)反射方向的關(guān)系。這兒有個(gè)比較復(fù)雜的數(shù)學(xué)公式來(lái)計(jì)算高光分量,相信讀者應(yīng)該能把他找出來(lái)。至于 環(huán)境光,實(shí)際的場(chǎng)景中光線(xiàn)是非常復(fù)雜的,即使沒(méi)有光源直接照射在物體上,它也將受到周?chē)矬w漫射出來(lái)的光線(xiàn)照亮。Phone 模型中,假設(shè)環(huán)境光是從四面八方來(lái)的強(qiáng)度一樣的光,因此環(huán)境光照分量基本上是不取決于任何方向向量的常量。最后,各個(gè)分量 是加在一起最終決定明暗的。
上面只討論光照強(qiáng)度,記得美術(shù)老師講過(guò),物體的顏色取決于光源本身的顏色,光照強(qiáng)度和物體材質(zhì)的顏色。然后D3D老師講過(guò),只要把那三個(gè)東西乘到一起就得到了最終的顏色。這里的乘是指各個(gè)顏色分量(紅綠藍(lán))分別相乘,實(shí)際上這是產(chǎn)生顏色過(guò)濾的方法。比如說(shuō),一個(gè)顏色值為(r,g,b,a)的顏色乘以50%灰(0.5,0.5,0.5,0.5)的結(jié)果就是(0.5r, 0.5g,0.5b,0.5a),亮度減了一半,相當(dāng)于帶了個(gè)50%灰的眼鏡看。
由于本篇著重介紹的是HLSL語(yǔ)言,所以對(duì)于光照模型,筆者不打算做過(guò)多說(shuō)明,將放到以后的文章中講述。
好了,來(lái)看代碼。每個(gè)Vertex Shader都要以頂點(diǎn)數(shù)據(jù)流中的數(shù)據(jù)作為其入口參數(shù)。但你頭腦里清楚哪個(gè)參數(shù)是位置,哪個(gè)是法線(xiàn),哪個(gè)是貼圖坐標(biāo),你完全可以把法線(xiàn)叫m_Position,叫abcd,叫……D3D如何知道把數(shù)據(jù)流中的法線(xiàn)信息往哪兒放呢?好,Semantic。D3D與我們約定了一套Semantic:其中POSITION是位置,NORMAL、TANGENT、BINOMAL分別是法線(xiàn)、切線(xiàn)、副法線(xiàn),TEXTURE0-7表示8套貼圖坐標(biāo)。
切線(xiàn)是指頂點(diǎn)的切線(xiàn)向量,副法線(xiàn)是與法線(xiàn)和切線(xiàn)正交的向量,都是預(yù)計(jì)算好并存在頂點(diǎn)數(shù)據(jù)里的。常用于凸凹貼圖技術(shù)。
Vertex Shader的輸出數(shù)據(jù),也就是返回值可以包含很多信息,但是必須有一個(gè)float4是表示位置的。因?yàn)镈3D認(rèn)為,你可以在頂點(diǎn)處理過(guò)程中不作任何光照處理,但是必須要把頂點(diǎn)轉(zhuǎn)換至正確的屏幕位置。除了位置以外,其他都將向下傳入Pixel Shader作為其入口參數(shù)。這些參數(shù)也用Semantic標(biāo)識(shí)。
搞清楚一點(diǎn)很重要,當(dāng)每個(gè)頂點(diǎn)的顏色被計(jì)算出來(lái)之后,像素的顏色將由它所在的三角形的三個(gè)頂點(diǎn)插值得到。還記得Direct3D Tutorials2嗎?

你僅僅指定了這個(gè)三角形頂點(diǎn)的顏色,而中間那些好看的漸變效果實(shí)際上是硬件自己對(duì)頂點(diǎn)顏色進(jìn)行插值得到每個(gè)像素的顏色。當(dāng)我們使用光照的時(shí)候,只不過(guò)是用光照計(jì)算出的頂點(diǎn)顏色代替了你手動(dòng)指定的顏色。貼圖坐標(biāo)也是這樣進(jìn)行插值的。
Pixel Shader所接收的Semantic是COLOR0-7(顏色),TEXCOORD0-7(貼圖坐標(biāo))。這里用個(gè)結(jié)構(gòu)體來(lái)放置所有的返回值,以及Vertex Shader的函數(shù)原形一并如下所示:
struct VS_OUTPUT
{
float4 Pos : POSITION;
float4 Intensity : COLOR0;
float4 Spec : COLOR1;
float3 Texcoord : TEXCOORD;
};
VS_OUTPUT VS( float3 InPos : POSITION, float3 InNormal : NORMAL, float2 InTexcoord : TEXCOORD )
Shader中的處理過(guò)程無(wú)非也就是計(jì)算出四個(gè)返回值分量。
首先位置(Pos)很好說(shuō),就是將頂點(diǎn)的原始位置(InPos)乘上變換矩陣。
float4x3 matWorld : WORLD;
float4x4 matViewProj : VIEWPROJ;
…
float3 P_World = mul(float4(InPos, 1), matWorld); // position to world space
Out.Pos = mul(float4(P_World, 1), matViewProj);
你看到這兩個(gè)全局變量被Semantic標(biāo)志為WORLD和VIEWPROJ,程序中將通過(guò)它們把當(dāng)前的世界變換矩陣,視矩陣和投影矩陣的乘積傳進(jìn)來(lái)。
這里有個(gè)優(yōu)化的原則。記住Vertex Shader是每處理一個(gè)頂點(diǎn)運(yùn)行一遍,如果某些信息對(duì)所有頂點(diǎn)(指同一批頂點(diǎn)流即同一物體中,使用同一個(gè)Shader的所有頂點(diǎn))來(lái)說(shuō)計(jì)算結(jié)果都一樣,那最好把它在程序中計(jì)算出來(lái)直接傳給Shader。例如三個(gè)變換矩陣的乘積。本例中,由于還需要把頂點(diǎn)位置變換置世界空間作他用,因此沒(méi)要求程序把矩陣都乘起來(lái)。
同理,每頂點(diǎn)能夠計(jì)算出來(lái)的東西就不要放在Pixel Shader中讓每像素都重復(fù)一遍。
將頂點(diǎn)乘以矩陣用到了HLSL的內(nèi)置指令mul(),實(shí)現(xiàn)矩陣乘法。不牽扯到投影的三維坐標(biāo)變換并不需要第四維w坐標(biāo),而且在這些變來(lái)變?nèi)サ倪^(guò)程中w會(huì)始終保持為1。因此程序傳進(jìn)來(lái)的頂點(diǎn)數(shù)據(jù)也是沒(méi)有w的。四維向量才被允許乘以4x4矩陣,因此我們還要把InPos和P_World補(bǔ)上w坐標(biāo),即float4(InPos, 1)。
然后我們把每個(gè)頂點(diǎn)照亮賦給Color。這幾個(gè)全局變量就是光照要用到的一些材質(zhì)顏色,燈光等信息。我想應(yīng)該能讓你看明白都是干什么的了。SpecPow是用于計(jì)算高光的,越大,反光面積越小。其余的我想也不用多解釋了吧。
float4 MtrlSpec = { 1.0f, 1.0f, 1.0f, 1.0f }; // material’s specular color, white
float SpecPow = 8;
float3 OmniPos : POSITION = { 0.577, 0.577, -0.577 }; // lights (world space)
float4 OmniColor = { 1.0f, 1.0 f, 1.0f, 1.0f }; // light’s color, white
float4 AmbLight = { 0.9f, 0.9f, 0.9f, 0.9f }; // ambient light color;
// camera (world space)
float3 CameraPos : CAMERAPOSITION = { 0.0f, 3.0f, 5.0f };
…
float3 ToLight = normalize( P_World – OmniPos );
float3 Normal = normalize( InNormal );
float4 Diff = dot( ToLight, Normal ) * OmniColor;
Out.Intensity = Diff + AmbLight;
// then, the specular component
float3 Reflection = reflection( -ToLight, Normal );
float3 ToEye = normalize( CameraPos – P_World );
float4 Spec = pow( dot( Reflection, ToEye ), SpecPow ) * MtrlSpec * OmniColor;
Out.Spec = Spec;
至于貼圖坐標(biāo),直接把它拷貝到返回值就行了。
Out.Texcoord = InTexcoord;
進(jìn)入Pixel Shader,所有入口參數(shù)由Vertex Shader中的計(jì)算結(jié)果插值得到。我們只不過(guò)簡(jiǎn)單的用tex2D()進(jìn)行了一下貼圖取樣,然后與光照強(qiáng)度混合得到最終結(jié)果。貼圖的顏色其實(shí)才是美術(shù)老師說(shuō)的“物體材質(zhì)的顏色”。
float4 PS ( VS_OUTPUT In ) : COLOR
{
float4 Tex = texture2D( TexSampler, In.Texcoord );
return Tex * In.Intensity + In.Spec;
}
總結(jié)
筆者已經(jīng)進(jìn)自己可能給大家講清楚了HLSL的一些基礎(chǔ)知識(shí)。從它的優(yōu)點(diǎn)作用,到基本概念,到渲染流程,最后用個(gè)例子給大家串了一遍。
這只是上篇,下一次,我們將看到:
l Shader是如何與你的程序進(jìn)行交互的
l Shader使用的極致——Effect File
l 介紹多種編輯、編譯、調(diào)試Shader的工具
l 等等
好,文章中如有疏漏,懇請(qǐng)大家指出,非常歡迎各位與我交流。我的郵箱是xuantz@frontfree.net,msn是phyligefox@hotmail.com
參考文獻(xiàn):
《Vertex Shader 結(jié)構(gòu)》 (http://www.gameres.com/Articles/Program/Visual/3D/VertexShader.htm)
《Introduction to the DirectX 9 High-Level Shader Language》Craig Peeper Microsoft Corporation, Jason L. Mitchell ATI Research (http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnhlsl/html/shaderx2_introductionto.asp)
《ATI™ Radeon X800 3D Architecture white paper》