Vulkan - 6. 깊이와 스텐실 버퍼

2022. 6. 21. 15:20Vulkan

 

개요

그래픽 프로그램에서, 물체의 앞뒤를 가리는 깊이 테스트는 폴리곤으로 논리적 세계를 만드는 데에 아주 중요한 도구입니다. 스텐실 테스트는 사용하기에 따라 유용한 도구가 될 수 있습니다. 현재 우리의 벌칸 프로그램에서는 프레임버퍼가 색 첨부물 이미지 뷰만 참조하고 있었죠. 이를 확장하여 파이프라인에서 깊이/스텐실 테스트를 할 수 있게 만들어 봅시다.

 

목차

1. 버퍼를 위한 이미지 뷰

2. 이미지를 깊이 버퍼로 사용하기

3. 이미지를 스텐실 버퍼로 사용하기

요약

과제

 

본문

1. 버퍼를 위한 이미지 뷰

잠시 이전의 그림을 떠올려 봅시다.

 

우리는 왜 여러 개의 프레임버퍼를 만들어 스왑체인에서 받아온 이미지 번호와 맞춰서 사용했던가요? 더블/트리플 버퍼링을 위해서였습니다. 요컨대 보여줄 이미지는 보여주고 새로 그리는 건 다른 이미지에 하는 겁니다. 위의 그림을 보면 프레임버퍼가 스왑체인에서 온 뷰 말고 색이 다른 뷰 하나를 또 가리키고 있네요. 저것이 바로 깊이/스텐실 버퍼로 사용되는 뷰입니다. 이것은 그림에서 보시는 것처럼 파이프라인(서브패스)당 한 개면 충분합니다. 종이 위에 동전을 올려 놓고 색칠하고 어머니한테 보여주는 동안 그 동전으로 다시 똑같은 걸 만들 수 있잖아요. 종이는 새로 쓰고요.

동전 프로타주. 깊이 버퍼는 동전이다.

굳이 표현하자면 깊이 버퍼가 동전을 밑에 놓고 그리는 거라면 스텐실 버퍼는 동전을 위에 놓고 외곽선을 따라 그리는 거겠죠. 원리는 전혀 다르긴 해도 아무튼 깊이/스텐실 버퍼가 파이프라인당 하나면 충분한 건 알겠습니다. 중요한 건 보여주고 있냐 아니냐, 그리고 어차피 보여줘야 할 건 각 픽셀의 색뿐이다 이렇게 둘입니다.

 

깊이와 스텐실 버퍼를 위한 첨부물 이미지는 GL 때랑 비슷한 느낌으로 생성할 수 있습니다. 깊이 버퍼 이미지를 생성하고 장치 메모리를 할당하고 메모리와 이미지 객체를 바인드하고 뷰를 얻어오면 첨부물로 사용할 수 있게 될 겁니다. 사실 '형식 선택'이 복잡해서 그렇지 앞의 정점 버퍼, 인덱스 버퍼랑 똑같죠? 코드를 만들어 봅시다.

 

더보기

색 버퍼를 위해 스왑체인에서 이미지를 받아 온 건 받아 온 거고, 이번에는 이미지를 직접 만들어야 합니다. 해상도는 스왑체인 해상도와 맞춰야 하므로 스왑체인 생성 후에 할 수 있겠죠. 해제 역시 스왑체인이 해제되면서 같이 해제됐던 색 첨부물과 다르게 직접 해제해야 하니, 핸들을 멤버로 갖고 있어야 합니다. 이미지와 뷰를 위한 멤버를 생성해 줍시다.

// VkPlayer.h
static VkImage dsImage;
static VkImageView dsImageView;
static VkDeviceMemory dsmem;

static bool createDSBuffer();
static void destroyDSBuffer();

저의 경우 생성 호출은 스왑체인 이미지 뷰 생성 직후에 하게 했습니다. 해제는 그 역순이고요.

// VkPlayer::init
...
&& createSwapchainImageViews()
&& createDSBuffer()
...

이미지 생성은 여느 때처럼 VkImageCreateInfo 구조체를 만들어야 합니다. 일단 이 정도 구성해 봅시다.

