Vulkan Tutorial 7 纹理贴图
目录
23 图像
图片库
暂存缓冲区
纹理图像
布局转换
将缓冲区复制到图像上
准备纹理图像
传输屏障掩码
清除
24 图像视图和采样器
纹理图像视图
采样器
Anisotropy 设备特征
25 组合图像采样器
更新描述符
纹理坐标系
着色器
23 图像
添加纹理将涉及以下步骤:
- 创建一个由设备内存支持的图像对象
- 用图像文件中的像素填充它
- 创建图像采样器
- 添加组合图像采样器描述符以从纹理中采样颜色
我们之前已经使用过图像对象,但它们是由交换链扩展自动创建的。这次我们必须自己创建一个。创建图像并用数据填充它类似于创建顶点缓冲区。我们将从创建暂存资源并用像素数据填充它开始,然后将其复制到我们将用于渲染的最终图像对象。我们将首先创建这个缓冲区并用像素值填充它,然后我们将创建一个图像来将像素复制到。
图像可以有不同的布局,这些布局会影响像素在内存中的组织方式。例如,由于图形硬件的工作方式,简单地逐行存储像素可能不会带来最佳性能。在对图像执行任何操作时,您必须确保它们具有最适合该操作的布局。
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR
: 最适合展示VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL
: 最适合作为从片段着色器写入颜色的附件VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL
:最适合作为传输操作中的源,例如vkCmdCopyImageToBufferVK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL
:最适合作为传输操作中的目的地,例如vkCmdCopyBufferToImageVK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL
:最适合从着色器采样
图片库
有许多库可用于加载图像,您甚至可以编写自己的代码来加载 BMP 和 PPM 等简单格式。 stb_image 库。stb_image库实现都写在头文件中,不需要编译成库,项目中直接引用头文件目录即可。
GitHub - nothings/stb: stb single-file public domain libraries for C/C++
项目属性 ----> C/C++ —> 附加包含目录 —> your_path\stb-master
新建文件夹textures,放入图像texture.jpg,将其大小调整为 512 x 512 像素,但您可以随意选择您想要的任何图像。该库支持最常见的图像文件格式,如 JPEG、PNG、BMP 和 GIF。
包含图像库:
#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>
void createTextureImage(){//创建一个新函数createTextureImage,我们将在其中加载图像并将其上传到 Vulkan 图像对象中int texWidth, texHeight, texChannels;stbi_uc* pixels = stbi_load("textures/texture.jpg", &texWidth, &texHeight, &texChannels, STBI_rgb_alpha);VkDeviceSize imageSize = texWidth * texHeight * 4;
//stbi_load函数将要加载的文件路径和通道数作为参数。该STBI_rgb_alpha值强制使用 alpha 通道加载图像if (!pixels) {throw std::runtime_error("failed to load texture image!");}}
暂存缓冲区
VkBuffer stagingBuffer;
VkDeviceMemory stagingBufferMemory;//缓冲区应该在主机可见内存中,以便我们可以映射它
createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory);
//从图像加载库中获取的像素值复制到缓冲区中:void* data;
vkMapMemory(device, stagingBufferMemory, 0, imageSize, 0, &data);memcpy(data, pixels, static_cast<size_t>(imageSize));
vkUnmapMemory(device, stagingBufferMemory);
//清理原始像素阵列:stbi_image_free(pixels);
纹理图像
虽然我们可以设置着色器来访问缓冲区中的像素值,但最好使用 Vulkan 中的图像对象来实现此目的。通过允许我们使用 2D 坐标,图像对象将使检索颜色变得更容易和更快。图像对象中的像素称为纹素,添加以下新类成员:
VkImage textureImage;
VkDeviceMemory textureImageMemory;
//图像的参数在VkImageCreateInfo结构中指定:VkImageCreateInfo imageInfo{};
imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
//imageType字段中指定的图像类型告诉 Vulkan 使用哪种坐标系来处理图像中的纹素
//创建 1D、2D 和 3D 图像
imageInfo.imageType = VK_IMAGE_TYPE_2D;
//该extent字段指定图像的尺寸,基本上是每个轴上有多少纹素
imageInfo.extent.width = static_cast<uint32_t>(texWidth);
imageInfo.extent.height = static_cast<uint32_t>(texHeight);
imageInfo.extent.depth = 1;
imageInfo.mipLevels = 1;
imageInfo.arrayLayers = 1;imageInfo.format = VK_FORMAT_R8G8B8A8_SRGB;
imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
//该usage字段与缓冲区创建期间的语义相同。该图像将用作缓冲区副本的目标,因此应将其设置为传输目标。
imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;//支持图形(因此也支持)传输操作的队列系列
imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;//samples标志与多重采样有关
imageInfo.flags = 0; // Optionalif (vkCreateImage(device, &imageInfo, nullptr, &textureImage) != VK_SUCCESS) {throw std::runtime_error("failed to create image!");
}
该tiling
字段可以具有以下两个值之一:
VK_IMAGE_TILING_LINEAR
pixels
: Texels 像我们的数组一样按行优先顺序排列VK_IMAGE_TILING_OPTIMAL
:纹理元素按照实现定义的顺序排列,以实现最佳访问- 如果您希望能够直接访问图像内存中的纹素,那么您必须使用
VK_IMAGE_TILING_LINEAR
initialLayout
图像的的 只有两个可能的值:
VK_IMAGE_LAYOUT_UNDEFINED
: GPU 不可用,第一个转换将丢弃纹素。VK_IMAGE_LAYOUT_PREINITIALIZED
: GPU 不可用,但第一个转换将保留纹素。
图像是用vkCreateImage创建的,它没有任何特别值得注意的参数。
VK_FORMAT_R8G8B8A8_SRGB
格式有可能不被图形硬件所支持。
为图像分配内存的方法与为缓冲区分配内存的方法完全相同。使用vkGetImageMemoryRequirements代替vkGetBufferMemoryRequirements,使用vkBindImageMemory代替vkBindBufferMemory 。
VkMemoryRequirements memRequirements;
vkGetImageMemoryRequirements(device, textureImage, &memRequirements);VkMemoryAllocateInfo allocInfo{};
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
allocInfo.allocationSize = memRequirements.size;
allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);if (vkAllocateMemory(device, &allocInfo, nullptr, &textureImageMemory) != VK_SUCCESS) {throw std::runtime_error("failed to allocate image memory!");
}vkBindImageMemory(device, textureImage, textureImageMemory, 0);
这个函数已经变得相当大了,而且在后面的章节中还需要创建更多的图像,所以我们应该把图像创建抽象成一个createImage
函数,就像我们对缓冲区所做的那样。创建这个函数,并将图像对象的创建和内存分配移到它上面。
布局转换
我们现在要写的函数涉及到再次记录和执行一个命令缓冲区,所以现在是把这个逻辑移到一两个辅助函数中的好时机:
VkCommandBuffer beginSingleTimeCommands() {VkCommandBufferAllocateInfo allocInfo{};allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;allocInfo.commandPool = commandPool;allocInfo.commandBufferCount = 1;VkCommandBuffer commandBuffer;vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer);VkCommandBufferBeginInfo beginInfo{};beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;vkBeginCommandBuffer(commandBuffer, &beginInfo);return commandBuffer;
}void endSingleTimeCommands(VkCommandBuffer commandBuffer) {vkEndCommandBuffer(commandBuffer);VkSubmitInfo submitInfo{};submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;submitInfo.commandBufferCount = 1;submitInfo.pCommandBuffers = &commandBuffer;vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE);vkQueueWaitIdle(graphicsQueue);vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
}
copyBuffer
的现有代码。现在你可以将该函数简化为:
void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) {VkCommandBuffer commandBuffer = beginSingleTimeCommands();VkBufferCopy copyRegion{};copyRegion.size = size;vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, ©Region);endSingleTimeCommands(commandBuffer);
}
创建一个新的函数来处理布局的转换:
void transitionImageLayout(VkImage image, VkFormat format, VkImageLayout oldLayout, VkImageLayout newLayout) {VkCommandBuffer commandBuffer = beginSingleTimeCommands();
//执行布局转换的最常见方法之一是使用图像内存屏障
//屏障通常用于同步访问资源,比如确保在从缓冲区读取之前完成对缓冲区的写
VkImageMemoryBarrier barrier{};
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
//前两个字段指定布局过渡。如果你不关心图片的现有内容,可以使用VK_IMAGE_LAYOUT_UNDEFINED作为oldLayout
barrier.oldLayout = oldLayout;
barrier.newLayout = newLayout;
//如果你使用屏障来转移队列家族的所有权,那么这两个字段应该是队列家族的索引
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
//image和subresourceRange指定受影响的图像和图像的具体部分。
//我们的图像不是一个数组,也没有MIP映射层,所以只指定了一个level 和layer
barrier.image = image;
barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
barrier.subresourceRange.baseMipLevel = 0;
barrier.subresourceRange.levelCount = 1;
barrier.subresourceRange.baseArrayLayer = 0;
barrier.subresourceRange.layerCount = 1;
vkCmdPipelineBarrier(commandBuffer,0 /* TODO */, 0 /* TODO */,0,0, nullptr,0, nullptr,1, &barrier
);//指定哪些涉及资源的操作类型必须在障碍物之前发生,哪些涉及资源的操作必须在障碍物上等待endSingleTimeCommands(commandBuffer);
}
将缓冲区复制到图像上
就像缓冲区拷贝一样,你需要指定缓冲区的哪一部分将被拷贝到图像的哪一部分。这是通过VkBufferImageCopy结构实现的
void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height) {VkCommandBuffer commandBuffer = beginSingleTimeCommands();VkBufferImageCopy region{};
region.bufferOffset = 0;
//bufferOffset “指定了像素值开始在缓冲区中的字节偏移。
//bufferRowLength”和 “bufferImageHeight”字段指定像素在内存中的排列方式
region.bufferRowLength = 0;
region.bufferImageHeight = 0;
//这两个字段中指定 0表示像素像我们的情况一样被紧密地排列region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
region.imageSubresource.mipLevel = 0;
region.imageSubresource.baseArrayLayer = 0;
region.imageSubresource.layerCount = 1;region.imageOffset = {0, 0, 0};
region.imageExtent = {width,height,1
};//缓冲区到图像的复制操作是通过vkCmdCopyBufferToImage函数排队的
vkCmdCopyBufferToImage(commandBuffer,buffer,image,VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,1,®ion
);endSingleTimeCommands(commandBuffer);
}
准备纹理图像
我们现在拥有完成设置纹理图像所需的所有工具,所以我们要回到createTextureImage
函数。我们在那里做的最后一件事是创建纹理图像。下一步是将暂存缓冲区复制到纹理图像上。这包括两个步骤。
- 将纹理图像过渡到
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL
。 - 执行缓冲区到图像的复制操作
传输屏障掩码
如果你现在启用了验证层来运行你的应用程序,那么你会看到它抱怨transitionImageLayout
中的访问掩码和管道阶段是无效的。我们仍然需要根据过渡中的布局来设置这些。
有两个过渡我们需要处理。
- Undefined → transfer destination:传输写入,不需要等待任何东西
- Transfer destination → shader reading:着色器读取应该等待传输写入,特别是片段着色器中的着色器读取,因为那是我们要使用纹理的地方。
这些规则是用以下访问掩码和管线阶段指定的:
VkPipelineStageFlags sourceStage;
VkPipelineStageFlags destinationStage;if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {barrier.srcAccessMask = 0;barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
//传输写入必须发生在流水线传输阶段。由于写操作不需要等待任何东西,
//你可以指定一个空的访问掩码和最早的流水线阶段VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT来进行前障操作sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
} else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
//VK_PIPELINE_STAGE_TRANSFER_BIT不是图形和计算管道中*真实的阶段。它更像是一个发生传输的伪阶段sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT;destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
} else {throw std::invalid_argument("unsupported layout transition!");
}vkCmdPipelineBarrier(commandBuffer,sourceStage, destinationStage,0,0, nullptr,0, nullptr,1, &barrier
);
清除
完成createTextureImage
函数,在最后清理暂存器和它的内存:
transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);vkDestroyBuffer(device, stagingBuffer, nullptr);vkFreeMemory(device, stagingBufferMemory, nullptr);//主纹理图像会一直使用到程序结束:
void cleanup() {cleanupSwapChain();vkDestroyImage(device, textureImage, nullptr);vkFreeMemory(device, textureImageMemory, nullptr);...
}
24 图像视图和采样器
纹理图像视图
我们之前已经看到,在交换链图像和帧缓冲区中,图像是通过图像视图而不是直接访问。我们也需要为纹理图像创建这样一个图像视图。
添加一个类成员,为纹理图像保存一个VkImageView,并创建一个新的函数createTextureImageView
,我们将在这里创建它:
VkImageView textureImageView;
//因为很多逻辑与createImageViews重复,你可能希望将其抽象为一个新的createImageView函数:VkImageView createImageView(VkImage image, VkFormat format) {VkImageViewCreateInfo viewInfo{};viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;viewInfo.image = image;viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;viewInfo.format = format;viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;viewInfo.subresourceRange.baseMipLevel = 0;viewInfo.subresourceRange.levelCount = 1;viewInfo.subresourceRange.baseArrayLayer = 0;viewInfo.subresourceRange.layerCount = 1;VkImageView imageView;if (vkCreateImageView(device, &viewInfo, nullptr, &imageView) != VK_SUCCESS) {throw std::runtime_error("failed to create texture image view!");}return imageView;
}
采样器
着色器有可能直接从图像中读取纹理,但当它们被用作纹理时,这并不是很常见。纹理通常是通过采样器访问的,它将应用滤波和变换来计算被检索的最终颜色。
这些过滤器有助于处理诸如过采样的问题。考虑一个被映射到几何体上的纹理,它的碎片多于纹理。如果你只是在每个片段的纹理坐标中选择最接近的texel,那么你会得到像第一张图片那样的结果。如果你通过线性插值将4个最接近的texel结合起来,那么你会得到一个更平滑的结果
![]()
欠采样是一个相反的问题,即你的纹理比片段多。这将导致在对高频图案进行采样时出现伪影,比如在一个尖锐的角度对棋盘纹理进行采样。
除了这些滤镜之外,采样器还可以处理变换问题。它决定了当你试图通过它的addressing模式读取图像外的文本时会发生什么。下面的图片显示了一些可能性。
创建一个函数createTextureSampler
来设置这样一个采样器对象。稍后我们将使用这个采样器从着色器的纹理中读取颜色。
void createTextureSampler() {
//采样器是通过VkSamplerCreateInfo结构配置的,该结构指定了它应该应用的所有过滤器和转换。
VkSamplerCreateInfo samplerInfo{};
samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
//magFilter和minFilter字段指定了如何插值被放大或缩小的文本。
//放大涉及到上面描述的过度取样问题,缩小涉及到欠取样问题
samplerInfo.magFilter = VK_FILTER_LINEAR;
samplerInfo.minFilter = VK_FILTER_LINEAR;//寻址模式可以通过 addressMode 字段指定每个轴
samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;//这两个字段指定是否应该使用各向异性过滤
samplerInfo.anisotropyEnable = VK_TRUE;
//maxAnisotropy “字段限制了可用于计算最终颜色的texel样本量。
//samplerInfo.maxAnisotropy = ???;
//一个较低的值会带来更好的性能,但质量较低。
//为了弄清我们可以使用哪个值,我们需要检索物理设备的属性:VkPhysicalDeviceProperties properties{};
vkGetPhysicalDeviceProperties(physicalDevice, &properties);
samplerInfo.maxAnisotropy = properties.limits.maxSamplerAnisotropy;samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;
//borderColor 字段指定了当用钳制边界寻址模式在图像之外取样时返回哪种颜色。
//可以返回黑色、白色或透明的浮点数或英寸格式
//unnormalizedCoordinates字段指定了你想用哪种坐标系统来处理图像中的纹理。
//如果这个字段是VK_TRUE,那么你可以简单地使用[0, texWidth)和[0, texHeight)范围内的坐标。
//如果它是VK_FALSE,那么在所有轴上都使用[0, 1)范围来处理texels。
//
samplerInfo.unnormalizedCoordinates = VK_FALSE;samplerInfo.compareEnable = VK_FALSE;
samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS;
//如果启用了比较功能,那么texels将首先与一个值进行比较,而比较的结果将用于过滤操作samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
samplerInfo.mipLodBias = 0.0f;
samplerInfo.minLod = 0.0f;
samplerInfo.maxLod = 0.0f;
}
注意,轴被称为U、V和W,而不是X、Y和Z,这是纹理空间坐标的惯例。
vk_sampler_address_mode_repeat
。当超出图像尺寸时重复纹理。vk_sampler_address_mode_mirrored_repeat
。和重复一样,但当超出尺寸时,将坐标倒置以镜像图像。vk_sampler_address_mode_clamp_to_edge
: 取最接近坐标的边缘的颜色,超出图像尺寸。vk_sampler_address_mode_mirror_clamp_to_edge
: 和钳制边缘一样,但使用与最接近的边缘相反的边缘。vk_sampler_address_mode_clamp_to_border
: 当取样超出图像的尺寸时,返回一个纯色。
采样器的功能现在已经完全定义了。添加一个类成员来保存采样器对象的句柄,用vkCreateSampler创建采样器:
VkSampler textureSampler;if (vkCreateSampler(device, &samplerInfo, nullptr, &textureSampler) != VK_SUCCESS) {throw std::runtime_error("failed to create texture sampler!");}
Anisotropy 设备特征
anisotropic filtering实际上是一个可选的设备功能
VkPhysicalDeviceFeatures deviceFeatures{};
deviceFeatures.samplerAnisotropy = VK_TRUE;//而即使现代显卡不支持它的可能性很小,我们也应该更新isDeviceSuitable来检查它是否可用:bool isDeviceSuitable(VkPhysicalDevice device) {...VkPhysicalDeviceFeatures supportedFeatures;
//vkGetPhysicalDeviceFeatures重新利用了VkPhysicalDeviceFeatures结构,通过设置布尔值来指示支持哪些功能,而不是要求哪些功能。vkGetPhysicalDeviceFeatures(device, &supportedFeatures);return indices.isComplete() && extensionsSupported && swapChainAdequate && supportedFeatures.samplerAnisotropy;
}
25 组合图像采样器
组合图像采样器使得着色器可以通过采样器对象来访问图像资源。我们将首先修改描述符布局、描述符池和描述符集,以包括这样一个组合图像取样器描述符。之后,我们将为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;
//确保设置`stageFlags’以表明我们打算在片段着色器中使用组合图像采样器描述符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();
我们还必须创建一个更大的描述符池,为组合图像采样器的分配腾出空间,在VkDescriptorPoolCreateInfo中添加另一个类型为VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER
的VkPoolSize 。转到createDescriptorPool
函数,并修改它,为这个描述符包括一个VkDescriptorPoolSize:
std::array<VkDescriptorPoolSize, 2> poolSizes{};
poolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
poolSizes[0].descriptorCount = static_cast<uint32_t>(MAX_FRAMES_IN_FLIGHT);
poolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
poolSizes[1].descriptorCount = static_cast<uint32_t>(MAX_FRAMES_IN_FLIGHT);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>(MAX_FRAMES_IN_FLIGHT);//最后一步是将实际的图像和采样器资源与描述符集中的描述符绑定。转到createDescriptorSets函数。for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; 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结构中指定。
纹理坐标系
纹理映射还缺少一个重要成分,那就是每个顶点的实际坐标。坐标决定了图像是如何实际映射到几何体上的。
glm::vec2 texCoord;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;
}#version 450layout(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
正确插值。
一个组合的图像采样器描述符在GLSL中由一个采样器统一表示。在片段着色器中添加一个对它的引用:
layout(binding = 1) uniform sampler2D texSampler;void main() {outColor = texture(texSampler, fragTexCoord);outColor = texture(texSampler, fragTexCoord * 2.0);outColor = vec4(fragColor * texture(texSampler, fragTexCoord).rgb, 1.0);
}
纹理是使用内置的texture
函数进行采样的。它需要一个 “采样器”和坐标作为参数。采样器会在后台自动处理过滤和转换的问题。现在当你运行应用程序时,你应该看到广场上的纹理了。
相关文章:

