Cg Programming In Unity Multiple Lights (Wiki翻译自用)


本教程是一个Pass包含了多个光源进行光照。特别的,它包含了在ForwardBase通道中Unity的所谓的”顶点灯“。

多个光照在一个Pass中

如“Diffuse Reflection”一节中所述,Unity前向渲染路径对最重要的光源使用单独的通道。之所以将他们称为“像素灯”,是因为内置的着色器使用逐像素光照对其进行渲染。所有Render Mode设置为Important的所有光源都被渲染为像素光 。如果项目设置的Quality中的Pixel Light Count允许更多的光照,则某些Render Mode设置为Auto的光源也将被渲染为像素光。其他光源会发生什么呢?Unity的内置着色器在ForwardBase传递中将四个额外的灯光渲染为顶点灯光。 顾名思义,内置着色器使用逐顶点照明来渲染这些灯光。 这就是本教程的内容。 (更多的光是通过球谐光照明得到的,此处并未涉及。)
在ForwardBase通道中,四个顶点光(即它们的位置和颜色)由以下内置uniform指定(请参见Unity的内置着色器变量文档):

   // 内置uniform的顶点光
  uniform half4 unity_LightColor[4];
   // 四种光源颜色队列
  uniform float4 unity_4LightPosX0; 
   //世界空间中4个光源的x坐标
  uniform float4 unity_4LightPosY0; 
   // 世界空间中4个光源的y坐标
  uniform float4 unity_4LightPosZ0; 
   // 世界空间中4个光源的z坐标
  uniform float4 unity_4LightAtten0; 
   // 距离平方衰减的比例因子

我们遵循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

通过将所有顶点光的总漫射照明初始化为黑色,然后将每个顶点光的漫反射添加到for循环末尾的vertexLighting的先前值,将总漫反射光累积在vertexLighting中。 请注意,对于某些GPU,Cg中的for循环受到严格限制。特别是对于某些GPU,限制(此处为0和4)必须为常数,即您甚至无法使用制服来确定限制。 (技术原因是必须在编译时知道限制,才能“展开”循环。)
这或多或少是在Unity的内置着色器中计算顶点光的方式。 但是,请记住,没有什么会阻止您使用这些“顶点光”来计算镜面反射或逐像素照明。

完整Shader代码

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的顶点灯。
  • 如何在Cg中使用for循环来一次计算多个灯的照度。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值