跳轉到內容

GLSL 程式設計/Unity/輪廓處的鏡面高光

來自 Wikibooks,開放世界中的開放書籍
里斯本行人照片。請注意由於背光產生的明亮輪廓。

本教程涵蓋了鏡面高光的菲涅爾係數

它是關於光照的多個教程之一,這些教程超越了 Phong 反射模型。但是,它基於使用 Phong 反射模型進行的光照,如“鏡面高光”部分(針對每個頂點的光照)和“平滑鏡面高光”部分(針對每個畫素的光照)中所述。如果您還沒有閱讀這些教程,建議您先閱讀。

許多材料(如啞光紙)在光線掠過表面時會顯示出強烈的鏡面反射;即,當背光從與觀察者相反的方向反射時,如左側的照片所示。菲涅爾係數解釋了一些材料的這種強反射。當然,輪廓明亮還有其他原因,例如半透明的頭髮或織物(見“半透明表面”部分)。

有趣的是,這種效果通常很難察覺,因為它最有可能發生在輪廓背景非常明亮的情況下。但是,在這種情況下,明亮的輪廓將與背景融合,因此幾乎無法察覺。

除了 Phong 反射模型使用的大多數向量之外,我們還需要歸一化的半程向量 H,它是在觀察者方向 V 和光源方向 L 之間的精確方向。

菲涅爾係數的 Schlick 近似

[編輯 | 編輯原始碼]

菲涅爾係數 描述了非導電材料對波長為 的非偏振光的鏡面反射率。Schlick 近似為

其中 V 是歸一化的觀察者方向,H 是歸一化的半程向量:H = (V + L) / |V + L|,其中 L 是歸一化的光源方向。 H·V = 1 時的反射率,即當光源方向、觀察者方向和半程向量都相同時。另一方面,H·V = 0 時變為 1,即當半程向量與觀察者方向正交時,這意味著光源方向與觀察者方向相反(即掠入射光反射的情況)。事實上, 在這種情況下與波長無關,材料的行為就像一個完美的鏡子。

使用內建的 GLSL 函式 mix(x,y,w) = x*(1-w) + y*w,我們可以將 Schlick 近似改寫為

     

這可能稍微更有效,至少在某些 GPU 上是如此。我們將透過允許不同的 值來考慮對波長的依賴性,每個顏色分量;也就是說,我們將它視為一個 RGB 向量。事實上,我們將它與來自 “鏡面高光”部分 的常數材質顏色 相一致。換句話說,菲涅爾因子增加了材質顏色 對觀察者方向和半程向量之間角度的依賴性。因此,我們在任何鏡面反射計算中用施裡克近似值(使用 )替換了常數材質顏色

例如,我們在 Phong 反射模型中對鏡面項的方程式是(參見 “鏡面高光”部分

用 Schlick 近似值來替換菲涅爾因子中的 ,其中 ,得到

該實現基於來自“平滑鏡面高光”部分的著色器程式碼。它只是計算了半程向量,幷包含了菲涅耳因子的近似值

            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
            {
               vec3 halfwayDirection = 
                  normalize(lightDirection + viewDirection);
               float w = pow(1.0 - max(0.0, 
                  dot(halfwayDirection, viewDirection)), 5.0);
               specularReflection = attenuation * vec3(_LightColor0) 
                  * mix(vec3(_SpecColor), vec3(1.0), w)
                  * pow(max(0.0, dot(
                  reflect(-lightDirection, normalDirection), 
                  viewDirection)), _Shininess);
            }

完整著色器程式碼

[編輯 | 編輯原始碼]

將上面的程式碼片段放入來自“平滑鏡面高光”部分的完整著色器中,會得到這個著色器

Shader "GLSL Fresnel highlights" {
   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 position; 
            // position of the vertex (and fragment) in world space 
         varying vec3 varyingNormalDirection; 
            // surface normal vector in world space

         #ifdef VERTEX
         
         void main()
         {				
            mat4 modelMatrix = _Object2World;
            mat4 modelMatrixInverse = _World2Object; // unity_Scale.w 
               // is unnecessary because we normalize vectors
            
            position = modelMatrix * gl_Vertex;
            varyingNormalDirection = normalize(vec3(
               vec4(gl_Normal, 0.0) * modelMatrixInverse));

            gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
         }
         
         #endif

         #ifdef FRAGMENT
         
         void main()
         {
            vec3 normalDirection = normalize(varyingNormalDirection);

            vec3 viewDirection = 
               normalize(_WorldSpaceCameraPos - vec3(position));
            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 - position);
               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
            {
               vec3 halfwayDirection = 
                  normalize(lightDirection + viewDirection);
               float w = pow(1.0 - max(0.0, 
                  dot(halfwayDirection, viewDirection)), 5.0);
               specularReflection = attenuation * vec3(_LightColor0) 
                  * mix(vec3(_SpecColor), vec3(1.0), w) 
                  * pow(max(0.0, dot(
                  reflect(-lightDirection, normalDirection), 
                  viewDirection)), _Shininess);
            }

            gl_FragColor = vec4(ambientLighting 
               + diffuseReflection + specularReflection, 1.0);
         }
         
         #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 position; 
            // position of the vertex (and fragment) in world space 
         varying vec3 varyingNormalDirection; 
            // surface normal vector in world space

         #ifdef VERTEX
         
         void main()
         {				
            mat4 modelMatrix = _Object2World;
            mat4 modelMatrixInverse = _World2Object; // unity_Scale.w 
               // is unnecessary because we normalize vectors
            
            position = modelMatrix * gl_Vertex;
            varyingNormalDirection = normalize(vec3(
               vec4(gl_Normal, 0.0) * modelMatrixInverse));

            gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
         }
         
         #endif

         #ifdef FRAGMENT
         
         void main()
         {
            vec3 normalDirection = normalize(varyingNormalDirection);

            vec3 viewDirection = 
               normalize(_WorldSpaceCameraPos - vec3(position));
            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 - position);
               float distance = length(vertexToLightSource);
               attenuation = 1.0 / distance; // linear attenuation 
               lightDirection = normalize(vertexToLightSource);
            }
            
            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
            {
               vec3 halfwayDirection = 
                  normalize(lightDirection + viewDirection);
               float w = pow(1.0 - max(0.0, 
                  dot(halfwayDirection, viewDirection)), 5.0);
               specularReflection = attenuation * vec3(_LightColor0) 
                  * mix(vec3(_SpecColor), vec3(1.0), w)
                  * pow(max(0.0, dot(
                  reflect(-lightDirection, normalDirection), 
                  viewDirection)), _Shininess);
            }

            gl_FragColor = 
               vec4(diffuseReflection + specularReflection, 1.0);
         }
         
         #endif

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

