跳轉到內容

GLSL 程式設計/Blender/檢視空間著色

來自 Wikibooks,開放世界開放書籍
一些變色龍能夠根據周圍的世界改變它們的顏色。

本教程介紹了制服變數。它建立在最小著色器教程RGB 立方體教程著色器除錯教程的基礎之上。

在本教程中,我們將研究一個著色器,它根據片段在“檢視空間”中的位置改變片段顏色,“檢視空間”只是世界空間的旋轉版本,其中相機位於原點並指向負 軸,而 軸與相機的垂直軸對齊;請參閱“頂點變換”中的描述。這個概念並不複雜;然而,它有極其重要的應用,例如使用燈光和環境貼圖進行著色。我們也將看看現實世界中的著色器;例如,如何讓非程式設計師使用你的著色器?

從物件空間變換到檢視空間

[編輯 | 編輯原始碼]

正如著色器除錯教程中提到的,屬性gl_Vertex指定了物件座標,即網格區域性物件(或模型)空間中的座標。物件空間(或物件座標系)對每個物件都是唯一的;但是,所有物件都變換到一個共同的座標系中——檢視空間。從概念上講,它們首先被變換到共同的世界空間(透過模型變換),然後變換到檢視空間(透過檢視變換)。在實踐中,這兩個變換被組合成一個變換(模型檢視變換)。

如果一個物件直接放入世界空間,那麼物件到世界的變換由 Blender 中物件的屬性中的變換部分指定。要檢視它,請在3D 檢視中選擇該物件,然後開啟屬性視窗物件選項卡,並展開變換部分。這裡有“位置”、“旋轉”和“縮放”引數,它們指定了頂點如何從物件座標變換到世界座標。頂點透過平移、旋轉和縮放的變換,以及變換的組合及其作為 4×4 矩陣的表示,在“頂點變換”中進行了討論。該頁面還討論了從世界空間到檢視空間的變換,它只依賴於相機的 position 和方向(可以透過選擇相機並開啟屬性視窗進行檢查;可以透過在3D 檢視選單中選擇檢視 > 相機來啟用它)。

在以下示例中,從物件空間到檢視空間的變換被放入一個 4×4 矩陣中,它也被稱為“模型檢視矩陣”(因為它結合了“模型變換”和“檢視變換”)。此矩陣在內建制服變數gl_ModelViewMatrix中可用(類似於我們在前面教程中使用的gl_ModelViewProjectionMatrix)。此制服在以下頂點著色器中使用,該著色器應該分配給最小著色器教程中 Python 指令碼的變數VertexShader

         varying vec4 position_in_view_space;

         void main()
         {
            position_in_view_space = gl_ModelViewMatrix * gl_Vertex;
               // transformation of gl_Vertex from object coordinates 
               // to view coordinates;
            
            gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
         }

片段著色器應該分配給變數FragmentShader

         
         varying vec4 position_in_view_space;

         void main()
         {
            float dist = distance(position_in_view_space, 
               vec4(0.0, 0.0, 0.0, 1.0));
               // computes the distance between the fragment position 
               // and the origin (4th coordinate should always be 1 
               // for points). The origin in view space is actually 
               // the camera position.
            
            if (dist < 5.0)
            {
               gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0); 
                  // color near origin
            }
            else
            {
               gl_FragColor = vec4(0.3, 0.3, 0.3, 1.0); 
                  // color far from origin
            }
         }

通常,OpenGL 應用程式必須設定制服變數的值;但是,Blender 負責設定預定義制服變數的值,例如gl_ModelViewMatrixgl_ModelViewProjectionMatrix(實際上是矩陣乘積gl_ProjectionMatrix * gl_ModelViewMatrix);因此,我們不必擔心它。(然而,在某些情況下,除非用3D 檢視 > 檢視 > 相機 > 活動相機啟用相機檢視,否則 Blender 似乎會錯誤地設定其中一些矩陣(特別是gl_ModelViewMatrix)。)

