Phong光照模型
一、Phong光照模型理论
Lambert模型可以较好的模拟出粗糙物体表面的光照效果,但是真实环境还存在着许多表面光滑的物体,例如金属、陶瓷等,Lambert光照模型无法较好地表现。
所以在1975年,Bui Tuong Phong提出了一种局部光照的经验模型,他认为物体表面反射光线由三部分组成:
SurfaceColor=CAmbient+CDiffuse+CSpecular
其中:
·CAmbient:环境光
·CDiffuse:漫反射
·CSpecular:镜面反射
物体除了受灯光影响,还会收到环境光(Ambient)影响。
当光线照射到一个表面光滑的物体时,除了有漫反射光之外,从某个角度还可以看到很强的反射光。
在接近镜面反射角的一个固定范围内,大部分入射光会被反射,这种现象被称为镜面反射。
镜面反射计算公式:
CSpecular=(Clight⋅MSpecular)saturate(v⋅r)Mshininess
其中:
·Clight:灯光亮度
·MSpecular:物体材质的镜面反射颜色
·v:视角方向(由顶点指向摄像机)
·r:光线的反射方向
·Mshininess:物体材质的光泽度
二、Shader中获取环境光变量
Unity中有一些可以直接使用的环境光变量,在UnityShaderVariables.cginc
中被声明。
Shader中可以使用的环境光变量
变量 |
类型 |
说明 |
unity_AmbientSky |
fixed4 |
Gradient类型环境中的Sky Color |
unity_AmbientEquator |
fixed4 |
Gradient类型环境中的Equator Color |
unity_AmbientGround |
fixed4 |
Gradient类型环境中的Ground Color |
UNITY_LIGHTMODEL_AMBIENT |
fixed4 |
Gradient类型环境中的Sky Color,将被unity_AmbientSky取代 |
依次点击Windows->Rendering->Lighting Setting
即可查看当前场景环境光。
需要把默认的SkyBox类型改为Gradient类型才能使用上述变量。
三、基于Phong光照模型的Shader
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
| Shader "Custom/Phong" { Properties { _MainColor("Main Color",Color)=(1,1,1,1) _SpecularColor("Specular Color",Color)=(0,0,0,0) _Shininess("Shininess",Range(1,100))=1 } SubShader { Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag
#include "UnityCG.cginc" #include "UnityLightingCommon.cginc"
struct v2f { float4 pos:SV_POSITION; fixed4 color:COLOR0; };
fixed4 _MainColor; fixed4 _SpecularColor; half _Shininess;
v2f vert(appdata_base v) { v2f o; o.pos=UnityObjectToClipPos(v.vertex);
float3 n=UnityObjectToWorldNormal(v.normal); n=normalize(n); fixed3 l=normalize(_WorldSpaceLightPos0.xyz); fixed3 view=normalize(WorldSpaceViewDir(v.vertex)); fixed ndot1=dot(n,l); fixed4 dif=_LightColor0*_MainColor*saturate(ndot1);
float3 ref=reflect(-l,n); ref=normalize(ref); fixed rdotv=saturate(dot(view,ref)); fixed4 spec=_LightColor0*_SpecularColor*pow(rdotv,_Shininess);
o.color=unity_AmbientSky+dif+spec; return o; }
fixed4 frag(v2f i):SV_Target { return i.color; } ENDCG } } }
|
四、逐像素光照
将Phong光照模型应用于Shader法线高光点边缘并不圆滑,是因为光照模型计算使用的是逐顶点(Per-Vertex)光照,而不是逐像素(Per-Pixel)光照。
对于细分较高的模型,逐顶点光照基本满足需求,但对于多边形数量较少的模型却不行,高光效果不理想。
逐像素光照使用像素着色器,颜色计算基于像素的计算,所以最终屏幕分辨率越高计算量越大。
将上面对Phong代码进行修改:
由于不是在顶点着色器中计算光照颜色,所以v2f不需要保存顶点色,而是需要保存世界空间法线向量和顶点坐标。
1 2 3 4 5 6
| struct v2f { float4 pos:SV_POSITION; float3 normal:TEXCOORD0; float4 vertex:TEXCOORD1; };
|
在顶点着色器中,将世界空间法线向量和顶点坐标传递给片段着色器。
1 2 3 4 5 6 7 8 9
| v2f vert(appdata_base v) { v2f o; o.pos=UnityObjectToClipPos(v.vertex); o.normal=v.normal; o.vertex=v.vertex;
return o; }
|
在片段着色器中进行计算。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| fixed4 frag(v2f i):SV_Target { float3 n=UnityObjectToWorldNormal(i.normal); n=normalize(n); fixed3 l=normalize(_WorldSpaceLightPos0.xyz); fixed3 view=normalize(WorldSpaceViewDir(i.vertex));
fixed ndotl=saturate(dot(n,l)); fixed4 dif=_LightColor0*_MainColor*ndotl;
float3 ref=reflect(-l,n); ref=normalize(ref); fixed rdotv=saturate(dot(ref,view)); fixed4 spec=_LightColor0*_SpecularColor*pow(rdotv,_Shininess);
return unity_AmbientSky+dif+spec; }
|
完整代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
| Shader "Custom/Per-Pixel Phong" { Properties { _MainColor("Main Color",Color)=(1,1,1,1) _SpecularColor("Specular Color",Color)=(0,0,0,0) _Shininess("Shininess",Range(1,100))=1 } SubShader { Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag
#include "UnityCG.cginc" #include "UnityLightingCommon.cginc"
struct v2f { float4 pos:SV_POSITION; float3 normal:TEXCOORD0; float4 vertex:TEXCOORD1; };
fixed4 _MainColor; fixed4 _SpecularColor; half _Shininess;
v2f vert(appdata_base v) { v2f o; o.pos=UnityObjectToClipPos(v.vertex); o.normal=v.normal; o.vertex=v.vertex; return o; }
fixed4 frag(v2f i):SV_Target { float3 n=UnityObjectToWorldNormal(i.normal); n=normalize(n); fixed3 l=normalize(_WorldSpaceLightPos0.xyz); fixed3 view=normalize(WorldSpaceViewDir(i.vertex));
fixed ndotl=saturate(dot(n,l)); fixed4 dif=_LightColor0*_MainColor*ndotl;
float3 ref=reflect(-l,n); ref=normalize(ref); fixed rdotv=saturate(dot(ref,view)); fixed4 spec=_LightColor0*_SpecularColor*pow(rdotv,_Shininess); return unity_AmbientSky+dif+spec; } ENDCG } } }
|
![Per-Pixel Phong渲染效果](Per-Pixel Phong渲染效果.png)
二者对比:
可以发现,逐像素光照效果比逐顶点光照更加细腻。
五、Blinn-Phong光照模型
1977年,Jim Blinn对Phong光照模型进行了算法优化,提出了Blinn Phong光照模型。
Blinn-Phong光照模型不再使用反射向量r计算镜面反射,而是使用半角向量 h 代替 r,h 为表示角方向 v 和灯光 l 的角平分线方向。
![Blinn Phong 镜面反射](Blinn Phong 镜面反射.JPG)
半角向量的计算公式:
h=normalize(v+l)
Blinn-Phong镜面反射计算公式:
CSpecular=(Clight⋅MSpecular)saturate(n⋅h)Mshininess
将片段着色器代码进行修改:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| fixed4 frag(v2f i):SV_Target { float3 n=UnityObjectToWorldNormal(i.normal); n=normalize(n); fixed3 l=normalize(_WorldSpaceLightPos0.xyz); fixed3 view=normalize(WorldSpaceViewDir(i.vertex));
fixed ndotl=saturate(dot(n,l)); fixed4 dif=_LightColor0*_MainColor*ndotl;
fixed3 h=normalize(l+view); fixed ndoth=saturate(dot(n,h)); fixed4 spec=_LightColor0*_SpecularColor*pow(ndoth,_Shininess);
return unity_AmbientSky+dif+spec; }
|
效果图:
二者视觉效果差距不大,但是计算性能被优化,减少计算了。