跳至內容

GLSL 程式設計/Blender/紋理層

來自華夏公益教科書
人皮膚層。

本教程介紹了**多紋理對映**,即在著色器中使用多個紋理影像。

它擴充套件了紋理球教程中的著色器程式碼,使其支援多個紋理,並展示了將它們組合在一起的方法。如果你還沒有讀過這些教程,現在就是個好機會。

表面層

[編輯 | 編輯原始碼]

許多真實表面(例如左側影像中的人類皮膚)由幾層不同顏色、透明度、反射率等組成。如果最頂層是不透明的並且不透光,那麼渲染表面時實際上並不重要。然而,在許多情況下,最頂層是(半)透明的,因此對錶面的準確渲染必須考慮多層。

實際上,包含在 Phong 反射模型中的鏡面反射(參見鏡面高光教程)通常對應於一個透明層,它會反射光線:人類皮膚上的汗水、水果上的蠟、嵌入色素顆粒的透明塑膠等等。另一方面,漫反射對應於最頂層透明層下方的層。

照亮這種分層表面不需要幾何模型來表示這些層:它們可以用一個單一的、無限薄的多邊形網格來表示。然而,照明計算必須對不同的層計算不同的反射,並且必須考慮光在層之間的傳輸(當光進入層時和當光離開層時)。這種方法的例子包括 Nvidia 的“Dawn”演示(參見“GPU Gems”一書的第 3 章,該書可以在網上獲取)以及 Nvidia 的“Human Head”演示(參見“GPU Gems 3”一書的第 14 章,該書也可以在網上獲取)。

本教程無法完整描述這些過程。只需說,層通常與紋理影像相關聯,以指定它們的特徵。這裡,我們只展示瞭如何使用兩種紋理和一種特定的組合方式。實際上,這個例子與層無關,因此說明了多紋理對映除了表面層之外還有更多應用。

未照亮地球的地圖。
陽光照射地球的地圖。

亮暗地球

[編輯 | 編輯原始碼]

由於人類活動,地球的陰暗面並不完全黑暗。相反,人造燈光標誌著城市的位置和延伸,如左側影像所示。因此,地球的漫射光照不應該僅僅使陽光照射表面的紋理影像變暗,而應該將其與陰暗面的紋理影像混合。請注意,陽光照射的地球遠比陰暗面的人造燈光亮;然而,我們降低了這種對比度,以便顯示夜間紋理。

著色器程式碼擴充套件了紋理球教程中的程式碼,使其支援兩種紋理影像,並使用漫射反射教程中描述的計算方法來處理單個方向光源

根據這個公式,漫射光照的級別 levelOfLighting 是 max(0, N·L)。然後,我們根據 levelOfLighting 混合白天紋理和夜間紋理的顏色。這可以透過將白天顏色乘以 levelOfLighting,將夜間顏色乘以 1.0 - levelOfLighting,然後將它們加在一起來確定片段的顏色來實現。或者,可以使用內建的 GLSL 函式 mixmix(a, b, w) = b*w + a*(1.0-w)),這很可能更有效。因此,片段著色器可以是(同樣使用我們特定的 longitudeLatitude 中的紋理座標計算)

         void main()
         {
            vec2 longitudeLatitude = vec2(
               (atan(texCoords.y, texCoords.x)/3.1415926+1.0)*0.5, 
               1.0 - acos(texCoords.z) / 3.1415926);

            nighttimeColor = 
               texture2D(nighttimeTexture, longitudeLatitude);    
            daytimeColor = 
               texture2D(daytimeTexture, longitudeLatitude);    
            gl_FragColor = mix(nighttimeColor, daytimeColor, 
               levelOfLighting);
               // = daytimeColor * levelOfLighting 
               // + nighttimeColor * (1.0 - levelOfLighting)
         }

請注意,這種混合與透明度教程中討論的 alpha 混合非常相似,只是我們在片段著色器內部執行混合,並使用 levelOfLighting 而不是應該混合到另一個紋理之上的紋理的 alpha 分量(即不透明度)。實際上,如果 daytimeTexture 指定了 alpha 分量(參見透明紋理教程),我們可以使用這個 alpha 分量來將 daytimeTexture 混合到 nighttimeTexture 上。這對應於一個部分透明的層,它位於一個不透明層的頂部,該不透明層在最頂層透明的地方可見。

