Vulkan沒有指定官方的着色器編程語言,而是採用SPIR-V二進制中間格式進行表示。開發人員一般需要基於某種着色器編程語言開發着色器,之後再編譯爲SPIR-V格式。可以選用GLSL着色器編程語言進行開發。
大型遊戲場景中,一般預先將着色器編譯爲SPIR-V格式保存在文件中,程序運行時直接加載SPIR-V數據,提高效率。
着色器的預編譯和調用過程如面流程所示:
一、着色器源代碼預編譯
首先編輯好GLSL的着色器文件,後綴依次爲:頂點着色器-.vert、片元着色器-.frag、細分控制着色器-.tesc、細分執行着色器-.tese、幾何着色器-.geom、計算着色器-.comp。之後使用Vulkan SDK中的glslang validator命令對着色器源代碼進行編譯。
glslangvalidator -V commonTexLight.vert -o commonTexLight.vert.spv
其中,V參數代表得到SPIR-V格式的數據,o代表輸出文件的路徑。
以下兩個文件給出了vert着色器和frag着色器的GLSL源代碼文件內容:
//給定了所用GLSL的版本
#version 400
//Vulkan中想使用着色器進行開發,需要開啓這兩個擴展
#extension GL_ARB_separate_shader_objects : enable//啓動GL_ARB_separate_shader_objects
#extension GL_ARB_shading_language_420pack : enable//啓動GL_ARB_shading_language_420pack
//聲明一致塊myBufferVals,包含接收總變換矩陣的成員mvp
layout (std140,set = 0, binding = 0) uniform bufferVals {//一致塊
mat4 mvp;//總變換矩陣
} myBufferVals;
layout (location = 0) in vec3 pos;//傳入的物體座標系頂點位置
layout (location = 1) in vec3 color;//傳入的頂點顏色
layout (location = 0) out vec3 vcolor;//傳到片元着色器的頂點顏色
//定義了輸出接口塊
out gl_PerVertex {
vec4 gl_Position;//頂點最終位置
};
//頂點着色器的主方法
void main() {
//計算最終頂點位置:最終變換矩陣*物體座標系下的頂點座標
gl_Position = myBufferVals.mvp * vec4(pos,1.0);
//傳遞頂點顏色給片元着色器
vcolor=color;
}
//給定了所用GLSL的版本
#version 400
//Vulkan中想使用着色器進行開發,需要開啓這兩個擴展
#extension GL_ARB_separate_shader_objects : enable//啓動GL_ARB_separate_shader_objects
#extension GL_ARB_shading_language_420pack : enable//啓動GL_ARB_shading_language_420pack
//定義兩個顏色數據值
layout (location = 0) in vec3 vcolor;//頂點着色器傳入的頂點顏色數據
layout (location = 0) out vec4 outColor;//輸出到渲染管線的片元顏色值
//片元着色器的主方法
void main() {
//將頂點着色器傳遞過來的顏色值輸出,最後的1.0代表A通道
outColor=vec4(vcolor.rgb,1.0);
}
二、用於加載着色器SPIR-V數據的結構體&類
結構體SpvDataStruct:存有兩個成員,size用於存儲SPIR-V數據的總字節數;data指向SPIR-V數據內存塊首地址的指針。
類FileUtil:具有loadSPV函數,加載SPIR-V文件。
typedef struct SpvDataStruct{//存儲SPIR-V數據的結構體
int size; //SPIR-V數據總字節數
uint32_t* data; //指向SPIR-V數據內存塊首地址的指針
} SpvData;
class FileUtil{
public: //此處省略了原有FileUtil類頭文件中的成員方法聲明
static SpvData& loadSPV(string fname); //加載Assets文件夾下的SPIR-V數據
};
三、類FuleUtil中的loadSPV函數
首先通過AAsetManager_open方法獲得AAsset對象,之後通過AAset_getLength獲取總字節數,然後構建了用於存儲SPIR-V數據的結構體實例,最後加載數據。
//加載Assets文件夾下的SPIR-V數據文件
SpvData& FileUtil::loadSPV(string fname){
AAsset* asset =AAssetManager_open(aam,fname.c_str(),AASSET_MODE_STREAMING);
assert(asset);
size_t size = AAsset_getLength(asset); //獲取SPIR-V數據文件的總字節數
assert(size > 0); //檢查總字節數是否大於0
SpvData spvData; //構建SpvData結構體實例
spvData.size=size; //設置SPIR-V數據總字節數
spvData.data=(uint32_t*)(malloc(size)); //分配相應字節數的內存
AAsset_read(asset, spvData.data, size); //從文件中加載數據進入內存
AAsset_close(asset); //關閉AAsset對象
return spvData; //返回SpvData結構體實例
}
四、根據SPIR-V文件創建着色器
首先利用FileUtil類中的loadSPV方法獲取頂點、片元着色器的SPIR-V數據,之後分別構建頂點着色器、片元着色器模塊創建信息結構體實例。
void ShaderQueueSuit_Common::create_shader(VkDevice& device){
//加載頂點着色器數據
SpvData spvVertData=FileUtil::loadSPV("shader/commonTexLight.vert.spv");
//加載片元着色器數據
SpvData spvFragData=FileUtil::loadSPV("shader/commonTexLight.frag.spv");
//此處省略了部分源代碼
VkShaderModuleCreateInfo moduleCreateInfo; //準備頂點着色器模塊創建信息
moduleCreateInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
moduleCreateInfo.pNext = NULL; //自定義數據的指針
moduleCreateInfo.flags = 0; //供將來使用的標誌
moduleCreateInfo.codeSize = spvVertData.size; //頂點着色器SPV數據總字節數
moduleCreateInfo.pCode = spvVertData.data; //頂點着色器SPV數據
//此處省略了部分源代碼
VkShaderModuleCreateInfo moduleCreateInfo; //準備片元着色器模塊創建信息
moduleCreateInfo.sType =VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
moduleCreateInfo.pNext = NULL; //自定義數據的指針
moduleCreateInfo.flags = 0; //供將來使用的標誌
moduleCreateInfo.codeSize = spvFragData.size; //片元着色器SPV數據總字節數
moduleCreateInfo.pCode = spvFragData.data; //片元着色器SPV數據
//此處省略了部分源代碼
}