Vulkan Tutorial 7 纹理贴图
目录 23 图像 图片库 暂存缓冲区 纹理图像 布局转换 将缓冲区复制到图像上 准备纹理图像 传输屏障掩码 清除 24 图像视图和采样器 纹理图像视图 采样器 Anisotropy 设备特征 25 组合图像采样器 更新描述符 纹理坐标系 着色器 23 图像 添加纹理将涉及以下步骤&am…...

LinkedBlockingQueue阻塞队列
➢ LinkedBlockingQueue阻塞队列 LinkedBlockingQueue类图 LinkedBlockingQueue 中也有两个 Node 分别用来存放首尾节点,并且里面有个初始值为 0 的原子变量 count 用来记录队列元素个数,另外里面有两个ReentrantLock的独占锁,分别用来控制…...

面试-Redis 常见问题,后续面试遇到新的在补充
面试-Redis 1.谈谈Redis 缓存穿透,击穿,雪崩及如何避免 缓存穿透:是指大量访问请求在访问一个不存在的key,由于key 不存在,就会去查询数据库,数据库中也不存在该数据,无法将数据存储到redis 中…...

2023年上半年数据库系统工程师上午真题及答案解析
1.计算机中, 系统总线用于( )连接。 A.接口和外设 B.运算器、控制器和寄存器 C.主存及外设部件 D.DMA控制器和中断控制器 2.在由高速缓存、主存和硬盘构成的三级存储体系中,CPU执行指令时需要读取数据,那么DMA控制器和中断CPU发出的数据地…...

