跳轉到內容

GLSL 程式設計/Unity/漫反射

來自 Wikibooks,開放世界開放書籍
月球表面反射的光線(在一個很好的近似中)只是漫反射。

本教程涵蓋每個頂點的漫反射

它是 Unity 中基本光照教程系列的第一篇。在本教程中,我們從單個方向光源的漫反射開始,然後包括點光源和多個光源(使用多遍)。後續教程涵蓋了其擴充套件,特別是鏡面反射、每個畫素的照明和雙面照明。

漫反射可以使用表面法線向量 N 和光線向量 L 計算,即指向光源的向量。

漫反射

[編輯 | 編輯原始碼]

月球幾乎完全表現出漫反射(也稱為朗伯反射),即光線向各個方向反射,沒有鏡面高光。其他此類材料的例子包括粉筆和啞光紙;事實上,任何看起來暗淡和啞光的表面。

在完美漫反射的情況下,觀察到的反射光強取決於表面法線向量和入射光線之間的角度的餘弦。如左圖所示,通常考慮從表面一點開始的歸一化向量,在那裡應該計算照明:歸一化表面法線向量N與表面正交,歸一化光線方向L指向光源。

對於觀察到的漫反射光,我們需要歸一化表面法線向量N和指向光源的歸一化方向L之間的角度的餘弦,它是點積N·L,因為任何兩個向量ab的點積a·b

.

對於歸一化向量,長度 |a| 和 |b| 都是 1。

如果點積N·L為負,則光源位於表面的“錯誤”一側,我們應該將反射設定為 0。這可以透過使用 max(0, N·L) 來實現,這確保了點積的值對於負點積被鉗制為 0。此外,反射光取決於入射光強和一個材料常數用於漫反射:對於黑色表面,材料常數為 0,對於白色表面,它為 1。漫反射強度的方程為

對於彩色光,此方程適用於每個顏色分量(例如紅色、綠色和藍色)。因此,如果變數,和表示顏色向量,乘法是逐分量執行的(它們是 GLSL 中的向量),此方程也適用於彩色光。這是我們在著色器程式碼中實際使用的。

一個方向光源的著色器程式碼

[編輯 | 編輯原始碼]

如果只有一個方向光源,實現 公式的著色器程式碼相對較小。為了實現該公式,我們遵循在“輪廓增強”部分中討論的有關實現公式的問題。

  • 應該在頂點著色器還是片段著色器中實現該公式?我們這裡嘗試頂點著色器。在“平滑鏡面高光”部分中,我們將探討在片段著色器中的實現。
  • 應該在哪個座標系中實現該公式?在Unity中,我們預設嘗試世界空間。(事實證明這是一個不錯的選擇,因為Unity在世界空間中提供了光線方向。)
  • 我們從哪裡獲取引數?這個問題的答案有點長。

我們使用著色器屬性(參見“在世界空間中著色”)讓使用者指定漫射材質顏色。我們可以從Unity特定的統一變數_WorldSpaceLightPos0獲取世界空間中的光源方向,以及從Unity特定的統一變數_LightColor0獲取光線顏色。如“在世界空間中著色”部分所述,我們必須用Tags {"LightMode" = "ForwardBase"}標記著色器通道,以確保這些統一變數具有正確的值。(下面我們將討論這個標籤的實際含義。)我們從屬性gl_Normal獲取物體座標中的表面法線向量。由於我們在世界空間中實現該公式,因此我們必須將表面法線向量從物體空間轉換為世界空間,如“輪廓增強”部分所述。

然後著色器程式碼如下所示。

