在统一缓冲章节我们第一次见到了描述符,这一章我们会介绍一个新的描述符,即组合图像采样器。该描述符让着色器通过一个采样器对象访问图像资源成为了可能。
修改描述符布局、描述符池和描述符集合以包括组合图像采样器描述符。之后,我们添加贴图坐标到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世界的相机显示。
暂无评论内容