bool VkPlayer::createDSBuffer() {
    VkImageCreateInfo imgInfo{};
    imgInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
    imgInfo.extent.width = swapchainExtent.width;
    imgInfo.extent.height = swapchainExtent.height;
    imgInfo.extent.depth = 1;
    imgInfo.arrayLayers = 1;
    imgInfo.imageType = VK_IMAGE_TYPE_2D;
    imgInfo.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
    imgInfo.mipLevels = 1;
    imgInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
    imgInfo.samples = VK_SAMPLE_COUNT_1_BIT;
    imgInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
}
  • 이미지는 최대 3차원으로 생성할 수 있으며, 대부분의 경우 2D 이미지를 사용하겠죠. 그게 sType 뒤 4개 필드의 의미입니다.
  • usage는 깊이/스텐실 용도이므로 저렇게 해 주면 되는데, COLOR_ATTACHMENT_BIT은 단독으로 존재했었지만 DEPTH_STENCIL은 엮여서만 있습니다. 궁금한 게 있으면 문서를 참고하세요.
  • 실시간으로 작성되는 이미지들은 밉 수준이 필요 없으니 mipLevels는 1을 줍니다.
  • arrayLayers는 1을 줍니다. 의미상 extent.depth랑 정확히 뭐가 다른지는 모르겠네요. 특별히 다른 게 있으면 추가하겠습니다. 확인 계층에서는 depth는 2D일 때 1이어야 하지만 arrayLayers는 그렇지 않은 걸 보면 다른 뜻이 없진 않은 모양입니다.
  • 보통 깊이 버퍼는 시작 시 클리어하므로 initialLayout은 UNDEFINED로 둡니다. 
  • samples는 멀티샘플링을 할 거면 정할 수 있지만, 기본적으로 1을 주면 됩니다. 색 버퍼에 멀티샘플링을 하면 깊이 버퍼도 멀티샘플링을 해야 할 걸요.
  • sharingMode는 깊이/스텐실의 경우 EXCLUSIVE 외에 줄 이유가 없을 겁니다. 그래서 queueFamilyIndex도 줄 필요가 없습니다.

이제 format, tiling 필드만 주면 됩니다. format은 앞서 보았던 R8G8B8A8 이런 거랑 같은 뜻인데 tiling은 처음 보는 말이죠. 이는 상대적으로 작은 기기에서 화면의 일부 단위로 교체하는 타일링이 아니라 텍셀의 배열을 말하는 겁니다. VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_TILING_LINEAR 옵션이 가능한데, 웬만하면 OPTIMAL을 써야겠죠? 그러려면 포맷과 타일링에 대하여 지원 여부를 확인해야 합니다. format에 대한 feature를 확인해야 하는데, 여기서 볼 건 VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT뿐입니다. 나머지는 문서를 참고하세요. 이렇게 하면 되겠습니다.

VkFormat imgFormat = VK_FORMAT_D32_SFLOAT;
VkImageTiling imgTiling = VK_IMAGE_TILING_LINEAR;
VkFormatProperties props;
vkGetPhysicalDeviceFormatProperties(physicalDevice.card, VK_FORMAT_D24_UNORM_S8_UINT, &props);
if (props.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) {
    imgFormat = VK_FORMAT_D24_UNORM_S8_UINT;
    imgTiling = VK_IMAGE_TILING_OPTIMAL;
}
else if (props.linearTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) {
    imgFormat = VK_FORMAT_D24_UNORM_S8_UINT;
    imgTiling = VK_IMAGE_TILING_LINEAR;
}

아마 벌칸 dll로 그래픽스 프로그램이 돌아갈 수 있는 컴퓨터라면 저 둘 중 하나는 되지 않을까 싶습니다. 이미지 형식은 타일링입니다. 저기서 얻은 값을 format과 tiling 멤버에 주면 됩니다.

imgInfo.format = imgFormat;
imgInfo.tiling = imgTiling;
if (vkCreateImage(device, &imgInfo, nullptr, &dsImage) != VK_SUCCESS) {
    fprintf(stderr, "Failed to create depth/stencil buffer image\n");
    return false;
}

