跳转到内容

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

除非另有说明,否则本页上的所有示例源代码均归属公共领域。
华夏公益教科书