跳轉到內容

Cg 程式設計/Unity/切面

來自華夏公益教科書,自由的教科書
菲利波·布魯內萊斯基於 1414-36 年繪製的佛羅倫薩大教堂圓頂切面圖。

本教程涵蓋了 **丟棄片段** 和 **正面和背面剔除**。本教程假設您熟悉頂點輸出引數,如 “RGB 立方體”部分 中所述。

本教程的主要主題是切除三角形或片段,即使它們是正在渲染的網格的一部分。主要有兩個原因:我們想透過三角形或片段(就像左邊圖中的屋頂,它只是部分切除),或者我們知道三角形無論如何都是不可見的;因此,我們可以透過不處理它來節省一些效能。GPU 以多種方式支援這些情況;我們將討論其中的兩種。

非常簡單的切面

[編輯 | 編輯原始碼]

以下著色器是一種非常簡單的方法,可以切除網格的某些部分:所有具有正 座標(即在它被建模的座標系中;有關座標系的詳細資訊,請參閱 “頂點變換”部分)的片段將被切除。

以下是程式碼

Shader "Cg shader using discard" {
   SubShader {
      Pass {
         Cull Off // turn off triangle culling, alternatives are:
         // Cull Back (or nothing): cull only back faces 
         // Cull Front : cull only front faces
 
         CGPROGRAM 
 
         #pragma vertex vert  
         #pragma fragment frag 
 
         struct vertexInput {
            float4 vertex : POSITION;
         };
         struct vertexOutput {
            float4 pos : SV_POSITION;
            float4 posInObjectCoords : TEXCOORD0;
         };
 
         vertexOutput vert(vertexInput input) 
         {
            vertexOutput output;
 
            output.pos = UnityObjectToClipPos(input.vertex);
            output.posInObjectCoords = input.vertex; 
 
            return output;
         }
 
         float4 frag(vertexOutput input) : COLOR 
         {
            if (input.posInObjectCoords.y > 0.0) 
            {
               discard; // drop the fragment if y coordinate > 0
            }
            return float4(0.0, 1.0, 0.0, 1.0); // green
         }
 
         ENDCG  
      }
   }
}

當您將此著色器應用於任何預設物件時,著色器將切除它們的一半。這是一種非常簡單的方法,可以生成半球或開口圓柱體。

丟棄片段

[編輯 | 編輯原始碼]

首先讓我們關注片段著色器中的 `discard` 指令。此指令基本上只是丟棄已處理的片段。(這在早期著色語言中被稱為片段“kill”;我可以理解片段更喜歡術語“discard”。)根據硬體,這可能是一種相當昂貴的技術,因為一旦有一個著色器包含 `discard` 指令,渲染效能可能會大幅下降(無論實際丟棄了多少片段,只要指令存在,就會導致一些重要最佳化的停用)。因此,您應儘可能避免使用此指令,特別是在遇到效能問題時。

還有一點需要注意:片段 `discard` 的條件只包含一個物件座標。結果是,您可以以任何方式旋轉和移動物件,而切除部分始終會與物件一起旋轉和移動。您可能想檢查一下世界空間中的切除是什麼樣的:更改頂點和片段著色器,以便世界座標 用於片段 `discard` 的條件中。提示:請參閱 “在世界空間中著色”部分,瞭解如何將頂點轉換為世界空間。

更好的切面

[編輯 | 編輯原始碼]

如果您不熟悉 Unity 中的指令碼編寫,可以嘗試以下想法來改進著色器:更改它,以便如果 座標大於某個閾值變數,則丟棄片段。然後引入一個著色器屬性,允許使用者控制此閾值。提示:請參閱 “在世界空間中著色”部分,瞭解有關著色器屬性的討論。

如果您熟悉 Unity 中的指令碼編寫,可以嘗試以下想法:為一個物件編寫一個指令碼,該指令碼引用另一個球體物件,並將該球體物件的逆模型矩陣(`GetComponent(Renderer).worldToLocalMatrix`)分配(使用 `GetComponent(Renderer).sharedMaterial.SetMatrix()`)給著色器的一個 `float4x4` 統一引數。在著色器中,計算片段在世界座標系中的位置,並將另一個球體物件的逆模型矩陣應用於片段位置。現在您有了片段在另一個球體物件區域性座標系中的位置;在這裡,很容易測試片段是在球體內部還是外部,因為在這個座標系中,預設的 Unity 球體以半徑為 0.5 圍繞原點居中。如果片段在另一個球體物件內部,則丟棄它。生成的指令碼和著色器可以藉助一個切割球體來切除任何物件表面的點,該球體可以在編輯器中像任何其他球體一樣互動地操作。

