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数据
//此处省略了部分源代码
}