跳轉到內容

GLSL 程式設計/Unity/拉絲金屬

來自 Wikibooks,開放世界中的開放書籍
拉絲鋁。請注意鏡面高光的形狀,它遠非圓形。

本教程涵蓋各向異性鏡面高光

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

雖然 Phong 反射模型對於紙張、塑膠和一些具有各向同性反射(即圓形高光)的其他材料來說相當不錯,但本教程專門介紹具有各向異性反射(即非圓形高光)的材料,例如左側照片中的拉絲鋁。

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

Ward 的各向異性反射模型

[編輯 | 編輯原始碼]

Gregory Ward 在他的作品“測量和建模各向異性反射”,計算機圖形(SIGGRAPH ’92 論文集),第 265-272 頁,1992 年 7 月中發表了一個合適的各向異性反射模型。(該論文的副本可以在 網上 獲得。)該模型用 BRDF(雙向反射分佈函式)來描述反射,BRDF 是一個四維函式,它描述了來自任何方向的光線如何反射到任何其他方向。他的 BRDF 模型由兩項組成:漫反射項,它是 ,以及一個更復雜的鏡面反射項。

讓我們先來看看漫反射項 只是一個常數(約為 3.14159),而 指定漫反射率。原則上,每個波長都需要一個反射率;然而,通常為三個顏色分量(紅色、綠色和藍色)中的每一個指定一個反射率。如果我們包含常數 只是代表漫反射材質顏色 ,我們首先在“漫反射”部分中看到它,但它也出現在 Phong 反射模型中(參見“鏡面高光”部分)。你可能想知道為什麼因子 max(0, L·N) 沒有出現在 BRDF 中。答案是 BRDF 的定義方式,它不包含該因子(因為它不是材料的真正屬性),但在進行任何光照計算時,應該將其與 BRDF 相乘。

因此,為了實現給定不透明材料的 BRDF,我們必須將 BRDF 的所有項與 max(0, L·N) 相乘,並且 − 除非我們要實現物理上正確的照明 − 我們可以將任何常數因子替換為使用者指定的顏色,這通常比物理量更容易控制。

對於他 BRDF 模型的鏡面項,Ward 在他的論文的方程 5b 中給出了一個近似值。我稍微調整了一下,使其使用歸一化表面法向量 N、歸一化指向觀察者的方向 V、歸一化指向光源的方向 L 以及歸一化半向量 H,它是 (V + L) / |V + L|。使用這些向量,Ward 對鏡面項的近似值變為

這裡, 是鏡面反射率,描述鏡面高光的顏色和強度; 是描述高光形狀和大小的材料常數。由於所有這些變數都是材料常數,我們可以將它們合併成一個常數 。因此,我們得到一個稍微簡短的版本

請記住,在著色器中實現它時,我們仍然必須將此 BRDF 項乘以 **L**·**N**,如果 **L**·**N** 小於 0,則將其設定為 0。此外,如果 **V**·**N** 小於 0,它也應該為 0,即如果我們從“錯誤”一側觀察表面。

還有兩個向量尚未描述:**T** 和 **B**。**T** 是表面上的刷子方向,**B** 與 **T** 正交,但也位於表面上。Unity 為我們提供了一個表面上的切線向量作為頂點屬性(參見 部分“著色器的除錯”),我們將使用它作為向量 **T**。計算 **N** 和 **T** 的叉積生成向量 **B**,它與 **N** 和 **T** 正交,正如它應該的那樣。

Ward BRDF 模型的實現

[編輯 | 編輯原始碼]

我們的實現基於 “平滑鏡面高光”部分 中的每個畫素光照著色器。我們需要另一個變化變數 tangentDirection 用於切線向量 T(即畫筆方向),我們計算另外兩個方向:halfwayVector 用於半程向量 HbinormalDirection 用於副法線向量 B。屬性是 _Color 用於 _SpecColor 用於 _AlphaX 用於 ,以及 _AlphaY 用於