이미지를 위한 GPU 메모리 공간을 줘야겠죠. 이건 버퍼 때랑 똑같으니 설명은 생략합니다.

VkMemoryRequirements mreq;
vkGetImageMemoryRequirements(device, dsImage, &mreq);
VkMemoryAllocateInfo memInfo{};
memInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
memInfo.allocationSize = mreq.size;
memInfo.memoryTypeIndex = findMemorytype(mreq.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, physicalDevice.card);
if (vkAllocateMemory(device, &memInfo, nullptr, &dsmem) != VK_SUCCESS) {
    fprintf(stderr, "Failed to allocate depth/stencil buffer image memory\n");
    return false;
}
if (vkBindImageMemory(device, dsImage, dsmem, 0) != VK_SUCCESS) {
    fprintf(stderr, "Failed to bind depth/stencil buffer image - memory\n");
    return false;
}

이미지 뷰는 이렇게 만들면 됩니다. 이전의 스왑체인의 이미지 뷰를 만들 때랑 같습니다.

VkImageViewCreateInfo viewInfo{};
viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
viewInfo.image = dsImage;
viewInfo.format = imgInfo.format;
viewInfo.components.r = viewInfo.components.g = viewInfo.components.b = viewInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
viewInfo.subresourceRange.baseMipLevel = 0;
viewInfo.subresourceRange.levelCount = 1;
viewInfo.subresourceRange.baseArrayLayer = 0;
viewInfo.subresourceRange.layerCount = 1;
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;

if (vkCreateImageView(device, &viewInfo, nullptr, &dsImageView) != VK_SUCCESS) {
    fprintf(stderr, "Failed to create depth/stencil buffer image view\n");
    return false;
}
return true;

해제도 이렇게 해 주세요.

void VkPlayer::destroyDSBuffer() {
    vkDestroyImageView(device, dsImageView, nullptr);
    vkFreeMemory(device, dsmem, nullptr);
    vkDestroyImage(device, dsImage, nullptr);
}

 

 

컴파일하고 실행해서 제대로 만들어지긴 했는지 확인해 봅시다. 전체 코드는 여기 있습니다.

 

2. 이미지를 깊이 버퍼로 사용하기

여기까지 공부를 알차게 했다면 아마 프레임버퍼에서 스왑 체인에서처럼 뷰를 참조하게 하고 파이프라인에 깊이(스텐실) 버퍼 사양을 명시하면 될 거라고 예상해 볼 수 있습니다. 코드로 구현해 봅시다.

 

더보기
방금 생성한 이미지 포맷 정보도 여기에 전달해야 하는데, 고정값이 아니다 보니 멤버로 저장해야겠습니다.
// VkPlayer.h
static VkFormat dsImageFormat;

// VkPlayer::createDSBuffer
dsImageFormat = imgInfo.format; // 형식이 정해지면 멤버에 넣어 줍니다.

createRenderPass0 함수에 오랜만에 돌아가 보면 색 버퍼의 사양이 설명되어 있죠. 그쪽에서 깊이/스텐실 버퍼도 기술해야 합니다. 우선 확장성 좋게 배열로 바꿔 줍시다.

// VkPlayer::createRenderPass0
VkAttachmentDescription attachments[2]{};
VkAttachmentDescription& colorAttachment = attachments[0];
VkAttachmentDescription& depthAttachment = attachments[1];
... (colorAttachment 구성한 부분)
depthAttachment.format = dsImageFormat;
depthAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
depthAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
depthAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
depthAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;

여기서 색 때랑 다른 건 STORE_OP도 신경 쓰지 않는다고 한 점입니다. 보여주는 데 유지해야 할 건 색 버퍼뿐이죠? 이것의 기준은 서브패스의 시작과 끝이니까, 예컨대 그림자 맵 같은 걸 쓸 땐 보존을 하게 해야 합니다. 스텐실은 아래에서 확인해 보자구요.

그 다음 색 첨부물에서 했던 것처럼 서브패스에서 위와 같은 사양을 참조하게 합니다. 렌더패스 생성 정보에서 pAttachments의 인덱스 1번을 참조하는 겁니다.