Shader "GLSL per-vertex diffuse lighting" {
   Properties {
      _Color ("Diffuse Material Color", Color) = (1,1,1,1) 
   }
   SubShader {
      Pass {	
         Tags { "LightMode" = "ForwardBase" } 
            // make sure that all uniforms are correctly set

         GLSLPROGRAM

         uniform vec4 _Color; // shader property specified by users

         // The following built-in uniforms (except _LightColor0) 
         // are also defined in "UnityCG.glslinc", 
         // i.e. one could #include "UnityCG.glslinc" 
         uniform mat4 _Object2World; // model matrix
         uniform mat4 _World2Object; // inverse model matrix
         uniform vec4 _WorldSpaceLightPos0; 
            // direction to or position of light source
         uniform vec4 _LightColor0; 
            // color of light source (from "Lighting.cginc")
         
         varying vec4 color; 
            // the diffuse lighting computed in the vertex shader
         
         #ifdef VERTEX
         
         void main()
         {				
            mat4 modelMatrix = _Object2World;
            mat4 modelMatrixInverse = _World2Object; // unity_Scale.w
               // is unnecessary because we normalize vectors
            
            vec3 normalDirection = normalize(
               vec3(vec4(gl_Normal, 0.0) * modelMatrixInverse));
            vec3 lightDirection = normalize(
               vec3(_WorldSpaceLightPos0));

            vec3 diffuseReflection = vec3(_LightColor0) * vec3(_Color)
               * max(0.0, dot(normalDirection, lightDirection));
            
            color = vec4(diffuseReflection, 1.0);
            gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
         }
         
         #endif

         #ifdef FRAGMENT
         
         void main()
         {
            gl_FragColor = color;
         }
         
         #endif

         ENDGLSL
      }
   } 
   // The definition of a fallback shader should be commented out 
   // during development:
   // Fallback "Diffuse"
}

使用此著色器時,請確保場景中只有一個光源,該光源必須是方向光源。如果沒有光源,您可以透過從主選單中選擇遊戲物件 > 建立其他 > 方向光來建立方向光源。此外,請確保“前向渲染路徑”處於活動狀態,方法是選擇編輯 > 專案設定 > 播放器,然後在檢查器檢視中將每平臺設定 > 其他設定 > 渲染 > 渲染路徑設定為前向。(有關“前向渲染路徑”的更多詳細資訊,請參見下文。)

備用著色器

[edit | edit source]

著色器程式碼中的Fallback "Diffuse"行定義了一個內建的備用著色器,以防Unity找不到合適的子著色器。對於我們的示例,如果Unity不使用“前向渲染路徑”(見下文)或無法編譯著色器程式碼,則它將使用備用著色器。透過為我們的著色器屬性選擇特定的名稱“_Color”,我們確保了這個內建的備用著色器也能訪問它。內建著色器的原始碼可以在Unity的網站上找到。檢查此原始碼似乎是確定合適的備用著色器及其使用的屬性名稱的唯一方法。

如前所述,如果著色器程式碼中存在編譯錯誤,Unity也將使用備用著色器。在這種情況下,錯誤只會報告在著色器的檢查器檢視中;因此,可能難以理解正在使用備用著色器。因此,在開發著色器時,通常最好註釋掉備用指令,但在最終版本中將其包含在內以提高相容性。

用於多個方向(畫素)光源的著色器程式碼

[edit | edit source]

到目前為止,我們只考慮了一個光源。為了處理多個光源,Unity會根據渲染和質量設定選擇不同的技術。在本文的教程中,我們只介紹“前向渲染路徑”。為了選擇它,請選擇編輯 > 專案設定 > 播放器,然後在檢查器檢視中將每平臺設定 > 其他設定 > 渲染 > 渲染路徑設定為前向。(此外,所有攝像機都應配置為使用播放器設定,預設情況下它們就是這樣。)

在本教程中,我們只考慮Unity所謂的畫素光源。對於第一個畫素光源(它始終是方向光源),Unity呼叫用Tags { "LightMode" = "ForwardBase" }標記的著色器通道(如我們上面的程式碼所示)。對於每個額外的畫素光源,Unity呼叫用Tags { "LightMode" = "ForwardAdd" }標記的著色器通道。為了確保所有光源都被渲染為畫素光源,您必須確保質量設定允許足夠的畫素光源:選擇編輯 > 專案設定 > 質量,然後在您使用的任何質量設定中增加標為畫素光源數量的數字。如果場景中的光源數量超過了畫素光源數量允許的數量,Unity只會將最重要的光源渲染為畫素光源。或者,您可以將所有光源的渲染模式設定為重要,以便將它們渲染為畫素光源。(有關不太重要的頂點光源的討論,請參見“多個光源”部分。)

到目前為止,我們的著色器程式碼對於ForwardBase通道來說是可以的。對於ForwardAdd通道,我們需要將反射光新增到已儲存在幀緩衝區中的光線。為此,我們只需將混合配置為將新的片段顏色(gl_FragColor)新增到幀緩衝區中的顏色即可。如“透明度”部分所述,這是透過加性混合方程實現的,該方程由以下行指定

