[vk] Texture Mapping - Combined image sampler

2021. 8. 16. 20:07그래픽스/vk

1. Introduction

- 튜토리얼의 uniform buffers 파트에서 descriptors 를 살펴보았다.

- 이번 챕터에서는 descriptor의 새로운 타입인  combined image sampler를 살펴볼것이다.

- 이 descriptor를 사용하면 shader가 이전장에서 만든것과 같은 sampler object를 통해

- image resource에 접근할 수 있다.

 

- 이와같은 combined image sampler descriptor를 포함하도록

- descriptor pool 과 descriptor set, descriptor layout을 수정하는것 부터 시작할 것이다.

- 그후 texture coordinates를 Vertex 에 추가할것이고, fragment shader를 수정하여

- vertex colors을 보간하는 대신 텍스처에서 색상을 읽을 수 있도록 할것이다.

 

 

 

 

 

2. Updating the descriptors

- combined image sampler descriptor를 위해 createDescriptorSetLayout 함수를 찾고

-  VkDescriptorSetLayoutBinding 구조체를 추가할것이다. 

- 간단하게 uniform buffer를 바인딩한 후에 추가하면된다.

2.1 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;

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 : VkShaderStageFlagBits 의 비트마스크로,

- - - - 바인딩에 대한 리소스에 접근할 수 있는 파이프라인 셰이더 단계를 지정한다.

- - - - fragment shader에서 combined image sampler descriptor를 사용할것임을 나타낼 수 있음

- - - - 또한 vertex shader에서 heightmap에 의해 정점의 grid를 동적으로 변형하는등의 목적으로

- - - - texture sampling을 사용하는것이 가능하다.

2.2 createDescriptorPool - VkDescriptorPoolCreateInfo

- 또한 combined image sampler를 할당할 공간을 만들기 위해 a larger descriptor pool 를 생성해야한다.

- 따라서 VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER 타입의

- VkDescriptorPoolSize를  VkDescriptorPoolCreateInfo에 추가해야한다.

   (We must also create a larger descriptor pool to make room for the allocation of the combined image sampler

   by adding another VkPoolSize of type VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER to the VkDescriptorPoolCreateInfo.)

 

- createDescriptorPool 함수로가서 이 descriptor를 위한 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());

- 부적절한 descriptor pools은 유효성 검사 레이어에서 포착하지 못하는 문제의 좋은 예시이다.

- Vulkan 1.1 부터 vkAllocateDescriptorSets는 pool이 충분히 크지 않은 경우,

- 오류코드 VK_ERROR_POOL_OUT_OF_MEMORY 와 함께 실패할 수 있지만, 

- 드라이버가 내부적으로 문제를 해결하려고할 수 도 있다.

- - - - 이것은 때때로 (하드웨어, 풀 사이즈, 할당 사이즈에 따라) 드라이버가

- - - - descriptor pool의 한계를 초과하는 할당을 처리할 수 있다는것을 의미한다.

- - - - 다른 경우 vkAllocateDescriptorSets가 실패하고 VK_ERROR_POOL_OUT_OF_MEMORY 를 리턴한다.

- - - - 할당이 일부 시스템에서는 성공하지만 다른 시스템에서 실패하는 경우 특히 실망 스러울 수 있다.

 

- Vulkan이 할당에 대한 책임을 드라이버에게 넘겨주었기 때문에

- 더이상 descriptor pool 생성 을 위한 descriptorCount 멤버에 지정한 값만큼만

- 특정 타입 (VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, etc.)의 descriptors를 할당하는것은

- 엄격한 요구사항이 아니다.

- 하지만 앞으로도 그렇게 하는것이 가장 좋은 방법이며,

- VK_LAYER_KHRONOS_validationBest Practice Validation를 활성화하면 이런 종류의 문제들을 경고해줄것이다.

 

- 마지막 단계는 실제 이미지와 sampler resources를 descriptors set의 descriptors에 바인딩하는것이다.

- createDescriptorSets  함수를 이어서 수정해보자.

2.3 createDescriptorPool - ImageInfo

for (size_t i = 0; i < swapChainImages.size(); 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;

    ...
}

- uniform buffer descriptor의 버퍼 리소스들을 VkDescriptorBufferInfo 구조체에 기술한것처럼

- combined image sampler 구조체 리소스들은 VkDescriptorImageInfo 구조체를 기술해야한다.

- 이전 장의 객체들이 함께 모이는것을 볼 수 있다.

2.4 createDescriptorPool - UpdateDescriptorSets

std::array<VkWriteDescriptorSet, 2> descriptorWrites{};

