Link

我们需要修改一些东西才能使纹理在着色器中工作。首先是修改顶点格式,以便我们存储 UV 坐标。

#include <glm/vec2.hpp> //now needed for the Vertex struct

// other code ....

struct Vertex {

	glm::vec3 position;
	glm::vec3 normal;
	glm::vec3 color;
	glm::vec2 uv;
	static VertexInputDescription get_vertex_description();
};

我们只需添加一个新的 glm::vec2 uv; 来在 Vertex 结构体中保存 UV 坐标。我们还需要将新属性添加到顶点描述中

VertexInputDescription Vertex::get_vertex_description()
{

	//position, normal, and color descriptions

	//UV will be stored at Location 3
	VkVertexInputAttributeDescription uvAttribute = {};
	uvAttribute.binding = 0;
	uvAttribute.location = 3;
	uvAttribute.format = VK_FORMAT_R32G32_SFLOAT;
	uvAttribute.offset = offsetof(Vertex, uv);

	description.attributes.push_back(positionAttribute);
	description.attributes.push_back(normalAttribute);
	description.attributes.push_back(colorAttribute);
	description.attributes.push_back(uvAttribute);
	return description;
}

我们将 UV 存储在属性位置 3 中,格式为 R32G32 float,这对于 glm::vec2 来说是完美的

现在我们需要从 obj 文件加载纹理坐标。

bool Mesh::load_from_obj(const char* filename)
{
	//load code

	// Loop over shapes
	for (size_t s = 0; s < shapes.size(); s++) {
		// Loop over faces(polygon)
		size_t index_offset = 0;
		for (size_t f = 0; f < shapes[s].mesh.num_face_vertices.size(); f++) {

			//other parameters


			//vertex uv
			tinyobj::real_t ux = attrib.texcoords[2 * idx.texcoord_index + 0];
			tinyobj::real_t uy = attrib.texcoords[2 * idx.texcoord_index + 1];

			new_vert.uv.x = ux;
			new_vert.uv.y = 1-uy;
		}
	}
}

我们以类似于处理其他参数的方式访问 texcoords 数组。现在 obj 加载代码将加载纹理坐标。对 uv.y 执行 1-y 操作非常重要,因为 Vulkan UV 坐标就是这样工作的。

在我们开始更改描述符和描述符布局以指向纹理之前,我们将为纹理光照着色器创建新的着色器。我们首先修改 tri_mesh.vert 着色器,以便它将 UV 坐标从属性传递到像素着色器

tri_mesh.vert

#version 450
layout (location = 0) in vec3 vPosition;
layout (location = 1) in vec3 vNormal;
layout (location = 2) in vec3 vColor;
layout (location = 3) in vec2 vTexCoord;

layout (location = 0) out vec3 outColor;
layout (location = 1) out vec2 texCoord;

//uniforms and ssbos


void main()
{
	mat4 modelMatrix = objectBuffer.objects[gl_InstanceIndex].model;
	mat4 transformMatrix = (cameraData.viewproj * modelMatrix);
	gl_Position = transformMatrix * vec4(vPosition, 1.0f);
	outColor = vColor;
	texCoord = vTexCoord;
}

我们创建一个新的像素着色器,在其中我们将纹理坐标显示为输出颜色。这是为了检查我们是否正确加载了参数。

textured_lit.frag

//glsl version 4.5
#version 450

//shader input
layout (location = 0) in vec3 inColor;
layout (location = 1) in vec2 texCoord;
//output write
layout (location = 0) out vec4 outFragColor;

layout(set = 0, binding = 1) uniform  SceneData{
	vec4 fogColor; // w is for exponent
	vec4 fogDistances; //x for min, y for max, zw unused.
	vec4 ambientColor;
	vec4 sunlightDirection; //w for sun power
	vec4 sunlightColor;
} sceneData;


void main()
{
	outFragColor = vec4(texCoord.x,texCoord.y,0.5f,1.0f);
}

init_pipelines() 函数中,我们将创建第二个管线,这将是纹理管线。