此著色器將頂點位置變換到檢視空間,並將其以 varying 變數的形式提供給片段著色器。對於片段著色器,varying 變數包含片段在檢視座標中的插值位置。根據此位置到檢視座標系原點的距離,將設定兩種顏色之一。因此,如果你使用此著色器移動一個物件,它將在靠近相機的地方變成綠色。離相機越遠,它就會變成深灰色。

從檢視空間變換到世界空間

[編輯 | 編輯原始碼]

著色器中的許多計算可以在檢視空間中執行。由於光源的位置是在檢視空間中指定的,並且預設情況下沒有提供到世界空間的變換,因此這通常是最方便和最有效的座標系。但是,在某些情況下,需要在世界空間中工作(例如,使用位於世界空間中的環境貼圖時)。在這些情況下,需要在制服變數中為著色器提供從檢視空間到世界空間的變換。制服變數必須在使用它的任何著色器的 main 函式之外定義

uniform mat4 viewMatrixInverse;

mat4 只是 4×4 矩陣的資料型別,而uniform 表示它對所有頂點和片段的值都相同,它必須在外部設定。實際上,在 Blender 中,它是在 Python 指令碼中使用以下行設定的

shader.setUniformMatrix4('viewMatrixInverse', value_of_uniform)

其中shader 是著色器程式的 Python 物件,'viewMatrixInverse' 是著色器中制服變數的名稱,而value_of_uniform 是應該設定的值。setUnifomMatrix4 用於設定 4×4 矩陣制服,如Blender 的 Python API中所述。更多函式可用於設定其他型別制服。

這裡,我們需要一個 4×4 矩陣,它將點從檢視空間(也稱為眼睛或相機空間)變換到世界空間,可以透過以下方式獲得

value_of_uniform = bge.logic.getCurrentScene().active_camera.camera_to_world

即當前場景提供了一個活動相機,它提供了我們需要的變換。(僅僅為了完整性:相機還提供了world_to_camera變換的矩陣,即檢視矩陣本身。)

我們現在可以重寫示例,使其在世界空間中工作而不是檢視空間,即如果片段在世界空間中靠近原點,則將它們著色為綠色

import bge

cont = bge.logic.getCurrentController()

VertexShader = """
         uniform mat4 viewMatrixInverse; // view to world 
            // transformation (the view matrix corresponds to the 
            // world to view transformation; thus, the inverse 
            // matrix corresponds to the view to world trafo)

         varying vec4 position_in_world_space;

         void main()
         {
            vec4 position_in_view_space = 
               gl_ModelViewMatrix * gl_Vertex;
               // transformation of gl_Vertex from object coordinates 
               // to view coordinates;
            position_in_world_space = viewMatrixInverse * 
               position_in_view_space;
               // transformation of gl_Vertex from view coordinates 
               // to world coordinates;
            
            gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
         }
"""

FragmentShader = """
         varying vec4 position_in_world_space;

         void main()
         {
            float dist = distance(position_in_world_space, 
               vec4(0.0, 0.0, 0.0, 1.0));
               // computes the distance between the fragment position 
               // and the origin (the 4th coordinate should always 
               // be 1 for points). 
            
            if (dist < 5.0)
            {
               gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0); 
                  // color near origin
            }
            else
            {
               gl_FragColor = vec4(0.3, 0.3, 0.3, 1.0); 
                  // color far from origin
            }
         }
"""

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

請注意,我們應該在每一幀中設定制服的當前值,而我們只設置一次著色器的原始碼。

還要注意,頂點著色器中還有一個額外的變換。這是一個矩陣向量乘法,它包含 16 次乘法,因此會消耗一些效能。如果可能,應該避免這種額外的變換。在實踐中,因此我們將在檢視空間中執行燈光計算,這樣就不必將光源的檢視空間位置變換到世界空間。

更多 OpenGL 特定的制服

[編輯 | 編輯原始碼]