然後,片段著色器與 “平滑鏡面高光”部分 中的版本非常相似,除了它規範化 tangentDirection,計算 halfwayVectorbinormalDirection,並實現鏡面部分的不同方程。此外,這個著色器僅計算一次點積 L·N 並將其儲存在 dotLN 中,這樣就可以重複使用它,而無需重新計算。它看起來像這樣

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

            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 halfwayVector = 
               normalize(lightDirection + viewDirection);
	    vec3 binormalDirection = 
               cross(normalDirection, tangentDirection);
            float dotLN = dot(lightDirection, normalDirection); 
               // compute this dot product only once
            
            vec3 ambientLighting = vec3(gl_LightModel.ambient) 
               * vec3(_Color);

            vec3 diffuseReflection = attenuation * vec3(_LightColor0) 
               * vec3(_Color) * max(0.0, dotLN);
            
            vec3 specularReflection;
            if (dotLN < 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
            {
               float dotHN = dot(halfwayVector, normalDirection);
               float dotVN = dot(viewDirection, normalDirection);
               float dotHTAlphaX = 
                  dot(halfwayVector, tangentDirection) / _AlphaX;
               float dotHBAlphaY = dot(halfwayVector, 
                  binormalDirection) / _AlphaY;

               specularReflection = attenuation * vec3(_SpecColor) 
                  * sqrt(max(0.0, dotLN / dotVN)) 
                  * exp(-2.0 * (dotHTAlphaX * dotHTAlphaX 
                  + dotHBAlphaY * dotHBAlphaY) / (1.0 + dotHN));
            }

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

注意術語 sqrt(max(0, dotLN / dotVN)),它是從 乘以 得來的。這確保了所有內容都大於 0。

完整的著色器程式碼

[編輯 | 編輯原始碼]

完整的著色器程式碼僅定義了適當的屬性和切線屬性。此外,它需要一個具有加性混合但沒有環境光照的第二個通道,用於額外的光源。

Shader "GLSL anisotropic per-pixel lighting" {
   Properties {
      _Color ("Diffuse Material Color", Color) = (1,1,1,1) 
      _SpecColor ("Specular Material Color", Color) = (1,1,1,1) 
      _AlphaX ("Roughness in Brush Direction", Float) = 1.0
      _AlphaY ("Roughness orthogonal to Brush Direction", Float) = 1.0
   }
   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 _AlphaX;
         uniform float _AlphaY;

         // 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
         varying vec3 varyingTangentDirection; 
            // brush direction in world space

         #ifdef VERTEX

         attribute vec4 Tangent; // tangent vector provided 
            // by Unity (used as brush direction)
         
         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));
            varyingTangentDirection = normalize(vec3(
               modelMatrix * vec4(vec3(Tangent), 0.0)));

            gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
         }
         
         #endif

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

            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 halfwayVector = 
               normalize(lightDirection + viewDirection);
	    vec3 binormalDirection = 
               cross(normalDirection, tangentDirection);
            float dotLN = dot(lightDirection, normalDirection); 
               // compute this dot product only once
            
            vec3 ambientLighting = 
               vec3(gl_LightModel.ambient) * vec3(_Color);

            vec3 diffuseReflection = attenuation * vec3(_LightColor0) 
               * vec3(_Color) * max(0.0, dotLN);
            
            vec3 specularReflection;
            if (dotLN < 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
            {
               float dotHN = dot(halfwayVector, normalDirection);
               float dotVN = dot(viewDirection, normalDirection);
               float dotHTAlphaX = 
                  dot(halfwayVector, tangentDirection) / _AlphaX;
               float dotHBAlphaY = 
                  dot(halfwayVector, binormalDirection) / _AlphaY;

               specularReflection = attenuation * vec3(_SpecColor) 
                  * sqrt(max(0.0, dotLN / dotVN)) 
                  * exp(-2.0 * (dotHTAlphaX * dotHTAlphaX 
                  + dotHBAlphaY * dotHBAlphaY) / (1.0 + dotHN));
            }

            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 _AlphaX;
         uniform float _AlphaY;

         // 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
         varying vec3 varyingTangentDirection; 
            // brush direction in world space

         #ifdef VERTEX

         attribute vec4 Tangent; // tangent vector provided 
            // by Unity (used as brush direction)
         
         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));
            varyingTangentDirection = normalize(vec3(
               modelMatrix * vec4(vec3(Tangent), 0.0)));

            gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
         }
         
         #endif

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

            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 halfwayVector = 
               normalize(lightDirection + viewDirection);
	    vec3 binormalDirection = 
               cross(normalDirection, tangentDirection);
            float dotLN = dot(lightDirection, normalDirection); 
               // compute this dot product only once
            
            vec3 diffuseReflection = attenuation * vec3(_LightColor0) 
               * vec3(_Color) * max(0.0, dotLN);
            
            vec3 specularReflection;
            if (dotLN < 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
            {
               float dotHN = dot(halfwayVector, normalDirection);
               float dotVN = dot(viewDirection, normalDirection);
               float dotHTAlphaX = 
                  dot(halfwayVector, tangentDirection) / _AlphaX;
               float dotHBAlphaY = 
                  dot(halfwayVector, binormalDirection) / _AlphaY;

               specularReflection = attenuation * vec3(_SpecColor) 
                  * sqrt(max(0.0, dotLN / dotVN)) 
                  * exp(-2.0 * (dotHTAlphaX * dotHTAlphaX 
                  + dotHBAlphaY * dotHBAlphaY) / (1.0 + dotHN));
            }

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

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

恭喜你完成了這個相當高階的教程!我們已經看到了

  • 什麼是 BRDF(雙向反射分佈函式)。
  • Ward 用於各向異性反射的 BRDF 模型是什麼。
  • 如何實現 Ward 的 BRDF 模型。

進一步閱讀

[編輯 | 編輯原始碼]

如果你還想了解更多

  • 關於 Phong 反射模型的光照,你可以閱讀 “鏡面高光”部分
  • 關於每個畫素的光照(即 Phong 著色),你可以閱讀 “平滑鏡面高光”部分
  • 關於 Ward 的 BRDF 模型,你可以閱讀他的文章“測量和建模各向異性反射”,計算機圖形學(SIGGRAPH ’92 論文集),第 265-272 頁,1992 年 7 月。(該論文的副本可 線上獲取。)或者你可以閱讀 Randi Rost 等人於 2009 年由 Addison-Wesley 出版的三版《OpenGL 著色語言》第 14.3 節,或 Wolfgang Engel、Jack Hoxley、Ralf Kornmann、Niko Suni 和 Jason Zink 編著的《程式設計頂點、幾何和畫素著色器》(第二版,2008 年)照明章節的第 8 節(可在 線上獲取)。



< GLSL 程式設計/Unity

除非另有說明,否則此頁面上的所有示例原始碼均為公共領域。
華夏公益教科書