본문 바로가기

Vulkan

Index buffer


이전 글 : Staging buffer

다음 글 : 행렬



Index buffer
인덱스 버퍼, 색인 버퍼


Introduction


실제 응용 프로그램에서 렌더링 할 3D 메쉬는 종종 여러 삼각형 사이에서 정점을 공유합니다. 이것은 직사각형 그리기와 같은 간단한 작업에서도 찾아 볼 수 있습니다.


사각형을 그리려면 두 개의 삼각형이 필요합니다. 즉 6 개의 정점이 있는 정점 버퍼가 필요합니다. 문제는 두 개의 정점의 데이터가 중복되어 50 %의 중복성이 발생하게 된다는 것 입니다 . 문제는 정점이 평균 3 개의 삼각형으로 사용 되는것 보다는 복잡한 메시로 된다는 것 이 더 큰 문제 입니다. 이 문제에 대한 해결책은 인덱스 버퍼를 사용하는 것입니다.


인덱스 버퍼는 본질적으로 정점 버퍼에 대한 포인터의 배열입니다. 이를 통해 정점 데이터를 재정렬하고 여러 정점에 대해 기존 데이터를 재사용 할 수 있습니다. 위의 그림은 4 개의 고유 정점 각각을 포함하는 정점 버퍼가 있는 경우 인덱스 버퍼가 사각형에 대해 어떻게 보이는지 보여줍니다. 처음 세 개의 인덱스는 오른쪽 위 삼각형을 정의하고 마지막 세 개의 인덱스는 왼쪽 아래 삼각형의 꼭지점을 정의합니다.



Index buffer creation

정점 버퍼의 생성


이 장에서는 정점 데이터를 수정하고 인덱스 데이터를 추가하여 그림과 같은 직사각형을 그립니다. 네 모서리를 나타 내기 위해 정점 데이터를 수정합니다.


const std::vector<Vertex> vertices = {
   {{-0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}},
   {{0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}},
   {{0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}},
   {{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}}
};


왼쪽 위 모서리가 빨간색이고 오른쪽 상단이 녹색이고 오른쪽 하단이 파란색이고 왼쪽 하단이 흰색입니다. 새로운 배열 인덱스를 추가하여 인덱스 버퍼의 내용을 나타냅니다. 오른쪽 상단의 삼각형과 왼쪽 하단의 삼각형을 그리려면 그림의 색인과 일치해야합니다.


const std::vector<uint16_t> indices = {
   0, 1, 2, 2, 3, 0
};


정점의 항목 수에 따라 인덱스 버퍼에 uint16_t 또는 uint32_t를 사용할 수 있습니다. 65535 개 미만의 고유 정점을 사용하기 때문에 지금은 uint16_t를 사용 할 수 있습니다.


정점 데이터와 마찬가지로 인덱스는 GPU가 액세스 할 수 있도록 VkBuffer 에 업로드해야 합니다. 인덱스 버퍼에 대한 리소스를 보유 할 두 개의 새 클래스 멤버를 정의합니다.

VkBuffer vertexBuffer;
VkDeviceMemory vertexBufferMemory;
VkBuffer indexBuffer;
VkDeviceMemory indexBufferMemory;


이제 우리가 추가 할 createIndexBuffer 함수는 createVertexBuffer와 거의 동일합니다.


void initVulkan() {
   ...
   createVertexBuffer();
   createIndexBuffer();
   ...
}

void createIndexBuffer() {
   VkDeviceSize bufferSize = sizeof(indices[0]) * indices.size();

   VkBuffer stagingBuffer;
   VkDeviceMemory stagingBufferMemory;
   createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, stagingBuffer, stagingBufferMemory);

   void* data;
   vkMapMemory(device, stagingBufferMemory, 0, bufferSize, 0, &data);
   memcpy(data, indices.data(), (size_t) bufferSize);
   vkUnmapMemory(device, stagingBufferMemory);

   createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, indexBuffer, indexBufferMemory);

   copyBuffer(stagingBuffer, indexBuffer, bufferSize);

   vkDestroyBuffer(device, stagingBuffer, nullptr);
   vkFreeMemory(device, stagingBufferMemory, nullptr);
}


주목할만한 차이점은 두 가지뿐입니다.

bufferSize는 인덱스 유형의 크기 인 uint16_t 또는 uint32_t의 크기에 해당하는 인덱스 수와 같습니다.

indexBuffer의 사용법은 VK_BUFFER_USAGE_VERTEX_BUFFER_BIT 대신 VK_BUFFER_USAGE_INDEX_BUFFER_BIT 가 되어야 합니다. 그 외에는 프로세스가 완전히 동일합니다. 인덱스의 내용을 복사하여 최종 장치 로컬 인덱스 버퍼에 복사하는 준비 버퍼를 만듭니다.


