完结 - 关于Vulkan的纹理映射及后续其他技巧

转载自最早发布在formu.ziyuesinicization.site的文章

晨魔(Morning Demon)

我先为顶点数据加纹理坐标:


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中文文档