着色器子產品概述
Vulkan 使用的着色器代碼格式是一種叫做 SPIR-V 的位元組碼,這一位元組碼格式可以在 Vulkan 和 OpenCL 上使用。可以用它來編寫圖形和計算着色器,在這裡,我們将它用于編寫圖形管線的着色器。GPU 廠商的編譯器将位元組碼轉換為原生代碼的工作複雜度遠遠低于直接編譯較進階的類 C 代碼。過去的經驗告訴我們使用類 C 代碼,比如GLSL 作為着色器代碼,會因為不同 GPU 廠商對代碼的不同解釋而造成大量問題,并且類 C 代碼的編譯器實作要比位元組碼編譯器複雜的多,GPU廠商實作的編譯器也極有可能存在錯誤,不同 GPU 廠商的實作也差異巨大。而使用位元組碼格式,上述的這些問題可以在極大程度上減少。雖然,Vulkan 使用位元組碼格式作為着色器代碼,但這并不意味着我們要直接書寫位元組碼來編寫着色器。Khronos 釋出了一個獨立于廠商的可以将 GLSL 代碼轉換為 SPIR-V 位元組碼的編譯器。這個編譯器可以驗證我們的着色器代碼是否完全符合标準,将 GLSL 代碼轉換為 SPIR-V 位元組碼。我們可以在應用程式運作時調用這個編譯器,動态生成 SPIR-V 位元組碼,但在本教程,我們沒有這樣做。這一編譯器已經被包含在了 LunarG的 Vulkan SDK 中,編譯器可執行檔案名稱為 glslangValidator.exe。
GLSL 是一個類 C 的着色器語言。使用 GLSL 編寫的程式包含了一個 main 函數,這一函數完成具體的運算操作。GLSL 使用全局變量進行輸入輸出,它包含了許多用于圖形程式設計的特性,比如向量和矩陣支援,用于計算叉積的函數,用于矩陣與向量相乘的函數,用于計算反射向量的函數等等。GLSL 中的向量類型叫做 vec,後跟一個表示向量元素數的數字。比如,用于表示一個三維空間位置的向量的類型為 vec3。GLSL 允許我們通路向量的分量比如.x,也允許我們使用表達式來建立新的向量值,比如vec3(1.0, 2.0, 3.0).xy 會傳回一個 vec2 類型的值。向量構造器也可以被組合使用,比如可以使用 vec3(vec2(1.0, 2.0), 3.0) 生成一個 vec3 類型的值。
頂點着色器:
頂點着色器對輸入的每個頂點進行處理。它可以接收頂點屬性作為輸入,比如世界坐标,顔色,法線和紋理坐标。它的輸出包括頂點最終的裁剪坐标和需要傳遞給片段着色器的頂點屬性,比如顔色和紋理坐标。這些
值會被插值處理後傳給頂點着色器。裁剪坐标是一個來自頂點着色器的四維向量,它的四個成分會被除以第四個成分來完成規範化。規範化後的坐标被映射到幀緩沖的坐标空間,如下圖所示:

我們可以直接将頂點着色器輸出的裁剪坐标的第四個成分設定為 1,然後作為規範裝置坐标。這樣裁剪坐标到規範裝置坐标就不會對坐标進行任何變換。通常,頂點坐标被存儲在一個頂點緩沖中,但對于 Vulkan 來說,建立頂點緩沖,然後填入資料要進行很多操作為了盡快讓我們的三角形顯示在螢幕上,我們暫時先直接将頂點坐标寫入頂點着色器,就像這樣:
#version 450
#extension GL_ARB_separate_shader_objects : enable
out gl_PerVertex{
vec4 gl_Position;
};
layout(location = 0) out vec3 fragColor;
vec2 positions[3] = vec2[](
vec2(0.0,-0.5),
vec2(0.5,0.5),
vec2(-0.5,0.5)
);
vec3 colors[3] = vec3[](
vec3(1.0,0.0,0.0),
vec3(0.0,1.0,0.0),
vec3(0.0,0.0,1.0)
);
void main(){
gl_Position = vec4(positions[gl_VertexIndex],0.0,1.0);
fragColor = colors[gl_VertexIndex];
}
着色器的 main 函數對于每個頂點執行一次。GLSL 内建的 gl_VertexIndex變量包含了目前頂點的索引。這一索引通常來說是用來引用頂點緩沖中的頂點資料,但在這裡,我們用它來引用我們在着色器中寫死的頂點資料。我們輸出的裁剪坐标由代碼中的 positions 數組給出了前兩個成分,剩餘兩個成分被我們設定為了 0.0 和 1.0。為了讓着色器代碼可以在 Vulkan 下工作,我們需要使用 GL_ARB_separate_shader_objects 擴充。
片段着色器:
我們的三角形由來自頂點着色器的三個頂點作為三角形的頂點構成,這一三角形範圍内的螢幕像素會被使用片段着色器處理後的片段進行填充。
将頂點的顔色傳遞給片段着色器,由片段着色器将顔色值輸出到幀緩沖上。在頂點着色器中添加顔色輸出變量,并在 main 函數中寫入顔色值到顔色輸出變量:
#version 450
#extension GL_ARB_separate_shader_objects : enable
layout(location = 0) in vec3 fragColor;
layout(location = 0) out vec4 outColor;
void main(){
outColor = vec4(fragColor, 1.0);
}
一組對應的輸入和輸出變量可以使用不同的變量名稱,編譯器可以通過定義它們時使用的 location 将它們對應起來。片段着色器的 main 函數現在被我們修改為輸出輸入的顔色變量作為片段顔色。三角形除了頂點之
外的片段顔色會被插值處理。
編譯着色器:
建立.bat,添加如下内容儲存,輕按兩下運作,就可以完成着色器代碼的編譯
D:/VulkanSDK/1.1.77.0/Bin32/glslangValidator.exe -V shader.vert
D:/VulkanSDK/1.1.77.0/Bin32/glslangValidator.exe -V shader.frag
pause
運作腳本後,看到兩個新的檔案 vert.spv 和 frag.spv。這兩個檔案的檔案名由編譯器自動推導出來,讀者可以使用自己喜歡的名稱重命名這兩個檔案。編譯腳本的執行過程中可能出現一些缺少特性的警告,讀者可以放心地忽略掉這些警告。
如果着色器代碼存在文法錯誤,編譯器會報告文法錯誤所在的行,以及錯誤出現的原因。
編譯器還支援将SPIR-V 格式的位元組碼反向編譯為便于人類閱讀的代碼格式。