Cg Programming/Unity/Cookies

本教程涵盖了光照空间中的投影纹理映射,它对实现聚光灯和方向光下的cookies很有用。(实际上,Unity会为任何聚光灯使用一个内置cookie。)

本教程是基于章节“光滑镜面高光”和“透明纹理”的代码。如果你没有阅读过这些章节,你应该先阅读一下。
这里写图片描述

现在生活中的镜头挡光板和Cookies

这里写图片描述
在现实生活中,镜头挡光板是一件有孔的材料(通常是块金属),它被放置在光源前面来操纵光束或阴影的形状。Cookies(或叫“cuculoris”)也具有类似用途,如图所示,它被放置在距离光源很远的地方。

Unity中的Cookies

在Unity中,当光源被选中时在Inspector Window中可以为每个光源指定一个cookie。这个cookie基本上就是一张alpha纹理贴图(查看章节“透明纹理”),它被放置在光源的前面并且沿着它移动(因此这跟镜头挡光板类似)。它使得光可以穿过纹理贴图的alpha分量为1的地方并且在alpha分量为0的地方屏蔽它。Unity关于聚光灯和方向光的cookies是正方形、二维的alpha纹理贴图。另一方面,点光源的cookies是立方体纹理,这个我们就不在这边介绍了。

为了实现一个cookie,我们必须扩展被cookie影响的任意表面的着色器。(这跟Untiy的投影工作是完全不一样的;参考章节“投影”。)我们必须根据着色器中光照计算的cookie来衰减每个光源的光。这里,我们使用章节“光滑镜面高光”中提到的逐像素光照;但是,这个技术可以被用到任何光照计算中。

为了找到cookie纹理中的相关位置,一个曲面光栅化点的位置被变换到光源的坐标系中。这个坐标系类似于摄像机的裁剪坐标系,它在章节“顶点变换”中有所描述。实际上,考虑坐标系的最好方法就是把光源看到摄像机。x和y光坐标跟这个假想摄像机的屏幕坐标相关。从世界坐标变换一个点到光源坐标实际非常简单,因为Unity提供了所需的4×4矩阵作为uniform变量_LightMatrix0。(否则我们就必须设置跟视变换和投影变换类似的矩阵,它在章节“顶点变换”中有所讨论。)