void VulkanEngine::init_pipelines()
{

	VkShaderModule texturedMeshShader;
	if (!load_shader_module("../../shaders/textured_lit.frag.spv", &texturedMeshShader))
	{
		std::cout << "Error when building the textured mesh shader" << std::endl;
	}

	//setup for the other pipeline

	//build the mesh triangle pipeline
	VkPipeline meshPipeline = pipelineBuilder.build_pipeline(_device, _renderPass);

	create_material(meshPipeline, texturedPipeLayout, "defaultmesh");


	//create pipeline for textured drawing
	pipelineBuilder._shaderStages.clear();
	pipelineBuilder._shaderStages.push_back(
		vkinit::pipeline_shader_stage_create_info(VK_SHADER_STAGE_VERTEX_BIT, meshVertShader));

	pipelineBuilder._shaderStages.push_back(
		vkinit::pipeline_shader_stage_create_info(VK_SHADER_STAGE_FRAGMENT_BIT, texturedMeshShader));

	VkPipeline texPipeline = pipelineBuilder.build_pipeline(_device, _renderPass);
	create_material(texPipeline, texturedPipeLayout, "texturedmesh");

	// other code ....
	vkDestroyShaderModule(_device, texturedMeshShader, nullptr);

	// add pipeline and pipeline layout to deletion code
	_mainDeletionQueue.push_function([=]() {
		vkDestroyPipeline(_device, texPipeline, nullptr);
		vkDestroyPipelineLayout(_device, texturedPipeLayout, nullptr);

		// other code ....
	}
}

我们现在可以添加一个新的渲染对象,它将是纹理网格。为此,我使用了 lost_empire.obj,这是一个 Minecraft 地图。这为检查纹理渲染提供了一个很好的测试用例。

void VulkanEngine::load_meshes()
{
	//other meshes
	Mesh lostEmpire{};
	lostEmpire.load_from_obj("../../assets/lost_empire.obj");

	upload_mesh(lostEmpire);

	_meshes["empire"] = lostEmpire;
}

void VulkanEngine::init_scene()
{
	//others
	RenderObject map;
	map.mesh = get_mesh("empire");
	map.material = get_material("texturedmesh");
	map.transformMatrix = glm::translate(glm::vec3{ 5,-10,0 });

	_renderables.push_back(map);

如果现在运行它,这应该可以工作,给我们这个图像。或者类似的图像

map

现在是时候将图像暴露给着色器了。我们将更改 textured_lit.frag,以便它可以访问纹理


layout(set = 2, binding = 0) uniform sampler2D tex1;

void main()
{
	vec3 color = texture(tex1,texCoord).xyz;
	outFragColor = vec4(color,1.0f);
}

我们将 sampler2d 纹理绑定到 Set 2,绑定点 0。目前我们正在使用集合 0 和 1,所以这将是第三个描述符集。由于我们有一个新的描述符集,我们需要创建其布局并将其添加到引擎中。我们将在 Material 结构体中添加一个纹理描述符参数,并且我们还将描述符布局存储到引擎类中

struct Material {
	VkDescriptorSet textureSet{VK_NULL_HANDLE}; //texture defaulted to null
	VkPipeline pipeline;
	VkPipelineLayout pipelineLayout;
};

class VulkanEngine {
public:

VkDescriptorSetLayout _singleTextureSetLayout;
}

init_descriptors() 中,我们将创建布局,但暂不分配描述符。

void VulkanEngine::init_descriptors()
{
	std::vector<VkDescriptorPoolSize> sizes =
	{
		{ VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 10 },
		{ VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 10 },
		{ VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 10 },
		//add combined-image-sampler descriptor types to the pool
		{ VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 10 }
	};

	//other descriptor layouts

	//another set, one that holds a single texture
	VkDescriptorSetLayoutBinding textureBind = vkinit::descriptorset_layout_binding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 0);

	VkDescriptorSetLayoutCreateInfo set3info = {};
	set3info.bindingCount = 1;
	set3info.flags = 0;
	set3info.pNext = nullptr;
	set3info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
	set3info.pBindings = &textureBind;

	vkCreateDescriptorSetLayout(_device, &set3info, nullptr, &_singleTextureSetLayout);
}

在初始化集合布局后,我们可以将其附加到纹理管线的布局。

init_pipelines() 中。


	//create pipeline layout for the textured mesh, which has 3 descriptor sets
	//we start from  the normal mesh layout
	VkPipelineLayoutCreateInfo textured_pipeline_layout_info = mesh_pipeline_layout_info;

	VkDescriptorSetLayout texturedSetLayouts[] = { _globalSetLayout, _objectSetLayout,_singleTextureSetLayout };

	textured_pipeline_layout_info.setLayoutCount = 3;
	textured_pipeline_layout_info.pSetLayouts = texturedSetLayouts;

	VkPipelineLayout texturedPipeLayout;
	VK_CHECK(vkCreatePipelineLayout(_device, &textured_pipeline_layout_info, nullptr, &texturedPipeLayout));

	pipelineBuilder._shaderStages.clear();
	pipelineBuilder._shaderStages.push_back(
		vkinit::pipeline_shader_stage_create_info(VK_SHADER_STAGE_VERTEX_BIT, meshVertShader));

	pipelineBuilder._shaderStages.push_back(
		vkinit::pipeline_shader_stage_create_info(VK_SHADER_STAGE_FRAGMENT_BIT, texturedMeshShader));

	//connect the new pipeline layout to the pipeline builder
	pipelineBuilder._pipelineLayout = texturedPipeLayout;
	VkPipeline texPipeline = pipelineBuilder.build_pipeline(_device, _renderPass);
	create_material(texPipeline, texturedPipeLayout, "texturedmesh");

我们现在需要在 init_scene() 函数中创建描述符集,以便我们的 texturedmesh 材质具有纹理集

我们为 vk_initializers 添加新的初始化器,用于采样器创建和描述符图像写入

//header
VkSamplerCreateInfo sampler_create_info(VkFilter filters, VkSamplerAddressMode samplerAddressMode = VK_SAMPLER_ADDRESS_MODE_REPEAT);
VkWriteDescriptorSet write_descriptor_image(VkDescriptorType type, VkDescriptorSet dstSet, VkDescriptorImageInfo* imageInfo, uint32_t binding);

//implementation
VkSamplerCreateInfo vkinit::sampler_create_info(VkFilter filters, VkSamplerAddressMode samplerAddressMode /*= VK_SAMPLER_ADDRESS_MODE_REPEAT*/)
{
	VkSamplerCreateInfo info = {};
	info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
	info.pNext = nullptr;

	info.magFilter = filters;
	info.minFilter = filters;
	info.addressModeU = samplerAddressMode;
	info.addressModeV = samplerAddressMode;
	info.addressModeW = samplerAddressMode;

	return info;
}
VkWriteDescriptorSet vkinit::write_descriptor_image(VkDescriptorType type, VkDescriptorSet dstSet, VkDescriptorImageInfo* imageInfo, uint32_t binding)
{
	VkWriteDescriptorSet write = {};
	write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
	write.pNext = nullptr;

	write.dstBinding = binding;
	write.dstSet = dstSet;
	write.descriptorCount = 1;
	write.descriptorType = type;
	write.pImageInfo = imageInfo;

	return write;
}

现在我们可以在 init_scene 中使用它。

	//create a sampler for the texture
	VkSamplerCreateInfo samplerInfo = vkinit::sampler_create_info(VK_FILTER_NEAREST);

	VkSampler blockySampler;
	vkCreateSampler(_device, &samplerInfo, nullptr, &blockySampler);



	Material* texturedMat=	get_material("texturedmesh");

	//allocate the descriptor set for single-texture to use on the material
	VkDescriptorSetAllocateInfo allocInfo = {};
	allocInfo.pNext = nullptr;
	allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
	allocInfo.descriptorPool = _descriptorPool;
	allocInfo.descriptorSetCount = 1;
	allocInfo.pSetLayouts = &_singleTextureSetLayout;

	vkAllocateDescriptorSets(_device, &allocInfo, &texturedMat->textureSet);

	//write to the descriptor set so that it points to our empire_diffuse texture
	VkDescriptorImageInfo imageBufferInfo;
	imageBufferInfo.sampler = blockySampler;
	imageBufferInfo.imageView = _loadedTextures["empire_diffuse"].imageView;
	imageBufferInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;

	VkWriteDescriptorSet texture1 = vkinit::write_descriptor_image(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, texturedMat->textureSet, &imageBufferInfo, 0);

	vkUpdateDescriptorSets(_device, 1, &texture1, 0, nullptr);

我们需要首先创建一个采样器。对于该采样器,我们将使用 VK_FILTER_NEAREST。这将使纹理看起来呈块状,这正是我们想要的。

对于描述符分配,它与我们在 init_descriptors() 中对其他描述符所做的方式相同。

要写入图像描述符,我们需要用要使用的 VkImageView、要使用的 VkSampler 和纹理布局填充 VkDescriptorImageInfo

在设置好描述符集和管线后,我们现在可以绑定描述符集以使用纹理进行绘制。

draw_objects() 中,在渲染循环内部,我们与其他绑定一起执行此操作

	if (object.material != lastMaterial) {

		//other bindings

		if (object.material->textureSet != VK_NULL_HANDLE) {
			//texture descriptor
			vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, object.material->pipelineLayout, 2, 1, &object.material->textureSet, 0, nullptr);

		}
	}

我们只会绑定纹理集(集合 #2),如果纹理集句柄不为空。这将使其余的绘制调用仍然可以正常渲染。

在这一点上,您应该得到此图像作为结果。 map