VkAttachmentReference depthAttachmentRef{};
depthAttachmentRef.attachment = 1;
depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;

그 다음 서브패스에서 이를 참조하게 하면 되겠습니다. 아래와 같이 서브패스 하나는 깊이/스텐실 첨부물 한 개만 쓸 수 있습니다. 형식을 어떻게 하든 깊이 버퍼와 스텐실 버퍼를 모두 사용하려면 첨부물 한 개에 다 해야 하나 봅니다.

VkSubpassDescription subpass{};
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpass.colorAttachmentCount = 1;
subpass.pColorAttachments = &colorAttachmentRef;
subpass.pDepthStencilAttachment = &depthAttachmentRef;

마지막으로 첨부물 기술자를 렌더패스 생성에 둘 다 포함하게 하면 되겠죠.

...
VkRenderPassCreateInfo info{};
info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
info.attachmentCount = sizeof(attachments) / sizeof(attachments[0]);
info.pAttachments = attachments;
...

이제 프레임버퍼 생성 시에 방금 만든 이미지 뷰를 첨부물로 넘깁니다.

// VkPlayer::createEndFramebuffers
... (createinfo의 나머지 정의하던 부분)

VkImageView attachments[] = { nullptr,dsImageView };
for (size_t i = 0; i < swapchainImageViews.size(); i++) {
    attachments[0] = swapchainImageViews[i];
    info.attachmentCount = sizeof(attachments) / sizeof(attachments[0]);
    info.pAttachments = attachments;
    if (vkCreateFramebuffer(device, &info, nullptr, &endFramebuffers[i]) != VK_SUCCESS) {
        fprintf(stderr, "Failed to create framebuffer\n");
        return false;
	}
}
return true;

그 다음 깊이, 스텐실 클리어 값을 줘야 합니다. 색 버퍼 클리어 값을 주기 위해 썼던 VkClearValue는 공용체로, 아래와 같은 형태를 가집니다. 그래서 전에는 그냥 배열인 줄 알고 썼지만, 같은 타입에 초기화 리스트로 float값과 stencil값을 따로 주고 배열에 넣어 주세요.

typedef union VkClearColorValue {
    float       float32[4];
    int32_t     int32[4];
    uint32_t    uint32[4];
} VkClearColorValue;

typedef struct VkClearDepthStencilValue {
    float       depth;
    uint32_t    stencil;
} VkClearDepthStencilValue;

typedef union VkClearValue {
    VkClearColorValue           color;
    VkClearDepthStencilValue    depthStencil;
} VkClearValue;

코드는 이렇게 하면 되겠습니다.

// VkPlayer::fixedDraw
... VkRenderPassBeginInfo를 만드는 부분.
VkClearValue clearColor = { 0.03f,0.03f,0.03f,1.0f };
VkClearValue clearDepthStencil = { 1.0f, 0 };
VkClearValue clearValue[] = { clearColor, clearDepthStencil };
rpbegin.clearValueCount = sizeof(clearValue) / sizeof(clearValue[0]);
rpbegin.pClearValues = clearValue;
...

이제 파이프라인에서 깊이 버퍼링을 가능하게 만들면 되겠습니다. 아래에서 보실 옵션은 사실 꽤 익숙하게 느껴질 것 같네요. 더 많은 옵션은 최소한 지금은 다루지 않겠습니다. 일단은 0으로 두고, 다른 것은 문서를 참고하세요.

// VkPlayer::createPipeline0
...
VkPipelineDepthStencilStateCreateInfo depthStencilInfo{};
depthStencilInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
depthStencilInfo.depthCompareOp = VK_COMPARE_OP_LESS;
depthStencilInfo.depthTestEnable = VK_TRUE;
depthStencilInfo.depthWriteEnable = VK_TRUE;
...

depthWriteEnable 정도만 짚고 넘어가자면, 이것도 1.0에서는 동적으로 바꿀 수 있는 녀석이 아닙니다. (1.3버전부터 가능합니다. 다만 1.3 버전을 쓰게 하면 쓸 수 있는 기기 수가 더 줄어들 가능성이 있다고 봅니다.) 반투명 객체가 아예 깊이 버퍼를 갱신하지 않게 만들려면, 별도의 파이프라인을 바인드해야 하겠죠.

 

