[vk] Texture Mapping - Image view and sampler

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

1. Image view and sampler

- 이번 챕터에서는 우리는 두개의 리소스를 더 만들것이다.

- 이것들은 graphics pipeline에서 필요하며, 한 이미지를 샘플링하는데 필요하다.

- 첫번째 리소스는 swapchain images로 작업하는 동안 이미 본것이지만

- 두번째 리소스는 새로운것이며, 셰이더가 이미지에서 texels을 읽는 방법과 관련이 있다.

 

 

 

 

2. Texture image view

- 이전에 swapchain images 그리고 framebuffer에서 보았듯이

- 이미지들은 직접 접근하지 않고 imageview들을 통해 접근한다.

- 그러므로 texture image를 위한 image view를 생성할 필요가 있다.

 

- texture image를 위한 VkImageView를 보유할 클래스멤버를 추가하고

- 이를 생성하는 새로운 함수 createTextureImageView를 만들것이다.

VkImageView textureImageView;

...

void initVulkan() {
    ...
    createTextureImage();
    createTextureImageView();
    createVertexBuffer();
    ...
}

...

void createTextureImageView() {

}

- 이 함수를 위한 코드는 createImageViews 를 기반으로한다.

- formatimage 만 변경하면 된다.

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;

- viewInfo.components 의 명시적인 초기화를 생략하였다. (VK_COMPONENT_SWIZZLE_IDENTITY)

- 마지막으로  vkCreateImageView를 호출함으로써 image view를 생성할 수 있다.

if (vkCreateImageView(device, &viewInfo, nullptr, &textureImageView) != VK_SUCCESS) {
    throw std::runtime_error("failed to create texture image view!");
}

- 기존의 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;
}

- 이제 createTextureImageView 함수는 간소화되었다.

void createTextureImageView() {
    textureImageView = createImageView(textureImage, VK_FORMAT_R8G8B8A8_SRGB);
}

createImageViews 함수 또한 간소화되었다.

void createImageViews() {
    swapChainImageViews.resize(swapChainImages.size());

    for (uint32_t i = 0; i < swapChainImages.size(); i++) {
        swapChainImageViews[i] = createImageView(swapChainImages[i], swapChainImageFormat);
    }
}

- image view는 확실히 프로그램 종료시 image를 제거하기전에 제거해줘야한다.

