Vulkan教程 – 21 组合图像采样器

在统一缓冲章节我们第一次见到了描述符,这一章我们会介绍一个新的描述符,即组合图像采样器。该描述符让着色器通过一个采样器对象访问图像资源成为了可能。

修改描述符布局、描述符池和描述符集合以包括组合图像采样器描述符。之后,我们添加贴图坐标到Vertex,修改片段着色器来从贴图读取颜色而不是仅仅对顶点颜色进行插值。

浏览到createDescriptorSetLayout方法,添加一个VkDescriptorSetLayoutBinding以便创建组合图像采样器描述符。我们把它放在统一缓冲之后:

VkDescriptorSetLayoutBinding samplerLayoutBinding = {};
samplerLayoutBinding.binding = 1;
samplerLayoutBinding.descriptorCount = 1;
samplerLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
samplerLayoutBinding.pImmutableSamplers = nullptr;
samplerLayoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;

std::array<VkDescriptorSetLayoutBinding, 2> bindings =
{ uboLayoutBinding, samplerLayoutBinding };

VkDescriptorSetLayoutCreateInfo layoutInfo = {};
layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
layoutInfo.bindingCount = static_cast<uint32_t>(bindings.size());
layoutInfo.pBindings = bindings.data();

确保设置了stageFlags以表明我们想要在片段着色器中使用组合图像采样器描述符。这就是决定片段着色器颜色的地方。可以在顶点着色器中使用贴图采样,例如通过高度图动态改变一片顶点。

现在运行程序会报错,说描述符池无法分配该布局的描述符集合,因为它没有任何组合图像采样器描述符。到createDescriptorPool中进行修改,为该描述符包含一个VkDescriptorPoolSize:

std::array<VkDescriptorPoolSize, 2> poolSizes = {};
poolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
poolSizes[0].descriptorCount = static_cast<uint32_t>(swapChainImages.size());
poolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
poolSizes[1].descriptorCount = static_cast<uint32_t>(swapChainImages.size());

VkDescriptorPoolCreateInfo poolInfo = {};
poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
poolInfo.poolSizeCount = static_cast<uint32_t>(poolSizes.size());
poolInfo.pPoolSizes = poolSizes.data();
poolInfo.maxSets = static_cast<uint32_t>(swapChainImages.size());

最终布局是绑定实际图像和采样器资源到描述符集合中的描述符。到createDescriptorSets:

for (size_t i = 0; i < swapChainImages.size(); i++) {
    VkDescriptorBufferInfo bufferInfo = {};
    bufferInfo.buffer = uniformBuffers[i];
    bufferInfo.offset = 0;
    bufferInfo.range = sizeof(UniformBufferObject);

    VkDescriptorImageInfo imageInfo = {};
    imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
    imageInfo.imageView = textureImageView;
    imageInfo.sampler = textureSampler;
    ...
}

组合图像采样器资源要通过VkDescriptorImageInfo指定,就和统一缓冲描述符的缓冲资源要在VkDescriptorBufferInfo中指定一样。这里就要将前几章的内容结合到一起了:

