完结 - 关于Vulkan的纹理映射及后续其他技巧
转载自最早发布在formu.ziyuesinicization.site的文章
我先为顶点数据加纹理坐标:
typedef struct Vertex
{
float position[3];
float color[3];
float texCoords[2];
} Vertex;
Vertex vertices[4] = {
{{-0.5f, 0.5f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f}},
{{ 0.5f, 0.5f, 0.0f}, {1.0f, 0.0f, 0.0f}, {1.0f, 0.0f}},
{{ 0.5f, -0.5f, 0.0f}, {1.0f, 0.0f, 0.0f}, {1.0f, 1.0f}},
{{-0.5f, -0.5f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, 1.0f}}
};
接着修改着色器:
IO.hlsl:
#ifndef IO_HLSL
#define IO_HLSL
struct VS_INPUT
{
float3 position : POSITION0;
float3 color : COLOR0;
float2 texCoords : TEXCOORD0;
};
struct VS_OUTPUT
{
float4 position : SV_POSITION;
float3 color : COLOR0;
float2 texCoords : TEXCOORD0;
};
#endif
vertex.hlsl:
#include "IO.hlsl"
cbuffer Buffer_0_s : register(b0) {
float4x4 transform;
};
VS_OUTPUT main(VS_INPUT input, uint vertexID : SV_VertexID)
{
VS_OUTPUT output;
output.position = mul(transform, float4(input.position, 1.0));
output.color = input.color;
output.texCoords = input.texCoords;
return output;
}
pixel.hlsl:
#include "IO.hlsl"
Texture2D tex : register(t1);
SamplerState samp : register(s1);
float4 main(VS_OUTPUT input) : SV_Target
{
float4 texColor = tex.Sample(samp, input.texCoords);
return float4(texColor.rgb, 1.0);
}
我们加载图片数据,这里用stb_image来实现:
int texWidth, texHeight, texChannels;
stbi_uc* pixels = stbi_load("vulkan.jpeg", &texWidth, &texHeight, &texChannels, STBI_rgb_alpha);
VkDeviceSize imageSize = texWidth * texHeight * 4;
我们创建并分配一个暂存缓冲,把图片数据暂存到暂存缓冲:
CreateAndAllocateBuffer(device, physicalDevice,
imageSize,
VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
&tempBuffer, &tempBufferMemory);
vkMapMemory(device, tempBufferMemory, 0, imageSize, 0, &data);
memcpy(data, pixels, (size_t)imageSize);
vkUnmapMemory(device, tempBufferMemory);
stbi_image_free(pixels);
之后我们创建纹理图像,并为这个纹理图像申请内存:
VkImage textureImage;
VkImageCreateInfo imageInfo{};
imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
imageInfo.imageType = VK_IMAGE_TYPE_2D;
imageInfo.extent.width = static_cast(texWidth);
imageInfo.extent.height = static_cast(texHeight);
imageInfo.extent.depth = 1;
imageInfo.mipLevels = 1;
imageInfo.arrayLayers = 1;
imageInfo.format = VK_FORMAT_R8G8B8A8_SRGB; // 匹配 stb 加载的 RGBA
imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
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;
result = vkCreateImage(device, &imageInfo, nullptr, &textureImage);
CheckError(result, "Create Texture Image");
VkMemoryRequirements texMemReqs;
vkGetImageMemoryRequirements(device, textureImage, &texMemReqs);
VkMemoryAllocateInfo texallocInfo{};
texallocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
texallocInfo.allocationSize = texMemReqs.size;
texallocInfo.memoryTypeIndex = FindMemoryType(physicalDevice, texMemReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
VkDeviceMemory textureImageMemory;
result = vkAllocateMemory(device, &texallocInfo, nullptr, &textureImageMemory);
CheckError(result, "Allocate Texture Memory");
vkBindImageMemory(device, textureImage, textureImageMemory, 0);
之后,像Part 3文章那样,把暂存缓冲的数据拷贝到我们的纹理图像里:
VkCommandBufferAllocateInfo textempAllocInfo{};
textempAllocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
textempAllocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
textempAllocInfo.commandPool = commandPool;
textempAllocInfo.commandBufferCount = 1;
VkCommandBuffer textempCmdBuffer;
vkAllocateCommandBuffers(device, &textempAllocInfo, &textempCmdBuffer);
VkCommandBufferBeginInfo textempBegin{};
textempBegin.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
textempBegin.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
vkBeginCommandBuffer(textempCmdBuffer, &textempBegin);
// UNDEFINED -> TRANSFER_DST_OPTIMAL
//转换纹理图像布局
VkImageMemoryBarrier barrier{};
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.image = textureImage;
barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
barrier.subresourceRange.baseMipLevel = 0;
barrier.subresourceRange.levelCount = 1;
barrier.subresourceRange.baseArrayLayer = 0;
barrier.subresourceRange.layerCount = 1;
barrier.srcAccessMask = 0;
barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
vkCmdPipelineBarrier(textempCmdBuffer,
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT,
0, 0, nullptr, 0, nullptr, 1, &barrier);
//开始拷贝数据
VkBufferImageCopy texregion{};
texregion.bufferOffset = 0;
texregion.bufferRowLength = 0;
texregion.bufferImageHeight = 0;
texregion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
texregion.imageSubresource.mipLevel = 0;
texregion.imageSubresource.baseArrayLayer = 0;
texregion.imageSubresource.layerCount = 1;
texregion.imageOffset = { 0, 0, 0 };
texregion.imageExtent = { static_cast(texWidth), static_cast(texHeight), 1 };
vkCmdCopyBufferToImage(textempCmdBuffer, tempBuffer, textureImage,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &texregion);
//再次转换布局,让着色器能够读取,用于采样
// TRANSFER_DST -> SHADER_READ_ONLY
barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
vkCmdPipelineBarrier(textempCmdBuffer,
VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
0, 0, nullptr, 0, nullptr, 1, &barrier);
vkEndCommandBuffer(textempCmdBuffer);
VkSubmitInfo texsubmitInfo{};
texsubmitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
texsubmitInfo.commandBufferCount = 1;
texsubmitInfo.pCommandBuffers = &textempCmdBuffer;
VkFence textempFence;
VkFenceCreateInfo texfenceInfo{};
texfenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
vkCreateFence(device, &texfenceInfo, nullptr, &textempFence);
vkQueueSubmit(graphicsQueue, 1, &texsubmitInfo, textempFence);
vkWaitForFences(device, 1, &textempFence, VK_TRUE, UINT64_MAX);
vkDestroyFence(device, textempFence, nullptr);
vkFreeCommandBuffers(device, commandPool, 1, &textempCmdBuffer);
vkDestroyBuffer(device, tempBuffer, nullptr);
vkFreeMemory(device, tempBufferMemory, nullptr);
接着,我们创建纹理图像视图和纹理采样器:
VkImageView textureImageView;
VkImageViewCreateInfo viewInfo{};
viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
viewInfo.image = textureImage;
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
viewInfo.format = VK_FORMAT_R8G8B8A8_SRGB;
viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
viewInfo.subresourceRange.baseMipLevel = 0;
viewInfo.subresourceRange.levelCount = 1;
viewInfo.subresourceRange.baseArrayLayer = 0;
viewInfo.subresourceRange.layerCount = 1;
vkCreateImageView(device, &viewInfo, nullptr, &textureImageView);
VkSampler textureSampler;
VkSamplerCreateInfo samplerInfo{};
samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
samplerInfo.magFilter = VK_FILTER_LINEAR;
samplerInfo.minFilter = VK_FILTER_LINEAR;
samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
samplerInfo.anisotropyEnable = VK_FALSE;
samplerInfo.maxAnisotropy = 1.0f;
samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;
samplerInfo.unnormalizedCoordinates = VK_FALSE;
samplerInfo.compareEnable = VK_FALSE;
samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS;
samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
samplerInfo.mipLodBias = 0.0f;
samplerInfo.minLod = 0.0f;
samplerInfo.maxLod = 1.0f;
vkCreateSampler(device, &samplerInfo, nullptr, &textureSampler);
接着我们要修改之前创建的描述集(descSet):
VkDescriptorSetLayoutBinding layoutBinding[2] = {};
layoutBinding[0].binding = 0;
layoutBinding[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
layoutBinding[0].descriptorCount = 1;
layoutBinding[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
//对应像素(片段)着色器中的t(1), s(1)
layoutBinding[1].binding = 1;
layoutBinding[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
layoutBinding[1].descriptorCount = 1;
layoutBinding[1].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
VkDescriptorSetLayout descLayout;
VkDescriptorSetLayoutCreateInfo layoutCreateInfo = {};
layoutCreateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
layoutCreateInfo.bindingCount = 2;
layoutCreateInfo.pBindings = layoutBinding;
vkCreateDescriptorSetLayout(device, &layoutCreateInfo, nullptr, &descLayout);
还需要修改描述池:
VkDescriptorPoolSize poolSize[2] = {};
poolSize[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
poolSize[0].descriptorCount = 1;
poolSize[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;//*
poolSize[1].descriptorCount = 1;//*
VkDescriptorPoolCreateInfo poolInfo = {};
poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
poolInfo.poolSizeCount = 2;
poolInfo.pPoolSizes = poolSize;
poolInfo.maxSets = 1;
VkDescriptorPool descPool;
vkCreateDescriptorPool(device, &poolInfo, nullptr, &descPool);
最后在更新描述集中,加入纹理描述:
VkDescriptorBufferInfo bufferInfo = {};
bufferInfo.buffer = uniBuffer;
bufferInfo.offset = 0;
bufferInfo.range = sizeof(Matrix);
VkDescriptorImageInfo dimageInfo = {};
dimageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
dimageInfo.imageView = textureImageView;
dimageInfo.sampler = textureSampler;
VkWriteDescriptorSet write[2] = {};
write[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
write[0].dstSet = descSet;
write[0].dstBinding = 0;
write[0].descriptorCount = 1;
write[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
write[0].pBufferInfo = &bufferInfo;
write[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
write[1].dstSet = descSet;
write[1].dstBinding = 1;
write[1].descriptorCount = 1;
write[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
write[1].pImageInfo = &dimageInfo;
vkUpdateDescriptorSets(device, 2, write, 0, nullptr);
跳出循环后,别忘了销毁之前创建的东西:
vkDestroySampler(device, textureSampler, nullptr);
vkDestroyImageView(device, textureImageView, nullptr);
vkDestroyImage(device, textureImage, nullptr);
vkFreeMemory(device, textureImageMemory, nullptr);
到这里,Vulkan的基本技巧已经都在这里,至于像Mipmaps,计算着色器,如果你能吃透这些基本技巧,配合着官方文档,以及一些第三方教程,其实是不算太难的,一些高级的光照渲染技巧,也早在OpenGL, Direct3D那个年代就已经出现,原理都在那,在Vulkan上实现也不難。
希望这些文章能帮到你,之前文章里的那些代码可在这个仓库找到。
最后再附上几个有用的文章:
一个优秀的第三方教程
Vulkan官方参考文档
Vulkan中文文档