正文
(顺手抄来一个图)
Phong与Blinn-Phong两个光照模型都是用来实现光照射在物体上,物体表现产生一个高光部分的效果的,两个模型也十分相似,毕竟后者是对前者进行了一个计算上的优化。虽然说两个光照模型都有了现成的代码函数封装,尤其是shaderforge插件跟新版本unity的shadergraph这两个东西,都能够直接拖拽几个图形化控件就能实现要说的这两个光照模型效果,简直是相当过份了。但理解原理之后,被别人问起来心都不会觉得虚。
Phong光照模型
理想情况下,光源射出的光线,通过镜面反射,正好在反射光方向观察,观察者可以接受到的反射光最多,那么观察者与反射方向之间的夹角就决定了能够观察到高光的多少。夹角越大,高光越小,夹角越小,高光越大。而另一个影响高光大小的因素是表面的光滑程度,表面越光滑,高光越强,表面月粗糙,高光越弱。L代表光源方向,N代表顶点法线方向,V代表观察者方向,R代表反射光方向。首先需要计算反射光的方向R,反射光方向R可以通过入射光方向和法向量求出,R + L = 2dot(N,L)N,进而推出R = 2dot(N,L)N - L。(至于如何推算出这个公式,CSDN有不少的博客都有笔记介绍,就是一个简单的向量与三角函数转换,不懂的可以百度下或者直接看这里)
(再顺手抄来一个图)
Blinn-Phong光照模型
Phong光照模型能够很好地表现高光效果,不过Blinn-Phong光照的缺点就是计算量较大,所以,在1977年,Jim Blinn对Phong光照进行了改进,称之为Blinn-Phong光照模型。
Blinn-Phong光照引入了一个概念,半角向量,用H表示。半角向量计算简单,通过将光源方向L和视线方向V相加后归一化即可得到半角向量。Phong光照是比较反射方向R和视线方向V之间的夹角,而Blinn-Phong改为比较半角向量H和法线方向N之间的夹角。半角向量的计算复杂程度要比计算反射光线简单得多,所以Blinn-Phong的性能要高得多,效果比Phong光照相差不多,所以OpenGL中固定管线的光照模型就是Blinn-Phong光照模型。
BlinnPhong光照模型的计算公式如下:
I(spcular) = I * k * pow(max(0,dot(N,H)), gloss) ,其中I为入射光颜色向量,k为镜面反射系数,gloss为光滑程度。
==================================================================
下面贴上两个光照模型的shaderlab代码
Shader "Custom/Phong" {
Properties{
_Specular("Specular", Color) = (1,1,1,1)
_Diffuse("Diffuse",Color) = (1,1,1,1)
_Gloss("Gloss",Range(1,255)) = 10
}
SubShader{
Pass{
Tags {"LightMode" = "ForwardBase" }
LOD 200
CGPROGRAM
#include "Lighting.cginc"
#pragma vertex vert
#pragma fragment frag
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f
{
float4 pos:SV_POSITION;
float3 worldNormal:NORMAL;
float3 worldPos:TEXCOORD1;
};
v2f vert(a2v v)
{
v2f o;
//矩阵变换 获取投影坐标下的顶点
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
//获取世界坐标下的法线坐标
o.worldNormal = normalize(mul(v.normal, (float3x3)_World2Object));
//o.worldNormal=normalize(mul((float3x3)_Object2World,v.normal));//与上面等价
o.worldPos = mul(_Object2World, v.vertex).xyz;
return o;
}
fixed4 frag(v2f i) :SV_Target
{
//获取环境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz*_Diffuse;
//获取灯光方向 并归一化
fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
//归一化世界坐标下的法线
fixed3 worldNormal = normalize(i.worldNormal);
//获取漫反射
fixed3 diffuse = _LightColor0.rgb*_Diffuse.rgb*saturate(dot(worldNormal, worldLight));
//获取反射光的方向 入射光方向为-worldLight,通过reflect函数获取反射光方向
fixed3 reflectDir = normalize(reflect(-worldLight, worldNormal));
//获取点在摄像机的观察位置,并归一化 (相机坐标-像素位置)
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
//phong的高光公式
fixed3 specular = _LightColor0.rgb*_Specular.rgb*pow(max(0, dot(reflectDir, viewDir)), _Gloss);
fixed3 color = diffuse + ambient + specular;
return fixed4(color, 1.0);
}
ENDCG
}
}
FallBack "Specular"
}
======================================================================
Shader "Custom/Blinn_Phong" {
Properties{
_Specular("Specular",color) = (1,1,1,1)
_Diffuse("Diffuse",color) = (1,1,1,1)
_Gloss("Gloss",Range(1,100)) = 20
}
SubShader{
Pass{
Tags { "RenderType" = "Opaque" }
LOD 200
CGPROGRAM
#include "Lighting.cginc"
#pragma vertex vert
#pragma fragment frag
float3 _Specular;
float3 _Diffuse;
float _Gloss;
struct a2v {
float4 vertex:POSITION;
float3 normal:NORMAL;
};
struct v2f {
float4 pos:SV_POSITION;
float3 worldNormal:NORMAL;
float3 worldPos:TEXCOORD0;
};
v2f vert(a2v a)
{
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, a.vertex);
o.worldNormal = mul(_Object2World, a.normal);
o.worldPos = mul(_Object2World, a.vertex).xyz;
return o;
}
float4 frag(v2f i):SV_Target
{
//获取环境光
float3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz*_Diffuse;
//获取灯光 并且归一化
float3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
//获取法线并归一化
float3 worldNormal = normalize(i.worldNormal);
//获取视线方向(摄像头位置-像素对应位置) 并归一化
float3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
//获取漫反射灯光
float3 diffuse = _LightColor0.rgb*_Diffuse.rgb*saturate(dot(worldNormal, worldLight));
//获取半角向量(光线方向+视线方向) 并归一化
float3 halfDir = normalize(worldLight + viewDir);
//高光公式
float3 specular = _LightColor0.rgb*_Specular.rgb*pow(saturate(dot(halfDir, worldNormal)),_Gloss);
float3 color = ambient + diffuse + specular;
return float4(color,1.0);
}
ENDCG
}
}
FallBack "Diffuse"
}
=======================================================================================
代码里已经有对部分关键代码进行注释了,就不过多赘述。而Blinn-Pong是引入了一个半角向量来代替反射的向量,所以着色器的运算量少了挺多,下面就直接对Blinn-Phong的着色器代码进行总结。
Blinn-Pong光照着色器
引用光照模型"Lighting.cginc"
定义a2v结构体,要获取模型的顶点与法线
定义v2f结构体,存储顶点信息(SV_POSITION) 世界法线 和顶点在世界坐标的位置
顶点函数里需要获取
顶点的投影坐标
顶点的世界法线
顶点的世界坐标
片段函数里需要获取
环境光,需要将光照模型中的环境光(UNITY_LIGHTMODEL_AMBIENT.xyz)与Diffuse相乘
归一化世界法线
归一化光照位置(_WorldSpaceLightPos0.xyz)
归一化点的视角位置(_WorldSpaceCameraPos.xyz-worldPos)
漫反射(_LightColor0.rgb*Diffuse.rgb*saturate(dot(worldNormal,lightDir)))
归一化半角向量(viewDir+lightDir)
最后的高光公式LightColor0.rbg*_Specular.rbg*pow(dot(halfDir,worldNormal),_Gloss)
返回 高光+环境光+漫反射光
总结
这两个光照模型其实都有一个中文名,就叫冯光照模型与布林-冯光照模型,但换成了中文后感觉就拗口了不少啊。从理解上的话Phong模型可以更直观地推出结论,而后者的一个半角向量我也不知道是怎么推算出来的,但少了一个点乘运算感觉代码都简洁了不少,有种前人种树后人荫的爽快感。
偷懒了快一年最近才有空憋出了这一编个人理解,看来持之以恒还是一个不容易实现的词语啊。虽然平时也有做笔记的习惯,但感觉记了笔记就放在那里了,时间一久就基本忘光,回头看一看也得楞个很久才能悟过来,这样写写博客也算是对自己的记忆来一个回顾吧,还是希望能继续坚持下去吧。。。