Shader入门—4.卡通材质(1.Blinn-Phong模型)

1.Blinn-Phong高光模型结合漫反射

前文所写的基本是比较基础的光照模型,有点没意思,我们这次来整点花货,前几年比较流行的卡通材质。首先我们要了解一下卡通材质是与之前的漫反射光照模型不同,卡通材质的光照模型主要是基于色调的光照模型,通过用漫反射系数对纹理进行一张张的采样,来控制漫反射的色调。高光处,我们用一块明显的纯色区域来表示。这时我们需要用到一个高光模型Blinn-Phong模型。除了这些,卡通渲染最具代表性的就是他明显的轮廓线。为了实现这种轮廓线,我们有几种方法,最常用的是通过两个PASS来分别渲染,一个PASS只渲染背面并便宜发现来得到轮廓选,而另外一个只需要渲染正面。

首先我们先了解一下高光模型,传统的高光模型公式如下

Phong高光模型

这个模型跟我上一节所学的漫反射光照模型有点类似,mspecular 是高光区域的颜色,v是视觉方向,r是反射方向mgloss是高光系数,系数越大则亮度越小。当我们获取r的时候,需要通过物体表面的法线单位向量与光源的单位向量通过计算得到

phong模型计算高光

我们的计算公式如下

高光反射的计算公式

如此我们便可以计算高光反射部分。

在这之后,Blinn提出了一个简单的方案来修改这个模型,他提出了一个向量h,他是通过vl取平均然后归一化后得到的

h的计算过程

修改过后的Blinn-Phong高光模型

我们分别来在程序代码中实现这两个高光光照模型。

漫反射部分与上篇类似,这边重点说下高光的代码

fixed3 reflectDir=normalize(reflect(-worldLightDir,worldNormal));
fixed3 viewDir=normalize(_WorldSpaceCammeraPos.xyz-unity_ObjectToWorld(v.vertex).xyz);

这里我们介绍下reflect函数,reflect(i,n),i是指入射方向,n是指法线方向,其中的参数类型可以是float,float2,float3,这里我们通过对光源方向取反,得到他的入射方向,这里取反的原因是,在我们用_WorldSpaceLightPos0这个函数来取得光源方向的时候,他是基于物体的表面反射而得到的值,所以在用入射光源的方向的时候,只需要取反就可以了。worldNormal则是正常通过mul(v.normal,(float3x3)unity_WorldToObject)来计算,这里我们得到的v.normal是基于物体坐标所得到的法线向量,我们用法线空间转换矩阵进行反乘,就能够得到在空间中的法线坐标,同样,法线是一个xyz向量,我们只需要去矩阵的前三行前三列就可以了,然后进行归一化。这里我提一下viewDir的计算过程,简单的来说就是空间相机坐标减去物体坐标,如果不够具象化,我们画一下就能够非常清晰的理解这个几何意义,

viewDir的具象化

其中向量C是空间相机坐标,向量O是空间物体坐标,两个相减,就得到了向量V,也就是我们的视角向量,在对他进行归一化。有了这两个值,我们就可以进行高光的代码编写了

fixed3 specular=_LightColor0.rgb*_Specular.rgb*pow(saturate(dot(viewDir,reflectDir)),_Gloss);

之后再加上我们上篇所得到diffuse与ambient就能得到一个完整的Phong-高光模型的漫反射模型。

Shader "Custom/DiffuseShader"
{
    Properties
    {
        _Diffuse("Diffuse Color",Color)=(1,1,1,1)
        _Specular("Specular Color",Color)=(1,1,1,1)
        _Gloss("Gloss",Range(8,255))=20
    }
    SubShader
    {
        pass{
            Tags{"LightModel"="ForwardBase"}
            CGPROGRAM
            
            #pragma vertex vert
            #pragma fragment frag
            #include "Lighting.cginc"

            fixed4 _Diffuse;
            fixed4 _Specular;
            float _Gloss;

            struct a2v{
                float4 vertex : POSITION;
                float3 normal : NORMAL;

            };

            struct v2f{
                float4 pos:SV_POSITION;
                float3 color:COLOR;
            };

            v2f vert(a2v v){
                v2f o;
                o.pos=UnityObjectToClipPos(v.vertex);

                fixed3 ambient=UNITY_LIGHTMODEL_AMBIENT.xyz;

                fixed3 worldNormal=normalize(mul(v.normal,(float3x3)unity_WorldToObject));

fixed3 worldLightDir=normalize(_WorldSpaceLightPos0.xyz);

                fixed3 lightNormal=normalize(_WorldSpaceLightPos0);

                fixed3 diffuse=_LightColor0.rgb*_Diffuse.rgb*saturate(dot(worldNormal,lightNormal));

                fixed3 reflectDir=normalize(reflect(-WorldLightDir,worldNormal));

                fixed3 viewDir=normalize(_WorldSpaceCameraPos.xyz-unity_ObjectToWorld(v.vertex).xyz);

                fixed3 specular=_LightColor0.rgb*_Specular*pow(saturate(dot(viewDir,reflectDir)),_Gloss);
                
                
                o.color=ambient.rgb+diffuse.rgb+specular;



                return o;
            }

            fixed4 frag(v2f i):SV_TARGET{
                return fixed4(i.color,1.0);
            }


            ENDCG
        }
    }
    FallBack "Diffuse"
}

