半兰伯特光照模型

实现了逐顶点和逐像素的兰伯特光照模型,我们再来看一下兰伯特光照模型的变种--半兰伯特光照。经过上面的对比,逐像素光照计算会获得更好的效果,所以我们下面就采用逐像素的方式来实现半兰伯特光照模型。
上面的shader计算光照的时候,我们计算法线方向和光方向的点乘值时,得到的结果有可能是负数,而兰伯特光照模型对于该情况的处理是,dot值为负数,说明该点不会受到光的照射,所以对于该光源,该点无光,直接使用max(0,diffuse)来将不应该受光的位置全都置为黑色。虽然听起来很有道理的样子,然而这种并好看。
 
 
然而,实际上,我们在现实世界中经常会发现,即使我们让一个物体不被光直接照射,我们也可能会看到物体,虽然亮度不是很高,这其实是由于物体之间光的反射造成的,也就是间接光照,间接光照是更高级的渲染,比如光线追踪算法等。但是在实时图形学,我们大部分情况是通过一个环境光(Ambient Light)统一代表了间接光,这样,即使在没有光的时候,我们也可以看见物体。
兰伯特光照出来的时候,貌似还没有这么高科技的技术,所以呢,有人就想到了一个取巧的技术(据说是《半条命》),既保证了兰伯特模型计算出来的光照结果大于0,又整体提升了亮度,使非直接受光面不是单纯的置为黑色。这是一个在图形学领域经常有的变换,区间转化,从(-1,1)转化到(0,1),如果不考虑无意义的负值,也可以说成从(0,1)转化到了(0.5,1)。方法很简单,乘以0.5再加上0.5。这样,原本亮度为1的地方,乘以0.5变成了0.5,加上0.5就又成了1,而原本光照强度为0的地方,就变成了0.5,原本为负数的地方,也能保证为大于0了。半兰伯特光照这种区间转化的原理图如下所示:
下面看一下逐像素计算的半兰伯特光照shader,比兰伯特光照的只是将法线向量与光方向向量的点乘结果用一种更好的方式区间转化到了(0,1)区间:
Shader "ApcShader/HalfLambert"  
{  
    //属性  
    Properties{  
        _Diffuse("Diffuse", Color) = (1,1,1,1)  
    }  
  
    //子着色器    
    SubShader  
    {  
        Pass  
        {  
            //定义Tags  
            Tags{ "RenderType" = "Opaque" }  
  
            CGPROGRAM  
            //引入头文件  
            #include "Lighting.cginc"  
            //定义Properties中的变量  
            fixed4 _Diffuse;  
            //定义结构体:应用阶段到vertex shader阶段的数据  
            struct a2v  
            {  
                float4 vertex : POSITION;  
                float3 normal : NORMAL;  
            };  
            //定义结构体:vertex shader阶段输出的内容  
            struct v2f  
            {  
                float4 pos : SV_POSITION;  
                float3 worldNormal : TEXCOORD0;  
            };  
  
            //定义顶点shader  
            v2f vert(a2v v)  
            {  
                v2f o;  
                o.pos = mul(UNITY_MATRIX_MVP, v.vertex);  
                //把法线转化到世界空间  
                o.worldNormal = mul(v.normal, (float3x3)_World2Object);  
                return o;  
            }  
  
            //定义片元shader  
            fixed4 frag(v2f i) : SV_Target  
            {  
                //归一化法线,即使在vert归一化也不行,从vert到frag阶段有差值处理,传入的法线方向并不是vertex shader直接传出的  
                fixed3 worldNormal = normalize(i.worldNormal);  
                //把光照方向归一化  
                fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);  
                //半兰伯特光照,将原来(-1,1)区间的光照条件转化到了(0,1)区间,既保证了结果的正确,又整体提升了亮度,保证非受光面也能有光,而不是全黑  
                fixed3 lambert = 0.5 * dot(worldNormal, worldLightDir) + 0.5;  
                //最终输出颜色为lambert光强*材质diffuse颜色*光颜色  
                fixed3 diffuse = lambert * _Diffuse.xyz * _LightColor0.xyz;  
                return fixed4(diffuse, 1.0);  
            }  
  
            //使用vert函数和frag函数  
            #pragma vertex vert  
            #pragma fragment frag     
  
            ENDCG  
        }  
  
    }  
    //前面的Shader失效的话,使用默认的Diffuse  
    FallBack "Diffuse"  
}  

  

看一下兰伯特光照模型和半兰伯特光照模型的对比:
 
所以,正如某图形学大牛说的,图形学这个,没有什么道理,只要看起来好看,那就行了!
 
posted @ 2017-09-12 15:26  筱槲  阅读(1945)  评论(0编辑  收藏  举报