Cg Programming/Unity/Multiple Lights多光源

本教程涵盖了在一个通道中的多光源照明。特别地,它涵盖了Unity所谓的在ForwardBase通道中的“顶点光”。
本教程是章节“平滑镜面高光”的扩展。如果你没有读过,你应该优先去看看。

一个通道中的多光源

如章节“漫反射”中讨论的,Unity的前向渲染路径会为最重要的光源使用独立通道。这被称作“像素光”因为内置着色器会用逐像素光照渲染它们。渲染模式设置为重要的所有光源会被当作像素光来渲染。如果Quality project settings中像素光数量允许更多的像素光,那么渲染模式设置为Auto的部分光源也会被当作像素光来渲染。如果Quality project settings中像素光数量允许更多的像素光,那么渲染模式设置为Auto的部分光源也会被当作像素光来渲染。那其它的光源会发生什么?Unity内置的着色器会在ForwardBase通道中作为顶点光照来渲染额外的4个灯光。顾名思义,内置着色器会用逐顶点光照渲染这些灯光。以上就是本教程所要讲的东西。(进一步的光照近似球面谐波光照,它不包括在这里。)

在ForwardBase通道中,4个顶点光(也就是它们的位置和颜色)是由以下的内置uniforms(参考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的值上。任何一个C/C++/JavaScrip程序员都应该熟悉for循环。注意,Cg中的for循环在一些GPU上是严重受限的;特别是对于一些GPU,界限(这里是0到4)必须是常量,也就是你甚至不能使用uniforms来定义界限。(技术上的原因是为了“展开”循环,在编译时间就要知道这个界限。)

上面或多或少解释了如何在Untiy的内置着色器中计算顶点光照。但是,请记住什么都阻止不了你用这些“顶点光”计算镜面反射或逐像素光照。

完整的着色器代码

在章节“光滑镜面高光”着色器代码的基础上,完整的着色器代码如下:

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" } // 4个顶点光的通道,环境光和第一个像素光

         CGPROGRAM
         #pragma multi_compile_fwdbase 
         #pragma vertex vert
         #pragma fragment frag

         #include "UnityCG.cginc" 
         uniform float4 _LightColor0; 
            // color of light source (from "Lighting.cginc")

         // 用户自定义属性
         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 = _Object2World;
            float4x4 modelMatrixInverse = _World2Object; 

            output.posWorld = mul(modelMatrix, input.vertex);
            output.normalDir = normalize(
               mul(float4(input.normal, 0.0), modelMatrixInverse).xyz);
            output.pos = mul(UNITY_MATRIX_MVP, input.vertex);

            // 4个"顶点光"的漫反射            
            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 = _Object2World;
            float4x4 modelMatrixInverse = _World2Object; 

            output.posWorld = mul(modelMatrix, input.vertex);
            output.normalDir = normalize(
               mul(float4(input.normal, 0.0), modelMatrixInverse).xyz);
            output.pos = mul(UNITY_MATRIX_MVP, 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 directives的文档

总结

恭喜,你又完成了一章的学习。我们学习了:

  • 如何指定Untiy的顶点光。
  • 在Cg中如何使用for循环来计算在一个通道中的多光源的光照。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值