1 为什么需要Hemisphere Lighting

我们通过观察现实世界中光的各种呈现形式,在计算机图形学中我们也模拟了各种光源效果,比如方向光、点光源、聚光灯、区域光、环境光等等,这些都是在图形学中比较传统的照明模型。在一些比较基础的照明模型中,环境光常常使用一种简单的偏黄色的颜色代替,但是现实世界中的环境光并不能使用一种简单的颜色来表示。

真实世界中的环境光(模拟太阳光照射到物体)其实更像是从一个穹顶射到物体,射到地面上的光再反射到物体上。正需要这种更加真实的模拟环境光,所以才提出了Hemisphere Lighting(半球光)。

2 Hemisphere Lighting 半球光

半球光照明建模为两个半球,上半球代表天空,下半球代表地面,两个半球分别选择合适的光照颜色,物体表面法向量向上的从上半球获得照明,物体表面法向量向下的从下半球获得照明,如下图所示。

计算机图形学 – Hemisphere Lighting 半球光-StubbornHuang Blog
计算机图形学 – Hemisphere Lighting 半球光-StubbornHuang Blog

上图中球顶部的点(黑色x)接收来自上半球天空的照明(使用天空的颜色做计算);球底部的点(白色x)接收来自下半球的照明(使用地面的颜色做计算);而球中间的位置则接收一般来自天空的光照,一半接收来自地面的光照(使用50%的天空颜色和50%的地面颜色做计算)。

物体表面任何一点的颜色可通过以下公式计算:

Color = a\cdot SkyColor + (1-a)\cdot GroundColor

其中,

\begin{array}{c}
a = 1.0 - (0.5\cdot \sin (\theta ) ), \theta \le 90^{\circ} \\
a = 0.5 \cdot \cos (\theta ),\theta \ge 90^{\circ} \\
\end{array}

\theta为法线与光源入射方向的夹角,如下图所示。

计算机图形学 – Hemisphere Lighting 半球光-StubbornHuang Blog

我们可以使用更简单但是结果差不多的方法计算a

a = 0.5 + 0.5 \cdot \cos (\theta )

这种简单的方法不需要再判断\theta的大小就可以直接计算a

上述两种a的函数曲线如下:

计算机图形学 – Hemisphere Lighting 半球光-StubbornHuang Blog
计算机图形学 – Hemisphere Lighting 半球光-StubbornHuang Blog

两条曲线的形状相似,曲线下方的面积差不多相同,第二种简易方法的着色器更加简单,执行速度更快,但是能获得近似的效果。

根据上述理论,我们可以实现一个Hemisphere Lighting半球光的顶点着色器,

uniform vec3 LightPosition;
uniform vec3 SkyColor;
uniform vec3 GroundColor;
void main()
{
    vec3 ecPosition = vec3(gl_ModelViewMatrix * gl_Vertex);
    vec3 tnorm = normalize(gl_NormalMatrix * gl_Normal);
    vec3 lightVec = normalize(LightPosition - ecPosition);
    float costheta = dot(tnorm, lightVec);
    float a = 0.5 + 0.5 * costheta;

    gl_FrontColor = mix(GroundColor, SkyColor, a);

    gl_Position = ftransform();
}

或者更加完整的GLSL计算片段

//hemisphere lighting shader with optional diffuse lighting path

//inputs
attribute vec4 vPosition;
attribute vec3 vNormal;

//outputs
varying vec4 color;

//structs
struct global_ambient
{
    vec4 direction;  // direction of top colour
    vec4 top;        // top colour
    vec4 bottom;     // bottom colour
};


struct _material
{
    vec4 diffuse;   
};

//uniforms
uniform mat4 p;     // perspective matrix
uniform mat4 mv;    // modelview matrix


uniform _material material; // material properties
uniform global_ambient global;  // global ambient uniform


//globals
vec4 mvPosition; // unprojected vertex position
vec3 N; // fixed surface normal

void main()
{
  //Transform the point
  mvPosition = mv*vPosition;  //mvPosition is used often
  gl_Position = p*mvPosition;

  //Make sure the normal is actually unit length, 
  //and isolate the important coordinates
  N = normalize((mv*vec4(vNormal,0.0)).xyz);

  //Make sure global ambient direction is unit length
  vec3 L = normalize(global.direction.xyz);

  //Calculate cosine of angle between global ambient direction and normal
  float cosTheta = dot(L,N);

  //Calculate global ambient colour
  float a = 0.5+(0.5*cosTheta);
  color = a * global.top * material.diffuse
          + (1.0-a)* global.bottom * material.diffuse;
  color.a = 1.0; //Override alpha from light calculations - only needs to be done once

}

更加真实的Hemisphere Lighting的Shader还可参考:https://github.com/hughsk/glsl-hemisphere-light

半球光对于模拟更加真实的环境光非常有效,这种照明模型可以用于预览模型,也可以与传统的照明模型相结合,即在半球光的基础上使用点光源,定向光源,聚光灯等。

参考链接