混合 一 一

混合會自動將所有結果鉗制在0到1之間;因此,我們不必擔心大於1的顏色或alpha值。

總而言之,我們用於多個方向光源的新著色器如下所示。

Shader "GLSL per-vertex diffuse lighting" {
   Properties {
      _Color ("Diffuse Material Color", Color) = (1,1,1,1) 
   }
   SubShader {
      Pass {	
         Tags { "LightMode" = "ForwardBase" } 
            // pass for first light source

         GLSLPROGRAM

         uniform vec4 _Color; // shader property specified by users

         // The following built-in uniforms (except _LightColor0) 
         // are also defined in "UnityCG.glslinc", 
         // i.e. one could #include "UnityCG.glslinc" 
         uniform mat4 _Object2World; // model matrix
         uniform mat4 _World2Object; // inverse model matrix
         uniform vec4 _WorldSpaceLightPos0; 
            // direction to or position of light source
         uniform vec4 _LightColor0; 
            // color of light source (from "Lighting.cginc")
         
         varying vec4 color; 
            // the diffuse lighting computed in the vertex shader
         
         #ifdef VERTEX
         
         void main()
         {				
            mat4 modelMatrix = _Object2World;
            mat4 modelMatrixInverse = _World2Object; // unity_Scale.w 
                // is unnecessary because we normalize vectors
            
            vec3 normalDirection = normalize(
               vec3(vec4(gl_Normal, 0.0) * modelMatrixInverse));
            vec3 lightDirection = normalize(
               vec3(_WorldSpaceLightPos0));

            vec3 diffuseReflection = vec3(_LightColor0) * vec3(_Color) 
               * max(0.0, dot(normalDirection, lightDirection));
            
            color = vec4(diffuseReflection, 1.0);
            gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
         }
         
         #endif

         #ifdef FRAGMENT
         
         void main()
         {
            gl_FragColor = color;
         }
         
         #endif

         ENDGLSL
      }

      Pass {	
         Tags { "LightMode" = "ForwardAdd" } 
            // pass for additional light sources
         Blend One One // additive blending 

         GLSLPROGRAM

         uniform vec4 _Color; // shader property specified by users

         // The following built-in uniforms (except _LightColor0) 
         // are also defined in "UnityCG.glslinc", 
         // i.e. one could #include "UnityCG.glslinc" 
         uniform mat4 _Object2World; // model matrix
         uniform mat4 _World2Object; // inverse model matrix
         uniform vec4 _WorldSpaceLightPos0; 
            // direction to or position of light source
         uniform vec4 _LightColor0; 
            // color of light source (from "Lighting.cginc")
         
         varying vec4 color; 
            // the diffuse lighting computed in the vertex shader
         
         #ifdef VERTEX
         
         void main()
         {				
            mat4 modelMatrix = _Object2World;
            mat4 modelMatrixInverse = _World2Object; // unity_Scale.w 
               // is unnecessary because we normalize vectors
            
            vec3 normalDirection = normalize(
               vec3(vec4(gl_Normal, 0.0) * modelMatrixInverse));
            vec3 lightDirection = normalize(
               vec3(_WorldSpaceLightPos0));

            vec3 diffuseReflection = vec3(_LightColor0) * vec3(_Color) 
               * max(0.0, dot(normalDirection, lightDirection));
            
            color = vec4(diffuseReflection, 1.0);
            gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
         }
         
         #endif

         #ifdef FRAGMENT
         
         void main()
         {
            gl_FragColor = color;
         }
         
         #endif

         ENDGLSL
      }
   } 
   // The definition of a fallback shader should be commented out 
   // during development:
   // Fallback "Diffuse"
}

這似乎是一個相當長的著色器;但是,除了ForwardAdd通道中的標籤和Blend設定之外,兩個通道是相同的。

點光源的更改

[edit | edit source]