设计模式概念
设计模式是软件工程领域中常用的解决问题的经验总结和最佳实践。它们提供了一套被广泛接受的解决方案,用于处理常见的设计问题,并促进可重用、可扩展和易于维护的代码。 设计模式的主要目标是提高软件的可重用性、可扩展性和灵活性,同时降低…...

arcpy批量对EXCE经纬度L进行投点,设置为wgs84坐标系,并利用该点计算每个区域内的核密度
以下是在 ArcPy 中批量对 Excel 经纬度 L 进行投点,设置为 WGS84 坐标系,并利用该点计算每个区域内的核密度的详细步骤: 1. 准备数据: 准备包含经纬度信息的 Excel 数据表格,我们假设文件路径为 "C:/Data/locations.xlsx&qu…...

Yolov5训练自己的数据集
先看下模型pt说明 YOLOv5s:这是 YOLOv5 系列中最小的模型。“s” 代表 “small”(小)。该模型在计算资源有限的设备上表现最佳,如移动设备或边缘设备。YOLOv5s 的检测速度最快,但准确度相对较低。 YOLOv5m࿱…...

Bert+FGSM中文文本分类
我上一篇博客已经分别用BertFGSM和BertPGD实现了中文文本分类,这篇文章与我上一篇文章BertFGSM/PGD实现中文文本分类(Loss0.5L10.5L2)_Dr.sky_的博客-CSDN博客的不同之处在于主要在对抗训练函数和embedding添加扰动部分、模型定义部分、Loss函数传到部分…...

