Cg Programming/Unity/Specular Highlights镜面高光

本教程涵盖了使用Phone反射模型的逐顶点光照(也叫做高洛德着色)。它在章节“漫反射”中通过两个额外的术语扩展了着色器代码:环境光和镜面反射。这三个术语一起构成了Phone反射模型。如果你没有读过章节“漫反射”,这会是一个非常好的机会来阅读它。

环境光

这里写图片描述
想想上面卡拉瓦乔的那幅画。白裙的大部分是在阴影中的,而没有任意部分是全黑的。很明显通常有一些光从墙壁反射回来,其它物体照亮场景中的一切–至少在某种程度上。在Phong反射模型中,这种效果会通过环境光的方法考虑进去,这个取决于总的环境光强这里写图片描述以及漫反射材质颜色这里写图片描述,环境光强的等式如下:
这里写图片描述

对比“漫反射”中的漫反射等式,这个等式也可以解释为光的红色、绿色和蓝色分量的向量等式。

在Unity中,一个统一的环境光照通过从主菜单选择Window > Lighting > Scene,设置Ambient Source为Colorc以及设置Ambient Color来指定。在Unity中的Cg着色器中,这个颜色可以使用UNITY_LIGHTMODEL_AMBIENT,它是章节“世界空间中的着色器”提到的预定义uniforms中的一个。(如果你选择Gradient来代替Color,那么UNITY_LIGHTMODEL_AMBIENT和unity_AmbientSky指定天空颜色,而Equator Color和Ground Color就由unity_AmbientEquator和unity_AmbientGround来指定。)

镜面高光

这里写图片描述高光反射的计算需要表面法向量N,指向光源的方向L,反射方向R和指向观察者的方向V。
如果你近距离地查看卡拉瓦乔的那副画,你将会看到一些高光:在鼻子上,在头发上,在嘴唇上,在琵琶上,在小提琴上,在碗上,在水果上,等等。Phong反射模型包括了一个镜面反射项,它可以在光亮的表面模拟高光;甚至还包括一个参数这里写图片描述来指定材质的光泽。这个光泽会指定高光范围的大小:反射度越大,高光范围就越小。

只有在几何反射方向R上,一个完美发光的表面才能从光源反射光。对于不完美的发光表面,光会被反射到R周围的发向上:反光度越小,高光区域就越宽。在数学上,归一化的反射方向R定义如下:
这里写图片描述

对于归一化法向量N和归一化的指向光源的方向L,在Cg中,函数float3 reflect(float3 I, float3 N) (或float4 reflect(float4 I, float4 N))会计算相同的反射向量,但只是针对从光源指向表面上点的方向I。因此,为了使用这个函数,我们必须把方向L取反。

高光反射部分会以观察者的方向V来计算高光反射。如上讨论,如果V离R越近,强度就越强,这里“越近”由反光度这里写图片描述来参数化。在Phong反射模型中,R和V之间夹角余弦值的这里写图片描述次方被用来生成不同反光度的高光。
跟漫反射的情况相似,我们应该截取负的余弦值到0。此外,对于镜面反射,镜面部分需要一个材质颜色这里写图片描述,它通常是白色的,这样所有高光就有入射光的颜色这里写图片描述。举例来说,卡拉瓦乔的画作中所有的高光都是白色的。Phong反射模型的镜面部分表示如下:
这里写图片描述

跟漫反射情况类似,如果光源在表面错误的一边的话应该忽略镜面部分;也就是如果点乘N·L的值是负数的话。

着色器代码

环境光的着色器代码很简单,就是向量和向量的乘法:

float3 ambientLighting =  UNITY_LIGHTMODEL_AMBIENT.rgb * _Color.rgb;

对于镜面反射的实现,我们需要世界空间中指向观察者的方向,它可以通过计算摄像机位置和顶点位置(都在世界空间)的差值得到。世界空间中摄像机的位置由Unity的统一参数_WorldSpaceCameraPos提供;顶点位置可以像章节“漫反射”中讨论的一样转换而来。世界空间中镜面部分的等式可以通过以下方式实现:

 float3 viewDirection = normalize(_WorldSpaceCameraPos 
               - mul(modelMatrix, input.vertex).xyz);

            float3 specularReflection;
            if (dot(normalDirection, lightDirection) < 0.0) 
               // 光源在“错误”的一面
            {
               specularReflection = float3(0.0, 0.0, 0.0); 
                  // 没有镜面反射
            }
            else // 光源在正确的一面
            {
               specularReflection = attenuation * _LightColor0.rgb 
                  * _SpecColor.rgb * pow(max(0.0, dot(
                  reflect(-lightDirection, normalDirection), 
                  viewDirection)), _Shininess);
            }

