本文章是由機器翻譯。

DirectX 要素

Pixel Shaders 與反光效果

Charles Petzold

下載代碼示例

Charles Petzold如果你能看到光子......好吧,你可以看到光子,或至少一些人。光子是彌補電磁波輻射的粒子,並且眼睛是敏感的光子波長在可見光範圍內。

但你不能看到光子作為他們飛所有的地方。那一定會很有趣。有時光子穿過物件 ; 有時他們吸收 ; 有時他們被反映 ; 但通常這是一個組合的所有的這些影響。有些最終從物體反彈回來的光子到達你的眼睛,讓每一個物件,其特定的顏色和紋理。

為極高品質的 3D 圖形,一種叫做射線追蹤技術可以實際繪製出類比這些無數的光子,模仿的反射和陰影效果的路徑。但很多簡單的技術用於更為傳統的需求。這往往是案件時使用 Direct3D — — 或者,對我來說,寫作在 Direct2D 利用 3D 的自訂效果。

重用的效果

如您所見,在本專欄的前面幾篇文章中,一個 Direct2D 效果基本上是在 GPU 運行的代碼的包裝。這樣的代碼被稱為一個著色器,和最重要的是,頂點著色器和圖元著色器。在每個這些著色器代碼稱為顯示視頻刷新率。頂點著色器被呼籲每個人在每個三角形三個頂點,彌補顯示受影響,雖然圖元著色器被呼籲在這些三角形內的每個圖元的繪圖物件。

很明顯,圖元著色器叫做頻率遠高於頂點著色器,所以它有道理繼續盡可能多盡可能處理頂點著色器,而不是圖元著色器。不過,這並非總是可能的和當使用這些著色器來類比光的反射,它通常是平衡和這些治理複雜的兩種著色器和靈活性的明暗變化之間的相互作用。

在 8 月期的這本雜誌,我提出了稱為 RotatingTriangleEffect 的構造頂點緩衝區組成的點和顏色,並允許將標準模型和相機轉換應用到頂點的 Direct2D 效應。我用這種效應來轉動三個三角形。那不是大量的資料。只共九個頂點,涉及三個三角形,我就提到過,相同的效果,可以用於很多較大的頂點緩衝區。

讓我們試試看:可下載程式 (msdn.microsoft.com/magazine/msdnmag1014) 為該欄目叫做 ShadedCircularText,它使用 RotatingTriangleEffect 沒有一個簡單的改變。

ShadedCircularText 程式返回到這一問題開始了探索今年早些時候的顯示在三個維度的棋盤格 2D 文本。ShadedCircularTextRenderer 類的建構函式在字體檔中載入,它,從創建字體,然後調用 GetGlyphRunOutline 來獲取字元輪廓路徑幾何圖形。該路徑幾何圖形是然後鑲嵌使用一個稱為積累實際三角形的 InterrogableTessellationSink 我創建的類。

後註冊 RotatingTriangleEffect ShadedCircularText­渲染器創建一個 ID2D1Effect 物件,基於這種效應。它然後將三角形的棋盤格的文本轉換成一個球體,基本上文字環繞赤道和兩極彎曲表面的頂點。每個頂點的顏色基於一種色調來自原始文本幾何的 X 座標。這將創建一個彩虹般的效果,和圖 1 顯示的結果。

三維文字彩虹從 ShadedCircularText
圖 1 三維文字彩虹從 ShadedCircularText

正如你所看到的一個小小的功能表裝飾頂部。該計畫實際上包括了實施更為傳統的光照模型的三個額外的 Direct2D 效果。他們都使用相同的點,採用同一種轉換和相同的動畫,所以你可以讓他們看到差異之間進行切換。差異涉及只有三角形的顏色網底。

右下角將顯示性能在幀每秒,但你會發現什麼在這個程式中導致放棄遠低於 60 除了如果別的事情正在進行。

高氏著色

光子飛在我們周圍,就像他們時常跳掉空氣中的氮氣和氧氣分子。即使在沒有直射陰暗的一天,還有大量的光線。環境光線往往非常均勻的方式照亮物件。

