标准光照模型只关心直接光照(direct light)。它把进入摄像机的光照分为4个部分:
自发光(emissive),这部分用于给定一个方向时,物体表面会向这个方向产生多少的光,当没有使用全局光照时,自发光物体不会照亮周围物体,只是本身看起来更亮而已。
高光反射(specular),这个部分用于描述当光线从光源照到物体表面时,物体镜面反射产生的光。
漫反射(diffuse)这个部分是光线从光源照到物体表面时,物体向各个方向产生的光。
环境光(ambient)这个部分用来描述其他间接的光。
1 漫反射
1.1 基本公式:
漫反射可使用兰伯特定律(Lamberts Law),即反射光线的强度与表面法线和光源方向之间的夹角的余弦成正比。计算公式如下:
Diffuse =(LC*MD)max(0,Dot(N*L))
其中LC(Light Color)是光照颜色和强度,MD(Material Diffuse)是材质的漫反射颜色,N(Normal)是表面法线向量,L(Light)是光源的单位矢量。max函数是为了防止法线和光源点乘结果为负,可防止物体被后面的光照照亮。
漫反射还可以通过一种兰伯特定律的视觉加强模型半兰伯特光照模型来计算,半兰伯特光照模型没有使用max函数来防止法线和光源方向的点乘为负的情况,而是对其结果进行了一个X倍的缩放再加上一个Y大小的偏移大多数情况下,x和y为0.5。计算公式如下:
Diffuse=(LC*MD)max(0,X(Dot(N*L))+Y)
这样就可以把法线和光源方向的点乘的结果范围从[-1, 1]映射到[0, 1]。就是兰伯特光照模型中,对物体背面的结果会映射到同一个值,即0;而在半兰伯特光照模型中,背面也是有明暗变换,会映射到不同的值。
1.2 Unity shader实现
使用SufaceShader 实现兰伯特漫反射Diffuse
Shader "Example/Diffuse Texture" { Properties { _MainTex ("Texture", 2D) = "white" {} } SubShader { Tags { "RenderType" = "Opaque" } CGPROGRAM #pragma surface surf SimpleLambert half4 LightingSimpleLambert (SurfaceOutput s, half3 lightDir, half atten) { half NdotL = dot (s.Normal, lightDir); half4 c; c.rgb = s.Albedo * _LightColor0.rgb * (NdotL * atten); c.a = s.Alpha; return c; }
struct Input { float2 uv_MainTex; }; sampler2D _MainTex; void surf (Input IN, inout SurfaceOutput o) { o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb; } ENDCG } Fallback "Diffuse" }
注释:
很多实例用的是float,这里根据最新的unity官方代码example, 使用half,shader中有三个基本类型, float(32位高精度浮点数) half (16位中精度浮点数) fixed(11位低精度浮点数)
half3 lightDir
是光线的方向,half atten
表示光衰减的系数。在计算光照的代码中,我们先将输入的s的法线值(在Normal mapping中的话这个值已经是法线图中的对应量了)和输入光线进行点积(dot函数是CG中内置的数学函数,希望你还记得,可以参考这里)。点积的结果在-1至1之间,这个值越大表示法线与光线间夹角越小,这个点也就应该越亮。之后使用max来将这个系数结果限制在0到1之间,是为了避免负数情况的存在而导致最终计算的颜色变为负数,输出一团黑,一般来说这是我们不愿意看到的。接下来我们将surf输出的颜色与光线的颜色_LightColor0.rgb
(由Unity根据场景中的光源得到的,它在Lighting.cginc中有声明)进行乘积,然后再与刚才计算的光强系数和输入的衰减系数相乘,最后得到在这个光线下的颜色输出
有些例子中,公式会变成c.rgb = s.Albedo * _LightColor0.rgb * (NdotL * atten*2);(关于difLight * atten * 2中为什么有个乘2,这是一个历史遗留问题,主要是为了进行一些光强补偿,可以参见这里的讨论
使用SufaceShader 实现半兰伯特 halfLambert
Shader "Example/HalfDiffuse Texture" { Properties { _MainTex ("Texture", 2D) = "white" {} } SubShader { Tags { "RenderType" = "Opaque" } CGPROGRAM #pragma surface surf HlafLambert half4 LightingHalfLambert (SurfaceOutput s, half3 lightDir, half atten) { half NdotL = dot (s.Normal, lightDir); half diff = NdotL * 0.5 + 0.5; half4 c; c.rgb = s.Albedo * _LightColor0.rgb * (diff * atten); c.a = s.Alpha; return c; } struct Input { float2 uv_MainTex; }; sampler2D _MainTex; void surf (Input IN, inout SurfaceOutput o) { o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb; } ENDCG Fallback "Diffuse"
}
Half Lambert是由Valve创造的可以使物体在低光线条件下增亮的技术,最早被用于半条命(Half Life)中以避免在低光下物体的走形。简单说就是把光强系数先取一半,然后在加0.5,这样一来,原来光强0的点,现在对应的值变为了0.5,而原来是1的地方现在将保持为1。也就是说模型贴图的暗部被增强变亮了,而亮部基本保持和原来一样,防止过曝
2 高光反射(BillinPhong)
2.1 公式
Blinn思想就是不计算反射方向R。他计算了一个新的矢量H,是通过V和L取平均再归一得到的。即:
specular =LC*MS * pow(max(0, N*H), gloss)
其中,V(View)是视角方向,LC(Light Color)是光照颜色和强度,MS(Material Spscular)是材质的高光反射颜色, gloss 是材质的反光度(Shininess)
...ShaderLab code... CGPROGRAM #pragma surface surf SimpleSpecular half4 LightingSimpleSpecular (SurfaceOutput s, half3 lightDir, half3 viewDir, half atten) { half3 h = normalize (lightDir + viewDir); half diff = max (0, dot (s.Normal, lightDir)); float nh = max (0, dot (s.Normal, h)); float spec = pow (nh, 48.0); half4 c; c.rgb = (s.Albedo * _LightColor0.rgb * diff + _LightColor0.rgb * spec) * atten; c.a = s.Alpha; return c; } struct Input { float2 uv_MainTex; }; sampler2D _MainTex; void surf (Input IN, inout SurfaceOutput o) { o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb; } ENDCG ...ShaderLab code...
备注:上面的例子,没有乘MS,有些shader会乘上这个系数.