void cleanup() {
    cleanupSwapChain();

    vkDestroyImageView(device, textureImageView, nullptr);

    vkDestroyImage(device, textureImage, nullptr);
    vkFreeMemory(device, textureImageMemory, nullptr);

 

 

 

 

3. Samplers

- 셰이더가 이미지에서 직접 텍셀을 읽는것이 가능하지만, 텍스처로 사용되는 경우에는 그리 일반적이지 않음.

- 텍스쳐들은 일반적으로 픽셀에 가정 적합한 색상을 계산하기 위해 

- 데이터를 필터링 및 변환을 적용하는 sampler를 통해 접근되어진다.

   (Textures are usually accessed through samplers,

   which will apply filtering and transformations to compute the final color that is retrieved.)

 

 

 

 

3.1 Oversampling

- 이러한 필터들은 oversampling 과 같은 문제를 처리하는데 도움이 된다.

- texel 보다 fragments이 더 많은 지오메트리에 매핑되는 텍스쳐와 같은 경우

- 각 fragments의 텍스처 좌표에 대해 가장 가까운 texel을 취하기만하면 첫 번째 이미지와 같은 결과를 얻을 수 있다.

- 만일 선형보간을 통해 4개의 가장 가까운 texels을 결합하면, 오른쪽과같은 더 부드러운 결과를 얻게될것이다.

- 물론 응용프로그램에서의 art style 요구사항이 왼쪽 방식을 사용하는게 더 좋을 수 있다. (마인크래프트같은)

- 그러나 오른쪽 방식이 conventional graphics applications에서 더 선호된다.

- sampler object는 texture에서 색상을 읽을 때 자동으로 이 필터링을 적용한다.

 

 

 

 

3.2 Undersampling

- undersampling은 반대로 texels이 fragments 보다 더 많은경우 생기는 문제이다.

- 이것은 예각에서 바둑판 텍스처와 같은 high frequency patterns을 샘플링할때

- 눈에 띄는 artifacts를 발생시킨다.

- 왼쪽 이미지와 같이 텍스처가 멀리서 흐릿한 mess 로 변함.

- 해결법은 이방성 필터링anisotropic filtering을 적용시키는것이며, sampler에의해 자동적으로 적용될 수 도 있다.

 (하지만 더 많은 리소스를 소비한다)

 

- 이러한 필터들 말고도 sampler는 transformations을 처리할 수 있다.

- 이것은 addressing mode를 통해 이미지 외부의 texel을 읽으려고할 때 어떤 일이 발생하는지 결정한다.

- 아래 이미지는 몇가지 가능성을 보여준다. 

 

 

 

3.3 CreateTextureSampler

- 이제 함수 createTextureSampler 를 생성하여 sampler object를 설정하자.

- 나중에 sampler를 사용함으로써, 셰이더에서 texture로부터 색을 읽을 수 있을것이다.

void initVulkan() {
    ...
    createTextureImage();
    createTextureImageView();
    createTextureSampler();
    ...
}

...

void createTextureSampler() {

}

 

3.3.1 VkSamplerCreateInfo

- samplers 은 VkSamplerCreateInfo구조체를 통해 구성되어지며,

- 적용할 수 있는 모든 filters과 transformations를 지정할 수 있다. 

VkSamplerCreateInfo samplerInfo{};
samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
samplerInfo.magFilter = VK_FILTER_LINEAR;
samplerInfo.minFilter = VK_FILTER_LINEAR;

- magFilter : magnified  texels을 보간할 방법을 지정. (확대 필터를 VkFilter 값으로 지정)

- - - - Magnification 은 oversampling 문제와 관련이 있다.

- minFilter :  minified texels을 보간할 방법을 지정. (축소 필터를 지정)

- - - - Minification 은 undersampling 문제와 관련이 있다.

- VK_FILTER_NEARESTVK_FILTER_LINEAR 중에 선택해야 하며, 위에서 본 문제를 해결하기 위한 방법이다.

samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;

- addressMode : addressing mode를 각 축마다 지정할 수 있다.

- - - - 축은 X, Y, Z 대신에 U, V, W 라고 하며, 이는 텍스쳐 공간좌표(texture space)에 대한 규칙이다.

- - - - 사용가능한 값은 아래와 같으며, 대부분은 위의 이미지에 나와있다.

  • VK_SAMPLER_ADDRESS_MODE_REPEAT: Repeat the texture when going beyond the image dimensions.
  • VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT: Like repeat, but inverts the coordinates to mirror the image when going beyond the dimensions.
  • VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE: Take the color of the edge closest to the coordinate beyond the image dimensions.
  • VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE: Like clamp to edge, but instead uses the edge opposite to the closest edge.
  • VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER: Return a solid color when sampling beyond the dimensions of the image.

- 여기서 사용하는 addressing mode는 이미지 외부의 sample을 사용하지 않기때문에 별로 중요하지않지만,

- repeat mode는 바닥과 벽과 같은 질감을 타일링하는데 사용하는 가장 일반적인모드이다.

samplerInfo.anisotropyEnable = VK_TRUE;

VkPhysicalDeviceProperties properties{};
vkGetPhysicalDeviceProperties(physicalDevice, &properties);
samplerInfo.maxAnisotropy = properties.limits.maxSamplerAnisotropy;

- anisotropyEnable : 등방성 필터링(anisotropic filtering) 활성화 여부 (Texel Anisotropic Filtering)

- - - - 성능이 문제가 되지 않는한 이것을 사용하지 않을 이유가 없다.

- maxAnisotropy : 최종 색상을 계산하는데 사용되는 최대 texel samples의 개수를 제한한다.

- - - - anisotropyEnable이 VK_TRUE일때 샘플러에서 사용하는 등방성 값 클램프로 VK_FALSE일때 무시됨

- - - - 낮은 값이면 더 좋은 성능을 얻을 수 있지만, 퀄리티가 낮다.

- - - - 어떤 값을 사용할 수 있는지 알아내려면, 다음과 같이 물리적 장치의 속성을 검색해야한다.

- - - - VkPhysicalDeviceProperties  구조체는 VkPhysicalDeviceLimits 타입인 멤버 limits 를 포함하고 있으며

- - - - limits는 maxAnisotropy 값으로 지정할 수 있는 최대값인 maxSamplerAnisotropy 멤버를 포함하고있다.

- - - - 만일 최고의 퀄리티를 원한다면, 이값을 직접 지정해주면된다.

samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;

- borderColor : border addressingmode로 클램프를 사용하여 이미지 외부를 샘플링할 때 반환되는 색상을 지정한다.

- - - - float나 int 타입인 black, white 그리고 transparent 를 지정할 수 있지만(VkBorderColor)

- - - - 임의의 색상을 지정할 수는 없다.

samplerInfo.unnormalizedCoordinates = VK_FALSE;

- unnormalizedCoordinates : 이미지의 texels을 처리하는데 사용할 좌표계를 지정한다.

- - - - 만일 VK_TRUE 를 사용할 경우 간단하게 [0, texWidth)[0, texHeight) 범위를 사용한다.

- - - - 만일 VK_FALSE 를 사용할 경우 [0, 1) 의 범위를 모든 축에 사용하여 지정된다.

- - - - 실제 응용프로그램은 대부분 항상 normalized coordinates를 사용한다.

- - - - 왜냐하면, 정확히 동일한 좌표로 해상도의 텍스처를 사용할 수 있기 때문이다.

samplerInfo.compareEnable = VK_FALSE;
samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS;

-compareEnable : 만일 comparison function이 활성화되어지면, texels은 먼저 값과 비교되고

- - - - 해당 비교 결과가 필터링 연산에 사용된다.

- - - - 주로 shadow map에서 percentage-closer filtering에 사용됨

- compareOp : 필터링하기전 가져온 데이터에 적용할 comparison function이 VkCompareOp 값으로 지정되어진다.

- 향후 챕터에서 살펴볼것이다.

samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
samplerInfo.mipLodBias = 0.0f;
samplerInfo.minLod = 0.0f;
samplerInfo.maxLod = 0.0f;

mipmapping 에 관련된 필드들로 later chapter에서 다룰것이다.

- mipmapping은 기본적으로 적용할 수 있는 또 다른 타입의 필터이다.

 

 

 

 

3.3.2 vkCreateSampler

- sampler의 기능이 이제 완전히 정의되었다.

- sampler object 핸들을 보유할 클래스맴버를 추가하고  vkCreateSampler함수를 통해 생성하자

VkImageView textureImageView;
VkSampler textureSampler;

...

void createTextureSampler() {
    ...

    if (vkCreateSampler(device, &samplerInfo, nullptr, &textureSampler) != VK_SUCCESS) {
        throw std::runtime_error("failed to create texture sampler!");
    }
}

- sampler를 생성할때 VkImage 에 대한 참조가 어느곳에도 없다.

- sampler는 텍스쳐에서 색상을 추출하는 인터페이스를 제공하는 별개의 객체이다.

- 이것은 차원과 관계없이 사용자가 원하는 모든 이미지에 적용시킬 수 있다. (1D,2D,3D 다 가능)

- 이것은 텍스처 이미지와 필터링을 단일 상태로 결합했던 많은 구 API들과의 차이점이다.

 

 

3.4 vkDestroySampler

- 더이상 이미지에 접근하지않는 프로그램 끝에서 제거해줘야한다.

void cleanup() {
    cleanupSwapChain();

    vkDestroySampler(device, textureSampler, nullptr);
    vkDestroyImageView(device, textureImageView, nullptr);

    ...
}

 

 

 

 

4. Anisotropy device feature

- 만일 지금 프로그램을 실행하게되면 유효성 검사 레이어에서 다음과 같은 메시지를 볼 수 있을 것이다. 

- 이것은 anisotropic filtering이 실제로 선택적인 device feature이기 때문에 발생하는 문제이다.

- 그러므로 createLogicalDevice 함수를 업데이트하여 이 기능을 요구할 필요가 있다.

VkPhysicalDeviceFeatures deviceFeatures{};
deviceFeatures.samplerAnisotropy = VK_TRUE;

- 최신 그래픽 카드가 이를 지원하지 않을 가능성은 매우 낮지만,

- 사용가능한지 확인하기 위해 isDeviceSuitable 함수를 아래와같이 업데이트해줘야한다.

 

bool isDeviceSuitable(VkPhysicalDevice device) {
    ...

    VkPhysicalDeviceFeatures supportedFeatures;
    vkGetPhysicalDeviceFeatures(device, &supportedFeatures);

    return indices.isComplete() && extensionsSupported && swapChainAdequate && supportedFeatures.samplerAnisotropy;
}

- vkGetPhysicalDeviceFeatures 을 통해  VkPhysicalDeviceFeatures 구조체의 용도를 변경하여

- boolean 값을 설정하여 요청하지 않고 지원되는 기능을 나타낼 수 있다.

    (The vkGetPhysicalDeviceFeatures repurposes the VkPhysicalDeviceFeatures struct to indicate which features are supported

    rather than requested by setting the boolean values.)

 

- 등방성 필터링을 지원하는지를 요구하는대신, 단순히 비활성화하여 사용하지 않을 수 도 있다.

    (Instead of enforcing the availability of anisotropic filtering, it's also possible to simply not use it by conditionally setting)

samplerInfo.anisotropyEnable = VK_FALSE;
samplerInfo.maxAnisotropy = 1.0f;

- 다음 챕터에서는 이미지와 샘플러 객체를 셰이더에 노출시켜 정사각형에 텍스처를 그릴것이다.

 

 

 

C++ code / Vertex shader / Fragment shader

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