也許你有一個物件,是藍綠色,與 RGB 值 (0、 0.5、 1.0)。如果光線是四分之一的最大亮度白色,可以將一個 RGB 值分配給 (0.25、 0.25、 0.25) 光。此物件的感知的顏色是紅色、 綠色和藍色成分的這些數位,或 (0、 0.125、 0.25) 產品。它仍然是藍綠色,但要暗許多。

但是簡單的 3D 場景不住周圍的光一個人。在現實生活中,物件通常有很多顏色的變化在其表面上,因此,即使如果他們均勻照明,物件仍然有可見的紋理。但是在簡單的 3D 場景中,只有被光線照亮一個藍綠色物件將只是看起來像均勻顏色未分化板。

為此,簡單的 3D 場景極大地得益于一些定向光。這是最簡單的方法假設這光來自遙遠的距離 (如太陽),所以光的方向是只適用于整個場景的單個向量。如果只有一個光源,通常它被假定來自觀眾的左肩,也許該載體是右手坐標系 (1、-1,-1)。這個方向性的光源也具有一種顏色,也許 (0.75、 0.75、 0.75),所以有時與周圍的光線 (0.25、 0.25、 0.25),實現最大的照明是。

定向表面反射的光的數量取決於光與表面的角度。(這是一個概念,探索我 2014 年 5 月 DirectX 因素列。)定向光是垂直于表面,並反射的光減少到零,當光線是從哪兒冒出來的表面或到來相切時發生最大的反射表面上的。

Lambert 余弦法 — — 德國數學家和物理學家約翰 · 海因裡希 Lambert (1728年 — — 1777 年) 的名字命名 — — 說,從表面的反射光的分數是光的方向和垂直于表面,被稱作表面正常的向量的方向之間的角度的負面余弦值。如果這兩個向量歸一化的處理 — — 那就是,如果他們有震級為 1 — — 這兩個向量之間的夾角的余弦的值是相同的向量的點積。

例如,如果光線呈 45 度角的特定曲面,余弦值大約是 0.7,所以這個數位乘以方向­傳統光 0.75 0.75 0.75) 的顏色和顏色的物件 (0、 0.5、 1.0) 從定向光 (0、 0.26、 0.53) 派生物件的顏色。從光線向顏色添加的。

然而,請記住那彎彎的 3D 場景中的物件不被彎曲的其實。在場景中的一切都由平坦三角形連接。如果每個三角形的照明基於表面的正常垂直于三角形本身,每個三角形將有不同的色澤均勻。這是精細的柏拉圖式的固體,如那些我 2014 年 5 月的專欄中顯示,但不是那麼好了曲面。曲面,你想要的顏色的三角形與彼此融合。

這意味著有必要為每個三角形有漸變的色,而不是統一的顏色。從方向性光源的顏色不能基於單一表面正常的三角形。相反,每個頂點的三角形應該有不同的顏色基於曲面法向該頂點處。這些頂點顏色然後可以在三角形的所有圖元內插。相鄰三角形然後混在一起彼此類似于一個曲面。

這種類型的網底被發明的法國電腦科學家 Henri 高氏 (b。 1944 年) 在一篇論文發表在 1971 年,因此被稱為高氏著色。

高氏著色是在 ShadedCircularText 程式中實現第二個選項。這個效應本身被稱為 GouraudShadingEffect,並且它需要頂點緩衝區和某種程度上更多的資料:

struct PositionNormalColorVertex
{
  DirectX::XMFLOAT3 position;
  DirectX::XMFLOAT3 normal;
  DirectX::XMFLOAT3 color;
  DirectX::XMFLOAT3 backColor;
};

有趣的是,因為是有效的文本被纏繞在一個球體中心在點 (0,0,0),每個頂點處的曲面法是相同的位置,但歸一化有大小是 1。 影響允許特有的色彩,對於每個頂點,但在這個程式中每個頂點獲取相同的顏色,是 (0、 0.5、 1) 和 (0.5,0.5,0.5) 的相同背景色的顏色用於如果後面的一個面朝觀眾。