光源的位置實際上是作為內建制服提供的,類似於gl_ModelViewMatrixgl_ProjectionModelViewMatrix。所有這些內建制服都是為 OpenGL 相容性配置檔案定義的。相應的變換在“頂點變換”中進行了詳細描述。

正如你在上面的著色器中看到的,這些制服不必定義;它們始終在 Blender 的 GLSL 著色器中可用。如果你必須定義它們,定義將如下所示

      uniform mat4 gl_ModelViewMatrix;
      uniform mat4 gl_ProjectionMatrix;
      uniform mat4 gl_ModelViewProjectionMatrix;
      uniform mat4 gl_TextureMatrix[gl_MaxTextureCoords];
      uniform mat3 gl_NormalMatrix; 
         // transpose of the inverse of gl_ModelViewMatrix
      uniform mat4 gl_ModelViewMatrixInverse;
      uniform mat4 gl_ProjectionMatrixInverse;
      uniform mat4 gl_ModelViewProjectionMatrixInverse;
      uniform mat4 gl_TextureMatrixInverse[gl_MaxTextureCoords];
      uniform mat4 gl_ModelViewMatrixTranspose;
      uniform mat4 gl_ProjectionMatrixTranspose;
      uniform mat4 gl_ModelViewProjectionMatrixTranspose;
      uniform mat4 gl_TextureMatrixTranspose[gl_MaxTextureCoords];
      uniform mat4 gl_ModelViewMatrixInverseTranspose;
      uniform mat4 gl_ProjectionMatrixInverseTranspose;
      uniform mat4 gl_ModelViewProjectionMatrixInverseTranspose;
      uniform mat4 gl_TextureMatrixInverseTranspose[
         gl_MaxTextureCoords];

      struct gl_MaterialParameters {
         vec4 emission; // = Properties > Material tab > Diffuse Color * 
            // Shading > Emit (alpha = 1)
         vec4 ambient; // = black
         vec4 diffuse; // = black 
            // if Properties > Material tab > Diffuse Color activated; 
            // diffuse = Properties > Object tab > Object Color 
         vec4 specular; // = Properties > Material tab > Specular Color
            // * Intensity (alpha = Intensity!)
         float shininess; 
            // = Properties > Material tab > Specular > Hardness / 4.0
      }; 
      uniform gl_MaterialParameters gl_FrontMaterial; // see above
      uniform gl_MaterialParameters gl_BackMaterial; 
         // same as gl_FrontMaterial

      struct gl_LightSourceParameters {
         vec4 ambient; // = black
         vec4 diffuse; // = Properties > Object Data tab > Color * 
            // Energy (alpha = 1)
         vec4 specular; // = Properties > Object Data tab > Color * 
            // Energy (alpha = 1)
         vec4 position; // in view space, w = 0 for Sun (directional), 
            // w = 1 otherwise 
         vec4 halfVector; // = average of vectors to light and viewer 
         vec3 spotDirection; // in view space
         float spotExponent; // = Properties > Object Data tab > 
            // Spot Shape > Blend * 128.0 
         float spotCutoff; // = Properties > Object Data tab > 
            // Spot Shape > Size / 2.0 (180.0 if not spotlight)
         float spotCosCutoff; // derived: cos(spotCutoff) 
            // (range: [1.0,0.0] or -1.0 if not spotlight)
         float constantAttenuation; // = 1.0
         float linearAttenuation; // = 0.0
         float quadraticAttenuation;// = 0.0
      };
      uniform gl_LightSourceParameters gl_LightSource[gl_MaxLights]; 
         // see above for each light

      struct gl_LightModelParameters 
      { 
         vec4 ambient; // = Properties > World tab > World > Ambient Color
            // * Material tab > Shading > Ambient
      };
      uniform gl_LightModelParameters gl_LightModel; // see above
      ...

實際上,OpenGL 的相容性配置檔案定義了更多(不太有趣的)制服;請參閱可從Khronos 的 OpenGL 頁面獲得的“OpenGL 著色語言 4.10.6 規範”的第 7 章。顯然 Blender 支援其中很多,但並非全部。