这一小段代码使用了章节“漫反射”着色器代码中相同的变量,以及另外使用了用户自定义参数_SpecColor 和_Shininess。(名字是被别指定的,这样备用着色器就能访问它们;查阅章节“漫反射”。)pow(a, b)是用来计算a的b次方这里写图片描述

如果环境光被加入到第一个通道(我们只需要一次)中,以及镜面反射被加入到章节“漫反射”中完整着色器的两个通道中的话,那么着色器看起来就像这样:

Shader "Cg per-vertex lighting" {
   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" } 
            // 环境光和第一个光源的通道

         CGPROGRAM

         #pragma vertex vert  
         #pragma fragment frag 

         #include "UnityCG.cginc"
         uniform float4 _LightColor0; 
            // 光源颜色 (从"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 col : COLOR;
         };

         vertexOutput vert(vertexInput input) 
         {
            vertexOutput output;

            float4x4 modelMatrix = _Object2World;
            float3x3 modelMatrixInverse = _World2Object;
            float3 normalDirection = normalize(
               mul(input.normal, modelMatrixInverse));
            float3 viewDirection = normalize(_WorldSpaceCameraPos 
               - mul(modelMatrix, input.vertex).xyz);
            float3 lightDirection;
            float attenuation;

            if (0.0 == _WorldSpaceLightPos0.w) // 方向光
            {
               attenuation = 1.0; // 没有衰减
               lightDirection = normalize(_WorldSpaceLightPos0.xyz);
            } 
            else // 点或聚光源
            {
               float3 vertexToLightSource = _WorldSpaceLightPos0.xyz
                  - mul(modelMatrix, input.vertex).xyz;
               float distance = length(vertexToLightSource);
               attenuation = 1.0 / distance; // 线性衰减 
               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) 
               // 光源在错误的一面
            {
               specularReflection = float3(0.0, 0.0, 0.0); 
                  // 没有镜面反射
            }
            else // 光源在正确的一面
            {
               specularReflection = attenuation * _LightColor0.rgb 
                  * _SpecColor.rgb * pow(max(0.0, dot(
                  reflect(-lightDirection, normalDirection), 
                  viewDirection)), _Shininess);
            }

            output.col = float4(ambientLighting + diffuseReflection 
               + specularReflection, 1.0);
            output.pos = mul(UNITY_MATRIX_MVP, input.vertex);
            return output;
         }

         float4 frag(vertexOutput input) : COLOR
         {
            return input.col;
         }

         ENDCG
      }

      Pass {    
         Tags { "LightMode" = "ForwardAdd" } 
            // 额外光源的通道
         Blend One One // 加性混合 

         CGPROGRAM

         #pragma vertex vert  
         #pragma fragment frag 

         #include "UnityCG.cginc"
         uniform float4 _LightColor0; // 光源颜色(从"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 col : COLOR;
         };

         vertexOutput vert(vertexInput input) 
         {
            vertexOutput output;

            float4x4 modelMatrix = _Object2World;
            float3x3 modelMatrixInverse = _World2Object;
            float3 normalDirection = normalize(
               mul(input.normal, modelMatrixInverse));
            float3 viewDirection = normalize(_WorldSpaceCameraPos 
               - mul(modelMatrix, input.vertex).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
                  - mul(modelMatrix, input.vertex).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);
            }

            output.col = float4(diffuseReflection 
               + specularReflection, 1.0);
               // no ambient contribution in this pass
            output.pos = mul(UNITY_MATRIX_MVP, input.vertex);
            return output;
         }

         float4 frag(vertexOutput input) : COLOR
         {
            return input.col;
         }

         ENDCG
      }
   }
   Fallback "Specular"
}

总结

恭喜你,刚刚学习了如何实现Phong反射模型。最后的代码我们主要实现的逐顶点的镜面高光。特别地,我们看到:

  • 在Phone反射模型中什么是环境光。
  • 在Phone反射模型中什么是镜面反射项。
  • 在Unity,在Cg中,这些镜面反射项如何实现。

深入阅读

如果你想知道得更多

  • 关于着色器代码,你应该阅读章节“漫反射”。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值