descriptorWrites[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptorWrites[0].dstSet = descriptorSets[i];
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 = 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;

vkUpdateDescriptorSets(device, static_cast<uint32_t>(descriptorWrites.size()), descriptorWrites.data(), 0, nullptr);

- descriptors는 버퍼와 마찬가지로  image info로 업데이트 되어야한다.

- 이 경우 pBufferInfo 대신에 pImageInfo 를 사용해야한다.

- 이제 이 descriptors은 shaders을 사용할 준비가 되어있다. 

 

 

 

 

3. 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 타입의 texture coordinates을 포함하도록하자.

- getAttributeDescriptions 함수에 VkVertexInputAttributeDescription 구조체 배열에 원소 하나 추가하여

- vertex shader에 대한 입력으로 텍스쳐 좌표에 접근하여 사용할 수있도록 할 수 있다.

- 정사각형 표면 전체를 보간하기위해 fragment shader에 이것들을 전달 할 수 있어야한다.

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) = top-left 

- (1, 1) = bottom-right

- 다양한 좌표로 자유롭게 실험해보아라.

- 0보다 작거나 1보다 큰 좌표를 사용하여 addressing modes가 잘 작동하는지 확인해보아라!

- 또한 sampler 생성시 모드를 변경하여 sampler가 어떻게 작동하는지 확인해보아라

 

 

 

 

4. Shaders

- 마지막 단계는 셰이더를 수정하여 texture로부터 색상을 샘플링하도록하는것이다.

- 먼저 vertex shader 를 수정하여 texture coordinates이 fragment shader로 전달될 수 있도록 해야한다.

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;
}

- per vertex colors 처럼 fragTexCoord 값은 rasterizer에 의해 정사각형 영역에 걸쳐 부드럽게 보간될것이다.

- fragment shader가 텍스처 좌표를 색상으로 출력하도록하여 이것을 시각화 할 수 있다.

#version 450

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);
}

- 아래와 같은 이미지를 보게될것이며, 다시 컴파일하는것을 잊지말자!

- green channel 은  horizontal coordinates를 표현하고

- red channel 은  vertical coordinates를 표현한다.

- 모서리의 black 과 yellow를 통해 (0,0) 에서 (1,1)까지 사각형 전체에서의

- 텍스쳐 좌표가 올바르게 보간되었음을 확인할 수 있다. 

- 색상을 사용하여 데이터를 시각화하는것은 더나은 옵션이 없기 때문에

- 셰이더 프로그래밍에서 printf를 사용한 디버깅과 동일한 디버깅이다.

 

 

- combined image sampler descriptor 는 GLSL에서 sampler uniform으로 표현된다.

- fragment shader에서 다음과 같은 참조를 추가하자.

layout(binding = 1) uniform sampler2D texSampler;

- 다른 타입의 이미지는 sampler1Dsampler3D 를 사용하여 올바른 바인딩을 해야한다.

void main() {
    outColor = texture(texSampler, fragTexCoord);
}

- 텍스처는 내장 함수 texture 를 사용하여 샘플링된다.

- 이 함수는 sampler 와 좌표를 인자로 받는다.

- sampler 는 자동적으로  filtering 과 transformations 를 처리한다.

- 이제 응용프로그램을 실행할때 아래와 같이 사각형에 텍스처가 표시되어야한다.

- 텍스처 좌표를 1보다 큰값으로 스케일링하여 addressing mode들을 실험해보아라

- 예를들어 VK_SAMPLER_ADDRESS_MODE_REPEAT 를 사용하였을때 아래와 같은 결과를 볼 수 있다.

void main() {
    outColor = texture(texSampler, fragTexCoord * 2.0);
}

- 또한 vertex colors을 사용함으로써 texture colors를 조작할 수 있다.

void main() {
    outColor = vec4(fragColor * texture(texSampler, fragTexCoord).rgb, 1.0);
}

- 알파 채널의 크기를 조정하지 않기 위해 여기에서 RGB와 알파 채널을 분리하였다.

 

- 이제 셰이더에서 이미지를 접근하는 방법을 알게되었다.  

- 이것은 framebuffers에서도 기록되는 이미지와 결합될 때 매우 강력한 기술이다.

   (This is a very powerful technique when combined with images that are also written to in framebuffers)

- 이러한 이미지를 입력으로 사용하여 3D 세계내에서

- post-processing 과 camera displays 과 같은 멋진 효과를 구현할 수 있다.

  (스왑체인 이미지를 텍스처로 사용하고 후처리 효과를 적용할 수 도 있다.

 

 

댓글들

-Robert Chisholm
Had to remember to enable blend in `VKPipelineColorBlendAttachmentState` to see transparency within the texture.

 

 

C++ code / Vertex shader / Fragment shader

 

https://vulkan-tutorial.com/en/Texture_mapping/Combined_image_sampler