我们需要修改一些东西才能使纹理在着色器中工作。首先是修改顶点格式,以便我们存储 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);
如果现在运行它,这应该可以工作,给我们这个图像。或者类似的图像
现在是时候将图像暴露给着色器了。我们将更改 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),如果纹理集句柄不为空。这将使其余的绘制调用仍然可以正常渲染。
在这一点上,您应该得到此图像作为结果。