爬楼梯问题-从暴力递归到动态规划(java)
爬楼梯,每次只能爬一阶或者两阶,计算有多少种爬楼的情况 爬楼梯--题目描述暴力递归递归缓存动态规划暴力递归到动态规划专题 爬楼梯–题目描述 一个总共N 阶的楼梯(N > 0) 每次只能上一阶或者两阶。问总共有多少种爬楼方式。 示…...

浏览器如何验证SSL证书?
浏览器如何验证SSL证书?当前SSL证书应用越来越广泛,我们看见的HTTPS网站也越来越多。点击HTTPS链接签名的绿色小锁,我们可以看见SSL证书的详细信息。那么浏览器是如何验证SSL证书的呢? 浏览器如何验证SSL证书? 在浏览器的菜单中…...

Linux :: 【基础指令篇 :: 文件及目录操作:(10)】:: ll 指令 :: 查看指定目录下的文件详细信息
前言:本篇是 Linux 基本操作篇章的内容! 笔者使用的环境是基于腾讯云服务器:CentOS 7.6 64bit。 学习集: C 入门到入土!!!学习合集Linux 从命令到网络再到内核!学习合集 目录索引&am…...

Java字符集/编码集
1 字符集/编码集 基础知识 计算机中储存的信息都是用二进制数表示的;我们在屏幕上看到的英文、汉字等字符是二进制数转换之后的结果 按照某种规则, 将字符存储到计算机中,称为编码。反之,将存储在计算机中的二进制数按照某种规则解析显示出来,称为解码。这里强调一下: 按照…...

