[译]Vulkan教程(29)组合的Image采样器
[译]Vulkan教程(29)组合的Image采样器
Combined image sampler 组合的image采样器
Introduction 入门
We looked at descriptors for the first time in the uniform buffers part of the tutorial. In this chapter we will look at a new type of descriptor: combined image sampler. This descriptor makes it possible for shaders to access an image resource through a sampler object like the one we created in the previous chapter.
在本教程的uniform buffer部分,我们首次认识了描述符。本章,我们将认识一个新类型的描述符:组合的image采样器(译者注:后文简称CIS)。这个描述符让shader通过采样器对象(例如我们在上一章创建的那个)存取image资源成为可能。
We'll start by modifying the descriptor layout, descriptor pool and descriptor set to include such a combined image sampler descriptor. After that, we're going to add texture coordinates to Vertex
and modify the fragment shader to read colors from the texture instead of just interpolating the vertex colors.
开始,我们修改描述符布局,描述符池和描述符set,以包含这样一个CIS描述符。之后,我们要添加纹理坐标到Vertex
,修改Fragment shader来从纹理读取颜色,而不是从顶点颜色插值。
Updating the descriptors 更新描述符
Browse to the createDescriptorSetLayout
function and add a VkDescriptorSetLayoutBinding
for a combined image sampler descriptor. We'll simply put it in the binding after the uniform buffer:
找到createDescriptorSetLayout
函数,为CIS描述符添加一个VkDescriptorSetLayoutBinding
。我们简单地将其放到uniform buffer后面的binding:
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();
Make sure to set the stageFlags
to indicate that we intend to use the combined image sampler descriptor in the fragment shader. That's where the color of the fragment is going to be determined. It is possible to use texture sampling in the vertex shader, for example to dynamically deform a grid of vertices by a heightmap.
确保设置stageFlags
to表示我们想在Fragment shader中用CIS描述符。那是决定Fragment颜色的地方。可以在顶点shader中用纹理采样,例如用于使顶点网格动态变形by heightmap。
If you would run the application with validation layers now, then you'll see that it complains that the descriptor pool cannot allocate descriptor sets with this layout, because it doesn't have any combined image sampler descriptors. Go to the createDescriptorPool
function and modify it to include a VkDescriptorPoolSize
for this descriptor:
如果你现在启用验证层运行程序,那么你会看到它抱怨说描述符池不能分配这个布局下的描述符set,因为它没有任何CIS描述符。找到createDescriptorPool
函数,修改它to包含VkDescriptorPoolSize
for这个描述符:
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());
The final step is to bind the actual image and sampler resources to the descriptors in the descriptor set. Go to the createDescriptorSets
function.
最后一步是,绑定image和采样器资源到描述符set中的描述符。找到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; ... }
The resources for a combined image sampler structure must be specified in a VkDescriptorImageInfo
struct, just like the buffer resource for a uniform buffer descriptor is specified in a VkDescriptorBufferInfo
struct. This is where the objects from the previous chapter come together.
CIS结构体的资源必须在VkDescriptorImageInfo
结构体中指定,就像buffer资源for uniform buffer描述符在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);
The descriptors must be updated with this image info, just like the buffer. This time we're using the pImageInfo
array instead of pBufferInfo
. The descriptors are now ready to be used by the shaders!
描述符必须用这个image信息更新,就像buffer那样。这次我们要用pImageInfo
数组,而不是pBufferInfo
。描述符现在准备好被shader使用了!
Texture coordinates 纹理坐标
There is one important ingredient for texture mapping that is still missing, and that's the actual coordinates for each vertex. The coordinates determine how the image is actually mapped to the geometry.
还有一个元素for纹理映射that没有提到,那就是每个顶点的纹理坐标。这个坐标决定了image如何映射到几何体上。
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; } };
Modify the Vertex
struct to include a vec2
for texture coordinates. Make sure to also add a VkVertexInputAttributeDescription
so that we can use access texture coordinates as input in the vertex shader. That is necessary to be able to pass them to the fragment shader for interpolation across the surface of the square.
修改Vertex
结构体to包含一个vec2
for纹理坐标。确保也添加一个VkVertexInputAttributeDescription
,这样我们可以用纹理坐标作为顶点shader的输入。这对于传递它们到Fragment shader后在四边形上插值是必要的。
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}} };
In this tutorial, I will simply fill the square with the texture by using coordinates from 0, 0
in the top-left corner to 1, 1
in the bottom-right corner. Feel free to experiment with different coordinates. Try using coordinates below 0
or above 1
to see the addressing modes in action!
本教程中,我们将简单地填入四边形with这个纹理by使用坐标-从左上角的0, 0
到右下角的1, 1
。你可以随意体验不同的坐标。尝试使用低于0
或高于1
的坐标to实际看看取址模式!
Shaders
The final step is modifying the shaders to sample colors from the texture. We first need to modify the vertex shader to pass through the texture coordinates to the fragment shader:
最后一步是修改shader来从纹理中采样颜色。我们首先需要修改顶点shader来传入纹理坐标到Fragment shader:
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; }
Just like the per vertex colors, the fragTexCoord
values will be smoothly interpolated across the area of the square by the rasterizer. We can visualize this by having the fragment shader output the texture coordinates as colors:
就像逐顶点的颜色一样,fragTexCoord
值会在四边形上被光栅器平滑地插值。我们可以看到这个by让Fragment shader输出纹理坐标为颜色:
#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); }
You should see something like the image below. Don't forget to recompile the shaders!
你会看到下图所示的内容。别忘记重编译shader!
The green channel represents the horizontal coordinates and the red channel the vertical coordinates. The black and yellow corners confirm that the texture coordinates are correctly interpolated from 0, 0
to 1, 1
across the square. Visualizing data using colors is the shader programming equivalent of printf
debugging, for lack of a better option!
绿色通道代表水平坐标,红色通道代表竖直坐标。黑色和黄色的角落确认了that纹理坐标被正确得在四边形上从0, 0
到1, 1
插值了。彩色可视化数据是shader编程中等价于printf
的调试方式,如果没有更好的选择!
A combined image sampler descriptor is represented in GLSL by a sampler uniform. Add a reference to it in the fragment shader:
一个CIS描述符在GLSL中用一个采样器uniform变量代表。在Fragment shader中添加对它的引用:
layout(binding = 1) uniform sampler2D texSampler;
There are equivalent sampler1D
and sampler3D
types for other types of images. Make sure to use the correct binding here.
对其他类型的image,有等价的sampler1D
和sampler3D
。确保这里使用正确的绑定。
void main() { outColor = texture(texSampler, fragTexCoord); }
Textures are sampled using the built-in texture
function. It takes a sampler
and coordinate as arguments. The sampler automatically takes care of the filtering and transformations in the background. You should now see the texture on the square when you run the application:
纹理通过内置的texture
函数进行采样。它接收一个sampler
和坐标作为参数。采样器自动在后台处理过滤和变换问题。你现在可以看到纹理在四边形上when你运行程序:
Try experimenting with the addressing modes by scaling the texture coordinates to values higher than 1
. For example, the following fragment shader produces the result in the image below when using VK_SAMPLER_ADDRESS_MODE_REPEAT
:
尝试试验取址模式by缩放纹理坐标到大于1
的值。例如,下述Fragment shader会产生下述image-when使用VK_SAMPLER_ADDRESS_MODE_REPEAT
模式:
void main() { outColor = texture(texSampler, fragTexCoord * 2.0); }
You can also manipulate the texture colors using the vertex colors:
你也可以修改纹理颜色-使用顶点颜色:
void main() { outColor = vec4(fragColor * texture(texSampler, fragTexCoord).rgb, 1.0); }
I've separated the RGB and alpha channels here to not scale the alpha channel.
我将RGB和alhpa通道分开了to不缩放alpha通道。
You now know how to access images in shaders! This is a very powerful technique when combined with images that are also written to in framebuffers. You can use these images as inputs to implement cool effects like post-processing and camera displays within the 3D world.
现在你指定如何在shader中读取image了!这是个非常强大的技术whne结合写入帧缓存的image。你可以用这些image作为输入来实现很酷的效果-例如后处理和3D世界的摄像机显示。
C++ code / Vertex shader / Fragment shader
微信扫码,自愿捐赠。天涯同道,共谱新篇。
微信捐赠不显示捐赠者个人信息,如需要,请注明联系方式。 |