Phong冯氏光照模型


在现实生活当中,光照是一个复杂的过程,在计算机当中模拟这样的光照是非常复杂的

我们可以用一种简化的模型来对现实世界进行近似的光照模拟

Phong冯氏光照就是其中的一种比较简单的光照模型,

它是一个关于模型表面上点的局部照明的经验模型(empirical model)

Bui Tuong Phong 发明

一个标准的Phong模型由以下三个部分组成

  • Ambient(环境光)
  • Diffuse(漫反射)
  • Specular(高光/镜面反射)

Phong Lighting Model


Ambient(环境光)

在生活当中,即使是黑暗的环境当中,也存在一些光照,例如星光、月光等,环境光就是模拟这些光照效果,物体一般来说不可能是绝对的黑暗,所以在Phong模型当中,我们通常用一个ambient系数乘上一个颜色,来模拟环境光

float ambientStrength=0.1;//环境光的强度系数
vec3 ambient = ambientStrength * lightColor;//环境光

Diffuse(漫反射)

在我们生活当中光线照射到物体表面,物体越靠近光源的面其亮度系数也就越高
漫反射能表现物体的方向性,这也是Phong当中效果最显著的部分
为了计算光源的方向与亮度的关系,我们就要引入法线,通过入射光与法线直线的角度关系,得到光照的强度

Diffuse Lighting

下面是具体的计算代码

//计算出法向量和光线方向的单位向量
vec3 norm = normalize(Normal); vec3 lightDir = normalize(lightPos - FragPos);
//判断漫反射强度系数
float diff = max(dot(norm, lightDir), 0.0);
//计算漫反射光照
vec3 diffuse = diff * lightColor;

首先要对计算的法向量和入射光向量,进行归一化,

将两个向量进行点积操作,并且限制在0~1之间得到光照的亮度系数

最后亮度系数乘上颜色即可

Specular(高光/镜面反射)

在现实生活中,一些光滑的物体除了基本的漫反射,还有高光的部分

光滑物体表面的高光,会随着人眼的视线发生改变,它依赖与人的观察方向

所以,我们就需要引入反射光向量与摄像机视角向量

Specular Lighting

$ \vec{R} $代表光线的反射光线。 人的视线与反射光线的夹角θ越小,观察到的镜面反射的效果越明显

float specularStrength = 0.5; //镜面反射强度
vec3 viewDir = normalize(viewPos - FragPos); //视线方向
vec3 reflectDir = reflect(-lightDir, norm); //反射光线
float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32); vec3 specular = specularStrength * spec * lightColor; //镜面反射光线

使用reflect函数得到反射光向量,reflect函数的推导过程可以参考的另外一篇 文章

float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);中的32代表高光的反射度(Shininess)。反射度越高,反射光的能力越强,高光点越小,如下图所示。

shininess

Blinn Phong(布林冯光照模型)

Phong的主要问题在于视线方向和反射方向之间的夹角必须小于90度,才能使镜面光项非零

V和R之间的角度大于90度。 Phong 没有正确模拟这样的情况。

在面向相机的点可能有微面,但 Phong 无法正确建模。

问题在于观察方向和反射方向之间的点积可能为负,这在通过方程的其余部分时不会导致合理的结果。

Phong冯氏光照的问题

关于Phong模型中反射和视角大于90度的问题,可以通过改变计算方式来解决

这个修改后的模型称为Blinn-Phong高光模型或者仅仅是Blinn高光模型

它在物理上并不比Phong模型更正确。但它确实比Phong模型更加全面

Blinn 模型使用一组不同的向量进行计算,这些向量在所有有效情况下都小于 90 度

Blinn 模型需要计算半角向量。 半角矢量是视图方向和光位置之间的中间方向

Blinn光照模型

计算半角向量

Blinn模型当中,使用入射光$\vec{L}$与摄像机视角$\vec{V}$,相加得到半角向量$\vec{H}$

即$ \vec{H} = \vec{L}+\vec{V} $

再用求得的半角向量$\vec{H}$与法向量$\vec{N}$进行点积运算,求得亮度系数

记得都要归一化哦!并且要限制在0~1之间

下面是在Unity当中实现的BlinnPhong

Shader “ShaderLearning/ShaderLearning”
{
  Properties
  {
      _Shininess(“Shininess”,Range(1,64))=1
  }
  SubShader
  {

      Tags {“LightMode”=“ForwardBase”}
      Pass
      {
          CGPROGRAM
          #pragma vertex vert
          #pragma fragment frag

          #include “UnityCG.cginc”
          #include “Lighting.cginc”

          struct v2f
          {
              float4 vertex : SV_POSITION;
              fixed4 color : COLOR;
          };


          float _Shininess;

          v2f vert (appdata_full v)
          {
              v2f o;
              o.vertex=UnityObjectToClipPos(v.vertex);
              //计算世界坐标系下的光向量
              float3 L=normalize(WorldSpaceLightDir(v.vertex));
              //物体坐标系法线转为世界坐标系法线
              float3 N=UnityObjectToWorldNormal(v.normal);
              //计算世界空间下归一化的的摄像机观察方向
              float3 V=normalize(_WorldSpaceCameraPos-mul(unity_ObjectToWorld,v.vertex));
              //计算漫反射
              float diffuse=saturate(dot(L,N));
              //计算镜面高光,使用半角向量
              float specular=pow(saturate(dot(N,normalize(L+V))),_Shininess);
              //光源颜色分别和漫反射和镜面高光的强度系数相乘,最终赋给颜色
              o.color=_LightColor0*(diffuse+specular);
              return o;
          }

          fixed4 frag (v2f i) : COLOR
          {
              //片远着色器,最终的颜色加上固定的环境光颜色
              return i.color+UNITY_LIGHTMODEL_AMBIENT;
          }
          ENDCG
      }
  }
}

BlinnPhongSphere

Phong与Blinn Phong的效果对比

左边Phong冯氏光照模型效果

右边BlinnPhong 布林冯光照模型效果

compare