2021. 8. 14. 03:24ㆍ그래픽스/vk
1. Introduction
- 3D meshes 은 다수의 삼각형으로구성되며 정점들을 공유한다.
- 이것은 간단한 사각형에서도 알 수 있다.
- 두개의 삼각형으로 사각형을 그려보면, 6개의 정점이 필요하게된다.
- 문제는 두개의 정점이 중복된다는것이고 이것은 50%에 해당하는 수치이다. (4개의 정점중 2개)
- 이 문제에 대한 해결방안은 index buffer를 사용하는것이다.
- index buffer는 본질적으로 정점 버퍼에 대한 포인터 배열이다.
- 이것은 vertex data를 재정렬하는것을 허용해주며
- 존재하는 다수의 정점 데이터를 재사용하게 해준다.
- 위 그림은 정점 4개를 가진 정점버퍼와 인덱스버퍼를 사용하여 사각형을 나타내는 한 예시이다.
- - - - 첫번째 3개의 indices는 오른쪽 위 의 삼각형을 정의하며
- - - - 마지막 3개의 indices는 왼쪽 아래 의 삼각형을 정의한다.
2. Index buffer creation
- 이번 챕터에서는 vertex data를 수정하고 index data를 추가하여 사각형을 그릴것이다. (위 그림처럼)
- 4개의 정점데이터를 가지도록 수정하자
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}}
};
- top left 는 red
- top right 는 green
- bottom right 는 blue
- bottom left 는 white
- 우리는 이제 새로운 배열 indices 를 추가하여 index buffer의 내용물을 표현할것이다.
const std::vector<uint16_t> indices = {
0, 1, 2, 2, 3, 0
};
- 이것은 uint16_t 이나 uint32_t 를 사용할 수 있다.
- index buffer는 vertices항목 수에 의존한다.
- 여기서는 uint16_t 타입을 사용한다 (65535 보다 작은 정점을 가짐)
- 이제 vertex data와 같이 이 indices를 VkBuffer 에 업로드하여 GPU에서 접근가능하게할 필요가 있다.
- 인덱스 버퍼의 리소스를 소유할 두개의 클래스 멤버를 정의하자
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 는 이제 indices 의 수에 indices 타입의 크기(uint16_t or uint32_t)를 곱한것과 같아졌고
- indexBuffer 목적으로 사용하므로 VK_BUFFER_USAGE_INDEX_BUFFER_BIT 를 사용한다
(vertex buffer는 VK_BUFFER_USAGE_VERTEX_BUFFER_BIT)
- 다른 프로세스는 정확하게 같다.
- staging 버퍼에 indices를 복사해오고 그다음에 마지막으로 device local index buffer 로 복사시키는것이다.
- 인덱스 버퍼 또한 프로그램 마지막에 제거하고 메모리를 해제해줘야한다.
void cleanup() {
cleanupSwapChain();
vkDestroyBuffer(device, indexBuffer, nullptr);
vkFreeMemory(device, indexBufferMemory, nullptr);
vkDestroyBuffer(device, vertexBuffer, nullptr);
vkFreeMemory(device, vertexBufferMemory, nullptr);
...
}
3. Using an index buffer
- index 버퍼를 사용하기위해서는 createCommandBuffers 에서 두개의 코드만 포함시키면된다.
- 우리는 첫번째로 index버퍼를 바인딩 시킬 필요가있다. (vertex 버퍼를 사용했던것처럼)
- 차이점은 오직 single indexbuffer만 사용가능하다는것이다.
(vkCmdBindVertexBuffers 함수에는 바인딩할 버퍼의 개수를 지정할 수 있다.)
- 이것은 불행하게도 각각의 vertex attribute에 다른 indices을 사용하는것이 불가능하다.,
- 그래서 여전히 우리는 속성이 하나만 변하더라도 정점 데이터를 완전히 복제해야한다.
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에게 이 인덱스 버퍼를 사용한다고 말하기위해 drawing command 를 변경할 필요가있다.
- vkCmdDraw 함수를 지우고 vkCmdDrawIndexed로 교체해야한다.
vkCmdDrawIndexed(commandBuffers[i], static_cast<uint32_t>(indices.size()), 1, 0, 0, 0);
- 이 함수의 호출은 vkCmdDraw 와 매우 유사하다.
- 파라미터 2, 3은 indices의 개수를 명시하고, instances의 개수를 명시한다.
- - - - indices의 개수는 vertex buffer에 전달될 정점의 수를 나타낸다. (삼각형의 정점의 수, 총 6개)
- - - - instancing을 사용하지 않으므로 그냥 1로 설정해놓는다.
- 파라미터 4 는 index buffer에서의 offset이다.
- - - - 만일 1 로 설정되어있으면, 그래픽 카드가 두번째 인덱스부터 읽기 시작한다
- 파라미터 5는 vertexOffset 이며, 인덱스 버퍼의 인덱스에 더할 오프셋을 지정한다.
- - - - indices[0] + offset , indices[1] + offset ....
- - - - specifies an offset to add to the indices in the index buffer
- - - - the value added to the vertex index before indexing into the vertex buffer.
- 파라미터 6는 첫번째 그릴 instance ID 이다. (offset for instancing)
- 이제 프로그램을 실행해보면 다음과 같은 사각형이 그려질것이다.
- 이제 인덱스 버퍼를 사용하여 정점을 재사용함으로써 메모리를 절약하는 방법을 알게되었다.
- 이것은 향후챕터 3D모델을 로드할때 상당히 중요한것이다.
- 이전 챕터에서 다수의 리소스를 할당된 단일 메모리를 사용한 버퍼로 할당 해야한다고 언급하였다.
(The previous chapter already mentioned that you should allocate multiple resources like buffers from a single memory allocation)
- 그러나 사실 더 해야할게 있다.
- Driver developers은 또한 multiple buffers를 하나의 단일 VkBuffer 로 저장 하는것을 권장한다.
(vertex buffer 와 index buffer를 하나의 버퍼로, 그리고 vkCmdBindVertexBuffers같은 명령어에서 offset을 활용)
- 이경우 이것의 장점은 데이터가 좀더 캐시 친화적이라는것이다 (가까이 모여있으니)
- 이것은 심지어 다수의 리소스를 위한 동일한 chunk of memory를 재사용할 수 있다.
(만일 데이터가 새로 고쳐지는경우 리소스들이 render operations 동안에 사용되지 않으면)
- 이것은 aliasing 으로 알려져있으며,
- 몇몇 Vulkan functions은 명시적인 flags을 통해 이것을 원하는대로 지정할 수있다.
C++ code / Vertex shader / Fragment shader
https://vulkan-tutorial.com/Vertex_buffers/Index_buffer
'그래픽스 > vk' 카테고리의 다른 글
[vk] Uniform buffers - Descriptor pool and sets (0) | 2021.08.16 |
---|---|
[vk] Uniform buffers - Descriptor layout and buffer (0) | 2021.08.16 |
[vk] Vertex buffers - Staging buffer (0) | 2021.08.14 |
[vk] Vertex buffers - Vertex buffer creation (0) | 2021.08.14 |
[vk] Vertex buffers - Vertex input description (0) | 2021.08.14 |