至此我们得到了一个基于phong高光模型的漫反射模型。

同理,我们将Blinn-Phone高光模型进行代码化,首先我们需要什么?再来看一下我们的Blinn-Phong模型公式

n是物体表面法线向量,h是新定义的光源向量与视觉方向取平均后的归一化。在代码中我们可以直接将worldLightDir与viewDir相加然后进行归一化计算就行了。所以这Blinn-Phong的高光部分如下代码呈现

fixed3 viewDir=normalize(_WorldSpaceCameraPos.xyz-unity_ObjectToWorld(v.normal).xyz);
fixed3 halfDir=normalize(worldLightDir,viewDir);

得到这两个参数我们就可以直接套用公式来计算高光部分了

fixed3 diffuse=_LightColor0.rgb*_Specular*pow(max(0,dot(worldNormal,halfDir)),_Gloss);

所以我们的Blinn-Phong高光漫反射模型圆满了。下面是我们的完整代码:

Shader "Custom/Blinn-PhongSpecularDiffuseShader"
{
    Properties{
        _Diffuse("Diffuse Color",Color)=(1,1,1,1)
        _Specular("Specular Color",Color)=(1,1,1,1)
        _Gloss("Gloss",Range(8,255))=20
    }

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

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Lighting.cginc"

            fixed4 _Diffuse;
            fixed4 _Specular;
            float  _Gloss;

            struct a2v{
                float4 vertex:POSITION;
                float3 normal:NORMAL;

            };

            struct v2f{
                float4 pos:SV_POSITION;
                fixed3 color:COLOR;
            };

            v2f vert(a2v v){
                v2f o;
                o.pos=UnityObjectToClipPos(v.vertex);

                fixed3 worldNormal=normalize(mul(v.normal,(float3x3)unity_WorldToObject));

                fixed3 lightDir=normalize(_WorldSpaceLightPos0.xyz);

                fixed3 diffuse=_LightColor0.rgb*_Diffuse*saturate(dot(worldNormal,lightDir));

                fixed3 ambient=UNITY_LIGHTMODEL_AMBIENT.xyz;

                fixed3 viewDir=normalize(_WorldSpaceCameraPos.xyz-UnityObjectToWorld(v.normal).xyz);

                fixed3 halfDir=normalize(lightDir+viewDir);

                fixed3 specular=_LightColor0.rgb*_Specular*pow(max(0,dot(worldNormal,halfDir)),_Gloss);

                o.color=ambient+diffuse+specular;

                return o;

            }

            fixed4 frag(v2f i):SV_TARGET{


                return fixed4(i.color,1.0);
            }



            ENDCG




        }

    }

    FallBack "Diffuse"
}

以上是我们完整的Blinn-Phong高光漫反射模型,但注意,这部分代码我们是在顶点着色器中所实现的,在片元着色器中如何实现网上有挺多的资料的,这里就不在赘述,原理跟这个差不多,只是在片元着色器中少了一步世界坐标下的物体法线反转换。

在ShaderGraph与UE4中如何实现BlinnPhong高光漫反射模型。由于时间太晚了。先睡一觉。。。。。明天看看能不能在摸鱼的时候做成。。。

Blinn-Phong高光漫反射模型

这是上午的摸鱼成果。需要注意的是shadergraph可以直接获取viewDirection,而不必像上述代码那样通过_WorldSpaceCameraPos.xyz-unity_ObjectToWolrd(v.normal).xyz 来获取,其他的思路跟代码思路一样,就不再赘述。

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注