GouraudShadingEffect 還需要更多的效果屬性。它必須能夠設置環境光色、 定向光顏色和方向的光的向量方向。GouraudShadingEffect 為頂點著色器將所有這些值轉移到較大的恒定緩衝區。頂點著色器本身所示圖 2

圖 2 的頂點著色器高氏著色

// Per-vertex data input to the vertex shader
struct VertexShaderInput
{
  float3 position : MESH_POSITION;
  float3 normal : NORMAL;
  float3 color : COLOR0;
  float3 backColor : COLOR1;
};
// Per-vertex data output from the vertex shader
struct VertexShaderOutput
{
  float4 clipSpaceOutput : SV_POSITION;
  float4 sceneSpaceOutput : SCENE_POSITION;
  float3 color : COLOR0;
};
// Constant buffer provided by effect.
cbuffer VertexShaderConstantBuffer : register(b1)
{
  float4x4 modelMatrix;
  float4x4 viewMatrix;
  float4x4 projectionMatrix;
  float4 ambientLight;
  float4 directionalLight;
  float4 lightDirection;
};
// Called for each vertex.
VertexShaderOutput main(VertexShaderInput input)
{
  // Output structure
  VertexShaderOutput output;
  // Get the input vertex, and include a W coordinate
  float4 pos = float4(input.position.xyz, 1.0f);
  // Pass through the resultant scene space output value
  //  (not necessary -- can be removed from both shaders)
  output.sceneSpaceOutput = pos;
  // Apply transforms to that vertex
  pos = mul(pos, modelMatrix);
  pos = mul(pos, viewMatrix);
  pos = mul(pos, projectionMatrix);
  // The result is clip space output
  output.clipSpaceOutput = pos;
  // Apply model transform to normal
  float4 normal = float4(input.normal, 0);
  normal = mul(normal, modelMatrix);
  // Find angle between light and normal
  float3 lightDir = normalize(lightDirection.xyz);
  float cosine = -dot(normal.xyz, lightDir);
  cosine = max(cosine, 0);
  // Apply view transform to normal
  normal = mul(normal, viewMatrix);
  // Check if normal pointing at viewer
  if (normal.z > 0)
  {
    output.color = (ambientLight.xyz + cosine *
                    directionalLight.xyz) * input.color;
  }
  else
  {
    output.color = input.backColor;
  }
  return output;
}

圖元著色器是 RotatingTriangleEffect,相同,所示圖 3。幕後頂點著色器和圖元著色器,之間的插值在整個三角形的頂點顏色顯示所以圖元著色器只是將傳遞到的顏色。

圖 3 的圖元著色器高氏著色

// Per-pixel data input to the pixel shader
struct PixelShaderInput
{
  float4 clipSpaceOutput : SV_POSITION;
  float4 sceneSpaceOutput : SCENE_POSITION;
  float3 color : COLOR0;
};
// Called for each pixel
float4 main(PixelShaderInput input) : SV_TARGET
{
  // Simply return color with opacity of 1
  return float4(input.color, 1);
}

結果顯示在圖 4,這個時候就 Windows Phone 8.1,而不是 Windows 8.1。ShadedCircularText 解決方案用新通用應用程式範本在Visual Studio中創建,並可以編譯兩個平臺。除了應用程式和 DirectXPage 類的兩個平臺之間共用所有代碼。這兩個程式的佈局的差異表明為什麼有不同的頁面定義的往往是個好主意,即使該程式的功能基本上是一樣的。

顯示的高氏著色模型
圖 4 顯示的高氏著色模型

正如你所看到的這個數位是打火機在其左上角區域中,清楚地顯示定向光源的作用和協助的表面的圓形外觀的幻覺中。

海防改進

高氏著色是一個歷史悠久的技術,但它有一個基本的缺陷:在高氏著色的定向光反射在三角形的中心量光被反射在頂點插值後的值。在頂點反射的光基於光的方向和這些頂點處的表面法線之間的夾角的余弦值。

但真的應該在這一點在三角形的中心的反射光基於表面正常處理。換句話說,顏色不應該插在三角形 ; 相反,表面法線應插值的三角形表面和反射的光計算基於正常的每個圖元。

