跳轉到內容

GLSL 程式設計/Unity/螢幕疊加

來自華夏公益教科書,開放的世界,開放的書籍
1934 年一部電影的片頭。

本教程涵蓋螢幕疊加,在 Unity 中也被稱為“GUI 紋理”。

這是關於非標準頂點變換的教程系列的第一個教程,這些教程偏離了在“頂點變換”部分中描述的標準頂點變換。本教程使用在“紋理球體”部分中描述的紋理以及在“透明度”部分中描述的混合。

Unity 的 GUI 紋理

[編輯 | 編輯原始碼]

螢幕疊加(即 Unity 術語中的 GUI 紋理)有很多應用,例如左側影像中的標題,還有其他 GUI(圖形使用者介面)元素,如按鈕或狀態資訊。這些元素的共同特徵是它們應該始終出現在場景的頂部,並且永遠不會被任何其他物件遮擋。這些元素也不應該受到任何相機移動的影響。因此,頂點變換應該直接從物體空間到螢幕空間。Unity 的 GUI 紋理允許我們透過在螢幕上指定位置渲染紋理影像來渲染這種元素。本教程嘗試藉助著色器來重現 GUI 紋理的功能。通常,你仍然會使用 GUI 紋理而不是這種著色器;但是,著色器允許更大的靈活性,因為你可以根據自己的需要對其進行任何方式的調整,而 GUI 紋理只提供有限的可能性。(例如,你可以更改著色器,使 GPU 在對被不透明 GUI 紋理遮擋的三角形進行光柵化時花費更少的時間。)

用 GLSL 著色器模擬 GUI 紋理

[編輯 | 編輯原始碼]

Unity 的 GUI 紋理的位置由渲染矩形的左下角的XY 座標(以畫素為單位)指定,其中 在螢幕中心,以及渲染矩形的寬度高度(以畫素為單位)。為了模擬 GUI 紋理,我們使用類似的著色器屬性

   Properties {
      _MainTex ("Texture", Rect) = "white" {}
      _Color ("Color", Color) = (1.0, 1.0, 1.0, 1.0)
      _X ("X", Float) = 0.0
      _Y ("Y", Float) = 0.0
      _Width ("Width", Float) = 128
      _Height ("Height", Float) = 128
   }

以及相應的 uniforms

         uniform sampler2D _MainTex;
         uniform vec4 _Color;
         uniform float _X;
         uniform float _Y;
         uniform float _Width;
         uniform float _Height;

