现在,我们为三角形准备了两组不同的着色器,但它们是在编译时硬编码的,无法在它们之间切换。让我们创建一个系统,允许我们通过按下空格键来切换三角形的着色器。
SDL 输入事件
因为我们想使用空格键,所以我们需要在每次按下该键时运行代码。幸运的是,我们正在使用的 SDL 库可以轻松做到这一点。
首先,让我们在 VulkanEngine 中添加一个整数变量,用于在着色器之间进行选择。
int _selectedShader{ 0 };
我们将使用 0 代表红色三角形,1 代表彩色三角形。稍后我们将添加更多着色器。
如果你查看 VulkanEngine::run()
函数,你可以看到我们有事件循环
//Handle events on queue
while (SDL_PollEvent(&e) != 0)
{
//close the window when user alt-f4s or clicks the X button
if (e.type == SDL_QUIT) bQuit = true;
}
我们现在使用事件循环来关闭窗口,但在这里我们也可以检查键盘事件或鼠标事件。要检测键盘事件,我们需要将代码更改为
while (SDL_PollEvent(&e) != 0)
{
if (e.type == SDL_QUIT)
{
bQuit = true;
}
else if (e.type == SDL_KEYDOWN)
{
if (e.key.keysym.sym == SDLK_SPACE)
{
_selectedShader += 1;
if(_selectedShader > 1)
{
_selectedShader = 0;
}
}
}
}
SDL_QUIT 不是唯一的事件,我们还有 SDL_KEYDOWN
和 SDL_KEYUP
等其他事件。如果事件类型是 SDL_KEYDOWN
,那么它就是一个按键按下事件。每当按下按键时,它都会被调用,如果按键一直被按下,事件将每秒重复调用几次(这取决于操作系统)。
一旦我们知道事件类型是按键按下事件,我们可以通过查看 key.keysym.sym 变量来检查是哪个键。如果你查看 SDL 文档,你将看到更多键码。我们空格键需要的键码是 SDLK_SPACE
然后我们旋转 _selectedShader 整数,使其从 0 变为 1,然后再回到 0。
现在输入部分完成了,我们可以在代码中切换着色器了。
多条管线
现在我们只有 1 条管线,但我们可以拥有任意多条。因为我们将拥有 2 条管线,所以我们将在 VulkanEngine 类中为第二条管线创建一个变量
VkPipeline _trianglePipeline;
VkPipeline _redTrianglePipeline;
我们将保留上一章中的 _trianglePipeline
来保存彩色三角形的管线,并将红色版本的管线放在 _redTrianglePipeline
中
让我们让 init_pipelines()
中的代码编译两条管线的着色器模块。
void VulkanEngine::init_pipelines()
{
//compile colored triangle modules
VkShaderModule triangleFragShader;
if (!load_shader_module("../../shaders/colored_triangle.frag.spv", &triangleFragShader))
{
std::cout << "Error when building the triangle fragment shader module" << std::endl;
}
else {
std::cout << "Triangle fragment shader successfully loaded" << std::endl;
}
VkShaderModule triangleVertexShader;
if (!load_shader_module("../../shaders/colored_triangle.vert.spv", &triangleVertexShader))
{
std::cout << "Error when building the triangle vertex shader module" << std::endl;
}
else {
std::cout << "Triangle vertex shader successfully loaded" << std::endl;
}
//compile red triangle modules
VkShaderModule redTriangleFragShader;
if (!load_shader_module("../../shaders/triangle.frag.spv", &redTriangleFragShader))
{
std::cout << "Error when building the triangle fragment shader module" << std::endl;
}
else {
std::cout << "Red Triangle fragment shader successfully loaded" << std::endl;
}
VkShaderModule redTriangleVertShader;
if (!load_shader_module("../../shaders/triangle.vert.spv", &redTriangleVertShader))
{
std::cout << "Error when building the triangle vertex shader module" << std::endl;
}
else {
std::cout << "Red Triangle vertex shader successfully loaded" << std::endl;
}
//other code ....
}
我们只需要为额外的着色器添加着色器模块。现在我们可以用这些模块编译管线了。因为我们使用了管线构建器抽象,所以创建非常相似但仅在着色器或几个变量上不同的管线非常简单。
我们将覆盖构建器中的着色器模块变量,并使其再次构建。
void VulkanEngine::init_pipelines()
{
// other code ....
//finally build the pipeline
_trianglePipeline = pipelineBuilder.build_pipeline(_device, _renderPass);
//clear the shader stages for the builder
pipelineBuilder._shaderStages.clear();
//add the other shaders
pipelineBuilder._shaderStages.push_back(
vkinit::pipeline_shader_stage_create_info(VK_SHADER_STAGE_VERTEX_BIT, &redTriangleVertShader));
pipelineBuilder._shaderStages.push_back(
vkinit::pipeline_shader_stage_create_info(VK_SHADER_STAGE_FRAGMENT_BIT, &redTriangleFragShader));
//build the red triangle pipeline
_redTrianglePipeline = pipelineBuilder.build_pipeline(_device, _renderPass);
}
在这种情况下,除了着色器阶段(我们使用不同的着色器模块)之外,管线配置完全相同。所以我们清除存储在构建器中的着色器阶段,然后替换它们。
现在我们已经初始化了 2 条不同的管线,所以我们可以实现选择代码。
在运行时切换管线
要更改着色器,我们只需要更改发送到 vkCmdBindPipeline 的内容,其他一切都相同,所以我们将添加一个分支来选择管线。
void VulkanEngine::draw()
{
// other code ....
vkCmdBeginRenderPass(cmd, &rpInfo, VK_SUBPASS_CONTENTS_INLINE);
//once we start adding rendering commands, they will go here
if(_selectedShader == 0)
{
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, _trianglePipeline);
}
else
{
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, _redTrianglePipeline);
}
vkCmdDraw(cmd, 3, 1, 0, 0);
//finalize the render pass
vkCmdEndRenderPass(cmd);
// other code ....
}
如果我们现在运行应用程序,我们应该能够在我们拥有的 2 个着色器之间切换,并看到它相应地显示。
下一步:清理