GLSL程式設計/Unity/世界空間著色

本教程介紹了**uniform變數**。它基於“最小著色器”部分、“RGB立方體”部分和“著色器除錯”部分。
在本教程中,我們將瞭解一個根據片段在世界中的位置改變片段顏色的著色器。這個概念並不複雜;然而,它有著極其重要的應用,例如使用燈光和環境貼圖進行著色。我們還將瞭解現實世界中的著色器;即,如何讓非程式設計師能夠使用你的著色器?
如“著色器除錯”部分所述,屬性gl_Vertex指定了物件座標,即網格區域性物件(或模型)空間中的座標。物件空間(或物件座標系)對每個遊戲物件都是特定的;但是,所有遊戲物件都變換到一個共同的座標系——世界空間。
如果一個遊戲物件直接放置到世界空間中,則物件到世界的變換由遊戲物件的Transform元件指定。要檢視它,請在**場景檢視**或**層次結構檢視**中選擇物件,然後在**檢查器檢視**中找到Transform元件。Transform元件中包含“位置”、“旋轉”和“縮放”引數,它們指定了頂點如何從物件座標變換到世界座標。(如果一個遊戲物件是某個物件組的一部分,在層次結構檢視中透過縮排顯示,則Transform元件僅指定從遊戲物件的物件座標到父物件的物件座標的變換。在這種情況下,實際的物件到世界變換由物件變換與其父級、祖父母等的變換組合給出。)頂點透過平移、旋轉和縮放的變換,以及變換的組合及其作為4×4矩陣的表示,在“頂點變換”部分中討論。
回到我們的示例:從物件空間到世界空間的變換被放入一個4×4矩陣中,該矩陣也被稱為“模型矩陣”(因為該變換也被稱為“模型變換”)。該矩陣在uniform變數unity_ObjectToWorld中可用(在Unity 5中,在舊版本中可能是_Object2World),它在以下著色器中定義和使用
Shader "GLSL shading in world space" {
SubShader {
Pass {
GLSLPROGRAM
uniform mat4 unity_ObjectToWorld;
// definition of a Unity-specific uniform variable
#ifdef VERTEX
varying vec4 position_in_world_space;
void main()
{
position_in_world_space = unity_ObjectToWorld * gl_Vertex;
// transformation of gl_Vertex from object coordinates
// to world coordinates;
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
#endif
#ifdef FRAGMENT
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
}
}
#endif
ENDGLSL
}
}
}
請注意,此著色器確保uniform的定義包含在頂點著色器和片段著色器中(儘管此特定的片段著色器不需要它)。這類似於“RGB立方體”部分中討論的varying變數的定義。
通常,OpenGL應用程式必須設定uniform變數的值;但是,Unity負責始終設定預定義uniform變數(如unity_ObjectToWorld)的正確值;因此,我們不必擔心它。
此著色器將頂點位置變換到世界空間,並將其作為varying傳遞給片段著色器。對於片段著色器,varying變數包含世界座標中片段的插值位置。根據此位置到世界座標系原點的距離,設定兩種顏色之一。因此,如果你在編輯器中移動帶有此著色器的物件,它將在世界座標系原點附近變成綠色。遠離原點,它將變成深灰色。
事實上,有幾個類似於unity_ObjectToWorld的預定義uniform變數。以下是一個簡短的列表(包括unity_ObjectToWorld),它出現在幾個教程的著色器程式碼中
// The following built-in uniforms (except _LightColor0 and
// _LightMatrix0) are also defined in "UnityCG.glslinc",
// i.e. one could #include "UnityCG.glslinc"
uniform vec4 _Time, _SinTime, _CosTime; // time values from Unity
uniform vec4 _ProjectionParams;
// x = 1 or -1 (-1 if projection is flipped)
// y = near plane; z = far plane; w = 1/far plane
uniform vec4 _ScreenParams;
// x = width; y = height; z = 1 + 1/width; w = 1 + 1/height
uniform vec4 unity_Scale; // w = 1/scale; see _World2Object
uniform vec3 _WorldSpaceCameraPos;
uniform mat4 unity_ObjectToWorld; // model matrix
uniform mat4 unity_WorldToObject; // inverse model matrix
// (all but the bottom-right element have to be scaled
// with unity_Scale.w if scaling is important)
uniform vec4 _LightPositionRange; // xyz = pos, w = 1/range
uniform vec4 _WorldSpaceLightPos0;
// position or direction of light source
uniform vec4 _LightColor0; // color of light source
uniform mat4 _LightMatrix0; // matrix to light space
如註釋所示,除了_LightColor0和_LightMatrix0之外,你還可以包含檔案UnityCG.glslinc來代替定義所有這些uniform變數。但是,由於某種未知的原因,_LightColor0和_LightMatrix0沒有包含在這個檔案中;因此,我們必須單獨定義它們
#include "UnityCG.glslinc"
uniform vec4 _LightColor0;
uniform mat4 _LightMatrix0;
Unity並不總是更新所有這些uniform變數。特別是,_WorldSpaceLightPos0、_LightColor0和_LightMatrix0僅針對適當標記的著色器傳遞正確設定,例如在Pass {...} 塊的第一行使用Tags {"LightMode" = "ForwardBase"};另請參閱“漫反射”部分。
另一類內建uniform變數是為OpenGL相容性配置檔案定義的,例如mat4矩陣gl_ModelViewProjectionMatrix,它等效於其他兩個內建uniform變數的矩陣乘積gl_ProjectionMatrix * gl_ModelViewMatrix。相應的變換在“頂點變換”部分中詳細描述。
如你在上面的著色器中看到的,這些uniform變數不必定義;它們在Unity中的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_LightModelParameters { vec4 ambient; };
uniform gl_LightModelParameters gl_LightModel;
...
事實上,OpenGL的相容性配置檔案定義了更多uniform變數;請參閱可在Khronos的OpenGL頁面上獲得的“OpenGL著色語言4.10.6規範”的第7章。Unity支援其中許多,但並非全部。
其中一些uniform變數是陣列,例如gl_TextureMatrix。實際上,可以使用一個矩陣陣列gl_TextureMatrix[0]、gl_TextureMatrix[1]、…、gl_TextureMatrix[gl_MaxTextureCoords - 1],其中gl_MaxTextureCoords是一個內建整數。
傳統上,習慣於在視空間中進行許多計算,視空間只是世界空間的旋轉和平移版本(有關詳細資訊,請參見“頂點變換”部分)。因此,OpenGL 只提供模型矩陣和檢視矩陣的乘積,即模型檢視矩陣,它在統一變數gl_ModelViewMatrix中可用。檢視矩陣不可用。Unity 也不提供它。
但是,unity_ObjectToWorld只是模型矩陣,而unity_WorldToObject是模型矩陣的逆矩陣。(除了右下角元素之外,所有元素都必須按untiy_Scale.w縮放。)因此,我們可以很容易地計算出檢視矩陣。數學表示式如下所示
換句話說,檢視矩陣是模型檢視矩陣和模型矩陣的逆矩陣的乘積(除了右下角元素為 1 外,它是unity_WorldToObject * unity_Scale.w)。假設我們已經定義了統一變數unity_WorldToObject和unity_Scale,我們可以在 GLSL 中以這種方式計算檢視矩陣
mat4 modelMatrixInverse = _World2Object * unity_Scale.w;
modelMatrixInverse[3][3] = 1.0;
mat4 viewMatrix = gl_ModelViewMatrix * modelMatrixInverse;
還有一種重要的統一變數型別:使用者可以設定的統一變數。實際上,在 Unity 中這些被稱為著色器屬性。您可以將它們視為著色器的引數。沒有引數的著色器通常只由其程式設計師使用,因為即使是最小的必要更改也需要一些程式設計。另一方面,使用具有描述性名稱的引數的著色器可以被其他人使用,即使是非程式設計師,例如 CG 藝術家。假設您在一個遊戲開發團隊中,並且一位 CG 藝術家要求您為其 100 次設計迭代中的每一次都調整您的著色器。顯而易見的是,一些引數(即使 CG 藝術家也可以使用)可以為您節省大量時間。此外,假設您想出售您的著色器:引數通常會極大地提高著色器的價值。
由於 Unity 的 ShaderLab 參考中對著色器屬性的描述已經相當不錯,這裡只舉一個如何在我們的示例中使用著色器屬性的例子。我們首先宣告屬性,然後定義相同名稱和相應型別的統一變數。
Shader "GLSL shading in world space" {
Properties {
_Point ("a point in world space", Vector) = (0., 0., 0., 1.0)
_DistanceNear ("threshold distance", Float) = 5.0
_ColorNear ("color near to point", Color) = (0.0, 1.0, 0.0, 1.0)
_ColorFar ("color far from point", Color) = (0.3, 0.3, 0.3, 1.0)
}
SubShader {
Pass {
GLSLPROGRAM
// uniforms corresponding to properties
uniform vec4 _Point;
uniform float _DistanceNear;
uniform vec4 _ColorNear;
uniform vec4 _ColorFar;
#include "UnityCG.glslinc"
// defines _Object2World and _World2Object
varying vec4 position_in_world_space;
#ifdef VERTEX
void main()
{
mat4 modelMatrix = _Object2World;
position_in_world_space = modelMatrix * gl_Vertex;
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
#endif
#ifdef FRAGMENT
void main()
{
float dist= distance(position_in_world_space, _Point);
if (dist < _DistanceNear)
{
gl_FragColor = _ColorNear;
}
else
{
gl_FragColor = _ColorFar;
}
}
#endif
ENDGLSL
}
}
}
使用這些引數,非程式設計師可以修改我們著色器的效果。這很好;但是,著色器的屬性(實際上,統一變數通常)也可以透過指令碼設定!例如,附加到正在使用著色器的遊戲物件的 JavaScript 可以使用以下幾行程式碼設定屬性
renderer.sharedMaterial.SetVector("_Point",
Vector4(1.0, 0.0, 0.0, 1.0));
renderer.sharedMaterial.SetFloat("_DistanceNear",
10.0);
renderer.sharedMaterial.SetColor("_ColorNear",
Color(1.0, 0.0, 0.0));
renderer.sharedMaterial.SetColor("_ColorFar",
Color(1.0, 1.0, 1.0));
如果您想更改使用此材質的所有物件的引數,請使用sharedMaterial;如果您只想更改一個物件的引數,請使用material。透過指令碼,您可以例如將_Point設定為另一個物件的位置(即其 Transform 元件的position)。透過這種方式,您可以只通過在編輯器中移動另一個物件來指定一個點。為了編寫這樣的指令碼,請在**專案檢視**中選擇**建立 > JavaScript**,然後複製並貼上此程式碼
@script ExecuteInEditMode() // make sure to run in edit mode
var other : GameObject; // another user-specified object
function Update () // this function is called for every frame
{
if (null != other) // has the user specified an object?
{
renderer.sharedMaterial.SetVector("_Point",
other.transform.position); // set the shader property
// _Point to the position of the other object
}
}
然後,您應該將指令碼附加到具有著色器的物件,並在**檢查器檢視**中將另一個物件拖放到指令碼的other變數中。
恭喜你,你做到了!(如果你想知道:是的,我在這裡也在自言自語。;))我們討論了
- 如何將頂點變換到世界座標。
- Unity 支援的最重要的特定於 Unity 的統一變數。
- Unity 支援的最重要的特定於 OpenGL 的統一變數。
- 如何透過新增著色器屬性使著色器更有用和更有價值。
如果你想了解更多
- 關於向量和矩陣運算(例如
distance()函式),您應該閱讀“向量和矩陣運算”部分。 - 關於標準頂點變換,例如模型矩陣和檢視矩陣,您應該閱讀“頂點變換”部分。
- 關於將變換矩陣應用於點和方向,您應該閱讀“應用矩陣變換”部分。
- 關於著色器屬性的規範,您應該閱讀 Unity 關於“ShaderLab 語法:屬性”的文件。