Unity Shader 实现 Lambert Phong BlinnPhong

发布时间 2023-03-24 15:07:50作者: kayiko

1.Lambert模型

 

 

 

  • 基于Lambert余弦定理构造出的模型,只计算了漫反射部分

代码部分解析

  • 可以看到Lambert模型中只返回了漫反射(diffuse)
  •  
  • 再看一下diffuse是什么组成的
  •  
  • 第一项为主光源的入射光颜色
  • 第二项为漫反射材质的颜色
  • 第三项 法线方向和光照方向的点积(cosθ=n.l)
  •  

     

 2.Phong模型

代码部分解析

 

  • 计算反射光方向,用着色器自带的reflect函数计算。注意:这里的光照方向是指往外的方向,所以要取反。
  • 高光部分需要的反射方向(r)和观察方向(v)的余弦值(这里cos等于两者点积)
    • 观察/视线方向如下:通过相机位置减去顶点像素点位置获得(世界坐标下做的运算)
    • 漫反射部分已经在上边Lambert模型中计算完成 

    • 计算高光部分=入射光的颜色 * 高光材质颜色 * (v·r)gloss

    • 环境光量(Unity的一个宏)* 漫反射材质
    •  

       

3.Blinn-Phone模型

 

  • 对Phong模型进行了改进,不用反射向量来计算高光
  • 关键:引入了半程向量h
  • 将高光部分的计算改为了法线(n)和半程向量(h)的点积
  • 指数依然是gloss(GAMES101中的p指数)

    代码部分解析

    • 半程向量的计算:光照方向和观察方向相加,再归一化
    • NdotH:就是半程向量和法线向量的夹角的余弦。得到高光方向

 

4.Phong模型和Blinn-Phong模型的区别

  • 总体来说就是Blinn-Phong计算更简单
  • 特殊情况:Phong模型中,材质反光度很低时,一部分高光的反射向量和观察方向夹角超过90°,就会产生高光缺失、断层的效果。 

5.Gourand模型

  • 逐顶点计算
  • 镜面高光效果差
  • 对应的着色频率:Gouraud shading(逐顶点)算出顶点法线,然后插值

 

  • 是在顶点着色器中计算的。(因为是逐顶点的)
  • 顶点颜色使用的是Phong模型的结果
  • 高光部分用的是观察方向和反射方向的余弦值
  • 计算完之后将它作为顶点所携带的颜色
  • 在片元/像素着色器中,直接将顶点携带的颜色赋值给了返回的颜色

具体实现代码

