Cg Programming/Unity/Specular Highlights at Silhouettes轮廓处的镜面高光

本教程镜面高光的菲涅耳系数。

这是关于光照教程中的其中一个教程,这个光照超出了Phone反射模型的范围。但是,它是基于章节“镜面高光”(逐顶点光照)以及章节“光滑镜面高光”(逐像素光照)中描述的Phone反射模型光照。如果你没阅读过那两章,建议先阅读一下。

Cg Programming/Unity/Specular Highlights at Silhouettes轮廓处的镜面高光_第1张图片

当光线掠过表面时,大多数材质(比如亚光纸)会表现出强烈的镜面反射;如上图所示,背光从观察者相反方向进行反射。对一些材质来说菲涅耳系数解释了这种强反射。当然,还有其它原因会导致明亮的轮廓,比如半透明的头发或织物(参考章节“半透明表面”)。

有趣地是,这种效果通过很难被看到,因为很可能轮廓的背景非常明亮。但是,在这种情况下,一个明亮的轮廓会混合进背景中,于是会变得很难被看到。

译者注:总感觉文章说得不是很明白,不知道是不是自己翻译的问题。

所谓菲涅耳反射,其实就是根据视角方向来控制反射程度。当光照射到物体表面时,被反射的光和入射的光之间有一定的比率关系,而这种关系就是用菲涅耳等式计算的。

菲涅耳系数的Schlick近似

Cg Programming/Unity/Specular Highlights at Silhouettes轮廓处的镜面高光_第2张图片
菲涅耳系数这里写图片描述描述了波长为λ的非偏振光照下非导电材质的镜面反射。Schlick的近似等式如下:
这里写图片描述
这里V是指向观察者的归一化方向,H是归一化的中间向量: H = (V + L) / |V + L|,L是指向光源的归一化方向。H·V = 1时这里写图片描述就是反射率,也就是指向光源的方向、指向观察者的方向以及中间向量都是等价的。当中间向量垂直于指向观察者的方向V,也就意味着指向光源的方向跟指向观察者的方向是相反的(即掠射光反射的情况)。实际上在这种情况下这里写图片描述是独立于波长的,并且材质表现得就像一面完美的镜子。

使用内置Cg函数lerp(x,y,w) = x*(1-w) + y*w,你可以改写Schlick的近似函数:
这里写图片描述
至少在一些GPU上面,这个会稍微更有效一点。我们将会通过允许每个颜色分量有不同的值这里写图片描述把波长的依赖性考虑进来,也就是我们认为它是一个RGB向量。实际上,我们把它认同于章节“镜面高光”中的这里写图片描述。菲涅耳系数会在指向观察者的方向和中间向量之间的角度上添加材质颜色这里写图片描述的独立性。于是,我们会在任何镜面反射运算中把常量材质颜色这里写图片描述替换为Schlick的近似等式(使用这里写图片描述)。

举例来说,在Phong反射模型中我们对于镜面项的等式是:
这里写图片描述
这里写图片描述替换为有这里写图片描述的菲涅耳系数的Schlick近似:
这里写图片描述

代码实现

该实现是基于章节“光滑镜面高光”中的着色器代码。这只是计算中间向量并且包含了菲涅耳系数的近似:

            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
            {
               float3 halfwayDirection = 
                  normalize(lightDirection + viewDirection);
               float w = pow(1.0 - max(0.0, 
                  dot(halfwayDirection, viewDirection)), 5.0);
               specularReflection = attenuation * _LightColor0.rgb 
                  * lerp(_SpecColor.rgb, float3(1.0, 1.0, 1.0), w) 
                  * pow(max(0.0, dot(
                  reflect(-lightDirection, normalDirection), 
                  viewDirection)), _Shininess);
            }

完整的着色器代码

Shader "Cg Fresnel highlights" {
   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 light source

         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;
            float3x3 modelMatrixInverse = _World2Object;

            output.posWorld = mul(modelMatrix, input.vertex);
            output.normalDir = normalize(mul(input.normal, modelMatrixInverse));
            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 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
            {
               float3 halfwayDirection = 
                  normalize(lightDirection + viewDirection);
               float w = pow(1.0 - max(0.0, 
                  dot(halfwayDirection, viewDirection)), 5.0);
               specularReflection = attenuation * _LightColor0.rgb 
                  * lerp(_SpecColor.rgb, float3(1.0, 1.0, 1.0), w) 
                  * 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")

         // 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;
            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
            {
               float3 halfwayDirection = 
                  normalize(lightDirection + viewDirection);
               float w = pow(1.0 - max(0.0, 
                  dot(halfwayDirection, viewDirection)), 5.0);
               specularReflection = attenuation * _LightColor0.rgb 
                  * lerp(_SpecColor.rgb, float3(1.0, 1.0, 1.0), w) 
                  * pow(max(0.0, dot(
                  reflect(-lightDirection, normalDirection), 
                  viewDirection)), _Shininess);
            } 
            return float4(diffuseReflection 
               + specularReflection, 1.0);
         }
         ENDCG
      }
   }
   Fallback "Specular"
}

艺术控制

对以上的实现,一个有意义的修改就是把power 5.0替换为用户指定的着色器属性。这将使艺术家可以根据他们的艺术需求来选择夸大或减弱菲涅耳系数的效果。

半透明表面的影响

菲涅耳系数除了会影响镜面高光,它还会影响不透明表面的不透明度α。实际上,菲涅耳系数描述了在掠射光照下的表面如何变得反射更强,这意味着少数光是被吸收、折射或是透射的,即透明度T减小,同时不透明度α = 1 - T增大。最终,菲涅耳系数可以用表面法向量N代替半矢量H来计算,以及使用以下等式,不透明表面的不透明度会从用户指定的值这里写图片描述(在表面法线方向上观察)增加到1:
这里写图片描述

在章节“轮廓增强”中,不透明度被认为是由于光线穿过半透明材质层而衰减的结果。这种不透明度应该由于以下方式增加反射率的不透明度结合起来(原文是:This opacity should be combined with the opacity due to increased reflectivity in the following way。这个怎么翻译!!!)。总的不透明度这里写图片描述就是1减去总的透明度这里写图片描述,它是根据衰减得到的透明度这里写图片描述(1-这里写图片描述)和根据菲涅耳系数得到的透明度这里写图片描述(1-这里写图片描述)相乘得到的。
这里写图片描述

这里写图片描述就是上面计算得到的不透明度,而这里写图片描述是章节“轮廓增强”中计算得到的不透明度。对于平行于表面法向量的观察方向来说,这里写图片描述这里写图片描述可以被用户指定。然后对于法向量来说,等式可以修正这里写图片描述,并且实际上它会修正所有常量,因此就可以为所有视图方向计算这里写图片描述。注意不管漫反射还是镜面反射都不应该乘以不透明度这里写图片描述,因为镜面反射已经乘以了菲涅耳系数,并且由于衰减这里写图片描述漫反射应该只是乘以不透明度。

总结

恭喜,你完成了一些比较高级的教程中的一章!我们看到了:

  • 什么是菲涅耳系数。
  • 什么是对菲涅耳系数的Schlick近似。
  • 对于镜面高光如何实现Schlick近似。
  • 如何对该实现添加更多的艺术控制。
  • 对于半透明表面如何使用菲涅耳系数。

你可能感兴趣的:(Cg,unity-shader)