跳轉到內容

GLSL 程式設計/Blender/反射表面

來自華夏公益教科書,開放的書籍,開放的世界
File:Cloud Gate Chicago.jpg
反射表面的示例:“雲門”雕塑,位於芝加哥。

本教程介紹了反射對映(以及用於實現它的立方體貼圖)。

這是關於高階紋理對映技術的一系列小型教程中的第二個教程;具體來說,是關於在 Blender 中使用立方體貼圖進行環境對映。本教程基於 平滑鏡面高光教程 中描述的逐畫素光照,以及在 紋理球體教程 中介紹的紋理對映概念。

天空盒是一個圍繞整個場景的(無限)大盒子。這裡,反射的攝像機光線(即視線)擊中了天空盒的紋理面之一。

使用天空盒進行反射對映

[編輯 | 編輯原始碼]

左側的插圖描述了使用靜態天空盒進行反射對映的概念:視線在物體表面的一點處被反射,並且反射的光線與天空盒相交以確定相應畫素的顏色。天空盒只是一個帶有紋理面的大立方體,它圍繞著整個場景。需要注意的是,天空盒通常是靜態的,不包含場景中的任何動態物體。但是,用於反射對映的“天空盒”通常渲染為從特定視角包含場景。然而,這超出了本教程的範圍。此外,本教程只涵蓋了反射的計算,不涵蓋天空盒的渲染。

為了在物體中反射天空盒,我們必須渲染物體並將來自攝像機的光線反射到表面法向量處的表面點。這種反射的數學原理與光線在表面法向量處的反射相同,我們在 鏡面高光教程 中討論過。

一旦我們獲得了反射的光線,就必須計算它與大型天空盒的交點。如果天空盒無限大,這個計算實際上會變得更容易:在這種情況下,表面點的​​位置完全無關緊要,因為它到座標系原點的距離與天空盒的大小相比是無限小的;因此,只有反射光線的​​方向很重要,而不是它的位置。因此,我們實際上也可以考慮從一個小天空盒的中心發出的光線,而不是從無限大天空盒中的某個地方發出的光線。(如果您不熟悉這個想法,您可能需要一些時間來接受它。)根據反射光線的​​方向,它將與紋理天空盒的六個面之一相交。我們可以計算出哪個面被相交以及面被相交的位置,然後在特定面的紋理影像中進行紋理查詢(參見 紋理球體教程)。但是,GLSL 提供了立方體貼圖,它使用方向向量來精確地支援在立方體的六個面中進行這種紋理查詢。因此,我們所需要做的就是為環境提供一個立方體貼圖作為著色器屬性,並使用帶有反射方向的 textureCube 指令來獲取立方體貼圖中相應位置的顏色。

Blender 立方體貼圖。

立方體貼圖

[編輯 | 編輯原始碼]

左側的影像顯示了 Blender 立方體貼圖,它在華夏公益教科書 “Blender 3D 菜鳥到專業” 中有介紹。實際上,它是 Blender 華夏公益教科書中描述的立方體貼圖的縮小版本,因為 GLSL 著色器的立方體貼圖的面的尺寸必須是 2 的冪,例如 128 × 128。

為了在球體中使用左側的立方體貼圖進行反射,新增一個 UV 球體網格,為它賦予一個材質,並像在 紋理球體教程 中一樣建立一個新的紋理。但是,在屬性視窗 > 紋理選項卡中選擇型別:環境貼圖,然後點選環境貼圖 > 影像檔案。然後下載影像,並在 Blender 中使用環境貼圖 > 開啟按鈕開啟它。(如果您選擇對映 > 座標:反射,則預覽 > 材質應顯示應用的反射貼圖。)

頂點著色器必須在世界空間中計算視線方向 viewDirection 和法線方向 normalDirection。我們在 鏡面高光教程 中看到了如何在檢視空間中計算它們。因此,我們使用這段程式碼進入世界空間,然後透過使用逆檢視矩陣進行變換來返回檢視空間,如 檢視空間著色教程 中所述。另一個複雜之處在於法向量必須使用轉置的逆矩陣進行變換(參見 “應用矩陣變換”)。在這種情況下,逆矩陣是逆檢視矩陣的逆,它只是檢視矩陣本身。因此,頂點著色器可以是

         uniform mat4 viewMatrix; // world to view transformation
         uniform mat4 viewMatrixInverse; 
            // view to world transformation

         varying vec3 viewDirection; // direction in world space 
            // in which the viewer is looking
         varying vec3 normalDirection; // normal vector in world space 
         
         void main()
         {
            vec4 positionInViewSpace = gl_ModelViewMatrix * gl_Vertex;
               // transformation of gl_Vertex from object coordinates 
               // to view coordinates
            vec4 viewDirectionInViewSpace = positionInViewSpace 
               - vec4(0.0, 0.0, 0.0, 1.0);
               // camera is always at (0,0,0,1) in view coordinates;
               // this is the direction in which the viewer is looking 
               // (not the direction to the viewer)
            viewDirection = 
               vec3(viewMatrixInverse * viewDirectionInViewSpace);
               // transformation from view coordinates to 
               // world coordinates
            vec3 normalDirectionInViewSpace = 
               gl_NormalMatrix * gl_Normal;
               // transformation of gl_Normal from object coordinates 
               // to view coordinates
            normalDirection = normalize(vec3(
               vec4(normalDirectionInViewSpace, 0.0) * viewMatrix));
               // transformation of normal vector from view coordinates 
               // to world coordinates with the transposed 
               // (multiplication of the vector from the left) of 
               // the inverse of viewMatrixInverse, which is viewMatrix
            gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
         }