인덱스 버퍼는 정점 버퍼처럼 프로그램 끝에서 제거 해야합니다.


void cleanup() {
   cleanupSwapChain();

   vkDestroyBuffer(device, indexBuffer, nullptr);
   vkFreeMemory(device, indexBufferMemory, nullptr);

   vkDestroyBuffer(device, vertexBuffer, nullptr);
   vkFreeMemory(device, vertexBufferMemory, nullptr);

   ...
}




Using an index buffer

인덱스 버퍼의 사용


그리기를 위해 인덱스 버퍼를 사용하는 것은 createCommandBuffers 에 대한 두 가지 변경을 포함합니다. 꼭 인덱스 버퍼를 바인드 할 필요가 있습니다. 차이점은 단일 인덱스 버퍼 만 가질 수 있다는 것입니다. 불행히도 각 꼭지점 특성에 대해 서로 다른 인덱스를 사용할 수 없기 때문에 하나의 특성 만 다를지라도 반드시 필요한 데이터를 복제해야 합니다.


vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, vertexBuffers, offsets);

vkCmdBindIndexBuffer(commandBuffers[i], indexBuffer, 0, VK_INDEX_TYPE_UINT16);


인덱스 버퍼는 vkCmdBindIndexBuffer 로 바인드되며, 인덱스 버퍼, 바이트 오프셋, 인덱스 데이터 유형이 매개 변수로 있습니다. 앞서 언급 했듯이 가능한 유형은 VK_INDEX_TYPE_UINT16 및 VK_INDEX_TYPE_UINT32 입니다.


인덱스 버퍼를 바인딩하는 것만으로는 아직 아무런 변화가 없으며 Vulkan이 인덱스 버퍼를 사용하도록 명령하기 위해 그리기 명령을 변경해야 합니다. vkCmdDraw 행을 제거하고 vkCmdDrawIndexed 로 바꾸십시오.


vkCmdDrawIndexed(commandBuffers[i], static_cast<uint32_t>(indices.size()), 1, 0, 0, 0);


이 함수에 대한 호출은 vkCmdDraw 와 매우 유사합니다. 처음 두 매개 변수는 인덱스 수와 인스턴스 수를 지정합니다. 인스턴싱을 사용하지 않으므로 인스턴스를 1 개만 지정하십시오. 인덱스의 수는, 정점 버퍼에 건네지는 정점의 수를 나타냅니다. 다음 매개 변수는 인덱스 버퍼에 대한 오프셋을 지정합니다. 값 1을 사용하면 그래픽 카드가 두 번째 인덱스에서 읽기 시작합니다. 두 번째 - 마지막 매개 변수는 인덱스 버퍼의 인덱스에 추가 할 오프셋을 지정합니다. final 매개 변수는 인스턴스화를위한 오프셋을 지정합니다. 우리는 사용하지 않습니다.


이제 프로그램을 실행하면 다음과 같이 표시됩니다.



이제 인덱스 버퍼를 사용하여 정점을 다시 사용하는 방법으로 메모리를 절약하는 방법을 알았습니다. 이것은 복잡한 3D 모델을 로드 할 장에서 특히 중요하게 될 것입니다.


이전 장에서는 이미 단일 메모리 할당에서 버퍼와 같은 여러 리소스를 할당해야 한다고 말했지만 사실은 한 걸음 더 나아가야 합니다. 드라이버 개발자는 버텍스 및 인덱스 버퍼와 같은 여러 버퍼를 단일 VkBuffer 에 저장하고 vkCmdBindVertexBuffers 와 같은 명령에서 오프셋을 사용하는 것이 좋습니다. 장점은 데이터가 더 가깝기 때문에 데이터가 캐시 친화적 인 것입니다. 물론 동일한 렌더링 작업 동안 사용되지 않으면 여러 리소스에 동일한 메모리 덩어리를 재사용 할 수도 있습니다. 데이터가 새로 고쳐지는 경우라면 마찬가지입니다. 이것은 aliasing 이라고 알려져 있으며 일부 Vulkan 함수에는이를 수행하도록 명시하는 명시 적 플래그가 있습니다.


C++ code / Vertex shader / Fragment shader


이전 글 : Staging buffer

다음 글 : 행렬



'Vulkan' 카테고리의 다른 글

Descriptor layout and buffer  (0) 2018.02.05
행렬  (0) 2018.02.05
Staging buffer  (0) 2018.01.25
Vertex buffer creation  (0) 2018.01.25
Vertex input description  (0) 2018.01.25