跳轉到內容

GLSL 程式設計/Unity/多盞燈光

來自華夏公益教科書,開放書籍,開放世界
隧道中多個有限範圍的地下鐵燈光。

本教程涵蓋了**在一次渲染中使用多個光源進行照明**。特別地,它涵蓋了 Unity 中的所謂“頂點燈光”,在 `ForwardBase` 渲染通道中使用。

本教程是對 “平滑鏡面高光”章節 的擴充套件。如果你還沒有閱讀過該教程,建議你首先閱讀它。

單次渲染多盞燈光

[編輯 | 編輯原始碼]

“漫反射”章節 中所述,Unity 的前向渲染路徑對最重要的光源使用單獨的渲染通道。這些通道被稱為“畫素燈光”,因為內建著色器使用逐畫素照明來渲染它們。所有將**渲染模式**設定為**重要**的光源都會被渲染為畫素燈光。如果**質量**專案設定中的**畫素燈光數量**允許使用更多畫素燈光,那麼一些**渲染模式**設定為**自動**的光源也會被渲染為畫素燈光。其他光源會怎樣?Unity 的內建著色器將四個額外的燈光作為**頂點燈光**渲染在 `ForwardBase` 通道中。顧名思義,內建著色器使用逐頂點照明來渲染這些燈光。這就是本教程的主題。(更多燈光將透過球諧光照進行近似,這裡不涵蓋。)

不幸的是,訪問這四個頂點燈光(即它們的位置和顏色)的方式有些不明確。以下是在 Windows 和 MacOS X 上 Unity 3.4 中看似有效的做法

   // Built-in uniforms for "vertex lights"
   uniform vec4 unity_LightColor[4];
      // array of the colors of the 4 light sources 
   uniform vec4 unity_4LightPosX0; 
      // x coordinates of the 4 light sources in world space
   uniform vec4 unity_4LightPosY0; 
      // y coordinates of the 4 light sources in world space
   uniform vec4 unity_4LightPosZ0; 
      // z coordinates of the 4 light sources in world space
   uniform vec4 unity_4LightAtten0; 
      // scale factors for attenuation with squared distance
   // uniform vec4 unity_LightPosition[4] is apparently not 
   // always correctly set in Unity 3.4
   // uniform vec4 unity_LightAtten[4] is apparently not 
   // always correctly set in Unity 3.4

根據你的平臺和 Unity 版本,你可能需要使用 `unity_LightPosition[4]` 來代替 `unity_4LightPosX0`、`unity_4LightPosY0` 和 `unity_4LightPosZ0`。類似地,你可能需要使用 `unity_LightAtten[4]` 來代替 `unity_4LightAtten0`。需要注意的是,以下內容不可用:任何 Cookie 紋理或到燈光空間的變換(因此也沒有聚光燈的方向)。此外,燈光位置的第四個分量也不可用;因此,無法確定一個頂點燈光是方向光、點光還是聚光燈。

在這裡,我們遵循 Unity 的內建著色器,僅使用逐頂點照明計算頂點燈光產生的漫反射。這可以透過在頂點著色器中使用以下 for 迴圈來實現

            vertexLighting = vec3(0.0, 0.0, 0.0);
            for (int index = 0; index < 4; index++)
            {    
               vec4 lightPosition = vec4(unity_4LightPosX0[index], 
                  unity_4LightPosY0[index], 
                  unity_4LightPosZ0[index], 1.0);
 
               vec3 vertexToLightSource = 
                   vec3(lightPosition - position);        
               vec3 lightDirection = normalize(vertexToLightSource);
               float squaredDistance = 
                  dot(vertexToLightSource, vertexToLightSource);
               float attenuation = 1.0 / (1.0  + 
                   unity_4LightAtten0[index] * squaredDistance);
               vec3 diffuseReflection = 
                  attenuation * vec3(unity_LightColor[index]) 
                  * vec3(_Color) * max(0.0, 
                  dot(varyingNormalDirection, lightDirection));         
 
               vertexLighting = vertexLighting + diffuseReflection;
            }

