GLSL(OpenGL 著色器語言)基礎語法
GLSL(OpenGL Shading Language)是 OpenGL 計算著色器的語言,語法類似于 C 語言,但提供了針對 GPU 的特殊功能,如向量運算和矩陣運算。
著色器的開頭總是要聲明版本,接著是輸入和輸出變量、uniform 和 main 函數。每個著色器的入口點都是main函數,在這個函數中我們處理所有的輸入變量,并將結果輸出到輸出變量中。
#version version_number
in type in_variable_name;
out type out_variable_name;
uniform type uniform_name;void main()
{// 處理輸入并進行一些圖形操作...// 輸出處理過的結果到輸出變量out_variable_name = weird_stuff_we_processed;
}
基本語法
版本聲明
每個 GLSL 著色器的第一行通常需要指定版本號:
#version 330 core // 330 core 表示使用 OpenGL 3.3 及以上的核心模式
#version 120 // OpenGL 2.1
#version 450 core(OpenGL 4.5 // OpenGL 4.5
變量聲明
類型 變量名;
常見的基本數據類型
類型 | 說明 | 示例 |
---|---|---|
int | 整數 | int a = 5; |
float | 浮點數 | float b = 3.14; |
bool | 布爾類型 | bool flag = true; |
vec2 | 2D向量 (x, y) | vec2 v = vec2(1.0, 2.0); |
vec3 | 3D向量 (x, y, z) | vec3 color = vec3(1.0, 0.5, 0.2); |
vec4 | 4D向量 (x, y, z, w) | vec4 position = vec4(1.0, 2.0, 3.0, 1.0); |
mat4 | 4×4 矩陣 | mat4 transform; |
紋理類型(Texture Types)
紋理類型用于表示不同的紋理對象,它們在圖形渲染中用于存儲圖片或紋理數據
類型 | 說明 |
---|---|
sampler2D | 2D紋理采樣器,通常用于訪問2D紋理圖像 |
sampler3D | 3D紋理采樣器,用于訪問3D紋理 |
samplerCube | 立方體紋理采樣器,用于訪問立方體貼圖(環境映射) |
sampler2DArray | 2D紋理數組采樣器,訪問多個2D紋理層 |
sampler2DShadow | 用于陰影映射的2D紋理采樣器 |
samplerCubeShadow | 用于陰影映射的立方體紋理采樣器 |
紋理采樣示例
#version 330 corein vec2 TexCoords; // 傳入的紋理坐標
out vec4 FragColor;// 輸出的顏色uniform sampler2D texture1; // 紋理變量,代表 GPU 采樣的 2D 貼圖void main()
{FragColor = texture(texture1, TexCoords); // 從紋理中獲取顏色,并輸出
}
uniform關鍵字:uniform 變量用于在 C++ 代碼和 Shader 之間傳遞數據。
與 in/out 不同,uniform 是全局變量,在著色器的所有調用中都保持相同的值。
聚合類型(Aggregate Types)
這些類型可以用來組合多個基本類型,提供更復雜的數據結構。
(1)數組類型(Array types):
float arr[10]; // 一個包含 10 個 float 的數組。
vec3 arr[5]; // 一個包含 5 個 vec3 向量的數組。
(2)結構體類型(Structure types):
sstruct Light {vec3 position;vec3 color;float intensity;
};
內置變量類型(Built-in Variables)
OpenGL著色器程序中預定義的變量,用于接收從應用程序傳遞的狀態或傳遞給其他著色器階段的值。
(1)頂點著色器中常見的內建變量
變量 | 說明 |
---|---|
gl_Position | 指定當前頂點的屏幕空間位置。它是 vec4 類型,必須在頂點著色器中設置 |
gl_PointSize | 指定繪制點的大小(只有在 GL_POINTS 繪制模式時有效) |
gl_VertexID | 當前頂點的索引 |
(2)片元著色器中常見的內建變量
變量 | 說明 |
---|---|
gl_FragColor | 最終輸出的顏色。片元著色器必須設置它來確定每個像素的顏色 |
gl_FragDepth | 指定片元的深度值 |
(3)全局變量
變量 | 說明 |
---|---|
gl_FragCoord | 片元著色器中的當前片元坐標 |
gl_TexCoord | 紋理坐標,用于在片元著色器中進行紋理采樣 |
向量操作
GLSL 提供了一些專門用于向量操作的語法
vec3 v1 = vec3(1.0, 2.0, 3.0);
vec3 v2 = vec3(0.5, 0.5, 0.5);vec3 sum = v1 + v2; // 向量加法
vec3 diff = v1 - v2; // 向量減法
vec3 scale = v1 * 2.0; // 向量數乘
float dotProduct = dot(v1, v2); // 點積
vec3 crossProduct = cross(v1, v2); // 叉積
向量分量訪問
vec4 color = vec4(1.0, 0.5, 0.2, 1.0);
float red = color.r; // red = 1.0
float green = color.g; // green = 0.5
float blue = color.b; // blue = 0.2
float alpha = color.a; // alpha = 1.0
xyzw 和 rgba 可以混用
vec3 rgb = color.rgb; // 取前三個分量
vec2 xy = color.xy; // 取前兩個分量
矩陣運算
mat4 transform = mat4(1.0); // 4×4 單位矩陣
vec4 position = vec4(1.0, 2.0, 3.0, 1.0);
vec4 result = transform * position; // 矩陣-向量乘法
GLSL 變量限定符
GLSL 提供了不同的變量限定符,決定變量的作用范圍和生命周期
(1)變量存儲限定符(Storage Qualifiers)
限定符 | 作用 | 適用著色器 |
---|---|---|
in | 輸入變量,從上一階段著色器傳入的數據 | 頂點、片元著色器 |
out | 輸出變量,傳遞給下一階段著色器 | 頂點、片元著色器 |
inout | 既可作為輸入,也可作為輸出 | |
uniform | 全局變量,供 CPU 傳入著色器,所有著色器階段共享 | 頂點、片元著色器 |
attribute | 舊版 GLSL(< 3.3),用于頂點著色器的輸入變量,已被 in 取代 | 頂點著色器 |
varying | 舊版 GLSL(< 3.3),用于頂點著色器和片元著色器之間傳遞數據,已被 in 和 out 取代 | 頂點、片元著色器 |
const | 常量,在編譯時確定,不能修改 | |
buffer | 訪問 Shader Storage Buffer Object(SSBO),用于計算著色器 |
uniform
首先,uniform是全局的(Global)。全局意味著uniform變量必須在每個著色器程序對象中都是獨一無二的,而且它可以被著色器程序的任意著色器在任意階段訪問。第二,無論你把uniform值設置成什么,uniform會一直保存它們的數據,直到它們被重置或更新。
代碼示范
(1)在著色器中使用 uniform 關鍵字,并帶上類型和名稱。
#version 330 core
out vec4 FragColor;
uniform vec4 ourColor;void main() {FragColor = ourColor;
}
(2)在OpenGL程序代碼中設置這個變量
float timeValue = glfwGetTime();
float greenValue = (sin(timeValue) / 2.0f) + 0.5f;// 利用時間動態計算顏色或效果
int vertexColorLocation = glGetUniformLocation(shaderProgram, "ourColor");// 查詢uniform ourColor的位置值
glUseProgram(shaderProgram);// 激活指定的著色器程序
glUniform4f(vertexColorLocation, 0.0f, greenValue, 0.0f, 1.0f);// 設置uniform值
QT代碼示范
Qt 封裝了glGetUniformLocation(),提供了uniformLocation()。
// 獲取 uniform 變量的位置
int location = shaderProgram.uniformLocation("ourColor");
if (location != -1) {shaderProgram.setUniformValue(location, QVector4D(0.0f, 1.0f, 0.0f, 1.0f));// 設置uniform值
}
或者,在QOpenGLShaderProgram中,甚至不需要手動獲取Location,可以直接使用setUniformValue(),它會自動查找uniformLocation()并設置值😃:
shaderProgram.setUniformValue("ourColor", QVector4D(0.0f, 1.0f, 0.0f, 1.0f));
總結
uniform變量用于在 程序(CPU)和 著色器(GPU)之間傳遞數據。uniform變量的值在渲染迭代中保持不變,直到顯式地更新。可以用于傳遞光照、變換、時間等不隨頂點或片段變化的參數。
“渲染迭代”是指每次繪制調用,每次調用glDrawArrays或 glDrawElements等函數時,都會渲染一批圖形。在一次渲染調用中,uniform變量的值是固定的,直到顯式地改變它。
(2)變量布局限定符(Layout Qualifiers)
關鍵字 | 作用 |
---|---|
layout(location = N) | 指定變量的綁定位置(常用于 in/out 變量) |
layout(binding = N) | 指定 uniform buffer、采樣器的綁定點 |
layout(origin_upper_left) | 設定坐標原點在左上角(用于片元著色器) |
layout(pixel_center_integer) | 指定像素中心對齊規則 |
(3)變量精度限定符(Precision Qualifiers)
關鍵字 | 作用 |
---|---|
highp | 高精度(32 位浮點數) |
mediump | 中精度(16 位浮點數) |
lowp | 低精度(10 位或 8 位浮點數) |
在 OpenGL ES(移動端)上,必須顯式聲明精度,但桌面版 GLSL 默認 highp。
(4)插值限定符(Interpolation Qualifiers)
關鍵字 | 作用 |
---|---|
smooth | 默認插值方式(透視校正插值) |
flat | 禁用插值,所有片元使用同一頂點的值 |
noperspective | 線性插值(無透視校正) |
(5)片元著色器輸出限定符(Fragment Shader Output Qualifiers)
關鍵字 | 作用 |
---|---|
discard | 丟棄當前片元,不進行顏色寫入 |
depth_any | 允許片元寫入任何深度值 |
depth_greater | 片元只能寫入更大的深度值 |
depth_less | 片元只能寫入更小的深度值 |
渲染管線的基本流程
OpenGL 渲染管線是一個從頂點數據到最終像素顏色輸出的流水線處理過程,主要分為以下幾個階段:
(1)應用階段(CPU端)
1?? 準備數據(VAO、VBO、EBO、Shader、Texture)
2??設置狀態(glEnable(GL_DEPTH_TEST) 等)
3??執行繪制(glDrawArrays() / glDrawElements())
👉GPU 端(渲染管線)
(2)頂點著色器(Vertex Shader)
運行在 GPU 上,對每個頂點執行一次。
關鍵任務
處理頂點數據(如頂點坐標、顏色、法線、紋理坐標) 。
計算 頂點的最終位置(gl_Position),將物體坐標系轉換為相機坐標系(裁剪空間),渲染相機范圍內的物體。
計算并輸出其他頂點屬性:顏色、紋理坐標等數據,以傳遞給下一階段。
輸入:
(1)頂點數據:頂點的位置、法線、顏色、紋理坐標等,這些數據通常保存在頂點緩沖對象(VBO)中。
(2)頂點著色器的輸入變量:包括位置、法線、顏色等,通過layout關鍵字來指定。
處理:
(1)頂點坐標變換:
使用 模型矩陣(Model Matrix):將頂點從模型空間轉換到世界空間。
使用 視圖矩陣(View Matrix):將頂點從世界空間轉換到相機空間,通常相當于相機的視圖矩陣。
使用投影矩陣(Projection Matrix):將頂點從相機空間轉換到裁剪空間,通常有兩種類型的投影:
1)正交投影(Orthographic projection)
2)透視投影(Perspective projection)
特性 | 正交投影(Orthographic Projection) | 透視投影Perspective Projection) |
---|---|---|
投影線 | 平行 | 收斂(有消失點) |
物體大小 | 不受視距影響,距離遠近無關 | 物體距離遠,投影變小;距離近,投影變大 |
深度感 | 無深度感,所有物體看起來是平的 | 有深度感,遠近物體具有明顯的大小差異 |
常見應用 | 2D 游戲、CAD、技術圖紙 | 3D 游戲、虛擬現實、電影動畫 |
適用場景 | 精確的比例和尺寸繪制(如建筑圖紙、設計圖紙) | 創建真實的場景深度感,模擬人眼視角 |
投影效果 | 所有物體大小一致,不會變形 | 物體隨著距離遠近而縮小或放大,產生透視效果 |
(2)法線變換:
法線需要從模型空間變換到世界空間或視圖空間,通常要用到模型矩陣的逆轉置矩陣來正確處理縮放等非線性變換,以防止模型縮放影響法線的方向。
(3)頂點屬性計算:
頂點著色器可以根據頂點的其他屬性(如法線、顏色、紋理坐標)來計算光照、顏色等值,傳遞給片段著色器。
輸出:
(1)gl_Position:這是裁剪空間中的頂點位置,是后續階段(如光柵化)裁剪的基礎。
(2)gl_PointSize:在繪制點時,控制點的大小。
(3)gl_ClipDistance:用于頂點裁剪的距離參數。
(4)其他頂點屬性(如顏色、法線、紋理坐標等)可以輸出到后續的片段著色器。
代碼示例
#version 330 corelayout (location = 0) in vec3 aPos; // 傳入頂點位置(綁定頂點位置到 location = 0)
layout (location = 1) in vec3 aColor; // 傳入頂點顏色
layout (location = 2) in vec3 inNormal; // 頂點法線out vec3 vColor; // 傳遞給片元著色器的顏色
out vec3 fragNormal; // 法線,傳遞給片段著色器uniform mat4 model; // 模型矩陣
uniform mat4 view; // 視圖矩陣
uniform mat4 projection; // 投影矩陣void main()
{// 將法線從模型空間變換到世界空間fragNormal = mat3(transpose(inverse(model))) * inNormal;gl_Position = projection * view * model * vec4(aPos, 1.0); // 計算最終的頂點位置,并賦值給 gl_PositionvColor = aColor; // 顏色傳遞給片元著色器
}
layout(location = n) 解釋
layout(location = n) 用來指定頂點屬性的綁定位置 n。這個 n 位置是在OpenGL中通過glVertexAttribPointer或glVertexAttribPointer函數指定的。
(1)為了明確匹配頂點數據和著色器變量
頂點著色器需要使用 特定位置的屬性 來獲得頂點數據,例如頂點位置、顏色或紋理坐標。通過 layout(location = n),可以明確告訴 OpenGL 每個頂點屬性的輸入順序和位置索引。避免因順序錯誤或不一致導致數據混亂的問題。
(2) 對于多屬性的支持
當你頂點數據包含多個屬性時,如:位置、顏色、法線、紋理坐標等,需要為每個屬性指定一個獨立的 location,確保 OpenGL 正確地將數據傳遞給著色器。
在QT中寫法如下:
👉 頂點數據的位置索引(n)必須和著色器中聲明的 layout(location = n) 對應。
gl_Position的解釋
gl_Position是OpenGL中一個非常特殊的內部變量,其類型是 vec4(四維向量),包含了頂點在三維空間中的位置以及齊次坐標的 w 分量。
vec4 gl_Position = vec4(x, y, z, w);
gl_Position定義了 頂點在裁剪空間中的位置,位于投影變換后的空間。
gl_Position作用: 當頂點著色器的輸出 gl_Position 被送入裁剪階段和光柵化階段后,OpenGL 會進行裁剪和透視除法:
- 裁剪:將視野外的頂點丟棄,只保留視野內的頂點。
- 透視除法:通過除以 gl_Position.w 來將頂點從裁剪空間轉換為屏幕空間,得到最終的窗口坐標。
注:裁剪階段和光柵化階段是OpenGL渲染管線中的固定階段,由OpenGL內部自動處理。
在上面的示例中,aPos 是傳入的頂點坐標。我們將模型矩陣、視圖矩陣和投影矩陣與 aPos 相乘,最終得到頂點的位置,并賦值給 gl_Position。
(3)圖元裝配
將頂點組合成 點(Point)、線(Line)、三角形(Triangle)。
(4)細分著色器(Tessellation Shader,可選)
用于將少量控制點細分成更多的幾何數據,以生成更精細的模型(如地形細節)。
細分控制著色器(Tessellation Control Shader, TCS)
細分評估著色器(Tessellation Evaluation Shader, TES)
細分曲面,提高模型精度
(5)幾何著色器(Geometry Shader,可選)
運行在 GPU 上,以整個圖元(如點、線段、三角形)為單位進行處理,而不是單獨處理每個頂點。
關鍵任務
修改或生成新的頂點(如擴展為線段或面)。
可以輸出多個頂點來生成新的幾何形狀。
繼續傳遞顏色、紋理坐標等數據。
代碼示例
#version 330 core
layout(triangles) in; // 輸入圖元類型(三角形)
layout(triangle_strip, max_vertices = 3) out; // 輸出圖元類型(三角形條帶)in vec3 vColor[]; // 從頂點著色器接收顏色數據
out vec3 fColor; // 傳遞給片元著色器void main()
{for (int i = 0; i < 3; i++) // 處理每個輸入頂點{gl_Position = gl_in[i].gl_Position; // 繼承輸入頂點的位置信息fColor = vColor[i]; // 繼承顏色信息EmitVertex(); // 輸出該頂點}EndPrimitive(); // 結束當前圖元
}
(6)光柵化(Rasterization)
將頂點數據轉換為像素點(片元),并決定哪些像素應該被繪制填充。以頂點為邊界,中間做插值運算,填充像素點。
(7)片元著色器(Fragment Shader)
運行在 GPU 上,對每個片元(像素)執行一次。
關鍵任務
紋理采樣:從紋理的像素 賦值給上一階段產生的像素點上;
計算最終顏色(紋理、光照、陰影、顏色混合計算);
輸出顏色給幀緩沖
【注】假若,頂點著色器4個,到了光柵化,插值后為100 100,那么到了片段著色器,則會進行100 * 100次運算。所以,在這個階段的運算量是指數級增長。故能在頂點著色器運算的,就不要放到片段著色器中運算,大大影響效率。
代碼示例
#version 330 core
in vec3 vColor; // 從頂點著色器傳入的顏色
//in vec3 fColor; // 從幾何著色器接收顏色
out vec4 FragColor; // 輸出最終顏色void main()
{FragColor = vec4(vColor, 1.0); // 計算最終顏色// FragColor = vec4(fColor, 1.0); // 計算最終顏色
}
注意:頂點著色器輸出的變量和片元著色器輸入的變量的名稱必須一致!
頂點著色器的輸出變量(out 變量)會作為輸入傳遞給片元著色器(in 變量)。OpenGL會自動匹配具有相同名稱的變量,從而使得頂點著色器的輸出可以作為片元著色器的輸入。
在OpenGL渲染管線中,著色器階段之間的數據傳遞是通過 變量名稱、類型和順序 來確保一致性的。通常,頂點著色器的輸出(out 變量)會作為下游階段(如片元著色器或幾何著色器)的輸入(in 變量)。確保變量名稱、類型和順序一致是實現數據正確傳遞的關鍵。
(8)測試與混合
深度測試(Depth Test) → 確保正確的遮擋關系。
模板測試(Stencil Test) → 實現裁剪效果。
混合(Blending) → 處理透明度,如 glEnable(GL_BLEND)。
(9)Framebuffer幀緩沖
最終顏色數據被寫入幀緩沖區(Framebuffer),并顯示到屏幕上。幀緩沖有個地址,是在內存里。我們通過不停的向Framebuffer中寫入數據, 顯示控制器就自動的從Framebuffer中取數據并顯示出來。
多個著色器都可以輸出顏色?
頂點著色器:負責處理每個頂點的顏色,并傳遞給幾何著色器或片元著色器。
幾何著色器:(如果存在) 可以修改頂點的顏色并重新輸出給片元著色器。
片元著色器: 計算最終的像素顏色,并寫入幀緩沖區。
總結
?GLSL語法類似C語言,但更適合GPU并行計算。
?頂點著色器主要進行坐標變換。
?片元著色器主要計算像素顏色。
?變量修飾符 in、out、uniform 控制數據流。
?向量、矩陣 計算是GLSL 的核心,優化GPU性能。