Apache配置与应用
目录 虚拟web主机httpd服务支持的虚拟主机类型基于域名配置方法基于IP配置方法基于端口配置方法 apache连接保持构建Web虚拟目录与用户授权限制Apache日志分割 虚拟web主机 虚拟Web主机指的是在同一台服务器中运行多个Web站点,其中每一个站点实际上并不独立占用整个…...

API自动化测试【postman生成报告】
PostMan生成测试报告有两种: 1、控制台的模式 2、HTML的测试报告 使用到一个工具newman Node.js是前端的一个组件,主要可以使用它来开发异步的程序。 一、控制台的模式 1、安装node.js 双击node.js进行安装,安装成功后在控制台输入node …...

探索OpenAI插件:ChatWithGit,memecreator,boolio
引言 在当今的技术世界中,插件扮演着至关重要的角色,它们提供了一种简单有效的方式来扩展和增强现有的软件功能。在本文中,我们将探索三个OpenAI的插件:ChatWithGit,memecreator,和boolio,它们…...

linux irq
中断上下部 软中断、tasklet、工作对列 软中断优点:运行在软中断上下文,优先级比普通进程高,调度速度快。 缺点:由于处于中断上下文,所以不能睡眠。 相对于软中断/tasklet,工作对列运行在进程上下文 h…...

