跳轉到內容

GLSL 程式設計/Unity/切面

來自華夏公益教科書,開放世界開放書籍
菲利波·布魯內萊斯基在 1414-36 年繪製的佛羅倫薩大教堂圓頂剖面圖。

本教程涵蓋了丟棄片段、確定渲染的是正面還是背面,以及正面和背面的剔除。本教程假設您熟悉在“RGB 立方體”部分中討論的變化變數。

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

非常便宜的切面

[編輯 | 編輯原始碼]

以下著色器是一種非常便宜的方式,可以切斷網格的某些部分:所有在物件座標中具有正座標的片段都會被切斷(即在它被建模的座標系中;有關座標系的詳細資訊,請參閱“頂點變換”部分)。以下是程式碼

Shader "GLSL 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
         
         GLSLPROGRAM
               
         varying vec4 position_in_object_coordinates;
 
         #ifdef VERTEX         
         
         void main()
         {
            position_in_object_coordinates= gl_Vertex;
            gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
         }
         
         #endif

         #ifdef FRAGMENT
         
         void main()
         {
            if (position_in_object_coordinates.y > 0.0) 
            {
               discard; // drop the fragment if y coordinate > 0
            }
            if (gl_FrontFacing) // are we looking at a front face?
            {
               gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0); // yes: green
            }
            else
            {
               gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); // no: red
            }
         }
         
         #endif

         ENDGLSL
      }
   }
}

當您將此著色器應用於任何預設物件時,著色器將切斷它們的一半。這是一種非常便宜的製作半球或開放圓柱體的方法。

丟棄片段

[編輯 | 編輯原始碼]

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

還有一點需要注意:片段discard的條件只包含物件座標。結果是,您可以以任何方式旋轉和移動物件,切斷部分將始終與物件一起旋轉和移動。您可能想看看在世界空間中進行切割的效果:修改頂點和片段著色器,以便使用世界座標在片段discard的條件中。提示:有關如何將頂點變換到世界空間,請參閱“在世界空間中進行著色”部分

更好的切面

[編輯 | 編輯原始碼]

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

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

區分正面和背面

[編輯 | 編輯原始碼]

片段著色器中有一個特殊的布林變數gl_FrontFacing,它指定我們是否正在檢視三角形的正面。通常,正面朝向網格的外部,背面朝向內部。(就像表面法線向量通常指向外部。)但是,區分正面和背面的實際方式是三角形中頂點的順序:如果相機以逆時針順序看到三角形的頂點,則它看到的是正面。如果它以順時針順序看到頂點,則它看到的是背面。

我們的片段著色器檢查變數gl_FrontFacing,如果gl_FrontFacingtrue(即片段是正面三角形的一部分;即它面向外部),則將綠色分配給輸出片段顏色;如果gl_FrontFacingfalse(即片段是背面三角形的一部分;即它面向內部),則將紅色分配給輸出片段顏色。事實上,gl_FrontFacing不僅允許您使用不同的顏色渲染表面的兩個面,還可以使用完全不同的樣式渲染。

請注意,根據三角形中頂點的順序來定義正面和背面可能會在頂點被映象時(即用負因子縮放)導致問題。Unity 試圖解決這些問題;因此,只需在遊戲物件的 Transform 元件中指定負縮放通常不會導致此問題。但是,由於 Unity 無法控制我們在頂點著色器中做什麼,我們仍然可以透過將一個(或三個)座標乘以 -1 來反轉內部,例如,透過在頂點著色器中以這種方式分配gl_Position

            gl_Position = gl_ModelViewProjectionMatrix 
            * vec4(-gl_Vertex.x, gl_Vertex.y, gl_Vertex.z, 1.0);

這只是將座標乘以 -1。對於球體,您可能認為什麼都不會發生,但實際上它會將正面變成背面,反之亦然;因此,現在內部是綠色,外部是紅色。(順便說一下,這個問題也會影響表面法線向量。)因此,要小心鏡子!

正面或背面的剔除

[編輯 | 編輯原始碼]

最後,著色器(更準確地說,著色器通道)包含程式碼行Cull Off。此行必須出現在GLSLPROGRAM之前,因為它不是 GLSL 程式碼。實際上,它是Unity ShaderLab 的指令,用於關閉任何三角形剔除。這很有必要,因為預設情況下會剔除背面,就像指定了程式碼行Cull Back一樣。您還可以使用Cull Front指定正面剔除。背面剔除預設啟用的原因是,物件的內部通常是不可見的;因此,背面剔除可以透過避免柵格化這些三角形來節省效能,如下所述。當然,我們能夠看到內部是因為我們丟棄了一些片段;因此,我們必須停用背面剔除。

剔除是如何工作的呢?三角形和頂點按常規處理。但是,在將頂點從視口變換到螢幕座標之後(參見“頂點變換”部分),圖形處理器會確定三角形的頂點是在螢幕上以逆時針順序還是順時針順序出現。根據此測試,每個三角形都被視為正面三角形或背面三角形。如果它是正面三角形,並且對正面三角形啟用了剔除,它將被丟棄,即,它的處理停止並且不會被柵格化。類似地,如果它是背面三角形,並且對背面三角形啟用了剔除。否則,三角形將按常規處理。

總結

[edit | edit source]

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

  • 如何丟棄片段。
  • 如何在不同的顏色中渲染正面和背面三角形。
  • 如何停用背面剔除的預設設定。
  • 如何啟用正面剔除。

進一步閱讀

[edit | edit source]

如果你還想了解更多


< GLSL 程式設計/Unity

除非另有說明,本頁面上的所有示例原始碼均已歸入公有領域。
華夏公益教科書