跳转到内容

GLSL 编程/Unity/镜面高光

来自维基教科书,自由的教科书,来自自由的世界
“演奏鲁特琴的阿波罗”(巴明顿庄园版本)由米开朗基罗·梅里西·达·卡拉瓦乔创作,约 1596 年。

本教程涵盖使用Phong 反射模型逐顶点光照(也称为Gouraud 着色)。

它扩展了“漫反射”部分中的着色器代码,增加了两个额外的项:环境光和镜面反射。这三个项共同构成了 Phong 反射模型。如果您还没有阅读“漫反射”部分,现在是一个很好的机会去阅读它。

环境光

[编辑 | 编辑源代码]

请仔细观察左侧的卡拉瓦乔的画作。虽然白衬衫的大部分区域都处于阴影中,但没有任何部分是完全黑色的。显然,总有一些光线从墙壁和其他物体反射出来,照亮场景中的所有东西——至少在一定程度上。在 Phong 反射模型中,这种效果通过环境光来考虑,环境光取决于一般环境光强度 和漫反射材料颜色。在环境光强度 的方程中

类似于“漫反射”部分中漫反射方程,这个方程也可以解释为光的红、绿、蓝分量的向量方程。

在 Unity 中,环境光可以通过从主菜单中选择编辑 > 渲染设置(在 Unity5 中选择窗口 > 光照)来指定。在 Unity 中的 GLSL 着色器中,此颜色始终可用作gl_LightModel.ambient,它是“世界空间中的着色”部分中提到的 OpenGL 兼容性配置文件的预定义制服之一。

镜面反射的计算需要表面法线向量 N,光源方向 L,反射光源方向 R 和观察者方向 V。

镜面高光

[编辑 | 编辑源代码]

如果你仔细观察卡拉瓦乔的画作,你会看到几个镜面高光:在鼻子上、在头发上、在嘴唇上、在鲁特琴上、在小提琴上、在弓上、在水果上等等。Phong 反射模型包含一个镜面反射项,可以模拟光滑表面上的这种高光;它甚至包含一个参数 来指定材料的光泽度。光泽度指定高光的大小:越光亮,高光越小。

一个完美光滑的表面只会反射来自光源的光,并且反射方向为R。对于光泽度低于完美的表面,光线会反射到R周围的方向:光泽度越低,散射范围越广。在数学上,归一化反射方向R 定义为

对于归一化表面法线向量N 和归一化光源方向L。在 GLSL 中,函数vec3 reflect(vec3 I, vec3 N)(或vec4 reflect(vec4 I, vec4 N))计算相同的反射向量,但对于从光源到表面上点的方向I。因此,我们必须取反我们的方向L 来使用此函数。

镜面反射项计算观察者V方向的镜面反射。如上所述,如果V 接近R,则强度应该很大,其中“接近度”由光泽度 参数化。在 Phong 反射模型中,RV 之间角度的余弦被用来生成不同光泽度的高光,该角度被提升到 次方。类似于漫反射的情况,我们应该将负余弦钳位到 0。此外,镜面项需要一个镜面反射材料颜色,通常为白色,这样所有高光都具有入射光的颜色。例如,卡拉瓦乔画作中所有的高光都是白色的。Phong 反射模型的镜面项为

漫反射类似,如果光源位于表面的“错误”一侧,则应忽略镜面反射项;即,如果点积N·L为负。

着色器代码

[edit | edit source]

环境光照的着色器代码很简单,使用逐分量向量-向量乘积

   vec3 ambientLighting = vec3(gl_LightModel.ambient) * vec3(_Color);

为了实现镜面反射,我们需要在世界空间中获取观察者的方向,我们可以通过世界空间中相机位置和顶点位置之间的差值来计算。世界空间中的相机位置由 Unity 在 uniform _WorldSpaceCameraPos 中提供;顶点位置可以转换为世界空间,如 “漫反射”部分所述。然后可以像这样在世界空间中实现镜面反射项的方程

            vec3 viewDirection = normalize(vec3(
               vec4(_WorldSpaceCameraPos, 1.0) 
               - modelMatrix * gl_Vertex));

            vec3 specularReflection;
            if (dot(normalDirection, lightDirection) < 0.0) 
               // light source on the wrong side?
            {
               specularReflection = vec3(0.0, 0.0, 0.0); 
                  // no specular reflection
            }
            else // light source on the right side
            {
               specularReflection = attenuation * vec3(_LightColor0) 
                  * vec3(_SpecColor) * pow(max(0.0, dot(
                  reflect(-lightDirection, normalDirection), 
                  viewDirection)), _Shininess);
            }

此代码片段使用与 “漫反射”部分中着色器代码相同的变量,另外还包括用户指定的属性 _SpecColor_Shininess。(名称特意选择为 fallback 着色器可以访问它们;请参阅 “漫反射”部分中的讨论。)pow(a, b) 计算 .

如果环境光照添加到第一遍(我们只需要一次),并且镜面反射添加到 “漫反射”部分中完整着色器的两遍,它看起来像这样