串口流控(CTS/RTS)使用详解
1.流控概念 在两个设备正常通信时,由于处理速度不同,就存在这样一个问题,有的快,有的慢,在某些情况下,就可能导致丢失数据的情况。 如台式机与单片机之间的通讯,接收端数据缓冲区已满࿰…...

kube-proxy模式详解
1 kube-proxy概述 kubernetes里kube-proxy支持三种模式,在v1.8之前我们使用的是iptables 以及 userspace两种模式,在kubernetes 1.8之后引入了ipvs模式,并且在v1.11中正式使用,其中iptables和ipvs都是内核态也就是基于netfilter&…...

汽车EDI:如何与Stellantis建立EDI连接?
Stellantis 是一家实力雄厚的汽车制造公司,由法国标致雪铁龙集团(PSA集团)和意大利菲亚特克莱斯勒汽车集团(FCA集团)合并而成,是世界上第四大汽车制造商,拥有包括标致、雪铁龙、菲亚特、克莱斯勒…...

【SCI征稿】1区计算机科学类SCI, 自引率低,对国人友好~
一、【期刊简介】 JCR1区计算机科学类SCI&EI 【期刊概况】IF: 7.0-8.0,JCR1区,中科院2区; 【终审周期】走期刊系统,3-5个月左右录用; 【检索情况】SCI&EI双检; 【自引率】1.30% 【征稿领域】发表人工智能…...

