1 前言
本文將介紹 GLSL 中數(shù)據(jù)類型、數(shù)組、結(jié)構(gòu)體、宏、運算符、向量運算、矩陣運算、函數(shù)、流程控制、精度限定符、變量限定符(in、out、inout)、函數(shù)參數(shù)限定符等內(nèi)容,另外提供了一個 include 工具,方便多文件管理 glsl 代碼,實現(xiàn)代碼的精簡、復(fù)用。
Unity 中 Shader 介紹詳見 → 【Unity3D】Shader常量、變量、結(jié)構(gòu)體、函數(shù),渲染管線介紹詳見 → 【OpenGL ES】渲染管線。
2 數(shù)據(jù)類型
2.1 基本數(shù)據(jù)類型
2.1.1 基本數(shù)據(jù)類型
| 類型 | 說明 | 案例 |
|---|---|---|
| void | 空類型,即不返回任何值 | void fun() { } |
| bool | 布爾類型 true、false | bool a = true; |
| int | 帶符號的整數(shù) | int a = 0; |
| float | 帶符號的浮點數(shù) | float a = 1.0; float b = 2.; |
| vec2、vec3、vec4 | 2 維、3 維、4 維浮點數(shù)向量 | vec2 a = vec2(1., 2.); vec2 b = vec2(1.); // ? vec2(1., 1.) vec3 c = vec3(a, 3.); // ? vec3(1., 2., 3.) |
| bvec2、bvec3、bvec4 | 2 維、3 維、4 維布爾向量 | bvec2 a = bvec2(true, false); bvec2 b = bvec2(true); // ? bvec2(true, true) bec3 c = bvec3(a, true); // ? bvec3(true, false, true) |
| ivec2、ivec3、ivec4 | 2 維、3 維、4 維整數(shù)向量 | ivec2 a = ivec2(1, 2); ivec2 b = ivec2(1); // ? ivec2(1, 1) ivec3 c = ivec3(a, 3); // ? ivec3(1, 2, 3) |
| mat2、mat3、mat4 | 2x2、3x3、4x4 浮點數(shù)矩陣 (列向量) | mat2 a = mat2(1., 2., 3., 4.); mat2 b = mat2(1.); // ? mat2(1., 1., 1., 1.) |
| sampler2D | 2D 紋理 | sampler2D sampler; |
| samplerCube | 盒紋理 | samplerCube sampler; |
說明:mat2、mat3、mat4 中的元素都是按照列向量的順序排列的,即 mat2 m = mat2(m11, m21, m12, m22) 對應(yīng)的公式如下。

