vulakn教程--Drawing a Triangle--Pipeline--Shader Module

发表于2016-12-05
评论0 2.3k浏览
原文地址 : vulkan-tutorial
着色器模块 Shader modules
      Vulkan 和之前的图形API有所不同,为了避免不同厂商移植代码的复杂性,Vulkan的着色器代码(shader code)采用字节码格式(bytecode) SPIR-V,而非人类可以阅读的文本格式,如GLSL(opengl 的一套体系)等。但这并不意味着我们要亲自手写字节码, 幸运的是LunarG SDK 已经提供了glslangValidator.exe ,这个程序已经在你的安装目录中了,所以我们在编写着色器程序的时候任然使用我们所熟悉的GLSL格式,glslangValidator.exe 将为我们把GLSL转换成SPIR-V。

Vertex shader
      它以vertex position 、mormal(法线)、texture Coordination 、color等作为输入,输出color、position、和texture Coordination等。最终作为输入传递给Fragment Sahder 的position 是以clip Coordination表示的。 
clip Coordination 是齐次坐标( homogeneous coordinates),他与FrameBuffer的坐标一一对应,如图:


      如同Drect3D ,齐次坐标的z轴取值为0到1。 
      对于我们将要画的三角形来讲,我们直接定义它的三个坐标为齐次坐标,这样可以更加简单,避免了坐标的转换。如图下图:


     通常坐标值都是被放在Vertex Buffer中的,但是Vertex Buffer 也是个比较大的概念,我们之后再讨论。这里我们将采用一种看起来不太规范的做法:直接把坐标硬编码到着色器代码里。
#version 450
#extension GL_ARB_separate_shader_objects : enable
out gl_PerVertex {
    vec4 gl_Position;
}; 
vec2 positions[3] = vec2[](
                        vec2(0.0, -0.5),
                        vec2(0.5, 0.5),
                        vec2(-0.5, 0.5)
                    );
void main() {
    gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);
}
      着色器需要 GL_ARB_separate_shader_objects 扩展才能工作,gl_VertexIndex是内置变量,代表Vertex Buffer的索引,这里表示顶点数组的索引,gl_position内置变量表示输出坐标。四元组坐标(x,y,z,w)中,z和w 采用哑坐标。

Fragment shader
    Vertex Shader的坐标点组成三角形,然后再用片原在屏幕上填充一块三角新区域。我们的Fragment Shader 被片原调用后为FragmentBuffer 产生颜色和深度值,我们的例子是使整个三角形呈现红色。
#version 450 
#extension GL_ARB_separate_shader_objects : enable 
layout(location = 0) out vec4 outColor; 
void main() { 
    outColor = vec4(1.0, 0.0, 0.0, 1.0); //r,g,b,a 取值[0.0 , 1.0]
}
Fragment shader不同于Vertex Shader有类似p_position的内置变量表示输出,我们必须明确为Fragment shader指定输出变量,如outColor,layout(location = 0) 表示outColor将用于第一个FrameBuffer 上。

Per-vertex colors
      整个三角形都是红色是不是太没意思了,下面这个三角形是不是更有趣一点? 如图:


     现在为Vertex Shader 里为每一个顶点添加颜色值:
vec3 colors[3] = vec3[](
    vec3(1.0, 0.0, 0.0), //r,g,b 
    vec3(0.0, 1.0, 0.0), 
    vec3(0.0, 0.0, 1.0) 
);
     然后添加输出到 Fragment Shader 里的颜色变量fragColor,修改main()函数:
layout(location = 0) out vec3 fragColor; 
void main() { 
    gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); 
    fragColor = colors[gl_VertexIndex];
 }
修改对应的Fragment Shader :
layout(location = 0) in vec3 fragColor; 
void main() { 
    outColor = vec4(fragColor, 1.0); 
}
Compiling the shaders
      在应用的根目录下建立文件夹shaders,然后建立两个文件:shader.vert和shader.frag, GLSL并没有限定文件的扩展名,我们这样定义只是为了见名知意。
Shader.vert 内容:
#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]; 
}
Shader.frag内容:
#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); 
}
Windows 上编译 :
C:/VulkanSDK/1.0.17.0/Bin32/glslangValidator.exe -V shader.vert C:/VulkanSDK/1.0.17.0/Bin32/glslangValidator.exe -V shader.frag pause
      注: 安装SDK时,glslangValidator.exe 已经自动被加入到环境变量里了,所以上述编译可以直接为: 
glslangValidator.exe -V shader.vert 
     编译过后GLSL 文件就变为SPIR-V文件了,我们将看到两个结果文件: vert.spv 和 frag.spv。

Loading a shader
     现在我们要把这两个着色器程序加载到应用中,然后在适当的时机插入到Pipeline。所以我们需要一个从文件加载到当前应用的工具函数:
static std::vector readFile(const std::string& filename) {
 
    std::ifstream file(filename, std::ios::ate | std::ios::binary);
    if (!file.is_open()) {
        throw std::runtime_error("failed to open file!");
    }
    size_t fileSize = (size_t) file.tellg();
    std::vector buffer(fileSize);
    file.seekg(0);
    file.read(buffer.data(), fileSize);
    file.close();
    return buffer;
}
    然后我们会在接下来的适当时机进行读取:
auto vertShaderCode = readFile("shaders/vert.spv"); 
auto fragShaderCode = readFile("shaders/frag.spv");
Creating shader modules
    要把着色器程序传递到Pipeline之前,我们需要把它们包裹成:VkShaderModule。让我们创建一个函数来完成包裹的过程:
void createShaderModule(const std::vector& code,  VDeleter& shaderModule) { 
}
     老规矩,先填充一个结构体:
kShaderModuleCreateInfo createInfo = {}; 
createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; createInfo.codeSize = code.size(); 
createInfo.pCode = (uint32_t*) code.data();
    然后创建 Modeule:
if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { 
    throw std::runtime_error("failed to create shader module!"); 
}
     我们声明两个Module变量:
VDeleter vertShaderModule {device, vkDestroyShaderModule};
VDeleter fragShaderModule {device, vkDestroyShaderModule};
//调用我们的创建函数
createShaderModule(vertShaderCode, vertShaderModule);
createShaderModule(fragShaderCode, fragShaderModule);
Shader stage creation
      看起来所有的工作都做完了,但我们还没有告诉Pipeline什么时候使用着色器程序,究竟Pipeline是在什么阶段使用哪一个着色器呢? 让我们先填充一个结构,这结构也是我们将要创建的Pipeline工作的一部分:
VkPipelineShaderStageCreateInfo vertShaderStageInfo = {}; vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; vertShaderStageInfo.module = vertShaderModule; vertShaderStageInfo.pName = "main";
Stage 、module和pNmae告诉Pipeline,我们要在Vertex Shader 阶段使用vertShaderModule,指明让它从main()函数开始调用。
Fragment Shader Module 类似:
VkPipelineShaderStageCreateInfo fragShaderStageInfo = {}; fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; fragShaderStageInfo.module = fragShaderModule; fragShaderStageInfo.pName = "main";

      最后我们定义一个包裹这两个结构的数组,我们在创建Pipeline的时候要用到:1
VkPipelineShaderStageCreateInfo shaderStages[] = {
    vertShaderStageInfo,  fragShaderStageInfo
};

原文源码: source code

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引