Vue.js优化策略与性能调优指南
导语:Vue.js是一款出色的前端框架,但在处理大规模应用或复杂场景时,性能问题可能会出现。本文将介绍一些Vue.js优化策略和性能调优指南,帮助您提升应用的性能和用户体验。 延迟加载:将应用的代码进行按需加载ÿ…...

HEVC环路后处理核心介绍
介绍 为什么需要环路后处理技术 hevc采用基于快的混合编码框架,方块效应、振铃效应、颜色偏差、图像模糊等失真效应依旧存在,为了降低此类失真影响,需要进行环路滤波技术; 采用的技术 去方块滤波DF,为了降低块效应…...

从组件化角度聊聊设计工程化
目录 设计系统 设计系统的定义 设计系统的优势 设计系统存在的问题 设计工程化 设计系统探索 设计系统落地实践 Design Token Design Token 实践 设计工程化理想方案构想 展望 参考文献 近几年围绕业务中台化的场景,涌现出了许多低代码平台。面对多组件…...

apache的配置和应用
文章目录 一、httpd服务支持的虚拟主机类型包括以下三种:二、构建Web虚拟目录与用户授权限制三、日志分割 虚拟Web主机指的是在同一台服务器中运行多个Web站点,其中每一个站点实际上并不独立占用整个服务器,因此被称为“虚拟”Web 主机。通过虚拟 Web 主…...

