跳转到内容

GLSL 编程/Unity/天光漫反射

来自 Wikibooks,开放的书籍,开放的世界
一个球形建筑,从上方被阴天照亮,从下方被绿色的水池照亮。注意建筑物照明不同的颜色。

本教程涵盖了半球照明

它基于漫射逐顶点照明,如“漫反射”部分中所述。如果你还没有阅读过本教程,你应该先阅读它。

半球照明基本上是用一个覆盖场景整个半球的巨大光源计算漫反射照明,例如天空。它通常还包括使用不同的颜色从下方对另一个半球进行照明,因为计算几乎是免费的。在左侧的照片中,球形建筑被阴天照亮。然而,周围的绿色水池也照亮了它,导致建筑物下半部分出现明显的绿色照明。

漫反射可以通过表面法线向量 N 和光源方向 L 来计算。

半球照明

[编辑 | 编辑源代码]

如果我们假设表面上一点周围的半球上的每个点(方向为L)都充当光源,那么我们应该通过积分来整合半球上所有点的漫射照明(如“漫反射”部分中所述,由 max(0, L·N) 给出)。让我们将半球旋转轴的归一化方向称为U(表示“向上”)。如果表面法线N指向U的方向,我们就有完全的照明,颜色由用户指定。如果它们之间有一个角度 γ(即 cos(γ) = U·N),那么只有半球的一个球形楔形(参见维基百科文章)会照亮表面点。与完全照明相比,这种照明的分数 w

  

因此,我们可以将入射光计算为 w 乘以用户指定的半球完全照明的颜色。相反方向的半球将用 1-w 乘以另一种颜色来照亮表面点(如果我们不需要它,它可能是黑色)。下一部分解释了如何推导出 w 的这个方程。

方程推导

[编辑 | 编辑源代码]

对于任何感兴趣的人(以及因为我在网上没有找到它),这里是对 w 方程的推导。我们在附着在表面点上的球坐标系中对距离为 1 的半球上的照明进行积分,其中N的方向是y轴的方向。如果NU指向相同的方向,则积分(除了用户指定的常数颜色之外)为

  

sin(θ) 是我们在半径为 1 的球面上积分的雅可比行列式,(x, y, z) 为 (cos(φ)sin(θ), sin(φ)sin(θ), cos(θ)),N = (0,1,0)。因此,积分变为

常数 π 将包含在用户定义的最大照明颜色中。如果NU之间存在一个角度 γ,其中 cos(γ) = U·N,那么积分只在球形楔形(从 γ 到 π)上进行

     

着色器代码

[编辑 | 编辑源代码]

该实现基于 “漫反射” 章节的代码。在一个更完善的实现中,其他光源的贡献也会被包含进来,例如使用 Phong 反射模型,如 “镜面反射” 章节中所讨论的。在这种情况下,半球光照将以与环境光照相同的方式包含在内。

然而,这里唯一的照光是由于半球光照。w 的方程为

我们在世界空间中实现它,也就是说,我们必须将表面法线向量 N 转换到世界空间(参见 “世界空间中的着色” 章节),而 U 由用户在世界空间中指定。我们对向量进行归一化并计算 w,然后使用 w 和 1-w 来计算基于用户指定颜色的照亮。实际上,这相当简单。

Shader "GLSL per-vertex hemisphere lighting" {
   Properties {
      _Color ("Diffuse Material Color", Color) = (1,1,1,1) 
      _UpperHemisphereColor ("Upper Hemisphere Color", Color) 
         = (1,1,1,1) 
      _LowerHemisphereColor ("Lower Hemisphere Color", Color) 
         = (1,1,1,1) 
      _UpVector ("Up Vector", Vector) = (0,1,0,0) 
   }
   SubShader {
      Pass {      
         GLSLPROGRAM
 
         // shader properties specified by users
         uniform vec4 _Color; 
         uniform vec4 _UpperHemisphereColor;
         uniform vec4 _LowerHemisphereColor;
         uniform vec3 _UpVector;
 
         // The following built-in uniforms 
         // are also defined in "UnityCG.glslinc", 
         // i.e. one could #include "UnityCG.glslinc" 
         uniform mat4 _Object2World; // model matrix
         uniform mat4 _World2Object; // inverse model matrix
 
         varying vec4 color; 
            // the hemisphere lighting computed in the vertex shader
 
         #ifdef VERTEX
 
         void main()
         {                                
            mat4 modelMatrix = _Object2World;
            mat4 modelMatrixInverse = _World2Object; // unity_Scale.w 
               // is unnecessary because we normalize vectors
 
            vec3 normalDirection = normalize(vec3(
               vec4(gl_Normal, 0.0) * modelMatrixInverse));
            vec3 upDirection = normalize(_UpVector);
 
            float w = 0.5 * (1.0 + dot(upDirection, normalDirection));
            color = (w * _UpperHemisphereColor 
               + (1.0 - w) * _LowerHemisphereColor) * _Color;
 
            gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
         }
 
         #endif
 
         #ifdef FRAGMENT
 
         void main()
         {
            gl_FragColor = color;
         }
 
         #endif
 
         ENDGLSL
      }
   } 
}

恭喜你完成了另一个教程!我们已经看到了

  • 什么是半球光照。
  • 半球光照的方程是什么。
  • 如何实现半球光照。

进一步阅读

[编辑 | 编辑源代码]

如果你还想了解更多

  • 关于漫反射光照,你应该阅读 “漫反射” 章节。
  • 关于半球光照,你可以阅读 Randi Rost 等人于 2009 年由 Addison-Wesley 出版书籍 “OpenGL 着色语言”(第三版)的第 12.1 章。



< GLSL Programming/Unity

除非另有说明,本页上的所有示例源代码均授予公有领域。
华夏公益教科书