正面或背面的剔除

[編輯 | 編輯原始碼]

最後,著色器(更具體地說是著色器通道)包含一行 `Cull Off`。此行必須出現在 `CGPROGRAM` 之前,因為它不在 Cg 中。事實上,它是 Unity 的 ShaderLab 的一個命令,用於關閉任何三角形剔除。這是必要的,因為預設情況下,背面會像指定了 `Cull Back` 行一樣被剔除。您也可以透過 `Cull Front` 指定正面剔除。背面剔除預設情況下處於活動狀態的原因是,物件的內部通常是不可見的;因此,背面剔除可以透過避免光柵化這些三角形來節省相當多的效能,如下所述。當然,我們能夠看到內部是因為我們已經丟棄了一些片段;因此,我們應該停用背面剔除。

剔除是如何工作的?三角形和頂點像往常一樣被處理。但是,在將頂點轉換為螢幕座標的視口變換之後(請參閱 “頂點變換”部分),圖形處理器會確定三角形的頂點是在螢幕上以逆時針順序出現還是以順時針順序出現。根據此測試,每個三角形都被視為正面三角形或背面三角形。如果它是正面三角形,並且正面三角形的剔除處於活動狀態,它將被丟棄,即它的處理停止,它不會被光柵化。類似地,如果它是背面三角形,並且背面三角形的剔除處於活動狀態。否則,該三角形將像往常一樣被處理。

我們可以將剔除用於什麼?一個應用是為正面和背面使用不同的著色器,即為物件的外部和內部使用不同的著色器。以下著色器使用兩個通道。在第一個通道中,只剔除正面,剩餘的正面以紅色渲染(如果片段沒有被丟棄)。第二個通道只剔除背面,並以綠色渲染剩餘的背面。

Shader "Cg shader with two passes using discard" {
   SubShader {

      // first pass (is executed before the second pass)
      Pass {
         Cull Front // cull only front faces
 
         CGPROGRAM 
 
         #pragma vertex vert  
         #pragma fragment frag 
 
         struct vertexInput {
            float4 vertex : POSITION;
         };
         struct vertexOutput {
            float4 pos : SV_POSITION;
            float4 posInObjectCoords : TEXCOORD0;
         };
 
         vertexOutput vert(vertexInput input) 
         {
            vertexOutput output;
 
            output.pos = UnityObjectToClipPos(input.vertex);
            output.posInObjectCoords = input.vertex; 
 
            return output;
         }
 
         float4 frag(vertexOutput input) : COLOR 
         {
            if (input.posInObjectCoords.y > 0.0) 
            {
               discard; // drop the fragment if y coordinate > 0
            }
            return float4(1.0, 0.0, 0.0, 1.0); // red
         }
 
         ENDCG  
      }

      // second pass (is executed after the first pass)
      Pass {
         Cull Back // cull only back faces

         CGPROGRAM 
 
         #pragma vertex vert  
         #pragma fragment frag 
 
         struct vertexInput {
            float4 vertex : POSITION;
         };
         struct vertexOutput {
            float4 pos : SV_POSITION;
            float4 posInObjectCoords : TEXCOORD0;
         };
 
         vertexOutput vert(vertexInput input) 
         {
            vertexOutput output;
 
            output.pos = UnityObjectToClipPos(input.vertex);
            output.posInObjectCoords = input.vertex; 
 
            return output;
         }
 
         float4 frag(vertexOutput input) : COLOR 
         {
            if (input.posInObjectCoords.y > 0.0) 
            {
               discard; // drop the fragment if y coordinate > 0
            }
            return float4(0.0, 1.0, 0.0, 1.0); // green
         }
 
         ENDCG  
      }
   }
}

請記住,Unity 著色器中只執行一個子著色器(取決於哪個子著色器最適合 GPU 的功能),但該子著色器的所有通道都會被執行。

在許多 GPU 上,使用具有語義 `VFACE` 的片段輸入引數在 Cg 中區分正面和背面的一種更有效的方法;請參閱 Unity 的著色器語義文件。但是,並非所有 GPU 都支援此功能。

恭喜您完成了另一個教程。(如果您嘗試過其中一項作業:幹得好!我還沒有。)我們已經瞭解了

  • 如何丟棄片段。
  • 如何指定正面和背面的剔除。
  • 如何使用剔除和兩個通道來為網格的內部和外部使用不同的著色器。

進一步閱讀

[編輯 | 編輯原始碼]

如果您想了解更多

< Cg 程式設計/Unity

除非另有說明,本頁面所有示例原始碼均屬於公有領域。
華夏公益教科書