進入越南出生的電腦科學家培祥豐 (1942年-1975),在 32 歲時死于白血病。 在他 1973年的博士論文,海防描述稍有不同的著色演算法。而不是插在三角形的頂點顏色,頂點的法向量的三角形上,樣條插值,然後從那些計算反射的光。

在實際意義上,Phong 光照效果需要反射光線,並將從頂點著色器移動到圖元著色器,以及致力於這項工作的不斷緩衝區的部分的計算。這增加的每圖元極大,加工量,但幸運的是,它正在做基於 GPU 在哪裡,你希望它似乎不會產生多大的差異。

頂點著色器 Phong 著色模型所示圖 5。一些輸入的資料 — — 例如顏色和背景色 — — 簡單地轉嫁圖元著色器。但它仍然很有用,適用所有變換。世界變換和兩個攝像機轉換必須應用到的位置,同時也計算了兩個法線 — — 一隻模型變換為反射的光,和另一個與視圖變換來確定是否一個表面面臨朝向或遠離查看者。

圖 5 的頂點著色器 Phong 著色模型

// Per-vertex data input to the vertex shader
struct VertexShaderInput
{
  float3 position : MESH_POSITION;
  float3 normal : NORMAL;
  float3 color : COLOR0;
  float3 backColor : COLOR1;
};
// Per-vertex data output from the vertex shader
struct VertexShaderOutput
{
  float4 clipSpaceOutput : SV_POSITION;
  float4 sceneSpaceOutput : SCENE_POSITION;
  float3 normalModel : NORMAL0;
  float3 normalView : NORMAL1;
  float3 color : COLOR0;
  float3 backColor : COLOR1;
};
// Constant buffer provided by effect
cbuffer VertexShaderConstantBuffer : register(b1)
{
  float4x4 modelMatrix;
  float4x4 viewMatrix;
  float4x4 projectionMatrix;
};
// Called for each vertex
VertexShaderOutput main(VertexShaderInput input)
{
  // Output structure
  VertexShaderOutput output;
  // Get the input vertex, and include a W coordinate
  float4 pos = float4(input.position.xyz, 1.0f);
  // Pass through the resultant scene space output value
  // (not necessary — can be removed from both shaders)
  output.sceneSpaceOutput = pos;
  // Apply transforms to that vertex
  pos = mul(pos, modelMatrix);
  pos = mul(pos, viewMatrix);
  pos = mul(pos, projectionMatrix);
  // The result is clip space output
  output.clipSpaceOutput = pos;
  // Apply model transform to normal
  float4 normal = float4(input.normal, 0);
  normal = mul(normal, modelMatrix);
  output.normalModel = normal.xyz;
  // Apply view transform to normal
  normal = mul(normal, viewMatrix);
  output.normalView = normal.xyz;
  // Transfer colors
  output.color = input.color;
  output.backColor = input.backColor;
  return output;
}

頂點著色器的輸出成為投入的圖元著色器,這些法線被插值的三角形表面上。圖元著色器可以然後完成這項工作通過計算反射的光,如中所示圖 6

圖 6 的圖元著色器 Phong 著色模型

// Per-pixel data input to the pixel shader
struct PixelShaderInput
{
  float4 clipSpaceOutput : SV_POSITION;
  float4 sceneSpaceOutput : SCENE_POSITION;
  float3 normalModel : NORMAL0;
  float3 normalView : NORMAL1;
  float3 color : COLOR0;
  float3 backColor : COLOR1;
};
// Constant buffer provided by effect
cbuffer PixelShaderConstantBuffer : register(b0)
{
  float4 ambientLight;
  float4 directionalLight;
  float4 lightDirection;
};
// Called for each pixel
float4 main(PixelShaderInput input) : SV_TARGET
{
  // Find angle between light and normal
  float3 lightDir = normalize(lightDirection.xyz);
  float cosine = -dot(input.normalModel, lightDir);
  cosine = max(cosine, 0);
  float3 color;
  // Check if normal pointing at viewer
  if (input.normalView.z > 0)
  {
    color = (ambientLight.xyz + cosine *
      directionalLight.xyz) * input.color;
  }
  else
  {
    color = input.backColor;
  }
  // Return color with opacity of 1
  return float4(color, 1);
}

