Vulkan Tutorial 27 combined image sampler
操作系統:Windows8.1
顯卡:Nivida GTX965M
開發工具:Visual Studio 2017
Introduction
我們在教程的uniform 緩沖區中首次了解了描述符。在本章節我們會看到一種新的描述符類型:combined image sampler 組合圖像取樣器。該描述符使著色器可以通過像上一章創建的采樣器對象來訪問圖像資源。
我們將首先修改描述符布局,描述符對象池和描述符集合,以包括這樣一個組合的圖像采樣器描述符。完成之後,我們會添加紋理貼圖坐標到Vertex數據中,並修改片段著色器從紋理中讀取顏色,而不是內插頂點顏色。
Updating the descriptors
瀏覽到createDesriptorSetLayout函數,並為組合圖像采樣器描述符添加一個VkDescriptorSetLayoutBinding。我們將簡單的在uniform buffer之後進行版定操作。
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();
確保stageFlags正確設置,指定我們打算在片段著色器中使用組合圖像采樣器描述符。這就是片段顏色最終被確定的地方。可以在頂點著色器中使用紋理采樣,比如,通過高度圖 heightmap 動態的變形頂點的網格。
如果你開啟validation layers運行程序,你將會看到它引起了描述符對象池不能使用這個布局分配描述符集合,因為它沒有任何組合圖像采樣器描述符。來到createDescriptorPool函數,以包含此描述符的VkDescriptorPoolSize:
std::array<VkDescriptorPoolSize, 2> poolSizes = {}; poolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; poolSizes[0].descriptorCount = 1; poolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; poolSizes[1].descriptorCount = 1; 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 = 1;
最後一步是將實際的圖像和采樣器資源綁定到描述符集合中的具體描述符。來到createDescriptorSet函數。
VkDescriptorImageInfo imageInfo = {}; imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; imageInfo.imageView = textureImageView; imageInfo.sampler = textureSampler;
組合圖像采樣器結構體的資源必須在VkDescriptorImageInfo結構進行指定。就像在VkDescriptorBufferInfo結構體中指定一個 uniform buffer descriptor 緩沖區資源一樣。這是上一章節中的對象匯集的代碼段。
std::array<VkWriteDescriptorSet, 2> descriptorWrites = {}; descriptorWrites[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; descriptorWrites[0].dstSet = descriptorSet; 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 = descriptorSet; 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);
描述符必須與此圖像信息一起更新,就像緩沖區一樣。這次我們使用pImageInfo數組代替pBufferInfo。描述符現在可以被著色器使用!
Texture coordinates
紋理映射的一個重要組成部分仍然缺少,這是每個頂點的實際坐標。坐標決定如何實際的映射到幾何圖形上。
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; } };
修改Vertex結構體包含vec2結構用於紋理坐標。確保加入VkVertexInputAttributeDescription結構體,如此我們就可以在頂點著色器中訪問紋理UV坐標數據。這是必要的,以便能夠將它們傳遞到片段著色器,以便在正方形的表面進行插值處理。
const std::vector<Vertex> vertices = { {{-0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}, {0.0f, 0.0f}}, {{0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}, {1.0f, 0.0f}}, {{0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}, {1.0f, 1.0f}}, {{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}, {0.0f, 1.0f}} };
在本教程中,使用坐標從左上角 0,0 到右下角的 1,1 來映射紋理,從而簡單的填充矩形。在這裏可以嘗試各種坐標。嘗試使用低於 0 或者 1 以上的坐標來查看尋址模式的不同表現。
Shaders
最後一步是修改著色器從紋理中采樣顏色。我們首先需要修改頂點著色器,傳遞紋理坐標到片段著色器。
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; }
就像每個頂點的顏色,fragTexCoord值通過光柵化平滑的插值到矩形區域內。我們可以通過使片段著色器輸出的紋理坐標為顏色來可視化看到這些:
#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); }
現在應該可以看到如下圖所示的效果。不要忘記重新編譯著色器!
綠色通道代表水平坐標,紅色通道代表垂直坐標。黑色和黃色角確認了紋理坐標正確的從 0,0 到 1,1 進行插值填充到方形中。使用顏色可視化在著色器中編程等價於 printf 調試,除此之外沒有更好的方法!
組合圖像采樣器描述符在GLSL中通過采樣器 uniform代替。在片段著色器中引用它:
layout(binding = 1) uniform sampler2D texSampler;
對於其他圖像有等價的 sampler1D 和 sampler3D 類型。確保正確的綁定操作。
void main() { outColor = texture(texSampler, fragTexCoord); }
紋理采用內置的 texture 函數進行采樣。它需要使用 sampler 和 坐標作為參數。采樣器在後臺自動處理過濾和變化功能。你應該可以看到紋理貼圖在方形上:
嘗試修改尋址模式放大大於 1 來觀測效果。比如,下面的片段著色器輸出的結果使用VK_SAMPLER_ADDRESS_MODE_REPEAT:
void main() { outColor = texture(texSampler, fragTexCoord * 2.0); }
還可以使用頂點顏色來處理紋理顏色:
void main() { outColor = vec4(fragColor * texture(texSampler, fragTexCoord).rgb, 1.0); }
將RGB和alha通道分離開,以便不分離alpha通道。
現在已經知道如何在著色器中訪問圖像!當與幀緩沖區中的圖像進行結合時,這是一個非常有效的技術。你可以看到這些圖像作為輸入實現很酷的效果,比如 post-processing和3D世界攝像機顯示。
項目代碼 GitHub 地址。
Vulkan Tutorial 27 combined image sampler