Link

现在,我们为三角形准备了两组不同的着色器,但它们是在编译时硬编码的,无法在它们之间切换。让我们创建一个系统,允许我们通过按下空格键来切换三角形的着色器。

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_KEYDOWNSDL_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 个着色器之间切换,并看到它相应地显示。

下一步:清理