然而,我不會告訴你結果的截圖。它是非常視覺上相同的高氏著色。高氏著色真的是一個好的近似。

鏡面高光

真正 Phong 光照效果的重要性是它使成為可能依賴于更準確的曲面法線的其他功能。

到目前為止在這篇文章中,您看到了是適當的漫反射表面的網底。這些都是的表面粗糙而枯燥,那往往將它們的表面反射的光線散射到。

是稍有光澤的表面反射光的稍有不同。如果傾斜的表面只是如此,定向光可以反彈,直接去找觀眾的眼睛。這通常被視為明亮白色的光,和它被稱為鏡面高光。你可以看到在較誇張的效果圖 7。如果這一數位有更清晰的曲線,白色的光會更當地語系化。

鏡面高光顯示
圖 7 鏡面高光顯示

得到這種效應似乎在第一,它可能是複雜的計算,但就只有幾行中的圖元著色器的代碼。這特別的技術,由美國國家航空航天局圖形 mavenJimBlinn (b。 1949 年)。

我們首先需要指示 3D 場景的觀眾看的方向向量。這是很容易的因為視圖鏡頭轉換已調整所有的座標,因此觀眾正在沿著 Z 軸:

float3 viewVector = float3(0, 0, -1);

下一步,計算一個向量,介於該視圖向量和光線的方向:

float3 halfway = -normalize(viewVector + lightDirection.xyz);

請注意負號。這使得向量指向相反的方向 — — 光源和觀眾之間的中途。

如果一個特定的三角形包含曲面的法線,完全符合這一半的向量,這意味著光線反射表面直接進入了觀眾的眼球。這會導致最大鏡面高光。

小雅突出顯示結果非零半路向量和曲面法線之間的夾角。這是另一個應用程式之間兩個向量,如果兩個向量歸一化的處理是點積相同的余弦值為:

float dotProduct = max(0.0f, dot(input.normalView, halfway));

此值從 1 為最大的鏡面高光,當兩個向量之間的角度為 0,0 表示沒有鏡面高光,到 dotProduct 範圍時發生兩個向量是垂直的。

然而,鏡面高光不應該可見的所有角度介於 0 和 90 度之間。它應當地語系化。它應該只存在於那些兩個向量之間的極小角度。你需要一個函數,它不會影響到 1,點產品,但會導致值小於 1,成為低得多。這是戰俘函數:

float specularLuminance = pow(dotProduct, 20);

這個戰俘函數點積 20 掌權。如果的點積是 1,則戰俘函數返回 1。 如果的點積是 0.7 (導致從兩個向量之間的夾角為 45 度),則該戰俘函數返回 0.0008,這實際上是 0 到照明去。使用較高的指數值,使效果更當地語系化。

現在都是必要的就是這一因素乘以定向光色和將它添加到的顏色已經計算從環境光線和定向光:

color += specularLuminance * directionalLight.xyz;

作為動畫變成圖創建白光撲通一的聲。

告別

,DirectX 因素列接近尾聲。這陷入 DirectX 一直是最具挑戰性的工作我的職業生涯,但因此也是最有收穫的和我希望能有機會,總有一天要回到這個強大的技術。


Charles Petzold 是長期貢獻 MSDN 雜誌和"程式設計視窗,第 6 版"的作者 (微軟出版社,2013年),一本關於編寫 Windows 8 應用程式書。 他的網站是 charlespetzold.com

感謝以下的微軟技術專家對本文的審閱:Doug埃裡克森

這一問題將標記CharlesPetzold 最後為固定在 MSDN 雜誌專欄作家。Charles離開來加入我們的團隊在 Xamarin,利用 Microsoft.NET 框架的跨平臺工具的領先供應商。Charles幾十年來一直與 MSDN 雜誌和撰寫了許多專欄,包括基金會、 UI 前沿技術和 DirectX 因數。我們祝他好對他新的事業。