2.1.2 向量分量訪問
GLSL 中的向量(vec2、vec3、vec4)可以表示一個空間坐標(biāo) (x, y, z, w),也可以表示一個顏色 (r, g, b, a),還可以表示一個紋理坐標(biāo) (s, t ,p, q),所以 GLSL 提供了多樣的分量訪問方式。
vec4 v = vec4(1.0, 2.0, 3.0, 4.0);
float x1 = v.x; // 1.0
float x2 = v.r; // 1.0
float x3 = v.s; // 1.0
float x4 = v[0]; // 1.0
vec3 xyz = v.xyz; // vec3(1.0, 2.0, 3.0)
vec3 stq = v.stq; // vec3(1.0, 2.0, 3.0)
vec3 rgb = v.rgb; // vec3(1.0, 2.0, 3.0)
vec3 abc = vec3(v[0], v[1], v[2]); // vec3(1.0, 2.0, 3.0)
2.1.3 數(shù)據(jù)類型轉(zhuǎn)換
GLSL 可以使用構(gòu)造函數(shù)進(jìn)行顯式類型轉(zhuǎn)換。
// 0或0.0轉(zhuǎn)換為false, 非0轉(zhuǎn)換為true
bool a1 = bool(1.0); // true
bool a2 = bool(0); // false
// true轉(zhuǎn)換為1或1.0, false轉(zhuǎn)換為0或0.0
int a3 = int(true); // 1
float a4 = float(false); // 0.0
int a5 = int(2.0); // 2
float a6 = float(1); // 1.0
2.2 數(shù)組
GLSL 只支持一維數(shù)組。
// float 數(shù)組
float[3] a = float[] (1.0, 2.0, 3.0);
float b[3] = float[] (1.0, 2.0, 3.0);
float c[3] = float[3] (1.0, 2.0, 3.0);
// vec 數(shù)組
vec2[2] d = vec2[] (vec2(0.0), vec2(1.0));
2.3 結(jié)構(gòu)體
struct light {
vec4 color;
vec3 pos;
};
const light lgt = light(vec4(1.0), vec3(0.0));
說明:結(jié)構(gòu)體中的字段不可用 const 修飾。
2.4 內(nèi)置變量
| 范圍 | 變量 | 說明 |
|---|---|---|
| 頂點著色器的 Output 變量 | highp vec4 gl_Position; | 頂點坐標(biāo)信息 |
| 頂點著色器的 Output 變量 | mediump float gl_PointSize; | 頂點大小 (只在 GL_POINTS 圖元模式下有效) |
| 片元著色器的 Input 變量 | mediump vec4 gl_FragCoord; | 片元在屏幕空間的坐標(biāo),假設(shè)屏幕寬高分別為 width、height x: 片元的x坐標(biāo),值域 [0, width - 1] y: 片元的x坐標(biāo),值域 [0, height - 1] z: 片元的深度坐標(biāo),值域 [0, 1] w: 總是 1,通常用于透視除法 |
| 片元著色器的 Input 變量 | bool gl_FrontFacing; | 標(biāo)志當(dāng)前圖元是否是正面圖元的一部分 |
| 片元著色器的 Output 變量 | mediump vec4 gl_FragColor; | 設(shè)置當(dāng)前片點的顏色 |
2.5 宏
與 C 語言一樣,GLSL 中也可以通過 #define 定義宏,如下。
#define PI 3.14159265359
另外,GLSL 也提供了一些內(nèi)置宏。
__LINE__ // 當(dāng)前源碼中的行號
__VERSION__ // 當(dāng)前glsl版本號, 如: 300
GL_ES // 當(dāng)前運行環(huán)境是否是 OPGL ES, 1 表示是
GL_FRAGMENT_PRECISION_HIGH // 當(dāng)前系統(tǒng)的片元著色器是否支持高浮點精度, 1表示支持
3 運算符
3.1 基礎(chǔ)運算符
| 優(yōu)先級 (越小越高) | 運算符 | 說明 | 結(jié)合性 |
|---|---|---|---|
| 1 | () | 聚組: a * (b + c) | N/A |
| 2 |
[] () . ++ -- |
數(shù)組下標(biāo) 方法參數(shù): fun(arg1, arg2) 屬性訪問 自增 (a++) / 自減 (a--) |
L - R |
| 3 |
++ -- + - ! |
自增 (++a) / 自減 (--a) 正 (+a) 負(fù) (-a) 號 取反 (!a) |
R - L |
| 4 | */ % |
乘法 / 除法運算 整數(shù)求余運算 (浮點數(shù)求余用 mod 函數(shù)) |
L - R |
| 5 | + - | 加法 / 減法運算 | L - R |
| 7 | < > <= >= | 關(guān)系運算符 | L - R |
| 8 | == != | 相等性運算符 | L - R |
| 12 | && | 邏輯與 | L - R |
| 13 | ^^ | 邏輯排他或 (用處基本等于 !=) | L - R |
| 14 | || | 邏輯或 | L - R |
| 15 | ? : | 三目運算符 | L - R |
| 16 | = += -= *= /= | 賦值和復(fù)合賦值 | L - R |
| 17 | , | 順序分配運算 | L - R |
說明:GLSL 中沒有隱式類型轉(zhuǎn)換,因此任何表達(dá)式左右兩側(cè)的類型必須一致,以下表達(dá)式都是錯誤的。
// 以下代碼運行時會報錯
int a = 2.;
int b = 1. + 2;
float c = 2;
float d = 2. + 1;
bool e = 0;
vec2 f = vec2(1., 2.) * 2;
3.2 向量運算符
// 標(biāo)量與向量運算
vec2 a = vec2(1., 2.) + 3.; // vec2(4., 5.)
vec2 b = 3. + vec2(1., 2.); // vec2(4., 5.)
vec2 c = vec2(1., 2.) * 3.; // vec2(3., 6.)
vec2 d = 3. * vec2(1., 2.); // vec2(3., 6.)
// 向量與向量運算
vec2 e = vec2(1., 2.) + vec2(3., 4.); // vec2(4., 6.)
vec2 f = vec2(1., 2.) * vec2(3., 4.); // vec2(3., 8.)
3.3 矩陣運算符
// 標(biāo)量與矩陣運算
mat2 a = mat2(1.) + 2.; // mat2(3.)
mat2 b = 2. + mat2(1.); // mat2(3.)
mat2 c = mat2(1.) * 2.; // mat2(2.)
mat2 d = 2. * mat2(1.); // mat2(2.)
// 向量與矩陣運算
vec2 e = vec2(1., 2.) * mat2(1., 2., 3., 4.); // vec2(5., 11.)
vec2 f = mat2(1., 2., 3., 4.) * vec2(1., 2.); // vec2(7., 10.)
// 矩陣與矩陣運算(矩陣對應(yīng)元素運算)
mat2 g = mat2(1.) + mat2(2.); // mat2(3.)
mat2 h = matrixCompMult(mat2(1.), mat2(2.)); // mat2(2.)
// 矩陣與矩陣運算(矩陣乘法)
mat2 i = mat2(1., 2., 3., 4.) * mat2(5., 6., 7., 8.); // mat2(23., 34., 31., 46.)
mat2 j = mat2(5., 6., 7., 8.) * mat2(1., 2., 3., 4.); // mat2(19., 22., 43., 50.)

