GLSL(wiki chs)是OpenGL(OpenGL ES、WebGL)的着色器語言,擁有如下特點:
1. 基於C語言的語法(如:大小寫敏感,每條語句必須以分號結尾),是一門面向過程的強類型語言(type sensitive language)
2. 預處理中沒有#include包含文件指令
3. 除了bool、int、uint、float基礎類型外,還支持數組類型,另外GLSL還內置了適合3D圖形操作的向量與矩陣類型,以及采樣器(紋理)類型
4. double、dvec*、dmat*類型需要#version 400及以上才支持
5. 更嚴格類型隱式轉換規則
6. 變量沒有賦初值時,都會被填充為false、0或0.0
7. if條件語句和switch條件語句與C語言一致
8. for循環語句和while循環語句與C語言一致
9. return、continue和break與C語言一致。另外引入了discard,該關鍵字只能在fs中使用,表示放棄當前片元,直接處理下一個片元。
10. 無指針、無字符和字符串類型
11. 無union、無enum
12. 向量、矩陣、自定義結構體變量可通過構造函數進行初始化
13. 增添了一些C++語言的特性:如函數重載
shader運行時,死循環或者崩潰,會導致進程無響應(彈出如下對話框),驅動崩潰重啟
與C語言一樣,包括:預處理 -- 編譯 -- 鏈接 -- 執行 四大步驟
注釋
單行注釋
// Hello GLSL.
多行注釋
/********************************* This is my first GLSL. Let's take a look. *********************************/
預處理
#version XXX // 用於指定當前glsl文件的版本
①不同的版本glsl,所能使用語言特性是不一樣的。如:layout (location = 0) in vec3 Position; // layout是330才支持的特性
②如果要加該#version指令,必須是當前glsl文件的第一句有效代碼行
③如果不加該#version指令,默認為GLSL 1.1,即:#version 110 注:WebGL1.0中vs和fs開頭不用寫#version版本,其語法對應GLSL ES 1.0(或GLSL 1.1)
#version 110 // GLSL 1.1 OpenGL 2.0 【默認】 #version 120 // GLSL 1.2 OpenGL 2.1 #version 130 // GLSL 1.3 OpenGL 3.0 #version 140 // GLSL 1.4 OpenGL 3.1 #version 150 // GLSL 1.5 OpenGL 3.2 #version 330 // GLSL 3.3 OpenGL 3.3 #version 330 core // 【同上】 #version 400 // GLSL 4.0 OpenGL 4.0 #version 410 // GLSL 4.1 OpenGL 4.1 #version 420 // GLSL 4.2 OpenGL 4.2 #version 430 // GLSL 4.3 OpenGL 4.3 #version 440 // GLSL 4.4 OpenGL 4.4 #version 450 // GLSL 4.5 OpenGL 4.5 #version 460 // GLSL 4.6 OpenGL 4.6 #version 150 compatibility // GLSL 1.5 並向前兼容GLSL 1.2、1.3、1.4的語法 #version 300 es // GLSL ES 3.0 或稱 ESSL 3.0 注:聲明為這個后,宏GL_ES會被定義為1
GLSL不同版本的差異點,可參考:這里
#extension extname : behaivor
extname為編譯器支持的擴展名稱
behaivor可以是require、enable、warn、disable
#extension all : disable // 禁用所有擴展 #extension GL_NV_shadow_samplers_cube : enable // 啟用名為GL_NV_shadow_samplers_cube的擴展
擴展行為說明
擴展行為 | 說明 |
require | 擴展是必需的。若當前擴展不受支持,將拋出錯誤 |
enable | 啟用擴展。若當前擴展不支持,將拋出警告 |
warn | 對於擴展的任何使用均拋出警告 |
disable | 禁用擴展。若當前擴展被使用,將拋出錯誤 |
#if #elif [defined(), !defined()] #else #ifdef #ifndef #endif // 條件編譯
#define TEST1 // 定義為空的宏 //#define TEST1 1 // 宏已被定義 編譯失敗 error C7101: Macro TEST1 redefined #ifdef TEST1 // 條件成立 #endif //#if TEST1 // 空值不能進行條件判斷 編譯失敗 error C0105: Syntax error in #if //#endif #undef TEST1 // 取消TEST1宏 #if TEST1 // 條件不成立 注:OpenGL有效; 但openGL es下 編譯失敗 error C0105: Syntax error in #if #endif #ifndef TEST1 // 條件成立 #endif #define TEST1 -1 // 定義為int的宏 #define TEST2 // 定義為空的宏 #if TEST1 && defined(TEST2) // 條件成立 #endif #define TEST3 true // 定義為bool的宏 #if TEST3 // 條件不成立 注:OpenGL有效;但openGL es下,bool不能進行條件判斷 編譯失敗 error C0105: Syntax error in #if #endif #if !TEST3 // 條件成立 注:OpenGL有效;openGL es下,bool不能進行條件判斷 編譯失敗 error C0105: Syntax error in #if #endif #if TEST3==true // 條件成立 注:OpenGL有效;openGL es下,bool不能進行條件判斷 編譯失敗 error C0105: Syntax error in #if #endif #if !defined(TEST0) && TEST1 && defined(TEST2) // 條件成立 #endif #define TEST4 100 // 定義為int的宏 #if TEST4 // 條件成立 #endif #if TEST4 > 150 #elif (TEST4 > 120) || TEST1 // 進入elif分支 #endif #if !TEST4 #else // 進入else分支 #endif #define TEST5 2.0 // 定義為float的宏 //#if TEST5 //float不能進行條件判斷 編譯失敗 error C0105: Syntax error in #if //#endif //#if TEST5>0.0 //float不能進行條件判斷 編譯失敗 error C0105: Syntax error in #if //#endif
#if
// Shader inputs and outputs keywords // // - ATTRIBUTE: used to mark a vertex shader inputs // - SHADER_IN: used to mark a non-vertex shader inputs // - SHADER_OUT: used to mark a non-fragment shader output // - OUT: used to mark a fragment shader output #if __VERSION__ >= 130 #define ATTRIBUTE in #define SHADER_IN in #define SHADER_OUT out #define OUT out #else #define ATTRIBUTE attribute #define SHADER_IN varying #define SHADER_OUT varying #define OUT #endif // Support array attributes #if __VERSION__ >= 130 #define ARRAY_ATTRIBUTE(name, size) name[size] #else #define ARRAY_ATTRIBUTE(name, size) name[size] #endif // Uniform blocks #if __VERSION__ >= 130 #define BEGIN_UNIFORM_BLOCK(name) uniform name { #define END_UNIFORM_BLOCK() }; #else #define BEGIN_UNIFORM_BLOCK(name) #define END_UNIFORM_BLOCK() #endif // Input and output blocks #if __VERSION__ >= 150 #define BEGIN_INPUT_BLOCK(name) in name { #define END_INPUT_BLOCK() }; #define BEGIN_OUTPUT_BLOCK(name) out name { #define END_OUTPUT_BLOCK() }; #else #define BEGIN_INPUT_BLOCK(name) #define END_INPUT_BLOCK() #define BEGIN_OUTPUT_BLOCK(name) #define END_OUTPUT_BLOCK() #endif // Texturing functions #if __VERSION__ >= 130 #define TEXTURE_2D texture #define TEXTURE_3D texture #define TEXTURE_RECT texture #define TEXTURE_CUBE texture #if __VERSION__ >= 150 #define TEXTURE_SIZE(sampler) textureSize(sampler) #else #define TEXTURE_SIZE(sampler) sampler ## _Size #endif #else #define TEXTURE_2D texture2D #define TEXTURE_3D texture3D #define TEXTURE_RECT texture2DRect #define TEXTURE_CUBE textureCube #endif // Invariance #if __VERSION__ >= 120 #define INVARIANT invariant #else #define INVARIANT #endif // Attribute location #if defined(GL_ARB_explicit_attrib_location) #define LOCATION(loc) layout(location = loc) #else #define LOCATION(loc) #endif // Geometry shader layout #if __VERSION__ >= 150 #define GEOMETRY_LAYOUT_IN(from) layout (from) in #define GEOMETRY_LAYOUT(to, max) layout (to, max_vertices = max) out #else #define GEOMETRY_LAYOUT_IN(from) #define GEOMETRY_LAYOUT(to, max) #endif // 為GLSL ES着色器腳本時,需要設置float和int類型的精度 #ifdef GL_ES precision mediump float;// 設置float類型的精度為中精度 注:高精度為highp、低精度為lowp precision mediump int; // 設置int類型的精度為中精度 注:高精度為highp、低精度為lowp #endif
#define #undef // 宏定義、宏取消
#define TEST1 100 // 定義TEST1宏為100 #ifdef TEST1 // 條件成立 #undef TEST1 // 取消TEST1宏的定義 #endif #if !defined(TEST1) // 條件成立 #endif #define SQUARE(a) ((a)*(a)) #define MAX(a,b) \ // 宏必須在一行寫完,多行寫時必須帶上 \行連接符 (((a) > (b)) ? (a) : (b)) #define MERGE(a, b) a##b // ## 字符拼接符
#line // 指示下一行的行號,及當前所在的文件;該命令會修改__FILE__、__LINE__的值
該命令是提供給編譯器使用的,程序員最好不要使用該命令
#if __LINE__==10 // 判斷當前行號是否為10 #endif #if __FILE__==0 // 條件成立 __FILE__好像缺省為0 #endif #line 116 // 指定下一行的行號為116 #if __LINE__==116 // 條件成立 #endif #line 200 5 // 指定下一行的行號為200,當前文件的ID標識為5 #if __FILE__==5 // 條件成立 #endif
#pragma // 用來控制編譯器的一些行為
#pragma optimize(on) // 開啟編譯器優化 【默認】 #pragma optimize(off) // 關閉編譯器優化 #pragma debug(on) // 開啟debug選項,以獲得更多的調試信息 #pragma debug(off) // 關閉debug選項 【默認】 #pragma STDGL invariant(all) // 讓所有的變量全都不變 注:因為編譯器需要保證不變性,所以會限制對着色器進行優化
#error // error命令被執行,會導致當前文件編譯失敗
#if __VERSION__ >= 150 // __VERSION__為內置宏,十進制整數,為當前GLSL文件的版本號 #error This is an error! // error C0000: This is an error! #endif
__VERSION__、__FILE__、__LINE__、GL_ES、GL_FRAGMENT_PRECISION_HIGH
#if __VERSION__ >= 330 // __VERSION__為內置宏,int類型,為當前GLSL文件的版本號 #endif #line 100 3 // 指定下一行的行號為100,當前文件的ID標識為3 #if __FILE__==3 // 條件成立 宏__FILE__為int類型,當前Source String的唯一ID標識 #endif #if __LINE__==103 // 條件成立 宏__LINE__為int類型,當前的行號 #endif #if GL_ES // OpenGL下,內置宏GL_ES未定義,條件不成立;openGL es下,GL_ES為1,條件成立 #if GL_FRAGMENT_PRECISION_HIGH // 若當前片元着色器支持高浮點精度,則宏GL_FRAGMENT_PRECISION_HIGH為1 precision highp float; // 設置float類型的精度為高精度 precision highp int; // 設置int類型的精度為高精度 #else precision mediump float;// 設置float類型的精度為中精度 precision lowp int; // 設置int類型的精度為低精度 #endif #endif
運算符
除了沒有指針相關的運算符外,其他的與c語言完全一致
注:對於向量、矩陣類型,運算符會在各個分量上進行
變量
變量名需要符合以下規則:
① 只能包括大小寫字母、數字和下划線
② 變量名不能以數字開頭
③ 不能是關鍵字或預留的關鍵字
④ 不能以gl_、webgl_或_webgl_開頭
全局變量:定義在函數體外的變量。作用域規則與c語言全局變量一致。
局部變量:定義在函數內的變量。作用域規則與c語言局部變量一致。
內置常量(以gl_開頭)
所在shader | 常量 |
vs | const mediump int gl_MaxVertexAttribs = 16; // 可用的頂點輸入變量(GLSL 1.3之前為attribute變量)的最大數量 const mediump int gl_MaxVertexUniformVectors = 256; // 可用的vec4統一變量(uniform變量)的最大數量 需要#version 410及以上 const mediump int gl_MaxVertexOutputVectors = 16; // 可用的頂點輸出變量(GLSL 1.3之前為varying變量)的最大數量 需要#version 410及以上 const mediump int gl_MaxVertexTextureImageUnits = 16; // 可用的紋理的最大數量 const mediump int gl_MaxCombinedTextureImageUnits = 32; // vs和fs中可用的紋理的最大數量的總和 |
fs | const mediump int gl_MaxFragmentInputVectors = 15; // 片元輸入變量(GLSL 1.3之前為varying變量)的最大數量 需要#version 410及以上 const mediump int gl_MaxTextureImageUnits = 16; // 可用的紋理的最大數量 const mediump int gl_MaxFragmentUniformVectors = 224; // 可用的vec4統一變量(uniform變量)的最大數量 需要#version 410及以上 const mediump int gl_MaxDrawBuffers = 4; // 多重渲染目標(MRT)的最大支持數量。 const mediump int gl_MinProgramTexelOffset = -8; // 內建ESSL函數texture*Offset偏移參數遲滯的最小偏移值 需要#version 410及以上 const mediump int gl_MaxProgramTexelOffset = 7; // 內建ESSL函數texture*Offset偏移參數遲滯的最大偏移值 需要#version 410及以上 |
內置變量(以gl_開頭)
所在shader | 變量 |
vs | vec4 gl_Position; //必須將變換后的位置寫入到這個變量中,用於輸出頂點位置的裁剪坐標。該變量用highp精度限定符聲明。 float gl_PointSize; // 用於寫入以像素表示的點精靈尺寸。在渲染點時使用。該變量用highp精度限定符聲明。 int gl_VertexID; // 輸入變量,用於保存當前頂點的索引。該變量用highp精度限定符聲明。 int gl_InstanceID; // 輸入變量,用戶保存當前圖元的編號。對於常規的繪圖調用,該值為0。該變量用highp精度限定符聲明。 bool gl_FrontFacing; // 一個特殊變量,但不是vs直接寫入,而是根據vs生成的位置和渲染的圖元類型生成的。
struct gl_DepthRangeParameters |
fs | vec4 gl_FragCoord; // 只讀變量,保存窗口相對坐標(x,y,z,1/w)。 bool gl_FrontFacing; // 只讀變量,片段是正面時為ture,否則為false。 vec2 gl_PointCoord; // 只讀變量,保存點精靈的紋理坐標,處於[0.0, 1.0]區間 float gl_FragDepth; // 一個只寫輸出變量。在fs中寫入時,覆蓋片段固定功能深度值。應謹慎使用該功能,可能禁用GPU的深度優化(如:Early-Z) vec4 gl_FragColor; // fs輸出的片元顏色 |
const變量
表示該變量的值不能被修改。聲明的同時必須對它進行初始化,聲明之后就不能再去改變它的值了,否則會導致編譯錯誤。
const int lightspeed = 399792458; // 光速 m/s const vec4 red = vec4(1.0, 0.0, 0.0); // 紅色 const mat4 identity = mat4(1.0); // 4x4單位矩陣
頂點輸入變量(GLSL 1.3之前為attribute變量)
與頂點相關的輸入型變量,被用來表示逐頂點的信息,必須聲明為全局變量,僅在vs中使用。如:頂點的position、normal、uv等。這些變量為cpu傳來的數據。
其只能是float或由float組合成的復合類型(vec2、vec3、vec4、mat2、mat3、mat4)
在#version 130及以上,頂點輸入變量使用關鍵字in,而不再使用attribute
#if __VERSION__ >= 130 in vec3 position; in vec3 normal; in vec2 uv; in vec2 uv2; #else attribute vec3 position; attribute vec3 normal; attribute vec2 uv; attribute vec2 uv2; #endif
頂點輸入變量可以使用布局限定符來修飾
layout (location = 0) in vec3 position; layout (location = 1) in vec3 normal;
location的值由void glVertexAttribPointer( GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void * pointer)函數的第一個參數index來指定
統一變量(uniform變量)
是只讀的,與頂點和像素無關的輸入型變量,被用來表示非逐頂點、非逐像素、各頂點、各像素共用的信息,必須聲明為全局變量,可在vs和fs中使用。如:mvp矩陣
可以是除了數組或結構體之外的任意類型。如果vs和fs中聲明了同名的統一變量(uniform變量),那么它就會被兩種着色器共享。
vs代碼:
#version 330 layout (location = 0) in vec3 Position; uniform mat4 gWVP; out vec4 Color; void main() { gl_Position = gWVP * vec4(Position, 1.0); Color = vec4(clamp(Position, 0.0, 1.0), 1.0); }
openGL代碼:
GLuint ShaderProgram = glCreateProgram(); // ... ... GLuint Location = glGetUniformLocation(ShaderProgram, "gWVP"); // 獲取shader中uniform mat4 gWVP變量的location if (Location != INVALID_UNIFORM_LOCATION) { float m[4][4] = { {0.0f, 1.0f, 0.5f, 0.2f}, {0.1f, 0.0f, 1.5f, 0.0f}, {0.0f, 1.2f, -0.5f, 0.1f}, {0.2f, 1.0f, 1.5f, 0.7f} }; glUniformMatrix4fv(Location, 1, GL_TRUE, (const GLfloat*)m); // 設置uniform mat4 gWVP變量的值 }
統一變量塊(uniform變量塊)
OpenGL中使用統一變量緩沖區對象來支持統一變量塊(uniform變量塊) 數據的存儲。
相比單一的統一變量(uniform變量),統一變量塊(uniform變量塊) 具有以下優勢:
① 更高效 ② 不受默認大小限制,增加了統一變量的可用存儲
vs代碼:
// ... ... layout (std140) uniform LightBlock { vec3 lightDirection; vec4 lightPosition; }; // ... ...
openGL代碼:
GLuint ShaderProgram = glCreateProgram(); // ... ... GLuint blockId, bufferId; GLint blockSize; GLuint bindingPoint = 1; GLfloat lightData[] = { // lightDirection (padding to vec4 based on std140 rule) 1.0f, 0.0f, 0.0f, 0.0f, // lightPosition 0.0f, 0.0f, 0.0f, 1.0f }; // Retrieve the uniform block index blockId = glGetUniformBlockIndex(ShaderProgram, "LightBlock"); // Associate the uniform block index with a binding point glUniformBlockBinding(ShaderProgram, blockId, bindingPoint); // Get the size of lightData // alternatively, we can calculate it using sizeof(lightData) in this example glGetActiveUniformBlockiv(ShaderProgram, blockId, GL_UNIFORM_BLOCK_DATA_SIZE, &blockSize); // Create and fill a buffer object glGenBuffers(1, &bufferId); glBindBuffer(GL_UNIFORM_BUFFER, bufferId); glBufferData(GL_UNIFORM_BUFFER, blockSize, lightData, GL_DYNAMIC_DRAW); // 將lightData數組的數據綁定到名為統一變量塊LightBlock上 // Bind the buffer object to the uniform block binding point glBindBufferBase(GL_UNIFORM_BUFFER, bindingPoint, bufferId);
布局限定符
限定符 | 說明 |
shared(默認) | 多個着色器程序間共享。可省略不寫 |
packed | 最小內存占用,無法在多個着色器程序間共享 |
std140 | 標准內存布局 |
column_major(默認) | 矩陣變量以列優先順序。可省略不寫 |
row_major | 矩陣變量以行優先順序 |
① shared、packed和std140有着不同地內存對齊方式
② 布局限定符可用於統一的變量塊,也可以用在統一變量塊中某個單獨的變量上 注:用在單獨變量上時,對於該變量會覆蓋掉全局對應的布局限定符
layout (shared, column_major) uniform TransformBlock { layout (std140, row_major) mat4 matViewProj; // matViewProj布局為std140行優先 layout (row_major) mat3 matNormal; // matNormal布局為shared行優先 mat3 matTexGen; // matViewProj布局為shared列優先 };
頂點輸出與片元輸入變量(GLSL 1.3之前為varying變量)
必須聲明為全局變量,其任務是從vs向fs傳輸數據。該數據對cpu是不可見的。
在vs和fs中必須聲明同名、同類型的該變量。
需要注意的是,vs中輸出變量的值並不是直接傳給fs的輸入變量。
在進入fs之前,會進行光柵化,根據繪制的圖形,對vs的輸出變量進行插值(Interpolation),然后再傳遞給fs的v輸入變量。
該類型的變量只能是float或由float組合成的復合類型(vec2、vec3、vec4、mat2、mat3、mat4)
在#version 130及以上,頂點輸出與片元輸入變量使用關鍵字in,而不再使用varying
與頂點輸入變量相比,頂點輸出與片元輸入變量不能使用layout布局限定符。
vs中頂點輸出變量
#if __VERSION__ >= 130 out vec3 vPosition; out vec3 vNormal; out vec2 vUv; out vec2 vUv2; #else varying vec3 vPosition; varying vec3 vNormal; varying vec2 vUv; varying vec2 vUv2; #endif
為了保證不變性,GLSL引入了invariant關鍵字來修飾頂點輸出變量。
主要的原因是,為了優化,編譯器可能對shader指令重新排序,可能導致2個着色器之間的等價計算不能保證產生完全相同的結果。特別是在進行多遍渲染時,尤其可能成為問題。
例如:fs中第一遍中計算鏡面反射光,第二遍計算環境光和漫反射光,頂點輸出變量如果不使用invariant關鍵字,精度帶來的小誤差會導致深度沖突(Z fighting)。
invariant關鍵字既可用於聲明變量,也可以用於已經聲明過的變量。
invariant vec2 texCoord; // 用於聲明變量 invariant gl_Position; // 用於已經聲明過的變量 #pragma STDGL invariant(all) // 讓所有的變量全都不變 注:因為編譯器需要保證不變性,所以會限制對着色器進行優化
fs中片元輸入變量
#if __VERSION__ >= 130 in vec3 vPosition; in vec3 vNormal; in vec2 vUv; in vec2 vUv2; #else varying vec3 vPosition; varying vec3 vNormal; varying vec2 vUv; varying vec2 vUv2; #endif
fs中片元輸出變量
layout(location = 0) out vec4 FragColor; void main() { FragColor = vec4(1.0, 0.0, 0.0, 0.0); }
注1:在典型情況下,只會渲染一個顏色緩沖區,這個時候,layout(location = 0)布局限定符可忽略不寫(默認輸出變量會進入位置0)
注2:當渲染到多個渲染目標(MRT)時,可以使用布局限定符指定每個輸出前往的渲染目標
插值限定符來修飾頂點輸出與片元輸入變量(GLSL 1.3之前為varying變量)
插值限定符 | 說明 | 示例 |
smooth(缺省) | 平滑着色 |
/*** vs ***/ smooth out vec3 v_color; // smooth可省略不寫 /*** fs ***/ smooth in vec3 v_color; // smooth可省略不寫 |
flat | 平面着色 | /*** vs ***/ flat out vec3 v_color; /*** fs ***/ flat in vec3 v_color; |
centroid | 質心采樣(centroid sampling)
使用多重采樣渲染時,centroid可用於強制插值發生在被渲染圖元內部 這可防止圖元的邊緣出現偽像 |
/*** vs ***/ centroid out vec3 v_color; /*** fs ***/ centroid in vec3 v_color; |
vs/fs中能容納的頂點輸入變量(attribute變量)、uniform變量、頂點輸出與片元輸入變量(varying變量)是儲存在硬件中的,其最大數目與設備有關,具體詳見下表:
變量類別 | 保存在硬件的哪個位置 | 是否會打包 | 內置全局變量(表示最大數量) | glGetintegerv查詢 | OpenGL ES 3.0最小值 |
頂點輸入變量(attribute變量) | No | const mediump int gl_MaxVertexAttribs | 16 | ||
vs:統一變量(uniform變量) | 常量存儲:對應一個向量的物理數組 | Yes | const mediump int gl_MaxVertexUniformVectors | GL_MAX_VERTEX_UNIFORM_VECTORS | 256 |
fs:統一變量(uniform變量) | 常量存儲:對應一個向量的物理數組 | Yes | const mediump int gl_MaxFragmentUniformVectors | GL_MAX_FRAGMENT_UNIFORM_VECTORS | 224 |
頂點輸出變量 片元輸入變量 (varying變量) |
插值器:對應一個向量的物理數組 |
Yes |
const mediump int gl_MaxVertexOutputVectors const mediump int gl_MaxFragmentInputVectors const mediump int gl_MaxVaryingVectors |
16 15 8(Open GL ES 2.0) |
注1:為了有效利用寶貴的硬件存儲資源,OpenGL會對變量打包再進行存放。
注2:用uniform修飾的變量、const變量、字面值(如:在代碼中寫的0,1.0等)都會占用常量儲存空間。
精度限定符
用來表示每種數據具有的精度(占用的字節數)。精度越高,計算結果誤差就越小、效果也更好,但也意味着更大的內存、更多能耗和更久的計算時間。
使用精度限定符,可以精細地控制shader程序在效果和性能間的平衡。
精度限定符 | 描述 | float數值范圍和精度 | int數值范圍 |
highp(高精度) | vs的最低精度
注:在某些webgl環境中,fs可能不支持highp精度 如果支持的話,會定義內置宏GL_FRAGMENT_PRECISION_HIGH |
范圍:(-2^62~2^62) 精度:2^-16 |
(-2^16~2^16) |
mediump(中精度) | fs的最低精度 |
范圍:(-2^14~2^14) 精度:2^-10 |
(-2^10~2^10) |
lowp(低精度) | 可以表示所有的顏色 | 范圍:(-2~2) 精度:2^-8 |
(-2^8~2^8) |
各精度限定符的數值范圍和精度實際上與系統環境有關,可動態地使用gl.GetShaderPrecisionFormat()來獲取
對單個變量
mediump float size; // 中精度的浮點型變量 highp vec4 position; // 具有高精度浮點型元素的vec4對象 lowp vec4 color; // 具有低精度浮點型元素的vec4對象
對整個文件
precision mediump float; // 所有float和由float組合成的復合類型(vec2、vec3、vec4、mat2、mat3、mat4)的數默認為中精度 precision highp int; // 所有int和由int組合成的復合類型的數默認為高精度
注:需要放在shader文件緊跟#version xxx的后面,其他語句之前
各數據類型的默認精度
shader類型 | 數據類型 | 默認精度 |
vs | int | highp |
float | highp | |
sampler2D | lowp | |
samplerCube | lowp | |
fs | int | mediump |
float | 無(需手動指定,否則會編譯報錯) |
|
sampler2D | lowp | |
samplerCube | lowp |
基礎類型
bool b1 = true; int n1 = 10; int n2 = -25; int n3 = 0; //int n4 = 3.5; // 編譯失敗! 不能將float隱式地轉換成int //uint u1 = 0; // 編譯失敗! 不能將int隱式地轉換成uint uint u2 = 0u; uint u3 = 100u; float f1 = 2.0; float f2 = 3; float f3 = 0.2f; float f4 = n1; float f5 = float(n1); float f6 = u2; float f7 = float(u3); //float f8 = b1; // 編譯失敗! 不能將bool隱式地轉換成float //float f9 = (float)b1;// 編譯失敗! 不能使用c類型的轉換方式 float f9 = float(b1); int n4 = int(f3); //int n5 = f3; // 編譯失敗! 不能將float隱式地轉換成int //int n6 = u3; // 編譯失敗! 不能將uint隱式地轉換成int int n7 = int(u3); //uint u4 = n1; // 編譯失敗! 不能將int隱式地轉換成uint uint u5 = uint(n2); //bool b2 = n1; // 編譯失敗! 不能將int隱式地轉換成bool bool b3 = bool(n1); //bool b4 = u3; // 編譯失敗! 不能將uint隱式地轉換成bool bool b5 = bool(u3); //bool b6 = f3; // 編譯失敗! 不能將float隱式地轉換成bool bool b7 = bool(f3);
數組
只支持一維數組。
const float a1[3] = float[](0.25, 0.5, 1.0); float a2[2] = float[2](0.3, 0.8); float a4[3]; a4[0] = 0.1; a4[1] = 0.2; a4[2] = 0.3; int len = a4.length(); // len為3 獲取數組a4的元素個數 const int N = 2; vec3 va1[N]; va1[0] = vec3(1.0, 0.0, 0.0); va1[1] = vec3(0.0, 0.0, 1.0); vec3 va2[2] = vec3[](vec3(1.0), vec3(0.2, 0.3, 0.5));
結構體(struct)
① 與C語言結構體一樣,不可從其他結構體上派生
② 與C語言結構體一樣,不允許有構造函數和析構函數
③ 可直接對2個結構體變量進行比較
#version 330 out vec4 FragColor; struct light // 與C語言一樣,不允許從其他結構體上派生 { vec4 color; vec3 position; //light(){} // 與C語言一樣,不允許有構造函數 //~light(){} // 與C語言一樣,不允許有析構函數 void test() { color.x = 0.2; } } GL1; // 定義light結構體,並定義全局變量GL1 light GL2; // 定義light結構體的全局變量GL2 void main() { // light結構體的構造函數參數的順序必須與結構體定義中的成員順序一致 // 如下:參數一 vec4(1.0, 1.0, 0.0, 1.0)會賦值給vec4 color 參數二 vec3(10.0, 10.0, 0.0)會賦值給vec3 position GL1 = light(vec4(1.0, 1.0, 0.0, 1.0), vec3(10.0, 10.0, 0.0)); GL1.test(); GL2.color.r = 1.0; GL2.position = vec3(3.0, 4.0, 5.0); light L1, L2; // 定義light結構體的局部變量L1,L2 L1.color = vec4(0.0, 1.0, 0.0, 1.0); L2 = L1; if (L1 == L2) // true 結構體支持==運算符比較 { } L2.position.x = 100; if (L1 != L2) // true 結構體支持!=運算符比較 { } FragColor = GL1.color; }
向量
向量的分量可以為:
① {x,y,z,w} : 用來獲取頂點坐標分量
② {r,g,b,a} : 用來獲取顏色分量
③ {s,t,p,q} :用來獲取紋理坐標分量
從向量中可以同時抽取多個分量,這個過程稱作混合(swizzling)。但要注意地是,以上三種不能相互混着使用,如:xyr、rgst、xrsw等
分量類型 | 2維 |
3維 |
4維 |
bool | bvec2 | bvec3 | bvec4 |
int | ivec2 | ivec3 | ivec4 |
uint | uvec2 | uvec3 | uvec4 |
float | vec2 | vec3 | vec4 |
vec2 v2a; // v2a為(0.0, 0.0) vec2 v2b = vec2(1.0f); // v2b為(1.0f, 1.0f) vec2 v2c = vec2(1.0, 0.0); // v2c為(1.0, 0.0) //vec3 v2d = v2c.xx; // 編譯失敗 incompatible types in initialization vec2 v2e; // v2e為(0.0, 0.0) v2e = v2c.yx; // v2e為(0.0, 1.0) vec2 v2f; // v2f為(0.0, 0.0) v2f = v2c.rr; // v2f為(1.0, 1.0) float f1 = v2c[0]; // f1為1.0 v2c[0]=v2c.x=v2c.r=v2c.s float f2 = v2c.y; // f1為0.0 v2c.y=v2c.g=v2c.t=v2c[1] // 向量運算 vec2 v2g = vec2(0.4, 0.6); vec2 v2h = vec2(0.1, 0.2); vec2 v2i = v2g + v2h; // v2i為(0.5, 0.8) vec2 v2j = v2g + 0.2; // v2j為(0.6, 0.8) vec2 v2k = v2h * 2; // v2k為(0.2, 0.4) vec2 v2m = v2g * v2h; // v2m為(0.04, 0.12) vec3 v3a = vec3(0.5); // v3a為(0.5, 0.5, 0.5) //vec3 v3b = vec3(1.0, 1.0); // 編譯失敗 too little data in type constructor vec3 v3c = vec3(1.0, 0.5, 0.25); // v3c為(1.0, 0.5, 0.25) //vec3 v3d = vec3(v2a); // 編譯失敗 cast not allowed vec3 v3e = vec3(v2a, 1.0); // v3e為(0.0, 0.0, 1.0) vec2 v2n = vec2(v3c); // 使用v3c的前2個元素 v2n為(1.0, 0.5) vec4 v4a = vec4(0.0, 0.3, 1.0, 0.5); // v4a為(0.0, 0.3, 1.0, 0.5) v4a.xw = vec2(0.2, 0.6); // v4a為(0.2, 0.3, 1.0, 0.6) v4a.gbr = vec3(0.0, 0.0, 1.0); // v4a為(1.0, 0.0, 0.0, 0.6) v4a.pq = vec2(1.0, 0.0); // v4a為(1.0, 0.0, 1.0, 0.0) vec4 v4b = vec4(v2c, v3c); // 先使用v2c來填充,如果沒填滿繼續使用v3c來填充 v4b為(1.0, 0.0, 1.0, 0.5) vec4 v4c; v4c = vec4(0.0, 0.3, 1.0, 0.5).wwwx; // v4c為(0.5, 0.5, 0.5, 0.0)
矩陣
分量類型均為float,按照列優先順序來存儲(列主序)
矩陣類型 | 解釋 |
mat2(mat2x2) | 由2個列向量vec2組成(可通過m[0]、m[1]來訪問) |
mat2x3 | 由2個列向量vec3組成(可通過m[0]、m[1]來訪問) |
mat2x4 | 由2個列向量vec4組成(可通過m[0]、m[1]來訪問) |
mat3x2 | 由3個列向量vec2組成(可通過m[0]、m[1]、m[2]來訪問) |
mat3(mat3x3) | 由3個列向量vec3組成(可通過m[0]、m[1]、m[2]來訪問) |
mat3x4 | 由3個列向量vec4組成(可通過m[0]、m[1]、m[2]來訪問) |
mat4x2 | 由4個列向量vec2組成(可通過m[0]、m[1]、m[2]、m[3]來訪問) |
mat4x3 | 由4個列向量vec3組成(可通過m[0]、m[1]、m[2]、m[3]來訪問) |
mat4(mat4x4) | 由4個列向量vec4組成(可通過m[0]、m[1]、m[2]、m[3]來訪問) |
vec2 v2a = vec2(1.0, 0.5); // v2a為(1.0, 0.5) vec3 v3a = vec3(0.3, 0.2, 0.9); // v3a為(0.3, 0.2, 0.9) vec4 v4a = vec4(0.2, 0.3, 0.8, 0.0); // v4a為(0.2, 0.3, 0.8, 0.0) mat2 m2a; // m2a為(0.0, 0.0, 0.0, 0.0) mat2 m2b = mat2(1.0); // m2b為(1.0, 0.0, 0.0, 1.0) // | 1.0 0.0 | // | 0.0 1.0 | mat2 m2c = mat2(0.2, 0.5, 0.0, 0.8); // m2c為(0.2, 0.5, 0.0, 0.8) // | 0.2 0.0 | // | 0.5 0.8 | mat2 m2d = mat2(v2a, 0.1, 0.3); // m2d為(1.0, 0.5, 0.1, 0.3) mat2 m2e = mat2(0.6, v3a); // m2e為(0.6, 0.3, 0.2, 0.9) mat2 m2f = mat2(v2a, v3a); // 先使用v2a來填充,如果沒填滿繼續使用v3a來填充 m2f為(1.0, 0.5, 0.3, 0.2) mat2 m2g = mat2(v4a); // m2g為(0.2, 0.3, 0.8, 0.0) // | 0.2 0.8 | // | 0.3 0.0 | mat2 m2h = mat2(v2a.yyxx); // m2g為(0.5, 0.5, 1.0, 1.0) // | 0.5 1.0 | // | 0.5 1.0 | mat2x3 m2x3a = mat2x3(v2a.xyx, v3a); // m2x3a為(1.0, 0.5, 1.0, 0.3, 0.2, 0.9) // | 1.0 0.3 | // | 0.5 0.2 | // | 1.0 0.9 | vec2 v2b = m2b[1]; // v2b為(0.0, 1.0) vec3 v3b = m2x3a[0]; // v3b為(1.0, 0.5, 1.0) int n = 1; vec3 v3c = m2x3a[n]; // v3c為(0.3, 0.2, 0.9) float f1 = m2c[1][0]; // f1為0.0 float f2 = m2c[0].y; // f2為0.5 float f3 = m2x3a[0].b; // f3為1.0 mat4 m4a = mat4(0.5); // m4a為(0.5, 0.0, 0.0, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0, 0.0, 0.5) // | 0.5 0.0 0.0 0.0 | // | 0.0 0.5 0.0 0.0 | // | 0.0 0.0 0.5 0.0 | // | 0.0 0.0 0.0 0.5 | // 矩陣與浮點數相加 mat4 m4b = m4a + 0.5; // m4b為(1.0, 0.5, 0.5, 0.5, 0.5, 1.0, 0.5, 0.5, 0.5, 0.5, 1.0, 0.5, 0.5, 0.5, 0.5, 1.0) // | 1.0 0.5 0.5 0.5 | // | 0.5 1.0 0.5 0.5 | // | 0.5 0.5 1.0 0.5 | // | 0.5 0.5 0.5 1.0 | // 矩陣與浮點數相乘 mat4 m4c = m4a * 2; // m4c為(1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0) // | 1.0 0.0 0.0 0.0 | // | 0.0 1.0 0.0 0.0 | // | 0.0 0.0 1.0 0.0 | // | 0.0 0.0 0.0 1.0 | mat4 m4d = mat4(1.0, 0.5, 0.5, 0.5, 0.5, 1.0, 0.5, 0.5, 0.5, 0.25, 0.25, 0.5, 0.5, 0.5, 0.5, 1.0); // | 1.0 0.5 0.5 0.5 | // | 0.5 1.0 0.25 0.5 | // | 0.5 0.5 0.25 0.5 | // | 0.5 0.5 0.5 1.0 | // 矩陣右乘向量 vec4 v4b = m4d * v4a; // v4b為(0.75, 0.6, 0.45, 0.65) 注:v4a被當成列向量 // v4b.x = m4d[0].x * v4a.x + m4d[1].x * v4a.y + m4d[2].x * v4a.z + m4d[3].x * v4a.w = 0.75 // v4b.y = m4d[0].y * v4a.x + m4d[1].y * v4a.y + m4d[2].y * v4a.z + m4d[3].y * v4a.w = 0.6 // v4b.z = m4d[0].z * v4a.x + m4d[1].z * v4a.y + m4d[2].z * v4a.z + m4d[3].z * v4a.w = 0.45 // v4b.w = m4d[0].w * v4a.x + m4d[1].w * v4a.y + m4d[2].w * v4a.z + m4d[3].w * v4a.w = 0.65 // 矩陣左乘向量 vec4 v4c = v4a * m4d; // v4c為(0.75, 0.8, 0.375, 0.65) 注:v4a被當成行向量 // v4c.x = v4a.x * m4d[0].x + v4a.y * m4d[0].y + v4a.z * m4d[0].z + v4a.w * m4d[0].w = 0.75 // v4c.y = v4a.x * m4d[1].x + v4a.y * m4d[1].y + v4a.z * m4d[1].z + v4a.w * m4d[1].w = 0.8 // v4c.z = v4a.x * m4d[2].x + v4a.y * m4d[2].y + v4a.z * m4d[2].z + v4a.w * m4d[2].w = 0.375 // v4c.w = v4a.x * m4d[3].x + v4a.y * m4d[3].y + v4a.z * m4d[3].z + v4a.w * m4d[3].w = 0.65 // 矩陣矩陣相乘 mat2 m2i = mat2(0.1, 0.0, 0.6, 0.2); // m2i為(0.1, 0.0, 0.6, 0.2) // | 0.1 0.6 | // | 0.0 0.2 | mat2 m2j = m2c * m2i; // m2j為(0.02, 0.05, 0.12, 0.46) // | m2c[0].x*m2i[0].x+m2c[1].x*m2i[0].y m2c[0].x*m2i[1].x+m2c[1].x*m2i[1].y | | 0.02 0.12 | // | m2c[0].y*m2i[0].x+m2c[1].y*m2i[0].y m2c[0].y*m2i[1].x+m2c[1].y*m2i[1].y | | 0.05 0.46 |
采樣器(紋理)
只能是uniform變量,該變量從OpenGL中接受紋理單元編號,使得shader能通過該編號來訪問紋理數據。
采樣器(sampler)類型:
大類型 | 小類型 | 說明 | 示例 |
浮點采樣器(不透明) | sampler1D | 1D紋理 | uniform sampler1D s; |
sampler2D | 2D紋理 | uniform sampler2D s; | |
sampler3D | 3D紋理 | uniform sampler3D s; | |
samplerCube | 立方圖紋理 | uniform samplerCube s; | |
samplerCubeShadow | 帶有對比的立方圖深度紋理 | uniform samplerCubeShadow s; | |
sampler1DShadow | 帶有對比的1D深度紋理 | uniform sampler1DShadow s; | |
sampler1DArray | 1D數組紋理 | uniform sampler1DArray s; | |
sampler1DArrayShadow | 帶有對比的1D數組深度紋理 | uniform sampler1DArrayShadow s; | |
sampler2DShadow | 帶有對比的2D深度紋理 | uniform sampler2DShadow s; | |
sampler2DArray | 2D數組紋理 | uniform sampler2DArray s; | |
sampler2DArrayShadow | 帶有對比的2D數組深度紋理 | uniform sampler2DArrayShadow s; | |
有符號整數采樣器(不透明) | isampler1D | 有符號整數1D紋理 | uniform isampler1D s; |
isampler2D | 有符號整數2D紋理 | uniform isampler2D s; | |
isampler3D | 有符號整數3D紋理 | uniform isampler3D s; | |
isamplerCube | 有符號整數立方圖紋理 | uniform isamplerCube s; | |
isampler1DArray | 有符號整數1D數組紋理 | uniform isampler1DArray s; | |
isampler2DArray | 有符號整數2D數組紋理 | uniform isampler2DArray s; | |
無符號整數采樣器(不透明) | usampler1D | 無符號整數1D紋理 | uniform usampler1D s; |
usampler2D | 無符號整數2D紋理 | uniform usampler2D s; | |
usampler3D | 無符號整數3D紋理 | uniform usampler3D s; | |
usamplerCube | 無符號整數立方圖紋理 | uniform usamplerCube s; | |
usampler1DArray | 無符號整數1D數組紋理 | uniform usampler1DArray s; | |
usampler2DArray | 無符號整數2D數組紋理 | uniform usampler2DArray s; |
除了=、==和!=外,采樣器變量不可以作為操作數參與運算
vs/fs中能使用采樣器的個數,詳見下表:
着色器類型 | 最大數量 | 最小數量 |
vs | gl_MaxVertexTextureImageUnits | 0 |
fs | gl_MaxTextureImageUnits | 8 |
函數
函數用法和C語言一樣
vs和fs執行的入口函數均為void main() { }
函數參數限定詞說明:
限定詞 | 說明 |
in | 缺省限定詞,可以省略不寫 |
const | 當前參數為常量,不可在函數內被修改 |
out | ① 進入函數時,會被初始化為0, 0.0或false ② 在函數內修改后,對外可見 |
inout | ① 接受函數外傳入的初始值 ② 在函數內修改后,對外可見 |
① 參數限定詞只需要用在函數上,函數調用時不用帶。
② 不允許函數遞歸 這一限制的原因是編譯器會把函數都內聯展開,以支持沒有堆棧的GPU
float square(float value) // 求平方 { return value * value; } void fuc1(const float value) // const參數不能在函數內部修改 { //value = 0.2; // assignment to const variable value 編譯錯誤 } void fuc2(out float value) // value進入函數時,會被重新初始化為0;在函數內修改,對外可見 { value += 0.5; } void fuc3(inout float value) // value的初始值從函數外傳入;在函數內修改,對外可見 { value += 0.5; } int fun4(int n) // 如果在main函數中直接或間接調用fun4函數,會編譯報錯:recursive call to function { if (n == 0 || n == 1) { return 1; } return fun4(n-1) + fun4(n-2); }
內置函數
類別 | 內置函數 |
角度函數 | radians //角度轉弧度 degrees // 弧度轉角度 |
三角函數 | sin // 正弦 cos // 余弦 tan // 正切 asin // 反正弦 acos // 反余弦 atan // 反正切 |
指數函數 | pow // x^y exp // 自然指數 log // 自然對數 exp2 // 2^x log2 // 以2為底對數 sqrt // 開平方 inversesqrt // 開平方倒數 |
通用函數 | abs // 絕對值 min // 最小值 max // 最大值 mod // 取余數 sign // 取正負號 floor // 向下取整 ceil // 向上取整 clamp // 限定范圍 mix // 線性內插 step // 步進函數 smoothstep // 艾米內插步進 fract // 獲取小數部分 |
幾何函數 | length // 矢量長度 distance // 兩點間距離 dot // 內積 cross // 外積 normalize // 歸一化 reflect // 矢量反射 faceforward // 使矢量“朝前” |
矩陣函數 | matrixCmpMult // 逐元素乘法 |
矢量函數 | lessThan // 逐元素小於 lessThanEqual // 逐元素小於等於 greaterThan // 逐元素大於 greaterThanEqual // 逐元素大於等於 equal // 逐元素相等 notEqual // 逐元素不等 any // 任一元素為true則為true all // 所有元素為true則為true not // 逐元素取補 |
紋理查詢函數 | texture2D // 在二維紋理中獲取紋素 textureCube // 在立方體紋理中獲取紋素 texture2DProj // texture2D的投影版本 texture2DLod // texture2D的金字塔版本 textureCubeLod // textureCube的金字塔版本 texture2DProjLod // texture2DLod的投影版本 |
限定符順序規則
一般變量中:不變性(invariant ) > 插值(smooth、flat) > 存儲(const、in、out、uniform、centroid in、centroid out) > 精度(highp、mediump、lowp)
參數變量中:存儲(const、in、out、uniform、centroid in、centroid out) > 參數(in、out、inout) > 精度(highp、mediump、lowp)
幫助手冊
OpenGL ES 1.1 API(含GLSL內置函數)幫助手冊:https://www.khronos.org/registry/OpenGL-Refpages/es1.1/xhtml/
OpenGL ES 2.0 API(含GLSL內置函數)幫助手冊:https://www.khronos.org/registry/OpenGL-Refpages/es2.0/
OpenGL ES 3.0 API(含GLSL內置函數)幫助手冊:https://www.khronos.org/registry/OpenGL-Refpages/es3.0/
OpenGL ES 3.1 API(含GLSL內置函數)幫助手冊:https://www.khronos.org/registry/OpenGL-Refpages/es3.1/
OpenGL ES 3.2 API(含GLSL內置函數)幫助手冊:https://www.khronos.org/registry/OpenGL-Refpages/es3/
OpenGL 2.1, GLX, and GLU幫助手冊:https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/
OpenGL 4.5 API(含GLSL內置函數)幫助手冊:https://www.khronos.org/registry/OpenGL-Refpages/gl4/
Khronos OpenGL and OpenGL ES 參考頁:https://www.khronos.org/registry/OpenGL-Refpages/
WebGL 1.0 API幫助手冊:https://www.khronos.org/registry/webgl/specs/latest/1.0/
WebGL 2.0 API幫助手冊:https://www.khronos.org/registry/webgl/specs/latest/2.0/
參考卡片
OpenGL ES 2.0(含API和GLSL)參考卡片:https://www.khronos.org/opengles/sdk/docs/reference_cards/OpenGL-ES-2_0-Reference-card.pdf
OpenGL ES 3.0(含API和GLSL)參考卡片:https://www.khronos.org/files/opengles3-quick-reference-card.pdf
OpenGL ES 3.1(含API和GLSL)參考卡片:https://www.khronos.org/files/opengles31-quick-reference-card.pdf
OpenGL ES 3.2(含API和GLSL)參考卡片:https://www.khronos.org/files/opengles32-quick-reference-card.pdf
OpenGL ES 3.2(含API和GLSL)參考卡片:https://www.khronos.org/files/opengl-quick-reference-card.pdf
OpenGL 3.2(含API和GLSL)參考卡片:https://www.khronos.org/files/opengl-quick-reference-card.pdf
OpenGL 4.1(含API和GLSL)參考卡片:https://www.khronos.org/files/opengl41-quick-reference-card.pdf
OpenGL 4.2(含API和GLSL)參考卡片:https://www.khronos.org/files/opengl42-quick-reference-card.pdf
OpenGL 4.3(含API和GLSL)參考卡片:https://www.khronos.org/files/opengl43-quick-reference-card.pdf
OpenGL 4.4(含API和GLSL)參考卡片:https://www.khronos.org/files/opengl44-quick-reference-card.pdf
OpenGL 4.5(含API和GLSL)參考卡片:https://www.khronos.org/files/opengl45-quick-reference-card.pdf
OpenGL 4.6(含API和GLSL)參考卡片:https://www.khronos.org/files/opengl46-quick-reference-card.pdf
WebGL 1.0(含API和GLSL)參考卡片:https://www.khronos.org/files/webgl/webgl-reference-card-1_0.pdf
WebGL 2.0(含API和GLSL)參考卡片:https://www.khronos.org/files/webgl20-reference-guide.pdf
Vulkan 1.0(含API)參考卡片:https://www.khronos.org/files/vulkan10-reference-guide.pdf
Vulkan 2.0(含API)參考卡片:https://www.khronos.org/files/vulkan11-reference-guide.pdf
參考