std::array<VkWriteDescriptorSet, 2> descriptorWrites = {};
descriptorWrites[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptorWrites[0].dstSet = descriptorSets[i];
descriptorWrites[0].dstBinding = 0;
descriptorWrites[0].dstArrayElement = 0;
descriptorWrites[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
descriptorWrites[0].descriptorCount = 1;
descriptorWrites[0].pBufferInfo = &bufferInfo;

descriptorWrites[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptorWrites[1].dstSet = descriptorSets[i];
descriptorWrites[1].dstBinding = 1;
descriptorWrites[1].dstArrayElement = 0;
descriptorWrites[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
descriptorWrites[1].descriptorCount = 1;
descriptorWrites[1].pImageInfo = &imageInfo;

vkUpdateDescriptorSets(device, static_cast<uint32_t>(descriptorWrites.size()),
    descriptorWrites.data(), 0, nullptr);

描述符一定要用该图像信息更新,就和缓冲的更新类似。这里用了pImageInfo数组和不是pBufferInfo。现在该描述符已经准备就绪可以被着色器使用了。

贴图映射还要一个配料,就是每个顶点的实际坐标。坐标决定了图像是如何映射到几何体上的。修改Vertex结构体:

struct Vertex {
    glm::vec2 pos;
    glm::vec3 color;
    glm::vec2 texCoord;

    static VkVertexInputBindingDescription getBindingDescription() {
        VkVertexInputBindingDescription bindingDescription = {};

        bindingDescription.binding = 0;
        bindingDescription.stride = sizeof(Vertex);
        bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;

        return bindingDescription;
    }

    static std::array<VkVertexInputAttributeDescription, 3>
        getAttributeDescriptions() {
        std::array<VkVertexInputAttributeDescription, 3>
            attributeDescriptions = {};

        attributeDescriptions[0].binding = 0;
        attributeDescriptions[0].location = 0;
        attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT;
        attributeDescriptions[0].offset = offsetof(Vertex, pos);
        attributeDescriptions[1].binding = 0;
        attributeDescriptions[1].location = 1;
        attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT;
        attributeDescriptions[1].offset = offsetof(Vertex, color);
        attributeDescriptions[2].binding = 0;
        attributeDescriptions[2].location = 2;
        attributeDescriptions[2].format = VK_FORMAT_R32G32_SFLOAT;
        attributeDescriptions[2].offset = offsetof(Vertex, texCoord);

        return attributeDescriptions;
    }
};

现在Vertex包含了一个vec2来放贴图坐标,确保添加了一个VkVertexInputAttributeDescription以便我们使用访问贴图坐标作为顶点着色器的输入。传递它们到片段着色器来进行整个正方形表面插值是很有必要的。

const std::vector<Vertex> vertices = {
	{{-0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}, {1.0f, 0.0f}},
	{{0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}, {0.0f, 0.0f}},
	{{0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}, {0.0f, 1.0f}},
	{{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}, {1.0f, 1.0f}}
};

本教程我就用从左上0,0的坐标到右下1,1的坐标来填充该正方形。尝试用别的坐标试试,用低于0或者高于1的值看看是什么效果。

最后的步骤是修改着色器来从贴图中采样颜色。首先修改顶点着色器来将贴图坐标传递到片段着色器:

layout(location = 0) in vec2 inPosition;
layout(location = 1) in vec3 inColor;
layout(location = 2) in vec2 inTexCoord;

layout(location = 0) out vec3 fragColor;
layout(location = 1) out vec2 fragTexCoord;

void main() {
    gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 0.0, 1.0);
    fragColor = inColor;
    fragTexCoord = inTexCoord;
}

就和逐顶点颜色那样,fragTexCoord的值也会由光栅化器在正方形区域平缓插值。我们可以看下效果,就是让片段着色器输出贴图坐标作为颜色:

#version 450
#extension GL_ARB_separate_shader_objects : enable

layout(location = 0) in vec3 fragColor;
layout(location = 1) in vec2 fragTexCoord;

layout(location = 0) out vec4 outColor;

void main() {
    outColor = vec4(fragTexCoord, 0.0, 1.0);
}

编译着色器,运行后效果如下:

绿色通道代表了水平坐标,红色通道表示垂直坐标。黑色和黄色的角确认贴图坐标是从0,0到1,1的整个正方形都正确插值的。使用颜色来进行可视化就和编程中用printf调试一样。

组合图像采样器描述符在GLSL中是用一个采样器统一体表示的。在片段着色器中向它添加一个引用:

layout(binding = 1) uniform sampler2D texSampler;

还有sampler1D和sampler3D用于其他图像类型,确保这里使用了正确的绑定。

贴图使用内置texture方法采样。它接收一个采样器和坐标作为参数,采样器自动处理过滤和变换,现在运行程序会会得到如下效果:

现在我们知道如何从着色器访问图像了,这是很强大的技术,结合也要写入到帧缓冲的图像,你可以使用这些图像作为实现炫酷效果的输入,如后期处理或者3D世界的相机显示。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容