藝術控制

[編輯 | 編輯原始碼]

上面實現的一個有用修改是將冪5.0替換為使用者指定的著色器屬性。這將使 CG 藝術家根據他們的藝術需求來誇大或衰減菲涅耳因子的影響。

對半透明表面的影響

[編輯 | 編輯原始碼]

除了影響鏡面高光之外,菲涅耳因子還應該影響半透明表面的不透明度。事實上,菲涅耳因子描述了表面如何對掠射光線變得更具反射性,這意味著吸收、折射或透射的光線更少,即透明度降低,因此不透明度提高。為此,可以使用表面法線向量N而不是半程向量H來計算菲涅耳因子,半透明表面的不透明度可以從使用者指定的數值(在表面法線方向的觀察中)增加到 1(獨立於波長)與

.

“輪廓增強”部分,不透明度被認為是由光線穿過一層半透明材料時衰減引起的。這種不透明度應該與由於反射率增加而導致的不透明度結合起來:總的不透明度是 1 減去總透明度,它是由於衰減引起的透明度(即 1 減去)和由於菲涅耳因子引起的透明度(即 1 減去)的乘積,即

     

是不透明度,如上計算,而是不透明度,如“輪廓增強”部分中計算的那樣。對於平行於表面法向量的視角,可以由使用者指定。然後,該方程固定用於法線方向,實際上,它固定了所有常數,因此可以針對所有視角進行計算。請注意,漫反射或鏡面反射都不應與不透明度相乘,因為鏡面反射已經與菲涅耳因子相乘,而漫反射應該只與由於衰減引起的不透明度相乘。

恭喜你完成了其中一個比較高階的教程!我們已經瞭解了:

  • 什麼是菲涅爾係數。
  • 施裡克對菲涅爾係數的近似方法。
  • 如何在鏡面高光中實現施裡克的近似方法。
  • 如何為實現新增更多藝術控制。
  • 如何將菲涅爾係數用於半透明表面。

進一步閱讀

[編輯 | 編輯原始碼]

如果你還想了解更多

  • 關於使用 Phong 反射模型進行光照,你可以閱讀 “鏡面高光”部分
  • 關於逐畫素光照(即 Phong 著色),你可以閱讀 “平滑鏡面高光”部分
  • 關於施裡克的近似方法,你可以閱讀他的文章“一種用於物理基礎渲染的廉價 BRDF 模型”,作者 Christophe Schlick,計算機圖形論壇,13(3):233—246,1994 年。或者你可以閱讀書籍“OpenGL 著色語言”(第 3 版)第 14.1 章,作者 Randi Rost 等,2009 年由 Addison-Wesley 出版,或書籍“程式設計頂點、幾何體和畫素著色器”(第 2 版,2008 年)的“光照”章節中的第 5 節,作者 Wolfgang Engel、Jack Hoxley、Ralf Kornmann、Niko Suni 和 Jason Zink(可以在 網上 獲得)。



< GLSL 程式設計/Unity

除非另有說明,本頁所有示例原始碼均授予公有領域。
華夏公益教科書