完整著色器程式碼

[編輯 | 編輯原始碼]

著色器程式碼將 gl_FrontMaterial.emission 按分量乘以夜間紋理的紋理顏色,以便可以透過更改材質選項卡陰影 > 發射的值來控制其整體亮度。此外,光源的顏色 gl_LightSource[0].diffuse 也按分量乘以最終顏色,以便考慮彩色光源。

import bge

cont = bge.logic.getCurrentController()

VertexShader = """
         varying float levelOfLighting; // the level of diffuse  
            // lighting that is computed in the vertex shader
         varying vec4 texCoords;

         void main()
         {				
            vec3 normalDirection = 
               normalize(gl_NormalMatrix * gl_Normal);
            vec3 lightDirection;
 
            if (0.0 == gl_LightSource[0].position.w) 
               // directional light?
            {
               lightDirection = 
                  normalize(vec3(gl_LightSource[0].position));
            } 
            else // point light or spotlight (or other kind of light) 
            {
               vec3 vertexToLightSource = 
                  vec3(gl_LightSource[0].position 
                  - gl_ModelViewMatrix * gl_Vertex);
               lightDirection = normalize(vertexToLightSource);
            }
            
            levelOfLighting = 
               max(0.0, dot(normalDirection, lightDirection));
            texCoords = gl_MultiTexCoord0;
            gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
         }
"""

FragmentShader = """
         varying float levelOfLighting; 
         varying vec4 texCoords;
         uniform sampler2D daytimeTexture;
         uniform sampler2D nighttimeTexture;
        
         void main()
         {
            vec2 longitudeLatitude = vec2(
               (atan(texCoords.y, texCoords.x)/3.1415926+1.0)*0.5, 
               1.0 - acos(texCoords.z) / 3.1415926);

            vec4 nighttimeColor = 
               texture2D(nighttimeTexture, longitudeLatitude) 
               * gl_FrontMaterial.emission;    
            vec4 daytimeColor = 
               texture2D(daytimeTexture, longitudeLatitude);    
            gl_FragColor = mix(nighttimeColor, daytimeColor, 
               levelOfLighting) * gl_LightSource[0].diffuse;
               // = daytimeColor * levelOfLighting 
               // + nighttimeColor * (1.0 - levelOfLighting) * ...
         }
"""

mesh = cont.owner.meshes[0]
for mat in mesh.materials:
    shader = mat.getShader()
    if shader != None:
        if not shader.isValid():
            shader.setSource(VertexShader, FragmentShader, 1)
            shader.setSampler('daytimeTexture', 0)
            shader.setSampler('nighttimeTexture', 1)

使用這個著色器時,你必須確保白天紋理位於材質選項卡 > 紋理列表中的第一個位置,而夜間紋理位於第二個位置。否則,你必須調整 Python 指令碼最後兩行中的整數。

恭喜!你已經完成了最後一個關於基礎紋理對映的教程。我們已經瞭解了

  • 表面層如何影響材質的外觀(例如人皮膚、打蠟的水果、塑膠等等)
  • 如何在對代表地球的球體進行紋理對映時考慮陰暗面的人造燈光。
  • 如何在著色器中實現這種技術。
  • 這與將 alpha 紋理混合到另一個不透明紋理之上的關係。

進一步閱讀

[編輯 | 編輯原始碼]

如果你還想了解更多

  • 關於基礎紋理對映,你應該閱讀紋理球教程
  • 關於漫射反射,你應該閱讀漫射反射教程
  • 關於 alpha 紋理,你應該閱讀透明紋理教程
  • 關於高階皮膚渲染,你可以閱讀 Randima Fernando(編輯)出版的《GPU Gems》一書中的第 3 章“‘Dawn’ 演示中的皮膚” by Curtis Beeson and Kevin Bjorke,該書由 Addison-Wesley 於 2004 年出版,可以在網上獲取,以及 Hubert Nguyen(編輯)出版的《GPU Gems 3》一書中的第 14 章“用於逼真的即時皮膚渲染的高階技術” by Eugene d’Eon and David Luebke,該書由 Addison-Wesley 於 2007 年出版,也可以在網上獲取。


< GLSL 程式設計/Blender

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