對於實際物件,我們可以使用一個僅包含兩個三角形的網格來形成一個矩形。但是,我們也可以只使用預設的立方體物件,因為背面剔除(以及退化為邊的三角形的剔除)允許我們確保僅對立方體的兩個三角形進行光柵化。預設立方體物件的角在物體空間中的座標為,即矩形的左下角在,右上角在。為了將這些座標變換為螢幕空間中使用者指定的座標,我們首先將它們變換為畫素中的光柵位置,其中 在螢幕的左下角

        uniform vec4 _ScreenParams; // x = width; y = height; 
           // z = 1 + 1.0/width; w = 1 + 1.0/height
        ...
        #ifdef VERTEX

        void main()
        {    
            vec2 rasterPosition = vec2(
               _X + _ScreenParams.x / 2.0 
               + _Width * (gl_Vertex.x + 0.5),
               _Y + _ScreenParams.y / 2.0 
               + _Height * (gl_Vertex.y + 0.5));
            ...

這種變換將我們立方體正面左下角從物體空間中的 變換為光柵位置vec2(_X + _ScreenParams.x / 2.0, _Y + _ScreenParams.y / 2.0),其中_ScreenParams.x 是螢幕寬度(以畫素為單位),_ScreenParams.y 是高度(以畫素為單位)。右上角從 變換為vec2(_X + _ScreenParams.x / 2.0 + _Width, _Y + _ScreenParams.y / 2.0 + _Height)。光柵位置很方便,實際上它們經常在 OpenGL 中使用;但是,它們並不是我們這裡真正需要的。

頂點著色器在gl_Position中的輸出是在所謂的“裁剪空間”中,如“頂點變換”部分所述。GPU 透過將這些座標除以透視除法的第四個座標gl_Position.w來將它們變換為 之間的歸一化裝置座標。如果我們將此第四個座標設定為 ,則此除法不會改變任何內容;因此,我們可以將gl_Position的前三個座標視為歸一化裝置座標中的座標,其中 指定了近平面上的螢幕左下角,而 指定了近平面上的螢幕右上角。(我們應該使用近平面以確保矩形位於其他所有內容的前面。)為了在gl_Position中指定任何螢幕位置,我們必須在此座標系中指定它。幸運的是,將光柵位置轉換為歸一化裝置座標並不太難。

            gl_Position = vec4(
               2.0 * rasterPosition.x / _ScreenParams.x - 1.0,
               2.0 * rasterPosition.y / _ScreenParams.y - 1.0,
               -1.0, // near plane 
               1.0 // all coordinates are divided by this coordinate
               );

正如你很容易驗證的那樣,這將光柵位置vec2(0,0)轉換為歸一化裝置座標 ,並將光柵位置vec2(_ScreenParams.x, _ScreenParams.y)轉換為 ,這正是我們需要的。

這對於從物件空間到螢幕空間的頂點變換來說已經足夠了。但是,我們仍然需要計算適當的紋理座標,以便在正確的位置查詢紋理影像。紋理座標應該介於 之間,這實際上很容易從物件空間中介於 之間的頂點座標中計算出來。

            textureCoords = 
               vec4(gl_Vertex.x + 0.5, gl_Vertex.y + 0.5, 0.0, 0.0);
               // for a cube, gl_Vertex.x and gl_Vertex.y 
               // are -0.5 or 0.5

使用變化的變數textureCoords,我們就可以使用一個簡單的片段程式在紋理影像中查詢顏色,並用使用者指定的顏色_Color進行調製。

         #ifdef FRAGMENT

         void main()
         {
            gl_FragColor = 
               _Color * texture2D (_MainTex, vec2(textureCoords));
         }

         #endif

就是這樣。

完整的著色器程式碼

[edit | edit source]

如果我們將所有部分放在一起,我們將得到以下著色器,它使用Overlay佇列在所有其他內容之後渲染物件,並使用 alpha 混合(見“透明度”部分)以允許透明紋理。它還停用深度測試以確保紋理永遠不會被遮擋。

Shader "GLSL shader for screen overlays" {
   Properties {
      _MainTex ("Texture", Rect) = "white" {}
      _Color ("Color", Color) = (1.0, 1.0, 1.0, 1.0)
      _X ("X", Float) = 0.0
      _Y ("Y", Float) = 0.0
      _Width ("Width", Float) = 128
      _Height ("Height", Float) = 128
   }
   SubShader {
      Tags { "Queue" = "Overlay" } // render after everything else

      Pass {
         Blend SrcAlpha OneMinusSrcAlpha // use alpha blending
         ZTest Always // deactivate depth test

         GLSLPROGRAM

         // User-specified uniforms
         uniform sampler2D _MainTex;
         uniform vec4 _Color;
         uniform float _X;
         uniform float _Y;
         uniform float _Width;
         uniform float _Height;

         // The following built-in uniforms 
         // are also defined in "UnityCG.glslinc", 
         // i.e. one could #include "UnityCG.glslinc" 
         uniform vec4 _ScreenParams; // x = width; y = height; 
            // z = 1 + 1.0/width; w = 1 + 1.0/height

         // Varyings
         varying vec4 textureCoords;

         #ifdef VERTEX


         void main()
         {
            vec2 rasterPosition = vec2(
               _X + _ScreenParams.x / 2.0 
               + _Width * (gl_Vertex.x + 0.5),
               _Y + _ScreenParams.y / 2.0 
               + _Height * (gl_Vertex.y + 0.5));
            gl_Position = vec4(
               2.0 * rasterPosition.x / _ScreenParams.x - 1.0,
               2.0 * rasterPosition.y / _ScreenParams.y - 1.0,
               -1.0, // near plane is -1.0
               1.0);

            textureCoords =
               vec4(gl_Vertex.x + 0.5, gl_Vertex.y + 0.5, 0.0, 0.0);
               // for a cube, gl_Vertex.x and gl_Vertex.y 
               // are -0.5 or 0.5
         }

         #endif

         #ifdef FRAGMENT

         void main()
         {
            gl_FragColor = 
               _Color * texture2D (_MainTex, vec2(textureCoords));
         }

         #endif

         ENDGLSL
      }
   }
}

當你將此著色器用於立方體物件時,紋理影像可能會根據相機的方向而出現和消失。這是由於 Unity 的裁剪造成的,Unity 不會渲染完全位於場景中相機可見區域(視錐體)之外的物件。此裁剪基於遊戲物件的傳統變換,這對我們的著色器來說沒有意義。為了停用此裁剪,我們可以簡單地使立方體物件成為相機的子物件(透過將它拖到層次結構檢視中的相機上)。如果立方體物件隨後被放置在相機的前面,它將始終保持在相同的相對位置,因此它不會被 Unity 裁剪。(至少在遊戲檢視中不會。)

不透明螢幕疊加的更改

[edit | edit source]

對著色器可以進行許多更改,例如不同的混合模式或不同的深度,以使 3D 場景中的幾個物件位於疊加層前面。在這裡,我們只關注不透明疊加層。

不透明螢幕疊加層會遮擋場景中的三角形。如果 GPU 瞭解此遮擋,它就不需要對這些被遮擋的三角形進行光柵化(例如,透過使用延遲渲染或早期深度測試)。為了確保 GPU 有機會應用這些最佳化,我們必須首先渲染螢幕疊加層,方法是設定

Tags { "Queue" = "Background" }

此外,我們應該避免混合,方法是移除Blend指令。透過這些更改,不透明螢幕疊加層可能會提高效能,而不是造成光柵化效能損失。

總結

[edit | edit source]

恭喜您完成了另一個教程。我們已經看到了

  • 如何使用 GLSL 著色器模擬 GUI 紋理。
  • 如何修改著色器以實現不透明螢幕疊加。

進一步閱讀

[編輯 | 編輯原始碼]

如果您還想了解更多


< GLSL 程式設計/Unity

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