4 函數(shù)
4.1 自定義函數(shù)
GLSL 允許在程序的最外部聲明函數(shù),函數(shù)不能嵌套、不能遞歸調(diào)用,且必須聲明返回值類型(無返回值時聲明為 void)在其他方面 GLSL 函數(shù)與 C 語言函數(shù)非常類似。
vec4 getPosition() {
vec4 pos = vec4(0.,0.,0.,1.);
return pos;
}
void doubleSize(inout float size) {
size = size * 2.0;
}
4.2 內(nèi)置函數(shù)
1)數(shù)值運算
sign(x)、abs(x) // 符號、絕對值
min(a, b)、max(a, b) // 最值函數(shù)
ceil(x)、floor(x)、round(x) // 取整函數(shù)
fract(x) // 取小數(shù)部分
mod(x, y) // 取余數(shù)
sqrt(x)、pow(x)、inversesqrt(x) // 冪函數(shù), inversesqrt(x)=1/sqrt(x)
exp(x)、exp2(x) // 指數(shù)函數(shù)(e^x、2^x)
log(x)、log2(x) // 對數(shù)函數(shù)
degrees(x)、radians(x) // 角度轉(zhuǎn)換函數(shù)
sin(x)、cos(x)、tan(x)、asin(x)、acos(x)、atan(x) // 三角函數(shù)
sinh(x)、cosh(x)、tanh(x) // 雙曲線函數(shù)
clamp(x, min, max) // 將x約束在min和max之間, 超過邊界就取邊界值
smoothstep(min, max, x) // 平滑比例, 公式: k=saturate((x-min)/(max-min)), y=k*k*(3-2*k)
mix(a, b, f) // 混合, 公式: y=(1-f)*a+f*b
step(a, b) // 如果a>b, 返回0; 如果a<=b, 返回1; 當(dāng)a、b是向量時, 每個分量獨立判斷, 如: step(fixed2(1,1),fixed(0,2))=(0,1)
說明:以上函數(shù)輸入的可以是:float、vec2、vec3、vec4,且可以逐分量操作;對于整數(shù)求余運算,只能用 %;對于浮點數(shù)求余運算,只能用 mod。
2)邏輯運算
bvec z = lessThan(x, y) // 逐分量比較x < y, 將結(jié)果寫入z的對應(yīng)位置
bvec z = lessThanEqual(x, y) // 逐分量比較x <= y, 將結(jié)果寫入z的對應(yīng)位置
bvec z = greaterThan(x, y) // 逐分量比較x > y, 將結(jié)果寫入z的對應(yīng)位置
bvec z = greaterThanEqual(x, y) // 逐分量比較x >= y, 將結(jié)果寫入z的對應(yīng)位置
bvec z = equal(x, y) // 逐分量比較x == y, 將結(jié)果寫入z的對應(yīng)位置
bvec z = notEqual(x, y) // 逐分量比較x != y, 將結(jié)果寫入z的對應(yīng)位置
bvec y = not(x) // bool矢量的逐分量取反
bool y = any(x) // 如果x的任意一個分量是true, 則結(jié)果為true
bool y = all(x) // 如果x的所有分量是true, 則結(jié)果為true
3)向量運算
distance(pos1, pos2) // 計算pos1與pos2之間的距離
length(vec) // 計算向量的模長
normalize(vec) // 計算向量的單位向量
dot(v1, v2) // 向量點乘
cross(v1, v2) // 向量叉乘
reflect(i, n) // 根據(jù)入射向量和法線向量, 計算反射向量(i和n不需要歸一化)
refract(i, n, ratio); // 根據(jù)入射向量、法線向量、折射率比值, 計算折射向量(i和n需要歸一化, ratio為入射介質(zhì)折射率/折射介質(zhì)折射率, 或sin(折射角)/sin(入射角))
4)矩陣運算
// 矩陣與矩陣運算(矩陣對應(yīng)元素運算)
mat2 a = mat2(1.) + mat2(2.); // mat2(3.)
mat2 b = matrixCompMult(mat2(1.), mat2(2.)); // mat2(2.)
// 矩陣與矩陣運算(矩陣乘法)
mat2 c = mat2(1., 2., 3., 4.) * mat2(5., 6., 7., 8.); // mat2(23., 34., 31., 46.)
mat2 d = mat2(5., 6., 7., 8.) * mat2(1., 2., 3., 4.); // mat2(19., 22., 43., 50.)