Shader "Custom/BaseLighting"
{
    Properties
    {
        
        _Diffuse ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Specular("Specular",Color) = (1,1,1,1)
        _Glossiness ("Smoothness", Range(1.0,255)) = 20
        _NormalMap("NormalMap",2D) = "white"{}
        _LocalNormalSild("LocalNormal",Range(0,1)) = 0
        _CubeMap("Env Map",CUBE)= ""{} 
        _MipScale("MipScale",Range(0,10)) =0
        _EnvScale("EnvScale",Range(0,1)) = 1
        [Toggle] _Phong("Phone", Int) = 0
        [Toggle] _BlinnPhong("Binn Phone", Int) = 0
        [Toggle] _IBL("IBL", Int) = 0

        //_Metallic ("Metallic", Range(0,1)) = 0.0
    }
    SubShader
    {
        Tags{"RenderType" = "Opaque"}
        LOD 100

        Pass{
        Tags { "LightMode"="ForwardBase" }

        CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag
        #pragma shader_feature _PHONE_
        #pragma shader_feature _BlinnPhone
        #include "UnityCG.cginc"
        #include "Lighting.cginc"
        

        
        
        float4 _Diffuse;
        float4 _Specular;
        float _Glossiness;
        float _LocalNormalSild;
        sampler2D _MainTex ; float4 _MainTex_ST;
        uniform sampler2D _NormalMap; uniform float4 _NormalMap_ST;
        samplerCUBE _CubeMap;
        float _MipScale;
        float _EnvScale;
        bool _Phong;
        bool _BlinnPhong;
        bool _IBL;
        


        struct appdata{
            float4 vertex :POSITION;
            float2 uv:TEXCOORD0;
            float3 normal :NORMAL;
            float4 tangent :TANGENT;

        };
        struct v2f {
            float4 Pos :SV_POSITION;
            float3 worldNormal: NORMAL;
            float2 uv :TEXCOORD0;
            
            float3 tangentDir :TEXCOORD1;
            float3 bitangentDir :TEXCOORD2;
            float3 worldPos :TEXCOORD3;
            float3 normalDir: TEXCOORD4;
            

        };
        v2f vert(appdata v){
            v2f o;
            o.Pos = UnityObjectToClipPos(v.vertex);
            o.uv = v.uv;
            o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
            o.worldNormal = UnityObjectToWorldNormal(v.normal);
            //计算切线和副切线
            o.normalDir = UnityObjectToWorldNormal(v.normal);
            o.tangentDir = normalize(mul(unity_ObjectToWorld,float4(v.tangent.xyz,0.0)).xyz);
            o.bitangentDir = normalize( cross(o.normalDir,o.tangentDir).xyz*v.tangent.w);//乘tangent.w来确定切线方向
            
            return o;
        
        }

        fixed4 frag(v2f i):SV_Target{
            float4 MainTex = tex2D(_MainTex,TRANSFORM_TEX(i.uv,_MainTex));//TRANSFORM_TEX用于控制纹理贴图的缩放和位移
            float3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz *_Diffuse*MainTex.rgb;
            //光方向
            float3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
            float3 worldNormal = normalize(i.worldNormal);
            i.worldNormal = worldNormal;
            float3 LightColor = _LightColor0.rbg;
            //TBN
            float3x3 tangentTransform = float3x3(i.tangentDir,i.bitangentDir,i.normalDir);

            //获取映射过去的法线数据(法线自身数据 需要通过TNB矩阵变换到世界空间)
            float3 normalLocal = UnpackNormal(tex2D(_NormalMap,TRANSFORM_TEX(i.uv,_NormalMap)));
            float3 normalWorld = normalize(mul(normalLocal,tangentTransform));

            float3 finiNormal = lerp(worldNormal,normalWorld,_LocalNormalSild);
            float NotL = max(0.0,dot(finiNormal,worldLight));
            //lambert计算法线后背面一般为黑色 这里使用环境光和光照做插值
            float3 diffuse = lerp(ambient.rgb*_Diffuse.rgb*MainTex.rgb,_LightColor0.rgb*_Diffuse.rgb*MainTex.rgb,NotL);
            //Lambert 结果
            
            float3 color = LightColor*MainTex.rgb*NotL;
            
            
            //Phong (比Lambert多了环境光和高光反射)
            //-worldLight表示入射光方向
            float3 reflectDir = normalize(reflect(-worldLight,finiNormal));
            float3 viewDir = normalize(_WorldSpaceCameraPos.xyz-i.worldPos.xyz);
            float VdotR = max(0.0,dot(reflectDir,viewDir));
            float3 specular = _LightColor0.rgb*_Specular.rgb*pow(VdotR,_Glossiness);
            //phone结果
            
             if(_Phong){
                color = diffuse + ambient+ specular;
             }
            
            //Blinn-phong(比Phong在计算高光时使用半程向量 计算更快 光照更真实 )
            float3 halfDir = normalize(worldLight+viewDir);
            //
            float NdotH = saturate(dot(halfDir,finiNormal));//将数值规范在0-1
            specular = _LightColor0.rgb*_Specular.rgb*pow(NdotH,_Glossiness);
            //Blin-phong结果
            if(_BlinnPhong){
                color = diffuse + ambient+ specular;
            }
            //环境贴图
            float3 worldRef = normalize(reflect(-viewDir,finiNormal));
            float4 reflcol = texCUBElod(_CubeMap,float4(worldRef.rgb,(255-_Glossiness)*8/(255)))*_EnvScale;

            specular = _LightColor0.rgb*_Specular.rgb*pow(NdotH,_Glossiness);
            specular = lerp(diffuse*specular,specular,_Glossiness/255);
            reflcol.rgb = lerp(reflcol*diffuse.rgb,reflcol,_Glossiness/255);
            if(_IBL){
                color = diffuse + reflcol.rgb + specular;
            }
            


            //float3 worldNormal = normalize(i.worldNormal);
            //float3 worldLight = normalize(_WorldSpaceLightPos0.xyz);//世界光方向
            //float3 lightColor = _LightColor0.rgb;
            //float3 ambient  = UNITY_LIGHTMODEL_AMBIENT.xyz * _Diffuse;
            //float3 directLight = max(0,dot(worldNormal,worldLight))*lightColor;
            //float3 MainTex = tex2D(_MainTex,i.uv).rgb;
            //float3 diffuseColor = MainTex *(directLight+ambient);
            
            return float4(color,1);

        }
        ENDCG
        }
        
    }
    FallBack "Diffuse"
}
View Code

效果对比

Lambert

 

 Phong

 

 BlinnPhong