为了最佳的效率,曲面点从世界空间向光源空间的变换应该在顶点着色器中通过_LightMatrix0乘以世界空间的点,举例来说就是这样:

 ...
         uniform float4x4 _LightMatrix0; // transformation 
            // from world to light space (from Autolight.cginc)
         ...

         struct vertexInput {
            float4 vertex : POSITION;
            float3 normal : NORMAL;
         };
         struct vertexOutput {
            float4 pos : SV_POSITION;
            float4 posWorld : TEXCOORD0;
               // position of the vertex (and fragment) in world space 
            float4 posLight : TEXCOORD1;
               // position of the vertex (and fragment) in light space
            float3 normalDir : TEXCOORD2;
               // surface normal vector in world space
         };

         vertexOutput vert(vertexInput input) 
         {
            vertexOutput output;

            float4x4 modelMatrix = _Object2World;
            float4x4 modelMatrixInverse = _World2Object;

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

除了uniform_LightMatrix0的定义,新的输出参数posLight以及计算posLight的指令,其它跟章节“光滑镜面高光”中的顶点着色器一样。

方向光源的Cookies

对于方向光源的cookie,我们只需要使用posLight中x和y光坐标作为在cookie纹理_LightTexture0中查询的纹理坐标。这个纹理查询应该在片元着色器中执行。然后最终的alpha分量应该乘以计算后的光照;举例来说:

            // compute diffuseReflection and specularReflection
            float cookieAttenuation = 1.0;
            if (0.0 == _WorldSpaceLightPos0.w) // directional light?
            {
               cookieAttenuation = 
                  tex2D(_LightTexture0, input.posLight.xy).a;
            }
            // compute cookieAttenuation for spotlights here

            return float4(cookieAttenuation 
               * (diffuseReflection + specularReflection), 1.0);

聚光灯的cookie

对于聚光灯来说,posLight中的x和y光坐标必须除以w光坐标。这个除法的特点是投影纹理映射以及相应于摄像机透视除法,它在章节“顶点变换”中有所描述。Unity定义了矩阵_LightMatrix0,这样我们就可以在除法之后把0.5加到两个坐标上去:

  cookieAttenuation = tex2D(_LightTexture0,input.posLight.xy / input.posLight.w 
                  + float2(0.5, 0.5)).a;

对于一些GPU来说,使用内置函数tex2Dproj会更有效率,它使用三个float3的纹理坐标并且在纹理查询前把前面两个坐标除以第三个坐标。这个方法的问题在于我们必须在除以posLight.w之后加上0.5;但是,tex2Dproj并不允许我们在内部除以第三个纹理坐标后加入任何东西。解决方案就是在除以posLight.w后加入0.5 * input.posLight.w,它对应于在除法后加入0.5:

               float3 textureCoords = float3(
                  input.posLight.x + 0.5 * input.posLight.w, 
                  input.posLight.y + 0.5 * input.posLight.w,
                  input.posLight.w);
               cookieAttenuation = 
                  tex2Dproj(_LightTexture0, textureCoords).a;

注意方向光的纹理查找也能通过把textureCoords设置为float3(input.posLight.xy, 1.0)tex2Dproj来实现。这个允许我们对方向光和聚光灯来说只使用一个纹理查找,它在一些GPU上性能更佳。

有时,投影纹理映射会带来一个负面效果:在投影边缘处,GPU使用一个高度mip map层级,它会导致一个可见的边界(特别对于被限制纹理坐标的纹理贴图)。避免这个最简单的办法就是不激活纹理贴图的mip map:在Project Window中找到并且选中纹理贴图;然后在Inspector Window中设置Texture Type为Advanced,并且不勾选Generate Mip Maps。不要忘记点击Apply按钮。

完整的着色器代码

对于完整的着色器代码我们使用了章节“光滑镜面高光”的ForwardBase通道的简单版本,因为Unity在ForwardBase通道中只使用没有cookie的方向光。所有有cookies的光源会被ForwardBase通道处理。我们忽略了聚光灯的cookies,_LightMatrix0[3][3]等于1.0(但是我们会在下章中介绍)。聚光灯总会有一张cookie纹理;如果用户没有指定,Unity会提供一张cookie纹理来生成聚光灯的形状;于是,应用cookie总是没问题的。方向光不总是有cookie;但是,如果只有一个方向光源没有cookie,它会在ForwardBase通道中被处理。于是,除非有超过一个光源没有cookies,我们能够假设所在在ForwardAdd通道中的方向光源都有cookies。在这种情况下,完整的着色器代码如下:

Shader "Cg per-pixel lighting with cookies" {
   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 ambient light 
            // and first directional light source without cookie

         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 - input.posWorld.xyz);
            float3 lightDirection = 
               normalize(_WorldSpaceLightPos0.xyz);

            float3 ambientLighting = 
               UNITY_LIGHTMODEL_AMBIENT.rgb * _Color.rgb;

            float3 diffuseReflection = 
               _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 = _LightColor0.rgb 
                  * _SpecColor.rgb * pow(max(0.0, dot(
                  reflect(-lightDirection, normalDirection), 
                  viewDirection)), _Shininess);
            }

            return float4(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")
         uniform float4x4 _LightMatrix0; // transformation 
            // from world to light space (from Autolight.cginc)
         uniform sampler2D _LightTexture0; 
            // cookie alpha texture map (from Autolight.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;
               // position of the vertex (and fragment) in world space 
            float4 posLight : TEXCOORD1;
               // position of the vertex (and fragment) in light space
            float3 normalDir : TEXCOORD2;
               // surface normal vector in world space
         };

         vertexOutput vert(vertexInput input) 
         {
            vertexOutput output;

            float4x4 modelMatrix = _Object2World;
            float4x4 modelMatrixInverse = _World2Object;

            output.posWorld = mul(modelMatrix, input.vertex);
            output.posLight = mul(_LightMatrix0, output.posWorld);
            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 - 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);
            }

            float cookieAttenuation = 1.0;
            if (0.0 == _WorldSpaceLightPos0.w) // directional light?
            {
               cookieAttenuation = tex2D(_LightTexture0, 
                  input.posLight.xy).a;
            }
            else if (1.0 != _LightMatrix0[3][3]) 
               // spotlight (i.e. not a point light)?
            {
               cookieAttenuation = tex2D(_LightTexture0, 
                  input.posLight.xy / input.posLight.w 
                  + float2(0.5, 0.5)).a;
            }

            return float4(cookieAttenuation 
               * (diffuseReflection + specularReflection), 1.0);
         }

         ENDCG
      }
   }
   Fallback "Specular"
}

注意点光源的cookie是使用立方体贴图的。这种类型的贴图会在章节“反射曲面”中讨论。

总结

恭喜,你学习了投影纹理映射中最重要的部分。我们学到了:

  • 如何实现方向光源的cookies。
  • 如何实现聚光灯的cookies(有和没有用户自定义cookies)。
  • 如何对不同的光源实现不同的着色器。
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值