GLSL 程式設計/向量和矩陣運算
GLSL 的語法與 C 非常相似(因此也與 C++ 和 Java 相似);然而,它有內建的資料型別和用於浮點向量和矩陣的函式,這些函式是 GLSL 特有的。這裡將討論這些函式。有關 GLSL 的完整描述,請參閱““進一步閱讀””部分的文獻資料。
在 GLSL 中,vec2、vec3 和 vec4 型別分別代表 2D、3D 和 4D 浮點向量。(還有用於整數和布林向量的型別,這裡不討論。)向量變數的定義方式與您在 C、C++ 或 Java 中定義這些型別的方式相同。
vec2 a2DVector;
vec3 three_dimensional_vector;
vec4 vector4;
用於浮點 2×2、3×3 和 4×4 矩陣的資料型別是:mat2、mat3 和 mat4
mat2 m2x2;
mat3 linear_mapping;
mat4 trafo;
在宣告浮點變數(包括向量和矩陣變數)時,您可以使用精度限定符 lowp、mediump 或 highp 來建議一個精度,例如
lowp vec4 color; // for colors, lowp is usually fine
mediump vec4 position; // for positions and texture coordinates, mediump is usually ok
highp vec4 astronomical_position; // for some positions and time, highp is necessary
//(precision of time measurement decreases over time
//and things get jumpy if they rely on absolute time since start of the application)
其思想是,lowp 變數需要的計算時間和儲存空間(以及頻寬)都比 mediump 變數少,而 mediump 變數需要的計算時間比 highp 變數少。因此,為了獲得最佳效能,您應該使用最低的精度,該精度仍然可以提供滿意的結果。(這裡只有少數例外情況,例如,訪問 lowp 向量的單個元素可能比訪問 mediump 向量慢,因為這需要額外的解碼。)
請注意,決定實際使用哪種精度的決定權在於編譯器和驅動程式;精度限定符只是程式設計師的建議。還要注意,lowp 變數的範圍可能非常有限(例如,從 -2.0 到 2.0),因此,lowp 對顏色最有用,但通常不適合位置。
為了定義所有浮點變數的預設精度,您應該使用 precision 命令,例如
precision mediump float;
這將在未為浮點變數指定顯式精度限定符的情況下使用 mediump。如果沒有該命令,則浮點數的預設精度在 OpenGL 或 OpenGL ES 頂點著色器中為 highp,在 OpenGL 片段著色器中為 highp,在 OpenGL ES 片段著色器中為未定義。(然而,在 Unity 中,precision 命令不可用,頂點著色器的預設精度為 highp,片段著色器的預設精度為 mediump。)
向量可以透過與資料型別同名的建構函式進行初始化和轉換
vec2 a = vec2(1.0, 2.0);
vec3 b = vec3(-1.0, 0.0, 0.0);
vec4 c = vec4(0.0, 0.0, 0.0, 1.0);
請注意,一些 GLSL 編譯器會抱怨如果使用整數來初始化浮點向量;因此,最好始終包含小數點。
您也可以在建構函式中使用一個浮點數將所有分量設定為相同的值
vec4 a = vec4(0.0); // = vec4(0.0, 0.0, 0.0, 0.0)
將更高維向量轉換為更低維向量也可以透過這些建構函式來實現
vec4 a = vec4(-1.0, 2.5, 4.0, 1.0);
vec3 b = vec3(a); // = vec3(-1.0, 2.5, 4.0)
vec2 c = vec2(b); // = vec2(-1.0, 2.5)
透過向這些建構函式提供正確數量的分量,可以將更低維向量轉換為更高維向量
vec2 a = vec2(0.1, 0.2);
vec3 b = vec3(0.0, a); // = vec3(0.0, 0.1, 0.2)
vec4 c = vec4(b, 1.0); // = vec4(0.0, 0.1, 0.2, 1.0)
類似地,可以初始化和構造矩陣。請注意,在矩陣建構函式中指定的值將被消耗以填充第一列,然後是第二列,依此類推。
mat3 m = mat3(
1.1, 2.1, 3.1, // first column (not row!)
1.2, 2.2, 3.2, // second column
1.3, 2.3, 3.3 // third column
);
mat3 id = mat3(1.0); // puts 1.0 on the diagonal
// all other components are 0.0
vec3 column0 = vec3(0.0, 1.0, 0.0);
vec3 column1 = vec3(1.0, 0.0, 0.0);
vec3 column2 = vec3(0.0, 0.0, 1.0);
mat3 n = mat3(column0, column1, column2); // sets columns of matrix n
如果使用較小的矩陣來構造較大的矩陣,則額外的行和列將設定為它們在單位矩陣中應該具有的值
mat2 m2x2 = mat2(
1.1, 2.1,
1.2, 2.2
);
mat3 m3x3 = mat3(m2x2); // = mat3(
// 1.1, 2.1, 0.0,
// 1.2, 2.2, 0.0,
// 0.0, 0.0, 1.0)
mat2 mm2x2 = mat2(m3x3); // = m2x2
如果使用較大的矩陣來構造較小的矩陣,則將選擇較大矩陣的頂部、左側子矩陣,例如,在上一示例的最後一行。
向量的分量可以透過使用 [] 運算子進行陣列索引來訪問(索引從 0 開始),或者透過使用 . 運算子和元素名稱 x, y, z, w 或 r, g, b, a 或 s, t, p, q 來訪問
vec4 v = vec4(1.1, 2.2, 3.3, 4.4);
float a = v[3]; // = 4.4
float b = v.w; // = 4.4
float c = v.a; // = 4.4
float d = v.q; // = 4.4
還可以透過擴充套件 . 符號(“swizzling”)來構造新的向量
vec4 v = vec4(1.1, 2.2, 3.3, 4.4);
vec3 a = v.xyz; // = vec3(1.1, 2.2, 3.3)
vec3 b = v.bgr; // = vec3(3.3, 2.2, 1.1)
vec2 c = v.tt; // = vec2(2.2, 2.2)
矩陣被認為是由列向量組成的,這些列向量可以透過使用 [] 運算子進行陣列索引來訪問。可以使用上面討論的方法來訪問結果(列)向量的元素
mat3 m = mat3(
1.1, 2.1, 3.1, // first column
1.2, 2.2, 3.2, // second column
1.3, 2.3, 3.3 // third column
);
vec3 column3 = m[2]; // = vec3(1.3, 2.3, 3.3)
float m20 = m[2][0]; // = 1.3
float m21 = m[2].y; // = 2.3
如果二元運算子 *, /, +, -, =, *=, /=, +=, -= 用於相同型別的向量之間,它們將按分量進行操作
vec3 a = vec3(1.0, 2.0, 3.0);
vec3 b = vec3(0.1, 0.2, 0.3);
vec3 c = a + b; // = vec3(1.1, 2.2, 3.3)
vec3 d = a * b; // = vec3(0.1, 0.4, 0.9)
特別要注意,a * b 表示兩個向量的按分量乘積,這線上性代數中並不常見。
對於矩陣,這些運算子也按分量進行操作,除了 * 運算子,它表示矩陣乘積,例如
在 GLSL 中
mat2 a = mat2(1., 2., 3., 4.);
mat2 b = mat2(10., 20., 30., 40.);
mat2 c = a * b; // = mat2(
// 1. * 10. + 3. * 20., 2. * 10. + 4. * 20.,
// 1. * 30. + 3. * 40., 2. * 30. + 4. * 40.)
對於逐元素矩陣乘法,提供了內建函式 matrixCompMult。
* 運算子也可以用來將浮點數(即標量)乘以向量或矩陣的所有元素(從左或右)。
vec3 a = vec3(1.0, 2.0, 3.0);
mat3 m = mat3(1.0);
float s = 10.0;
vec3 b = s * a; // vec3(10.0, 20.0, 30.0)
vec3 c = a * s; // vec3(10.0, 20.0, 30.0)
mat3 m2 = s * m; // = mat3(10.0)
mat3 m3 = m * s; // = mat3(10.0)
此外,* 運算子可用於對應維度的矩陣向量乘法,例如
在 GLSL 中
vec2 v = vec2(10., 20.);
mat2 m = mat2(1., 2., 3., 4.);
vec2 w = m * v; // = vec2(1. * 10. + 3. * 20., 2. * 10. + 4. * 20.)
注意向量必須從右邊乘以矩陣。
如果向量從 **左邊** 乘以矩陣,則結果對應於從左邊將行向量乘以矩陣。這相當於將列向量從右邊乘以 **轉置** 矩陣。
在元件中
因此,從左側將向量乘以矩陣對應於從右側將向量乘以轉置矩陣。
vec2 v = vec2(10., 20.);
mat2 m = mat2(1., 2., 3., 4.);
vec2 w = v * m; // = vec2(1. * 10. + 2. * 20., 3. * 10. + 4. * 20.)
由於沒有內建函式來計算轉置矩陣,因此此技術非常有用:只要需要將向量乘以轉置矩陣,就可以將其從左側乘以原始矩陣。此技術的幾個應用在“應用矩陣變換”部分進行了描述。
內建向量和矩陣函式
[edit | edit source]逐元素函式
[edit | edit source]如前所述,函式
TYPE matrixCompMult(TYPE a, TYPE b) // component-wise matrix product
對矩陣型別 mat2、mat3 和 mat4(表示為 TYPE)計算逐元素乘積。
以下函式對型別為 float, vec2, vec3 和 vec4 的變數(表示為 TYPE)逐元素執行操作。
TYPE min(TYPE a, TYPE b) // returns a if a < b, b otherwise
TYPE min(TYPE a, float b) // returns a if a < b, b otherwise
TYPE max(TYPE a, TYPE b) // returns a if a > b, b otherwise
TYPE max(TYPE a, float b) // returns a if a > b, b otherwise
TYPE clamp(TYPE a, TYPE minVal, TYPE maxVal)
// = min(max(a, minVal), maxVal)
TYPE clamp(TYPE a, float minVal, float maxVal)
// = min(max(a, minVal), maxVal)
TYPE mix(TYPE a, TYPE b, TYPE wb) // = a * (TYPE(1.0) - wb) + b * wb
TYPE mix(TYPE a, TYPE b, float wb) // = a * TYPE(1.0 - wb) + b * TYPE(wb)
還有更多內建函式,這些函式也按逐元素方式工作,但對於向量來說不太有用,例如,abs、sign、floor、ceil、fract、mod、step、smoothstep、sqrt、inversesqrt、pow、exp、exp2、log、log2、radians(將度轉換為弧度)、degrees(將弧度轉換為度)、sin、cos、tan、asin、acos、atan(帶一個引數和帶兩個引數分別用於帶符號分子和帶符號分母)、lessThan、lessThanEqual、greaterThan、greaterThanEqual、equal、notEqual 和 not。
幾何函式
[edit | edit source]以下函式對於向量運算特別有用。TYPE 可以是:float, vec2, vec3 和 vec4(每行僅一項)。
vec3 cross(vec3 a, vec3 b) // = vec3(a[1] * b[2] - a[2] * b[1],
// a[2] * b[0] - a[0] * b[2],
// a[0] * b[1] - a[1] * b[0])
float dot(TYPE a, TYPE b) // = a[0] * b[0] + a[1] * b[1] + ...
float length(TYPE a) // = sqrt(dot(a, a))
float distance(TYPE a, TYPE b) // = length(a - b)
TYPE normalize(TYPE a) // = a / length(a)
TYPE faceforward(TYPE n, TYPE i, TYPE nRef)
// returns n if dot(nRef, i) < 0, -n otherwise
TYPE reflect(TYPE i, TYPE n) // = i - 2. * dot(n, i) * n
// this computes the reflection of vector 'i'
// at a plane of normalized(!) normal vector 'n'
物理函式
[edit | edit source]函式
TYPE refract(TYPE i, TYPE n, float r)
計算折射光線的方向,其中 i 指定入射光線的歸一化(!) 方向,n 指定兩種光學介質(例如空氣和水)介面的歸一化(!) 法向量。向量 n 應指向 i 所來的方向,即 n 和 i 的點積應為負數。浮點數 r 是光線所來介質的折射率與表面另一側介質的折射率的比率。因此,如果光線從空氣(折射率約為 1.0)射入水(折射率 1.33)表面,則比率 r 為 1.0 / 1.33 = 0.75。函式的計算方法為
float d = 1.0 - r * r * (1.0 - dot(n, i) * dot(n, i));
if (d < 0.0) return TYPE(0.0); // total internal reflection
return r * i - (r * dot(n, i) + sqrt(d)) * n;
如程式碼所示,如果發生全內反射(參見維基百科中的條目),即光線未穿過兩種材料之間的介面,則函式將返回長度為 0 的向量。
進一步閱讀
[edit | edit source]OpenGL 的所有 GLSL 詳細資訊都在 “OpenGL 著色語言 4.10.6 規範” 中,可在Khronos OpenGL 網站上獲取。
關於 OpenGL 的 OpenGL 著色語言的更易理解的描述可以在許多關於 OpenGL 的書的最新版本中找到,例如,在 Dave Shreiner 於 2009 年由 Addison-Wesley 出版發行的 “OpenGL 程式設計指南:學習 OpenGL 版本 3.0 和 3.1 的官方指南”(第 7 版)第 15 章中。
OpenGL ES 2.0 的所有 GLSL 詳細資訊都在 “OpenGL ES 著色語言 1.0.17 規範” 中,可在“Khronos OpenGL ES API 登錄檔”上獲取。
關於 OpenGL ES 著色語言的更易理解的描述可以在 Aaftab Munshi、Dan Ginsburg 和 Dave Shreiner 合著的 “OpenGL ES 2.0 程式設計指南” 第 5 章和附錄 B 中找到(參見其網站)。