Buf 教程 - 使用 Protobuf 生成 Golang 代码和 Typescript 类型定义
简介 Buf 是一款更高效、开发者友好的 Protobuf API 管理工具,不仅支持代码生成,还支持插件和 Protobuf 格式化。 我们可以使用 Buf 替代原本基于 Protoc 的代码生成流程,一方面可以统一管理团队 Protoc 插件的版本、代码生成配置ÿ…...

Java 锁 面试题(ReentrantLock、synchronized)
Java 锁 面试题(ReentrantLock、synchronized) 1. 锁2. ReentrantLock2.1 ReentrantLock 的实现原理2.2 AQS 是什么?2.3 CAS 是什么? 3. synchronized3.1 synchronized 的实现原理3.2 synchronized 的锁升级过程3.2.1 无锁3.2.2 偏…...

Python中的缩进是什么意思?
在Python中,缩进是指在代码中使用空格或制表符来表示代码块的层次结构。Python使用缩进作为语法的一部分,以定义代码的逻辑结构和代码块的范围。缩进在Python中具有以下几个重要的方面和含义。 代码块的开始和结束: 缩进在Python中用于标识代…...

2023年9月数学建模:最小二乘优化、曲线拟合与函数逼近
2023年9月数学建模国赛期间提供ABCDE题思路加Matlab代码,专栏链接(赛前一个月恢复源码199,欢迎大家订阅):http://t.csdn.cn/Um9Zd 目录 1. 最小二乘优化 1.1 最小二乘优化的原理 1.2 最小二乘优化的方法...

java8内部调用无法引用值的问题
问题:Variable used in lambda expression should be final or effectively final 具体原因: 这段代码试图将 20 赋给一个局部变量,它无法通过编译,但绝非编写错误。 这实际上是语言的设计者有意为之,用以鼓励用户使用…...

《嵌入式系统》知识总结10:使用位带操作操纵GPIO
位操作 汇编层面 外设控制常要针对字中某个位(Bit)操作 以字节编址的存储器地址空间中,需要3步骤(读出-修改-写回) 1.(从外设)读取包含该位的字节数据 2. 设置该位为0或1、同时屏蔽其他位&am…...