Shader "GLSL per-vertex lighting" {
   Properties {
      _Color ("Diffuse Material Color", Color) = (1,1,1,1) 
      _SpecColor ("Specular Material Color", Color) = (1,1,1,1) 
      _Shininess ("Shininess", Float) = 10
   }
   SubShader {
      Pass {	
         Tags { "LightMode" = "ForwardBase" } 
            // pass for ambient light and first light source

         GLSLPROGRAM

         // User-specified properties
         uniform vec4 _Color; 
         uniform vec4 _SpecColor; 
         uniform float _Shininess;

         // The following built-in uniforms (except _LightColor0) 
         // are also defined in "UnityCG.glslinc", 
         // i.e. one could #include "UnityCG.glslinc" 
         uniform vec3 _WorldSpaceCameraPos; 
            // camera position in world space
         uniform mat4 _Object2World; // model matrix
         uniform mat4 _World2Object; // inverse model matrix
         uniform vec4 _WorldSpaceLightPos0; 
            // direction to or position of light source
         uniform vec4 _LightColor0; 
            // color of light source (from "Lighting.cginc")
         
         varying vec4 color; 
            // the Phong 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 viewDirection = normalize(vec3(
               vec4(_WorldSpaceCameraPos, 1.0) 
               - modelMatrix * gl_Vertex));
            vec3 lightDirection;
            float attenuation;

            if (0.0 == _WorldSpaceLightPos0.w) // directional light?
            {
               attenuation = 1.0; // no attenuation
               lightDirection = normalize(vec3(_WorldSpaceLightPos0));
            } 
            else // point or spot light
            {
               vec3 vertexToLightSource = vec3(_WorldSpaceLightPos0 
                  - modelMatrix * gl_Vertex);
               float distance = length(vertexToLightSource);
               attenuation = 1.0 / distance; // linear attenuation 
               lightDirection = normalize(vertexToLightSource);
            }
            
            vec3 ambientLighting = 
               vec3(gl_LightModel.ambient) * vec3(_Color);

            vec3 diffuseReflection = 
               attenuation * vec3(_LightColor0) * vec3(_Color) 
               * max(0.0, dot(normalDirection, lightDirection));
            
            vec3 specularReflection;
            if (dot(normalDirection, lightDirection) < 0.0) 
               // light source on the wrong side?
            {
               specularReflection = vec3(0.0, 0.0, 0.0); 
                  // no specular reflection
            }
            else // light source on the right side
            {
               specularReflection = attenuation * vec3(_LightColor0) 
                  * vec3(_SpecColor) * pow(max(0.0, dot(
                  reflect(-lightDirection, normalDirection), 
                  viewDirection)), _Shininess);
            }

            color = vec4(ambientLighting + diffuseReflection 
               + specularReflection, 1.0);
            gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
         }
         
         #endif

         #ifdef FRAGMENT
         
         void main()
         {
            gl_FragColor = color;
         }
         
         #endif

         ENDGLSL
      }

      Pass {	
         Tags { "LightMode" = "ForwardAdd" } 
            // pass for additional light sources
         Blend One One // additive blending 

         GLSLPROGRAM

         // User-specified properties
         uniform vec4 _Color; 
         uniform vec4 _SpecColor; 
         uniform float _Shininess;

         // The following built-in uniforms (except _LightColor0) 
         // are also defined in "UnityCG.glslinc", 
         // i.e. one could #include "UnityCG.glslinc" 
         uniform vec3 _WorldSpaceCameraPos; 
            // camera position in world space
         uniform mat4 _Object2World; // model matrix
         uniform mat4 _World2Object; // inverse model matrix
         uniform vec4 _WorldSpaceLightPos0; 
            // direction to or position of light source
         uniform vec4 _LightColor0; 
            // color of light source (from "Lighting.cginc")
         
         varying vec4 color; 
            // the diffuse 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 viewDirection = normalize(vec3(
               vec4(_WorldSpaceCameraPos, 1.0) 
               - modelMatrix * gl_Vertex));
            vec3 lightDirection;
            float attenuation;

            if (0.0 == _WorldSpaceLightPos0.w) // directional light?
            {
               attenuation = 1.0; // no attenuation
               lightDirection = normalize(vec3(_WorldSpaceLightPos0));
            } 
            else // point or spot light
            {
               vec3 vertexToLightSource = vec3(_WorldSpaceLightPos0 
                  - modelMatrix * gl_Vertex);
               float distance = length(vertexToLightSource);
               attenuation = 1.0 / distance; // linear attenuation 
               lightDirection = normalize(vertexToLightSource);
            }
            
            // vec3 ambientLighting = 
            //    vec3(gl_LightModel.ambient) * vec3(_Color);

            vec3 diffuseReflection = 
               attenuation * vec3(_LightColor0) * vec3(_Color) 
               * max(0.0, dot(normalDirection, lightDirection));
            
            vec3 specularReflection;
            if (dot(normalDirection, lightDirection) < 0.0) 
               // light source on the wrong side?
            {
               specularReflection = vec3(0.0, 0.0, 0.0); 
                  // no specular reflection
            }
            else // light source on the right side
            {
               specularReflection = attenuation * vec3(_LightColor0) 
                  * vec3(_SpecColor) * pow(max(0.0, dot(
                  reflect(-lightDirection, normalDirection), 
                  viewDirection)), _Shininess);
            }

            color = vec4(diffuseReflection + specularReflection, 1.0); 
               // no ambient lighting in this pass
            gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
         }
         
         #endif

         #ifdef FRAGMENT
         
         void main()
         {
            gl_FragColor = color;
         }
         
         #endif

         ENDGLSL
      }
   } 
   // The definition of a fallback shader should be commented out 
   // during development:
   // Fallback "Specular"
}

总结

[edit | edit source]

恭喜,您刚刚学习了如何实现 Phong 反射模型。具体来说,我们已经看到了

  • Phong 反射模型中的环境光照是什么。
  • Phong 反射模型中的镜面反射项是什么。
  • 如何在 Unity 的 GLSL 中实现这些项。

进一步阅读

[edit | edit source]

如果您还想了解更多


< GLSL 编程/Unity

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