이제 컴파일하고 실행하면 특별히 오류 메시지 같은 게 안 떠야 합니다.

하지만 깊이 버퍼를 활성화시켰으니, 잘 작동하는지 확인해야겠죠. 정점 버퍼를 조금 확장해서 더 큰 것을 한 번 더 그리게 해 봅시다. 아직 (동적) 공유 버퍼 하나를 가지고 여러 객체에 대하여 참조하게 하는 걸 안 배웠으니 일단 정점 버퍼 데이터 부분을 이렇게 고쳐 줍니다. Z좌표가 0.9로, CVV의 far면에 더 가깝게 했습니다.

// VkPlayer::createFixedVertexBuffer
Vertex ar[]{
    {{-0.5,0.5,0},{1,0,0}}, // 좌하: 기본 적색
    {{0.5,0.5,0},{0,1,0}},  // 우하: 기본 녹색
    {{0.5,-0.5,0},{0,0,1}}, // 우상: 기본 청색
    {{-0.5,-0.5,0},{1,1,1}}, // 좌상: 기본 백색

    {{-1,1,0.9},{1,1,1}},
    {{1,1,0.9},{1,1,1}},
    {{1,-1,0.9},{1,1,1}},
    {{-1,-1,0.9},{1,1,1}}
};

그 다음 그리기 부분에 이렇게 하나를 더 추가해 줍시다. 뒤에서 2번째 것은 정점 인덱스가 정점 배열의 몇 번째부터 가리키는 건지라고 했었죠.

vkCmdDrawIndexed(commandBuffers[commandBufferNumber], 6, 1, 0, 0, 0);
vkCmdDrawIndexed(commandBuffers[commandBufferNumber], 6, 1, 0, 4, 0); // 새로 추가됨

결과는 이렇습니다. LESS여야 통과하니까 당연히 앞서 그린 알록달록한 녀석은 여전히 보이고 있습니다.

깊이 테스트를 꺼 봅시다.

depthStencilInfo.depthTestEnable = VK_FALSE;

 

더 나중에 그려진 흰 사각형이 알록달록한 녀석을 가려 버렸죠. 깊이 테스트가 의도한 대로 작동하는 모양입니다.

여기까지의 코드는 여기에서 확인해 보세요.

 

질문이 있다. GL에서는 early depth test라는, 래스터화와 조각 셰이더 사이에서 깊이 테스트를 하는 것을 암시적으로 지원했었지? 조각 셰이더에서 깊이 값을 안 바꾸게 돼 있다면 그런 식으로 해 주는 걸로 알고 있는데, 벌칸은 아마 이걸 명시적으로 할 거 아냐? 정확히 내가 뭘 해야 할까?
GL 사양 상으로는 깊이와 스텐실, 가위 등의 테스트는 조각 셰이더 이후에 이뤄지지. 이걸 조각 셰이더 이전으로 돌리려면 하드웨어에서 최적화를 위해 암시적으로 지원되거나, 4.2 이상 버전 혹은 확장을 통해 지원하도록 명시할 수 있어. 여길 참고하자. 벌칸에서도 비슷한 방법일 거라 하는군.
저 "layout(early_fragment_tests) in;"을 쓰면 조각 셰이더에서 깊이 값을 정하는 것은 다 무효화되고, 조각을 discard하는 경우에도 깊이, 스텐실 값은 업데이트가 될 거라는 사실은 주의하라고. 이건 그냥 읽어 보는 것도 좋겠네.

 

3. 이미지를 스텐실 버퍼로 사용하기

이미지 뷰까지 준비가 다 돼 있기 때문에 스텐실 버퍼를 쓰는 것도 그대로 하기만 하면 됩니다. 스텐실 연산은 다양하게 사용할 수 있는데, 여기서는 위에서처럼 단순히 작동 확인을 위해 흰 정사각형에 대한 조각이 스텐실 테스트를 통과하지 못하게 해 봅시다. 스텐실 테스트는 깊이 테스트보다 먼저 일어나죠. (그래서 값 업데이트 방식이 스텐실 탈락 / 스텐실 통과, 깊이 탈락 / 스텐실, 깊이 통과 이렇게 3종류로 나뉘어 있잖아요.) 그래서 이번에는 위의 테스트 코드에서 알록달록이의 z좌표를 더 크게 두겠습니다. 현 상태에서는 하얀색 정사각형만 돌아가고 있는 게 정상입니다.