此外,我們必須在 Python 指令碼中設定 viewMatrixviewMatrixInverse 的一致性。

      viewMatrix = \
          bge.logic.getCurrentScene().active_camera.world_to_camera
      shader.setUniformMatrix4('viewMatrix', viewMatrix)
      viewMatrixInverse = \
          bge.logic.getCurrentScene().active_camera.camera_to_world
      shader.setUniformMatrix4('viewMatrixInverse', viewMatrixInverse)

現在進入片段著色器。立方體貼圖在 GLSL 著色器中與常規的二維紋理類似,但使用 samplerCube 而不是 sampler2D

            uniform samplerCube cubeMap;

一致性在 Python 指令碼中設定,就像任何其他紋理取樣器一樣

         shader.setSampler('cubeMap', 0)

其中 0 指定紋理列表中的位置。

為了在片段著色器中反射視線方向,我們可以使用 GLSL 函式 reflect,如 鏡面高光教程 中所述

            vec3 reflectedDirection = 
               reflect(viewDirection, normalize(normalDirection));

為了在立方體貼圖中執行紋理查詢並將結果顏色儲存在片段顏色中,我們通常只需使用

             gl_FragColor = textureCube(cubeMap, reflectedDirection);

但是,Blender 似乎重新排列了立方體面;因此,我們必須調整 reflectedDirection 的座標

            gl_FragColor = textureCube(cubeMap, 
               vec3(reflectedDirection.x, -reflectedDirection.z, 
               reflectedDirection.y));

這將產生與材質預覽一致的反射。

完整的著色器程式碼

[編輯 | 編輯原始碼]

然後著色器程式碼變為

import bge

cont = bge.logic.getCurrentController()

VertexShader = """
         uniform mat4 viewMatrix; // world to view transformation
         uniform mat4 viewMatrixInverse; 
            // view to world transformation

         varying vec3 viewDirection; // direction in world space 
            // in which the viewer is looking
         varying vec3 normalDirection; // normal vector in world space 
        
         void main()
         {
            vec4 positionInViewSpace = gl_ModelViewMatrix * gl_Vertex;
               // transformation of gl_Vertex from object coordinates 
               // to view coordinates

            vec4 viewDirectionInViewSpace = positionInViewSpace 
               - vec4(0.0, 0.0, 0.0, 1.0);
               // camera is always at (0,0,0,1) in view coordinates;
               // this is the direction in which the viewer is looking 
               // (not the direction to the viewer)
            
            viewDirection = 
               vec3(viewMatrixInverse * viewDirectionInViewSpace);
               // transformation from view coordinates 
               // to world coordinates
               
            vec3 normalDirectionInViewSpace = 
               gl_NormalMatrix * gl_Normal;
               // transformation of gl_Normal from object coordinates 
               // to view coordinates

            normalDirection = normalize(vec3(
               vec4(normalDirectionInViewSpace, 0.0) * viewMatrix));
               // transformation of normal vector from view coordinates 
               // to world coordinates with the transposed 
               // (multiplication of the vector from the left) of 
               // the inverse of viewMatrixInverse, which is viewMatrix
            
            gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
         }
"""

FragmentShader = """
         varying vec3 viewDirection;
         varying vec3 normalDirection;
         uniform samplerCube cubeMap;

         void main()
         {
            vec3 reflectedDirection = reflect(viewDirection, 
               normalize(normalDirection));

            gl_FragColor = textureCube(cubeMap, 
               vec3(reflectedDirection.x, -reflectedDirection.z, 
               reflectedDirection.y));
               // usually this would be: gl_FragColor = 
               // textureCube(cubeMap, reflectedDirection);
               // however, Blender appears to reshuffle the faces a bit
         }
"""

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('cubeMap', 0)
        viewMatrix = \
            bge.logic.getCurrentScene().active_camera.world_to_camera
        shader.setUniformMatrix4('viewMatrix', viewMatrix)
        viewMatrixInverse = \
            bge.logic.getCurrentScene().active_camera.camera_to_world
        shader.setUniformMatrix4('viewMatrixInverse', \ 
            viewMatrixInverse)

恭喜!您已經完成了第一個關於環境貼圖的教程。我們已經看到了

  • 如何在物體中計算天空盒的反射。
  • 如何在 Blender 中匯入合適的立方體貼圖以及如何使用它們進行反射對映。

進一步閱讀

[編輯 | 編輯原始碼]

如果您還想了解更多


< GLSL 程式設計/Blender

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