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

本教程介紹了制服變數。它建立在最小著色器教程、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_ModelViewMatrix和gl_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 次乘法,因此會消耗一些效能。如果可能,應該避免這種額外的變換。在實踐中,因此我們將在檢視空間中執行燈光計算,這樣就不必將光源的檢視空間位置變換到世界空間。
光源的位置實際上是作為內建制服提供的,類似於gl_ModelViewMatrix和gl_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。實際上,這些在 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。
- 如何透過新增遊戲屬性來使著色器更有用、更有價值。
如果你想了解更多資訊
- 關於向量和矩陣運算(例如
distance()函式),你應該閱讀“向量和矩陣運算”。 - 關於標準頂點變換,例如模型矩陣和檢視矩陣,你應該閱讀“頂點變換”。
- 關於將變換矩陣應用於點和方向,你應該閱讀“應用矩陣變換”。
- 關於在 Blender 的 Python API 中設定著色器 uniform,你應該閱讀 Blender 的 關於 bge.types.BL_Shader 類別的文件。