在方向光源的情況下,_WorldSpaceLightPos0指定光線來自的方向。然而,在點光源(或聚光燈源)的情況下,_WorldSpaceLightPos0指定光源在世界空間中的位置,我們必須將指向光源的方向計算為從世界空間中頂點的位置到光源位置的差向量。由於點的第4個座標是1,方向的第4個座標是0,因此我們可以輕鬆地區分這兩種情況。

            vec3 lightDirection;

            if (0.0 == _WorldSpaceLightPos0.w) // directional light?
            {
               lightDirection = normalize(vec3(_WorldSpaceLightPos0));
            } 
            else // point or spot light
            {
               lightDirection = normalize(
                  vec3(_WorldSpaceLightPos0 - modelMatrix * gl_Vertex));
            }

雖然方向光源沒有光線衰減,但我們應該為點光源和聚光燈源新增一些距離衰減。當光線從三維空間中的一個點向外傳播時,它在更大的距離處覆蓋著越來越大的虛擬球體。由於這些球體的表面積隨著半徑的增大而二次方增大,而每個球體的總光量相同,因此每單位面積的光量隨著距離點光源距離的增大而二次方減小。因此,我們應該將光源的強度除以到頂點的距離的平方。

由於二次衰減速度相當快,因此我們使用線性距離衰減,即我們用距離而不是距離的平方來除以強度。程式碼可以是

            vec3 lightDirection;
            float attenuation;

            if (0.0 == _WorldSpaceLightPos0.w) // directional light?
            {
               attenuation = 1.0; // no attenuation
               lightDirection = normalize(vec3(_WorldSpaceLightPos0));
            } 
            else // point or spot light
            {
               vec3 vertexToLightSource = vec3(_WorldSpaceLightPos0 
                  - modelMatrix * gl_Vertex);
               float distance = length(vertexToLightSource);
               attenuation = 1.0 / distance; // linear attenuation 
               lightDirection = normalize(vertexToLightSource);
            }

然後應該將因子attenuation乘以_LightColor0來計算入射光;請參見下面的著色器程式碼。請注意,聚光燈源具有額外的功能,這些功能超出了本教程的範圍。

還要注意,這段程式碼不太可能為您提供最佳效能,因為任何if 通常都很昂貴。由於_WorldSpaceLightPos0.w 是0或1,實際上重寫程式碼以避免使用if 並進一步最佳化並不太難。

            vec3 vertexToLightSource = vec3(_WorldSpaceLightPos0 
               - modelMatrix * gl_Vertex * _WorldSpaceLightPos0.w);
            float one_over_distance =  
               1.0 / length(vertexToLightSource);
            float attenuation = 
               mix(1.0, one_over_distance, _WorldSpaceLightPos0.w); 
            vec3 lightDirection = 
               vertexToLightSource * one_over_distance;

但是,為了清晰起見,我們將使用包含if 的版本。(“保持簡單,愚蠢!”)

多方向光和點光源的完整著色器程式碼如下:

Shader "GLSL per-vertex diffuse lighting" {
   Properties {
      _Color ("Diffuse Material Color", Color) = (1,1,1,1) 
   }
   SubShader {
      Pass {	
         Tags { "LightMode" = "ForwardBase" } 
            // pass for first light source

         GLSLPROGRAM

         uniform vec4 _Color; // shader property specified by users

         // The following built-in uniforms (except _LightColor0) 
         // are also defined in "UnityCG.glslinc", 
         // i.e. one could #include "UnityCG.glslinc" 
         uniform mat4 _Object2World; // model matrix
         uniform mat4 _World2Object; // inverse model matrix
         uniform vec4 _WorldSpaceLightPos0; 
            // direction to or position of light source
         uniform vec4 _LightColor0; 
            // color of light source (from "Lighting.cginc")
         
         varying vec4 color; 
            // the diffuse lighting computed in the vertex shader
         
         #ifdef VERTEX
         
         void main()
         {				
            mat4 modelMatrix = _Object2World;
            mat4 modelMatrixInverse = _World2Object; // unity_Scale.w 
               // is unnecessary because we normalize vectors
            
            vec3 normalDirection = normalize(
               vec3(vec4(gl_Normal, 0.0) * modelMatrixInverse));
            vec3 lightDirection;
            float attenuation;

            if (0.0 == _WorldSpaceLightPos0.w) // directional light?
            {
               attenuation = 1.0; // no attenuation
               lightDirection = normalize(vec3(_WorldSpaceLightPos0));
            } 
            else // point or spot light
            {
               vec3 vertexToLightSource = vec3(_WorldSpaceLightPos0 
                  - modelMatrix * gl_Vertex);
               float distance = length(vertexToLightSource);
               attenuation = 1.0 / distance; // linear attenuation
               lightDirection = normalize(vertexToLightSource);
            }

            vec3 diffuseReflection = 
               attenuation * vec3(_LightColor0) * vec3(_Color) 
               * max(0.0, dot(normalDirection, lightDirection));
            
            color = vec4(diffuseReflection, 1.0);
            gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
         }
         
         #endif

         #ifdef FRAGMENT
         
         void main()
         {
            gl_FragColor = color;
         }
         
         #endif

         ENDGLSL
      }

      Pass {	
         Tags { "LightMode" = "ForwardAdd" } 
            // pass for additional light sources
         Blend One One // additive blending 

         GLSLPROGRAM

         uniform vec4 _Color; // shader property specified by users

         // The following built-in uniforms (except _LightColor0) 
         // are also defined in "UnityCG.glslinc", 
         // i.e. one could #include "UnityCG.glslinc" 
         uniform mat4 _Object2World; // model matrix
         uniform mat4 _World2Object; // inverse model matrix
         uniform vec4 _WorldSpaceLightPos0; 
            // direction to or position of light source
         uniform vec4 _LightColor0; 
            // color of light source (from "Lighting.cginc")
         
         varying vec4 color; 
            // the diffuse lighting computed in the vertex shader
         
         #ifdef VERTEX
         
         void main()
         {				
            mat4 modelMatrix = _Object2World;
            mat4 modelMatrixInverse = _World2Object; // unity_Scale.w 
               // is unnecessary because we normalize vectors
            
            vec3 normalDirection = normalize(
               vec3(vec4(gl_Normal, 0.0) * modelMatrixInverse));
            vec3 lightDirection;
            float attenuation;

            if (0.0 == _WorldSpaceLightPos0.w) // directional light?
            {
               attenuation = 1.0; // no attenuation
               lightDirection = normalize(vec3(_WorldSpaceLightPos0));
            } 
            else // point or spot light
            {
               vec3 vertexToLightSource = vec3(_WorldSpaceLightPos0 
                  - modelMatrix * gl_Vertex);
               float distance = length(vertexToLightSource);
               attenuation = 1.0 / distance; // linear attenuation 
               lightDirection = normalize(vertexToLightSource);
            }

            vec3 diffuseReflection = 
               attenuation * vec3(_LightColor0) * vec3(_Color) 
               * max(0.0, dot(normalDirection, lightDirection));
            
            color = vec4(diffuseReflection, 1.0);
            gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
         }
         
         #endif

         #ifdef FRAGMENT
         
         void main()
         {
            gl_FragColor = color;
         }
         
         #endif

         ENDGLSL
      }
   } 
   // The definition of a fallback shader should be commented out 
   // during development:
   // Fallback "Diffuse"
}

請注意,ForwardBase 通道中的光源始終是方向光;因此,第一個通道的程式碼實際上可以簡化。另一方面,對兩個通道使用相同的 GLSL 程式碼,可以更容易地在需要編輯著色器程式碼的情況下將程式碼從一個通道複製貼上到另一個通道。

如果著色器存在問題,請記住透過選擇**編輯 > 專案設定 > 播放器**,然後在**檢視器檢視**中將**每平臺設定 > 其他設定 > 渲染 > 渲染路徑**設定為**前向**來啟用“前向渲染路徑”。

聚光燈的更改

[edit | edit source]

Unity 使用餅乾紋理來實現聚光燈,如“餅乾”部分所述;但是,這有點複雜。這裡,我們將聚光燈視為點光源。

總結

[edit | edit source]

恭喜!您剛剛學習了 Unity 的每畫素燈光的工作原理。這對於以下有關更高階照明的教程至關重要。我們還看到了

  • 漫反射是什麼以及如何用數學方法描述它。
  • 如何在著色器中為單個方向光源實現漫反射。
  • 如何擴充套件著色器以支援具有線性衰減的點光源。
  • 如何進一步擴充套件著色器以處理多個每畫素燈光。

進一步閱讀

[edit | edit source]

如果您想了解更多


< GLSL 程式設計/Unity

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