跳轉到內容

GLSL 程式設計/Blender/平滑鏡面高光

來自華夏公益教科書
使用逐頂點光照渲染的環面網格。
使用逐畫素光照渲染的環面網格。

本教程涵蓋了逐畫素光照(也稱為Phong 著色)。

它基於鏡面高光教程。如果您還沒有閱讀過該教程,您應該先閱讀它。逐頂點光照(即為每個頂點計算表面光照,然後插值頂點顏色)的主要缺點是質量有限,特別是對於鏡面高光,如左側的圖形所示。解決方法是逐畫素光照,它根據插值的法向量為每個片段計算光照。雖然生成的影像質量明顯更高,但效能成本也很高。

逐畫素光照 (Phong 著色)

[編輯 | 編輯原始碼]

逐畫素光照也稱為 Phong 著色(與逐頂點光照形成對比,逐頂點光照也稱為 Gouraud 著色)。這不能與 Phong 反射模型(也稱為 Phong 光照)混淆,Phong 反射模型透過環境項、漫射項和鏡面項計算表面光照,如鏡面高光教程中所述。

逐畫素光照的關鍵思想很容易理解:法向量和位置為每個片段插值,並在片段著色器中計算光照。

著色器程式碼

[編輯 | 編輯原始碼]

除了最佳化之外,基於逐頂點光照的著色器程式碼實現逐畫素光照非常簡單:光照計算從頂點著色器移動到片段著色器,頂點著色器必須將光照計算所需的屬性寫入 varying。然後,片段著色器使用這些 varying 來計算光照(而不是頂點著色器使用的屬性)。就是這樣。

在本教程中,我們調整了鏡面高光教程中的著色器程式碼以進行逐畫素光照。生成的頂點著色器如下所示

         varying vec4 position; 
            // position of the vertex (and fragment) in view space 
         varying vec3 varyingNormalDirection; 
            // surface normal vector in view space

         void main()
         {                              
            position = gl_ModelViewMatrix * gl_Vertex; 
            varyingNormalDirection = 
               normalize(gl_NormalMatrix * gl_Normal);             

            gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
         }

大部分工作現在都在片段著色器中完成

         varying vec4 position; 
            // position of the vertex (and fragment) in view space 
         varying vec3 varyingNormalDirection; 
            // surface normal vector in view space

         void main()
         {
            vec3 normalDirection = normalize(varyingNormalDirection);
            vec3 viewDirection = -normalize(vec3(position)); 
            vec3 lightDirection;
            float attenuation;
 
            if (0.0 == gl_LightSource[0].position.w) 
               // directional light?
            {
               attenuation = 1.0; // no attenuation
               lightDirection = 
                  normalize(vec3(gl_LightSource[0].position));
            } 
            else // point light or spotlight (or other kind of light) 
            {
               vec3 positionToLightSource = 
                  vec3(gl_LightSource[0].position - position);
               float distance = length(positionToLightSource);
               attenuation = 1.0 / distance; // linear attenuation 
               lightDirection = normalize(positionToLightSource);
 
               if (gl_LightSource[0].spotCutoff <= 90.0) // spotlight?
               {
                  float clampedCosine = max(0.0, dot(-lightDirection, 
                     gl_LightSource[0].spotDirection));
                  if (clampedCosine < gl_LightSource[0].spotCosCutoff) 
                     // outside of spotlight cone?
                  {
                     attenuation = 0.0;
                  }
                  else
                  {
                     attenuation = attenuation * pow(clampedCosine, 
                        gl_LightSource[0].spotExponent);   
                  }
               }
            }

            vec3 ambientLighting = vec3(gl_LightModel.ambient) 
               * vec3(gl_FrontMaterial.emission);
              
            vec3 diffuseReflection = attenuation 
               * vec3(gl_LightSource[0].diffuse) 
               * vec3(gl_FrontMaterial.emission)
               * 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(gl_LightSource[0].specular) 
                  * vec3(gl_FrontMaterial.specular) 
                  * pow(max(0.0, dot(reflect(-lightDirection, 
                  normalDirection), viewDirection)), 
                  gl_FrontMaterial.shininess);
            }

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

請注意,頂點著色器將歸一化向量寫入 varyingNormalDirection,以確保所有方向在插值中都得到同等權重。片段著色器再次對其進行歸一化,因為插值後的方向不再是歸一化的。

恭喜,現在您知道逐畫素 Phong 光照是如何工作的。我們已經看到了

  • 為什麼逐頂點光照提供的質量有時不夠(特別是由於鏡面高光)。
  • 逐畫素光照的工作原理以及如何基於逐頂點光照的著色器實現它。

進一步閱讀

[編輯 | 編輯原始碼]

如果您仍然想了解更多


< GLSL 程式設計/Blender

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