组合图像采样器:
一种新的描述符类型,着色器可以通过这一类型的描述符访问图像资源.
1.修改描述符布局,描述符池和描述符集来使用组合图像采样器描述符
2.添加纹理坐标信息到 Vertex 结构体
3.修改片段着色器从纹理中读取颜色数据。
示例:
//顶点结构体
struct Vertex{
glm::vec2 pos;
glm::vec3 color;
glm::vec2 texCoord;//纹理坐标
//返回 Vertex 结构体的顶点数据存放方式
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;
}
};
//定义顶点数据--交叉顶点属性 (interleaving vertex attributes)。
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}}
};
//提供着色器使用的每一个描述符绑定信息
void createDescriptorSetLayout(){
//描述每一个绑定
VkDescriptorSetLayoutBinding uboLayoutBinding = {};
//指定着色器使用的描述符绑定
uboLayoutBinding.binding = 0;
//描述符类型,这里指定的是一个 uniform 缓冲对象
//着色器变量可以用来表示 uniform 缓冲对象数组
uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
//数组中元素的个数
uboLayoutBinding.descriptorCount = 1;
//在哪一个着色器阶段被使用,这里只在顶点着色器使用
uboLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
//用于和图像采样相关的描述符
uboLayoutBinding.pImmutableSamplers = nullptr ;
//填写用于组合图像采样器描述符的 VkDescriptorSetLayoutBinding 结构体信息
VkDescriptorSetLayoutBinding samplerLayoutBinding = {};
samplerLayoutBinding.binding = 1;
samplerLayoutBinding.descriptorCount = 1;
samplerLayoutBinding.descriptorType =
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
samplerLayoutBinding.pImmutableSamplers = nullptr;
/**
stageFlags 成员变量指明在片段着色器中使用组合图像采样器描述符。
在顶点着色器也可以进行纹理采样,一个常见的用途是在顶点着色
器中使用高度图纹理来对顶点进行变形
*/
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();
if(vkCreateDescriptorSetLayout(device,&layoutInfo,nullptr,
&descriptorSetLayout)!= VK_SUCCESS){
throw std::runtime_error("failed to create descriptor set layout");
}
}
//描述符池的创建
void 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());;
if(vkCreateDescriptorPool(device,&poolInfo,nullptr,
&descriptorPool) != VK_SUCCESS){
throw std::runtime_error("failed to create descriptor pool!");
}
}
//创建描述符集对象
void createDescriptorSets(){
std::vector<VkDescriptorSetLayout> layouts(swapChainImages.size(),
descriptorSetLayout);
//创建描述符集相关信息
VkDescriptorSetAllocateInfo allocInfo = {};
allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
//指定分配描述符集对象的描述符池
allocInfo.descriptorPool = descriptorPool;
//分配的描述符集数量
allocInfo.descriptorSetCount =
static_cast<uint32_t>(swapChainImages.size());
//使用的描述符布局
allocInfo.pSetLayouts = layouts.data();
/**
在这里,我们为每一个交换链图像使用相同的描述符布局创建对应的描述符集。
但由于描述符布局对象个数要匹配描述符集对象个数,所以,
我们还是需要使用多个相同的描述符布局对象。
*/
descriptorSets.resize(swapChainImages.size());
//分配地描述符集对象,每一个都带有一个 uniform 缓冲描述符
if(vkAllocateDescriptorSets( device , &allocInfo,
descriptorSets.data()) != VK_SUCCESS){
throw std::runtime_error("failed to allocate descriptor sets!");
}
//配置描述符集对象
for(size_t i =0;i<swapChainImages.size();i++){
//配置描述符引用的缓冲对象
VkDescriptorBufferInfo bufferInfo = {};
//以指定缓冲对象
bufferInfo.buffer = uniformBuffers[i];
bufferInfo.offset = 0;
//可以访问的数据范围,
//需要使用整个缓冲,可以将range成员变量的值设置为 VK_WHOLE_SIZE
bufferInfo.range = sizeof(UniformBufferObject);
//绑定图像和采样器到描述符集中的描述符
VkDescriptorImageInfo imageInfo = {};
imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
imageInfo.imageView = textureImageView;
imageInfo.sampler = textureSampler;
std::array<VkWriteDescriptorSet , 2> descriptorWrites = {};
//更新描述符的配置
descriptorWrites[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
//指定要更新的描述符集对象
descriptorWrites[0].dstSet = descriptorSets[i];
/**
在这里,我们将 uniform 缓冲绑定到索引 0。需要注意描述符可以是数组,
所以我们还需要指定数组的第一个元素的索引,在这里,我们
没有使用数组作为描述符,将索引指定为 0 即可
*/
//缓冲绑定
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[0].pImageInfo = nullptr;
//指定描述符引用的缓冲视图
descriptorWrites[0].pTexelBufferView = nullptr;
//更新图像和采样器到描述符配置
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;
/**
* @brief vkUpdateDescriptorSets
* 函数可以接受两个数组作为参数:
* VkWriteDescriptorSet 结构体数组和 VkCopyDescriptorSet 结构体数组。
* 后者被用来复制描述符对象
*/
vkUpdateDescriptorSets(device,
static_cast<uint32_t>(descriptorWrites.size()),
descriptorWrites.data(),0,nullptr);
//更新描述符需要使用图像资源信息。至此,我们就可以在着色器中使用描述符了
}
}
顶点着色器:
#version 450
#extension GL_ARB_separate_shader_objects : enable
layout(binding = 0) uniform UniformBufferObject {
mat4 model;
mat4 view;
mat4 proj;
} ubo;
layout(location = 0) in vec2 inPostion;
layout(location = 1) in vec3 inColor;
layout(location = 2) in vec2 inTexCoord;
layout(location = 0) out vec3 fragColor;
layout(location = 1) out vec2 fragTexCoord;
out gl_PerVertex{
vec4 gl_Position;
};
void main(){
gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPostion,0.0,1.0);
fragColor = inColor;
fragTexCoord = inTexCoord;
}
修改顶点着色器将纹理坐标传递给片段着色器
fragTexCoord 的值会被插值后传递给片段着色器。我们可以通过将纹理坐标输出为片段颜色来形象地认知这一插值过程
片段着色器:
#version 450
#extension GL_ARB_separate_shader_objects : enable
layout(binding = 1) uniform sampler2D texSampler;
layout(location = 0) in vec3 fragColor;
layout(location = 1) in vec2 fragTexCoord;
layout(location = 0) out vec4 outColor;
void main(){
outColor = texture(texSampler,fragTexCoord);
}
使用 uniform 变量来表示组合图像采样器描述符
对于一维图像和二维图像需要使用对应的 sampler1D 和 sampler2D 变量类型来绑定描述符。
在 GLSL 纹理采样需要使用 GLSL 内建的 texture 函数。texture 函数使用一个采样器变量和一个纹理坐标作为参数对纹理进行采样。采样器会自动按照我们设定的过滤和变换方式对纹理进行采样
一些其他的效果,可以设置试一下,看一下:
//1
outColor = texture(texSampler,fragTexCoord);
//2
outColor = vec4(fragColor*texture(texSampler,fragTexCoord).rgb,1.0);