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

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

我们的计算公式如下

如此我们便可以计算高光反射部分。
在这之后,Blinn提出了一个简单的方案来修改这个模型,他提出了一个向量h,他是通过v与l取平均然后归一化后得到的

修改过后的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的计算过程,简单的来说就是空间相机坐标减去物体坐标,如果不够具象化,我们画一下就能够非常清晰的理解这个几何意义,

其中向量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高光漫反射模型。由于时间太晚了。先睡一觉。。。。。明天看看能不能在摸鱼的时候做成。。。

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