5)紋理查詢函數(shù)
vec4 texture(sampler2D sampler, vec2 coord);
vec4 texture2D(sampler2D sampler, vec2 coord);
vec4 texture2DProj(sampler2D sampler, vec3 coord);
vec4 texture2DProj(sampler2D sampler, vec4 coord);
vec4 textureCube(samplerCube sampler, vec3 coord);
5 流程控制
GLSL 的流控制與 C 語言非常相似,主要有 if、for、while、continue、break,不同的是 GLSL 中多了 discard。使用 discard 會退出片元著色器,不會執(zhí)行后面的操作,片元也不會寫入幀緩沖區(qū)。
for (int i = 0; i < 10; i++) {
sum += a[i];
if (sum > 5.0)
break;
}
while (i < 10) {
sum += a[i];
if (i % 3 == 1)
continue;
i++;
}
do {
sum += a[i];
if (sum > 5.0)
discard;
i++;
} while (i < 10)
6 限定符
6.1 精度限定符
片元著色器中,對于浮點數(shù) GLSL 有 highp(高)、mediump(中)、lowp(低) 三種精度。
lowp float color;
varying mediump vec2 Coord;
lowp ivec2 foo(lowp mat3);
highp mat4 m;
在片元著色器的第一行加上 precision highp float,表示設(shè)定了默認(rèn)的精度,所有沒有顯式表明精度的變量都會按照默認(rèn)精度處理。
precision highp float;
通過判斷系統(tǒng)環(huán)境,來選擇合適的精度。
#ifdef GL_ES
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif
#endif
6.2 變量限定符
| 限定符 | 說明 |
|---|---|
| none | 默認(rèn)的變量限定符,可省略,可讀寫。 |
| const | 修飾的變量為只讀類型,變量在定義時必須初始化。 |
| attribute | 只能在頂點著色器中使用,修飾全局只讀變量,一般用于修飾頂點屬性,如:頂點的位置、法線、紋理坐標(biāo)、顏色等。 |
| uniform | 修飾全局只讀變量,一般用于修飾程序傳遞給 shader 的變量,如:屏幕寬高比、光源位置等。 |
| varying | 修飾需要進(jìn)行光柵化插值的變量,在片元著色器中是只讀的,如:紋理坐標(biāo)等。 |
// 頂點著色器
attribute vec4 a_position;
attribute vec2 a_texCoord0;
varying vec2 v_texCoord0;
void main() {
gl_Position = vec4(a_position, 1.0);
v_texCoord0 = a_texCoord0;
}
// 片元著色器
precision highp float;
uniform sampler2D u_texture;
varying vec2 v_texCoord0;
void main() {
gl_FragColor = texture(u_texture, v_texCoord0);
}
6.3 函數(shù)參數(shù)限定符
函數(shù)的參數(shù)默認(rèn)以拷貝的形式傳遞,即值傳遞,我們可以為參數(shù)添加限定符實現(xiàn)引用傳遞,GLSL 中提供的參數(shù)限定符如下。
| 限定符 | 說明 |
|---|---|
| in | 默認(rèn)的限定符,參數(shù)是值傳遞,在函數(shù)中可讀寫。 |
| out | 參數(shù)是引用傳遞,在函數(shù)中只能寫(write-only)。 |
| inout | 參數(shù)是引用傳遞,在函數(shù)中可讀寫(read-write)。 |
除了函數(shù)的參數(shù)可以用 in、out、inout 修飾,全局變量也可以用這些限定符修飾,如下。
// 頂點著色器
in vec3 a_position;
in vec2 a_texCoord0;
out vec2 v_texCoord0;
void main() {
gl_Position = vec4(a_position, 1.0);
v_texCoord0 = a_texCoord0;
}
// 片元著色器
precision highp float;
uniform sampler2D u_texture;
in vec2 v_texCoord0;
out vec4 o_fragColor;
void main() {
o_fragColor = texture(u_texture, v_texCoord0);
}
7 include
GLSL 中沒有提供 #include 功能,如以下代碼會運行報錯。
#include <shaders/utils/constant.glsl>
要想實現(xiàn)一個 glsl 文件依賴另一個 glsl 文件,可以使用以下工具加載 glsl 字符串。
ShaderUtils.java
import android.content.Context;
import java.util.HashSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Shader工具類
* @author little fat sheep
*/
public class ShaderUtils {
// 匹配: #include <xxx> 或 #include “xxx”, 并且不以"http://"開頭
private static final String INCLUDE_REGEX = "(?m)^(?!//\\s*)#include\\s+(<([^>]+)>|\"([^\"]+)\")";
/**
* 通過asset加載shader
* @param vertexShader 頂點著色器路徑, 如: "shaders/origin_vert.glsl"
* @param fragmentShader 片元著色器路徑, 如: "shaders/origin_frag.glsl"
*/
public static String[] loadShader(Context context, String vertexShader, String fragmentShader) {
String vertex = StringUtils.loadString(context, vertexShader);
String fragment = StringUtils.loadString(context, fragmentShader);
String vertex1 = replaceIncludeFiles(context, vertex);
String fragment1 = replaceIncludeFiles(context, fragment);
return new String[] { vertex1, fragment1 };
}
/**
* 通過資源id加載shader
* @param vertexShader 頂點著色器資源id, 如: R.raw.origin_vertex
* @param fragmentShader 片元著色器資源id, 如: R.raw.origin_fragment
*/
public static String[] loadShader(Context context, int vertexShader, int fragmentShader) {
String vertex = StringUtils.loadString(context, vertexShader);
String fragment = StringUtils.loadString(context, fragmentShader);
String vertex1 = replaceIncludeFiles(context, vertex);
String fragment1 = replaceIncludeFiles(context, fragment);
return new String[] { vertex1, fragment1 };
}
/**
* 將shader字符串中#include的文件替換為文件內(nèi)容
*/
public static String replaceIncludeFiles(Context context, String shaderContent) {
Pattern pattern = Pattern.compile(INCLUDE_REGEX);
HashSet<String> set = new HashSet<>(); // 用于去掉重復(fù)的include
return replaceIncludeFiles(context, shaderContent, pattern, set);
}
/**
* 將shader字符串中#include的文件替換為文件內(nèi)容(include的文件中可能也有include, 需要遞歸調(diào)用)
*/
private static String replaceIncludeFiles(Context context, String shaderContent, Pattern pattern, HashSet<String> set) {
Matcher matcher = pattern.matcher(shaderContent);
StringBuffer sb = new StringBuffer(shaderContent.length());
while (matcher.find()) {
String angleBrackets = matcher.group(2); // 尖括號內(nèi)的路徑
String quotationMarks = matcher.group(3); // 引號內(nèi)的路徑
String file = angleBrackets != null ? angleBrackets : quotationMarks;
if (set.contains(file)) {
matcher.appendReplacement(sb, ""); // 刪除重復(fù)的include
} else {
set.add(file);
String includeShader = StringUtils.loadString(context, file); // 加載include文件中的字符串
String quoteShader = Matcher.quoteReplacement(includeShader); // 替換字符串中的轉(zhuǎn)義字符
String wholeShader = replaceIncludeFiles(context, quoteShader, pattern, set); // 遞歸替換字串中的include
matcher.appendReplacement(sb, wholeShader); // 將字符串添加到sb中
}
}
matcher.appendTail(sb);
return sb.toString();
}
}
StringUtils.java
import android.content.Context;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
/**
* 字符串工具類
* @author little fat sheep
*/
public class StringUtils {
/**
* 根據(jù)資源路徑讀取字符串
* @param assetPath 資源路徑, 如: "shaders/origin_vert.glsl"
*/
public static String loadString(Context context, String assetPath) {
String str = "";
try (InputStream inputStream = context.getAssets().open(assetPath)) {
str = loadString(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
return str;
}
/**
* 根據(jù)資源id讀取字符串
* @param rawId 資源id, 如: R.raw.origin_vertex
*/
public static String loadString(Context context, int rawId) {
String str = "";
try (InputStream inputStream = context.getResources().openRawResource(rawId)) {
str = loadString(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
return str;
}
private static String loadString(InputStream inputStream) {
StringBuilder sb = new StringBuilder();
try (BufferedReader br = new BufferedReader(new InputStreamReader(inputStream))) {
String line;
while ((line = br.readLine()) != null) {
sb.append(line).append("\n");
}
} catch (IOException e) {
e.printStackTrace();
}
return sb.toString();
}
}
聲明:本文轉(zhuǎn)自【OpenGL ES】GLSL基礎(chǔ)語法。