跳转至内容

Cg 编程/Unity/多重灯光

来自维基教科书,开放世界开放书籍
隧道中有限范围内的多条地铁灯光。

本教程涵盖了单遍多重光源照明。特别是,它涵盖了 Unity 所谓的 ForwardBase 通道中的“顶点灯光”。

本教程是 “平滑镜面高光”部分 的扩展。如果你还没有阅读过该教程,你应该先阅读它。

单遍多重灯光

[编辑 | 编辑源代码]

“漫反射”部分 所述,Unity 的正向渲染路径使用单独的通道来处理最重要的光源。这些被称为“像素灯光”,因为内置着色器使用每像素照明来渲染它们。所有将渲染模式设置为重要的光源都将作为像素灯光渲染。如果质量项目设置的像素灯光数量允许使用更多像素灯光,那么一些将渲染模式设置为自动的光源也会作为像素灯光渲染。其他光源会发生什么?Unity 的内置着色器将另外四个灯光作为顶点灯光渲染到 ForwardBase 通道中。顾名思义,内置着色器使用每顶点照明来渲染这些灯光。这就是本教程所要讲的内容。(进一步的灯光将通过球谐照明来近似,这里不讨论。)

ForwardBase 通道中,四个顶点灯光(即它们的位置和颜色)由以下内置制服指定(参见 Unity 内置着色器变量的文档

  // Built-in uniforms for "vertex lights"
  uniform half4 unity_LightColor[4];
   // array of the colors of the 4 light sources 
  uniform float4 unity_4LightPosX0; 
   // x coordinates of the 4 light sources in world space
  uniform float4 unity_4LightPosY0; 
   // y coordinates of the 4 light sources in world space
  uniform float4 unity_4LightPosZ0; 
   // z coordinates of the 4 light sources in world space
  uniform float4 unity_4LightAtten0; 
   // scale factors for attenuation with squared distance

我们遵循 Unity 的内置着色器,只计算顶点灯光(假设它们是点光源)的漫反射,使用每顶点照明。这可以使用顶点着色器中的以下 for 循环来计算

      // Diffuse reflection by four "vertex lights"      
      float3 vertexLighting = float3(0.0, 0.0, 0.0);
      #ifdef VERTEXLIGHT_ON
      for (int index = 0; index < 4; index++)
      {  
        float4 lightPosition = float4(unity_4LightPosX0[index], 
         unity_4LightPosY0[index], 
         unity_4LightPosZ0[index], 1.0);
 
        float3 vertexToLightSource = 
         lightPosition.xyz - output.posWorld.xyz;    
        float3 lightDirection = normalize(vertexToLightSource);
        float squaredDistance = 
         dot(vertexToLightSource, vertexToLightSource);
        float attenuation = 1.0 / (1.0 + 
         unity_4LightAtten0[index] * squaredDistance);
        float3 diffuseReflection = attenuation 
         * unity_LightColor[index].rgb * _Color.rgb 
         * max(0.0, dot(output.normalDir, lightDirection));     
 
        vertexLighting = vertexLighting + diffuseReflection;
      }
      #endif

所有顶点灯光的总漫反射照明在 vertexLighting 中累加,通过将其初始化为黑色,然后在 for 循环结束时将每个顶点灯光的漫反射添加到 vertexLighting 的先前值。for 循环对于任何 C/C++/Java/JavaScript 程序员来说应该很熟悉。请注意,Cg 中的 for 循环对于某些 GPU 来说是严重受限的;特别是限制(这里:0 和 4)对于某些 GPU 来说必须是常量,也就是说,你甚至不能使用制服来确定限制。(技术原因是,为了“展开”循环,必须在编译时知道限制。)

这或多或少是 Unity 内置着色器中顶点灯光的计算方式。但是,请记住,没有什么能阻止你使用这些“顶点灯光”来计算镜面反射或每像素照明。

完整着色器代码

[编辑 | 编辑源代码]

“平滑镜面高光”部分 的着色器代码上下文中,完整着色器代码为

Shader "Cg 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
 
     CGPROGRAM
     #pragma multi_compile_fwdbase 
     #pragma vertex vert
     #pragma fragment frag
 
     #include "UnityCG.cginc" 
     uniform float4 _LightColor0; 
      // color of light source (from "Lighting.cginc")
 
     // User-specified properties
     uniform float4 _Color; 
     uniform float4 _SpecColor; 
     uniform float _Shininess;
 
     struct vertexInput {
      float4 vertex : POSITION;
      float3 normal : NORMAL;
     };
     struct vertexOutput {
      float4 pos : SV_POSITION;
      float4 posWorld : TEXCOORD0;
      float3 normalDir : TEXCOORD1;
      float3 vertexLighting : TEXCOORD2;
     };
 
     vertexOutput vert(vertexInput input)
     {     
      vertexOutput output;
 
      float4x4 modelMatrix = unity_ObjectToWorld;
      float4x4 modelMatrixInverse = unity_WorldToObject; 
 
      output.posWorld = mul(modelMatrix, input.vertex);
      output.normalDir = normalize(
        mul(float4(input.normal, 0.0), modelMatrixInverse).xyz);
      output.pos = UnityObjectToClipPos(input.vertex);
 
      // Diffuse reflection by four "vertex lights"      
      output.vertexLighting = float3(0.0, 0.0, 0.0);
      #ifdef VERTEXLIGHT_ON
      for (int index = 0; index < 4; index++)
      {  
        float4 lightPosition = float4(unity_4LightPosX0[index], 
         unity_4LightPosY0[index], 
         unity_4LightPosZ0[index], 1.0);
 
        float3 vertexToLightSource = 
         lightPosition.xyz - output.posWorld.xyz;    
        float3 lightDirection = normalize(vertexToLightSource);
        float squaredDistance = 
         dot(vertexToLightSource, vertexToLightSource);
        float attenuation = 1.0 / (1.0 + 
         unity_4LightAtten0[index] * squaredDistance);
        float3 diffuseReflection = attenuation 
         * unity_LightColor[index].rgb * _Color.rgb 
         * max(0.0, dot(output.normalDir, lightDirection));     
 
        output.vertexLighting = 
         output.vertexLighting + diffuseReflection;
      }
      #endif
      return output;
     }
 
     float4 frag(vertexOutput input) : COLOR
     {
      float3 normalDirection = normalize(input.normalDir); 
      float3 viewDirection = normalize(
        _WorldSpaceCameraPos - input.posWorld.xyz);
      float3 lightDirection;
      float attenuation;
 
      if (0.0 == _WorldSpaceLightPos0.w) // directional light?
      {
        attenuation = 1.0; // no attenuation
        lightDirection = 
         normalize(_WorldSpaceLightPos0.xyz);
      } 
      else // point or spot light
      {
        float3 vertexToLightSource = 
         _WorldSpaceLightPos0.xyz - input.posWorld.xyz;
        float distance = length(vertexToLightSource);
        attenuation = 1.0 / distance; // linear attenuation 
        lightDirection = normalize(vertexToLightSource);
      }
 
      float3 ambientLighting = 
        UNITY_LIGHTMODEL_AMBIENT.rgb * _Color.rgb;
 
      float3 diffuseReflection = 
        attenuation * _LightColor0.rgb * _Color.rgb 
        * max(0.0, dot(normalDirection, lightDirection));
 
      float3 specularReflection;
      if (dot(normalDirection, lightDirection) < 0.0) 
        // light source on the wrong side?
      {
        specularReflection = float3(0.0, 0.0, 0.0); 
         // no specular reflection
      }
      else // light source on the right side
      {
        specularReflection = attenuation * _LightColor0.rgb 
         * _SpecColor.rgb * pow(max(0.0, dot(
         reflect(-lightDirection, normalDirection), 
         viewDirection)), _Shininess);
      }
 
      return float4(input.vertexLighting + ambientLighting 
        + diffuseReflection + specularReflection, 1.0);
     }
     ENDCG
   }
 
   Pass {  
     Tags { "LightMode" = "ForwardAdd" } 
      // pass for additional light sources
     Blend One One // additive blending 
 
     CGPROGRAM
 
     #pragma vertex vert 
     #pragma fragment frag 
 
     #include "UnityCG.cginc" 
     uniform float4 _LightColor0; 
      // color of light source (from "Lighting.cginc")
 
     // User-specified properties
     uniform float4 _Color; 
     uniform float4 _SpecColor; 
     uniform float _Shininess;
 
     struct vertexInput {
      float4 vertex : POSITION;
      float3 normal : NORMAL;
     };
     struct vertexOutput {
      float4 pos : SV_POSITION;
      float4 posWorld : TEXCOORD0;
      float3 normalDir : TEXCOORD1;
     };
 
     vertexOutput vert(vertexInput input) 
     {
      vertexOutput output;
 
      float4x4 modelMatrix = unity_ObjectToWorld;
      float4x4 modelMatrixInverse = unity_WorldToObject; 
 
      output.posWorld = mul(modelMatrix, input.vertex);
      output.normalDir = normalize(
        mul(float4(input.normal, 0.0), modelMatrixInverse).xyz);
      output.pos = UnityObjectToClipPos(input.vertex);
      return output;
     }
 
     float4 frag(vertexOutput input) : COLOR
     {
      float3 normalDirection = normalize(input.normalDir);
 
      float3 viewDirection = normalize(
        _WorldSpaceCameraPos.xyz - input.posWorld.xyz);
      float3 lightDirection;
      float attenuation;
 
      if (0.0 == _WorldSpaceLightPos0.w) // directional light?
      {
        attenuation = 1.0; // no attenuation
        lightDirection = 
         normalize(_WorldSpaceLightPos0.xyz);
      } 
      else // point or spot light
      {
        float3 vertexToLightSource = 
         _WorldSpaceLightPos0.xyz - input.posWorld.xyz;
        float distance = length(vertexToLightSource);
        attenuation = 1.0 / distance; // linear attenuation 
        lightDirection = normalize(vertexToLightSource);
      }
 
      float3 diffuseReflection = 
        attenuation * _LightColor0.rgb * _Color.rgb
        * max(0.0, dot(normalDirection, lightDirection));
 
      float3 specularReflection;
      if (dot(normalDirection, lightDirection) < 0.0) 
        // light source on the wrong side?
      {
        specularReflection = float3(0.0, 0.0, 0.0); 
         // no specular reflection
      }
      else // light source on the right side
      {
        specularReflection = attenuation * _LightColor0.rgb 
         * _SpecColor.rgb * pow(max(0.0, dot(
         reflect(-lightDirection, normalDirection), 
         viewDirection)), _Shininess);
      }
 
      return float4(diffuseReflection 
        + specularReflection, 1.0);
        // no ambient lighting in this pass
     }
 
     ENDCG
   }
 
  } 
  Fallback "Specular"
}

使用 #pragma multi_compile_fwdbase#ifdef VERTEXLIGHT_ON ... #endif 是必要的,以确保当 Unity 不提供数据时,不会计算顶点照明;另见 Unity 关于 multi_compile 指令的文档

恭喜你,你已经完成了本教程。我们已经看到了

  • Unity 的顶点灯光是如何指定的。
  • 如何在 Cg 中使用 for 循环来计算单遍多重灯光的照明。

进一步阅读

[编辑 | 编辑源代码]

如果你还想了解更多

< Cg 编程/Unity

除非另有说明,本页的所有示例源代码都属于公有领域。
华夏公益教科书