其中一些制服是陣列,例如gl_TextureMatrix。實際上,一個矩陣陣列gl_TextureMatrix[0]gl_TextureMatrix[1]gl_TextureMatrix[2]、...、gl_TextureMatrix[gl_MaxTextureCoords - 1] 是可用的,其中gl_MaxTextureCoords 是一個內建整數。

其他的是結構體,例如gl_LightModel;因此,你必須使用點符號來訪問它的成員,例如gl_LightModel.ambient 用於環境場景顏色。

計算模型矩陣

[編輯 | 編輯原始碼]

正如我們上面所見,可以使用 Python 指令碼訪問檢視矩陣 和逆檢視矩陣 。另一方面,OpenGL 提供了模型矩陣 和檢視矩陣 的乘積,即模型檢視矩陣 ,它可以在 uniform gl_ModelViewMatrix 中獲取。不太容易獲取的是模型矩陣

但是,我們可以很容易地計算它。數學公式如下

  

換句話說,模型矩陣是逆檢視矩陣和模型檢視矩陣的乘積。假設我們已經定義了 uniform viewMatrixInverse,我們可以用 GLSL 中的這種方法來計算模型矩陣

      mat4 modelMatrix = viewMatrixInverse * gl_ModelViewMatrix;

使用者指定的 Uniform:遊戲屬性

[編輯 | 編輯原始碼]

Uniform 變數有一個重要的應用:使用者可以設定的 uniform。實際上,這些在 Blender 中被稱為遊戲屬性。你可以把它們看作是物件的引數,更準確地說,是它們著色器的引數。沒有引數的著色器通常只被它的程式設計師使用,因為即使是最小的必要更改也需要一些程式設計。另一方面,使用具有描述性名稱的引數的著色器可以被其他人使用,即使是非程式設計師,例如 CG 藝術家。想象一下,你是一個遊戲開發團隊,一個 CG 藝術家要求你為 100 次設計迭代中的每一次都調整你的著色器。顯而易見,幾個引數,即使是 CG 藝術家也可以玩弄它們,可能會為你節省大量的時間。此外,想象一下你想要出售你的著色器:引數通常會極大地提高你的著色器的價值。

有關遊戲屬性的 Blender 文件 描述瞭如何設定一個新的遊戲屬性:選擇 Python 指令碼附加到的物件。開啟邏輯編輯器,從選單中選擇檢視 > 屬性(或者按n或點選左上角的小圖示)。點選新增遊戲屬性,將名稱設定為my_uniform,型別設定為Float。然後,我們可以在著色器中定義一個 uniform(如果我們想自找麻煩,它可以有不同的名稱),並使用 Python 函式設定它

shader.setUniform1f('my_uniform', cont.owner.get('my_uniform'))

其中第一個'my_uniform'指的是著色器中的名稱,第二個'my_uniform'指的是遊戲屬性的名稱。setUniform1f用於設定一維浮點數 uniform,而其他函式可用於設定其他型別的 uniform,如 Blender 的 Python API 文件 中所述。

一個簡單的例子可以使用下面的 Python 指令碼,它使用遊戲屬性的值來設定片段顏色紅色分量的強度

import bge

cont = bge.logic.getCurrentController()

VertexShader = """
   void main()
   {
      gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
   }

"""

FragmentShader = """
   uniform float my_uniform;

   void main()
   {   
      gl_FragColor = vec4(my_uniform, 0.0, 0.0, 1.0);
   }
"""

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.setUniform1f('my_uniform', cont.owner.get('my_uniform'))

恭喜你,你成功了!我們討論了

  • 如何將頂點轉換到檢視座標系。
  • 如何將頂點轉換到世界座標系。
  • Blender 支援的最重要的 OpenGL 特定 uniform。
  • 如何透過新增遊戲屬性來使著色器更有用、更有價值。

進一步閱讀

[編輯 | 編輯原始碼]

如果你想了解更多資訊


< GLSL 程式設計/Blender

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