所有頂點燈光產生的總漫反射光照在 `vertexLighting` 中累加,首先將其初始化為黑色,然後在 for 迴圈結束時將每個頂點燈光的漫反射新增到 `vertexLighting` 的先前值。對於任何 C/C++/Java/JavaScript 程式設計師來說,for 迴圈應該很熟悉。需要注意的是,for 迴圈有時受到嚴格限制;特別是迴圈限制(這裡為 0 和 4)必須是常量,即你甚至不能使用 uniform 來確定迴圈限制。(技術原因是,為了“展開”迴圈,迴圈限制必須在編譯時已知。)

這或多或少是 Unity 內建著色器中計算頂點燈光的方式。但是,請記住,沒有什麼能阻止你使用這些“頂點燈光”來計算鏡面反射或逐畫素照明。

完整著色器程式碼

[編輯 | 編輯原始碼]

“平滑鏡面高光”章節 的著色器程式碼中,完整的著色器程式碼如下

Shader "GLSL per-pixel lighting with vertex lights" {
   Properties {
      _Color ("Diffuse Material Color", Color) = (1,1,1,1) 
      _SpecColor ("Specular Material Color", Color) = (1,1,1,1) 
      _Shininess ("Shininess", Float) = 10
   }
   SubShader {
      Pass {      
         Tags { "LightMode" = "ForwardBase" } // pass for 
            // 4 vertex lights, ambient light & first pixel light
 
         GLSLPROGRAM
         #pragma multi_compile_fwdbase 
 
         // User-specified properties
         uniform vec4 _Color; 
         uniform vec4 _SpecColor; 
         uniform float _Shininess;
 
         // The following built-in uniforms (except _LightColor0) 
         // are also defined in "UnityCG.glslinc", 
         // i.e. one could #include "UnityCG.glslinc" 
         uniform vec3 _WorldSpaceCameraPos; 
            // camera position in world space
         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")
 
         // Built-in uniforms for "vertex lights"
         uniform vec4 unity_LightColor[4];
         uniform vec4 unity_4LightPosX0; 
            // x coordinates of the 4 light sources in world space
         uniform vec4 unity_4LightPosY0; 
            // y coordinates of the 4 light sources in world space
         uniform vec4 unity_4LightPosZ0; 
            // z coordinates of the 4 light sources in world space
         uniform vec4 unity_4LightAtten0; 
            // scale factors for attenuation with squared distance

         // Varyings         
         varying vec4 position; 
            // position of the vertex (and fragment) in world space 
         varying vec3 varyingNormalDirection; 
            // surface normal vector in world space
         varying vec3 vertexLighting;
 
         #ifdef VERTEX
 
         void main()
         {                                            
            mat4 modelMatrix = _Object2World;
            mat4 modelMatrixInverse = _World2Object; // unity_Scale.w 
               // is unnecessary because we normalize vectors
 
            position = modelMatrix * gl_Vertex;
            varyingNormalDirection = normalize(vec3(
               vec4(gl_Normal, 0.0) * modelMatrixInverse));
 
            gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
 
            // Diffuse reflection by four "vertex lights"            
            vertexLighting = vec3(0.0, 0.0, 0.0);
            #ifdef VERTEXLIGHT_ON
            for (int index = 0; index < 4; index++)
            {    
               vec4 lightPosition = vec4(unity_4LightPosX0[index], 
                  unity_4LightPosY0[index], 
                  unity_4LightPosZ0[index], 1.0);
 
               vec3 vertexToLightSource = 
                  vec3(lightPosition - position);        
               vec3 lightDirection = normalize(vertexToLightSource);
               float squaredDistance = 
                  dot(vertexToLightSource, vertexToLightSource);
               float attenuation = 1.0 / (1.0 + 
                  unity_4LightAtten0[index] * squaredDistance);
               vec3 diffuseReflection =  
                  attenuation * vec3(unity_LightColor[index]) 
                  * vec3(_Color) * max(0.0, 
                  dot(varyingNormalDirection, lightDirection));         
 
               vertexLighting = vertexLighting + diffuseReflection;
            }
            #endif
         }
 
         #endif
 
         #ifdef FRAGMENT
 
         void main()
         {
            vec3 normalDirection = normalize(varyingNormalDirection); 
            vec3 viewDirection = 
               normalize(_WorldSpaceCameraPos - vec3(position));
            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 - position);
               float distance = length(vertexToLightSource);
               attenuation = 1.0 / distance; // linear attenuation 
               lightDirection = normalize(vertexToLightSource);
            }
 
            vec3 ambientLighting = 
                vec3(gl_LightModel.ambient) * vec3(_Color);
 
            vec3 diffuseReflection = 
               attenuation * vec3(_LightColor0) * vec3(_Color) 
               * max(0.0, dot(normalDirection, lightDirection));
 
            vec3 specularReflection;
            if (dot(normalDirection, lightDirection) < 0.0) 
               // light source on the wrong side?
            {
               specularReflection = vec3(0.0, 0.0, 0.0); 
                  // no specular reflection
            }
            else // light source on the right side
            {
               specularReflection = attenuation * vec3(_LightColor0) 
                  * vec3(_SpecColor) * pow(max(0.0, dot(
                  reflect(-lightDirection, normalDirection), 
                  viewDirection)), _Shininess);
            }
 
            gl_FragColor = vec4(vertexLighting + ambientLighting 
               + diffuseReflection + specularReflection, 1.0);
         }
 
         #endif
 
         ENDGLSL
      }
 
      Pass {      
         Tags { "LightMode" = "ForwardAdd" } 
            // pass for additional "pixel lights"
         Blend One One // additive blending 
 
         GLSLPROGRAM
 
         // User-specified properties
         uniform vec4 _Color; 
         uniform vec4 _SpecColor; 
         uniform float _Shininess;
  
         // The following built-in uniforms (except _LightColor0) 
         // are also defined in "UnityCG.glslinc", 
         // i.e. one could #include "UnityCG.glslinc" 
         uniform vec3 _WorldSpaceCameraPos; 
            // camera position in world space
         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")
 
         // Varyings
         varying vec4 position; 
            // position of the vertex (and fragment) in world space 
         varying vec3 varyingNormalDirection; 
            // surface normal vector in world space
 
         #ifdef VERTEX
 
         void main()
         {                                
            mat4 modelMatrix = _Object2World;
            mat4 modelMatrixInverse = _World2Object; // unity_Scale.w 
               // is unnecessary because we normalize vectors
 
            position = modelMatrix * gl_Vertex;
            varyingNormalDirection = normalize(vec3(
               vec4(gl_Normal, 0.0) * modelMatrixInverse));
 
            gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
         }
 
         #endif
 
         #ifdef FRAGMENT
 
         void main()
         {
            vec3 normalDirection = normalize(varyingNormalDirection);
 
            vec3 viewDirection = 
               normalize(_WorldSpaceCameraPos - vec3(position));
            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 - position);
               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));
 
            vec3 specularReflection;
            if (dot(normalDirection, lightDirection) < 0.0) 
               // light source on the wrong side?
            {
               specularReflection = vec3(0.0, 0.0, 0.0); 
                  // no specular reflection
            }
            else // light source on the right side
            {
               specularReflection = attenuation * vec3(_LightColor0) 
                  * vec3(_SpecColor) * pow(max(0.0, dot(
                  reflect(-lightDirection, normalDirection), 
                  viewDirection)), _Shininess);
            }
 
            gl_FragColor = vec4(diffuseReflection 
               + specularReflection, 1.0);
         }
 
         #endif
 
         ENDGLSL
      }
   } 
   // The definition of a fallback shader should be commented out 
   // during development:
   // Fallback "Specular"
}

使用 `#pragma multi_compile_fwdbase` 和 `#ifdef VERTEXLIGHT_ON ... #endif` 看起來是必要的,以確保當 Unity 不提供資料時不會計算頂點照明。

恭喜你已經完成了本教程。我們已經瞭解了

  • Unity 中頂點燈光的指定方式。
  • 如何使用 for 迴圈在 GLSL 中計算一次渲染中多個燈光的照明。

進一步閱讀

[編輯 | 編輯原始碼]

如果你想了解更多


< GLSL 程式設計/Unity

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