// VkPlayer::createFixedVertexBuffer
Vertex ar[]{
    {{-0.5,0.5,0.99},{1,0,0}},
    {{0.5,0.5,0.99},{0,1,0}},
    {{0.5,-0.5,0.99},{0,0,1}},
    {{-0.5,-0.5,0.99},{1,1,1}},

    {{-1,1,0.9},{1,1,1}},
    {{1,1,0.9},{1,1,1}},
    {{1,-1,0.9},{1,1,1}},
    {{-1,-1,0.9},{1,1,1}}
};

스텐실을 활성화하는 코드도 간단합니다. 바로 들어가 봅시다.

 

더보기

먼저 첨부물 기술자를 수정해야겠죠. 깊이 버퍼랑 같은 이유로 같은 내용으로 하면 됩니다.

// VkPlayer::createRenderPass0
...
depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
depthAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
...

그 다음 파이프라인에서 연산을 등록하면 됩니다. stencilTestEnable은 쉽게 알 수 있죠. 그 다음, '앞면'과 '뒷면'에 대하여 별도의 스텐실 연산을 적용하게 할 수 있습니다. 이는 각각 front, back이라는 VkStencilOpState를 통해 지정할 수 있는데요, 지금 back은 거르도록 한 만큼 front만 정해 줍니다.

// VkPlayer::createPipeline0
...
depthStencilInfo.stencilTestEnable = VK_TRUE;
depthStencilInfo.front.compareMask = 0xff;
depthStencilInfo.front.writeMask = 0xff;
depthStencilInfo.front.compareOp = VkCompareOp::VK_COMPARE_OP_EQUAL;
depthStencilInfo.front.failOp = VkStencilOp::VK_STENCIL_OP_KEEP;
depthStencilInfo.front.depthFailOp = VkStencilOp::VK_STENCIL_OP_KEEP;
depthStencilInfo.front.passOp = VkStencilOp::VK_STENCIL_OP_INCREMENT_AND_CLAMP;
depthStencilInfo.front.reference = 0x00;
...

reference는 아시다시피 각 조각이 처음에 가지고 있을 값이라고 볼 수 있습니다. compareMask와 writeMask는 비교하기 전과 쓰기 전, 버퍼의 값과 조각이 갖고 있던 값을 이것과 비트 AND합니다. compareOp는 통과 기준을 알리고, failOp, depthFailOp, passOp는 각각 스텐실 탈락 / 스텐실 통과, 깊이 탈락 / 스텐실, 깊이 통과 시 버퍼의 해당 위치의 값을 어떻게 갱신할지를 담습니다. 여기선 단순히 가장 먼저 그린 놈이 자리 임자라고 하기로 했으니 저렇게 합니다.

 

이 말고 할 일은 없습니다. 스텐실 테스트를 켜거나 꺼서 컴파일하면 각각 알록달록이 보이고/안 보일 겁니다.

여기까지의 코드는 필요한 경우 참고해 보세요.

 

요약

이전에 프레임버퍼에서 색 첨부물은 스왑체인으로부터 이미지 뷰를 받아와서 참조했었는데, 이번에는 한 개의 이미지를 직접 만들어 깊이, 스텐실 버퍼용으로 사용해 보았습니다.

 

그래픽 카드에서 이미지 형식과 타일링 방식에 대하여 깊이/스텐실 용도를 지원하는지를 확인해 보고 나머지 이미지 생성 정보는 채워 넣었었죠. 이후로는 정점 버퍼와 비슷하게 메모리를 할당하고 뷰를 통해 프레임버퍼에서 첨부물로 참조하게 했습니다. 깊이 버퍼와 스텐실 버퍼에 대한 연산은 GL 때랑 쓸 수 있는 내용이 비슷한 수준입니다.

 

과제

생각나면 추가하겠습니다.