1.13 인덱스 버퍼
다음 튜토리얼에서 모델 로딩을 구현하기 전에 인덱스 버퍼 개념을 살펴봐야 합니다. 이전에 `triangle-list`와 같은 다양한 삼각형 렌더링 토폴로지를 살펴보았지만, 여러 삼각형이 공유하는 중복 정점이라는 단점을 발견했습니다.
플레이그라운드 실행 - 1_13_indices`triangle-strip`은 메모리 효율이 더 좋지만, 모든 객체를 3D 메시를 수동으로 분할하지 않고 이 토폴로지를 사용하여 쉽게 표현할 수는 없습니다. 예를 들어, 간단한 큐브는 수직 벽에 하나, 위쪽과 아래쪽에 두 개, 총 세 개의 `triangle-strip`이 필요합니다. 찻주전자와 같은 복잡한 3D 모델의 경우 이 분할은 사소하지 않으며, 잠재적으로 수백 또는 수천 개의 조각으로 나뉘어 이상적이지 않을 수 있습니다.
인덱스 버퍼는 대안적인 솔루션을 제공합니다. 개념은 간단합니다:
연결성에 관계없이 고유한 정점 풀을 유지합니다.
정점 연결성을 정의하는, 정점 풀에 대한 인덱스를 저장하는 인덱스 버퍼를 도입합니다.
정점 데이터(좌표, 색상, 텍스처 좌표)를 중복하는 대신, 공유 정점에 대한 중복 인덱스만 저장합니다.
이 접근 방식은 중복되는 것이 인덱스뿐이므로 상당한 저장 공간을 절약합니다. 인덱스는 단순히 정수이기 때문입니다. 인덱스 버퍼는 메모리 절약을 위한 더 일반적인 솔루션이기도 합니다.
인덱스 버퍼를 구현해 봅시다. 셰이더 코드는 그래픽 파이프라인이 토폴로지 및 정점 처리를 담당하므로 변경되지 않습니다.
const positions = new Float32Array([
-100.0, 100.0, 0.0,
-100.0, 100.0, 200.0,
100.0, 100.0, 0.0,
100.0, 100.0, 200.0,
100.0, -100.0, 0.0,
100.0, -100.0, 200.0,
-100.0, -100.0, 0.0,
-100.0, -100.0, 200.0,
]);
const positionBuffer = createGPUBuffer(device, positions, GPUBufferUsage.VERTEX);
큐브의 8개 고유 정점을 모두 포함하는 정점 풀을 정의합니다. 인덱스 버퍼가 정점 토폴로지를 처리합니다.
const indices = new Uint16Array([0, 1, 2, 3, 4, 5, 6, 7, 0, 1]);
const indexBuffer = createGPUBuffer(device, indices, GPUBufferUsage.INDEX);
const indices2 = new Uint16Array([3, 1, 5, 7]);
const index2Buffer = createGPUBuffer(device, indices2, GPUBufferUsage.INDEX);
두 개의 인덱스 버퍼를 생성합니다: 하나는 수직 벽(단일 `triangle-strip`)용이고 다른 하나는 큐브의 바닥(큐브는 열린 뚜껑을 가짐)용입니다. 16비트로도 큐브와 같은 간단한 기하학에 충분하므로 인덱스 버퍼에는 `Uint16Array`를 사용합니다.
primitive: {
topology: 'triangle-strip',
stripIndexFormat: 'uint16',
frontFace: 'ccw',
cullMode: 'none'
},
파이프라인 정의에서 토폴로지를 `triangle-strip`으로 지정하고, 그리기 위해 인덱스 버퍼를 사용함을 나타내기 위해 `stripIndexFormat`을 `uint16`으로 설정합니다.
passEncoder = commandEncoder.beginRenderPass(renderPassDesc);
passEncoder.setViewport(0, 0, canvas.width, canvas.height, 0, 1);
passEncoder.setPipeline(pipeline);
passEncoder.setBindGroup(0, uniformBindGroup);
passEncoder.setVertexBuffer(0, positionBuffer);
passEncoder.setIndexBuffer(indexBuffer, 'uint16');
passEncoder.drawIndexed(10);
passEncoder.setIndexBuffer(index2Buffer, 'uint16');
passEncoder.drawIndexed(4);
passEncoder.end();
그리기 명령을 실행하려면 먼저 정점 풀을 제공하는 정점 버퍼(`positionBuffer`)를 설정합니다. 각 그리기 호출에서 `setIndexBuffer`를 사용하여 인덱스 버퍼를 지정하고 렌더링할 인덱스 수와 함께 `drawIndexed`를 호출합니다. GPU 파이프라인은 인덱스 버퍼를 사용하여 정점 인덱스를 검색하고 해당 정점을 가져와 셰이더로 전달합니다. 이 접근 방식은 이전 렌더링과 동일한 결과를 달성하지만 GPU로 전송되는 데이터가 더 적습니다.
초기에 `triangle-strip`의 한계를 논의했지만, 이 예제에서는 인덱스 버퍼를 사용했음에도 불구하고 열린 큐브를 두 개의 `triangle-strip`으로 수동으로 분할했다는 점에 유의해야 합니다. 인덱스 버퍼의 사용은 삼각형 토폴로지 선택과 상충되지 않습니다. `triangle-list`를 선택하더라도 인덱스 버퍼를 사용하면 상당한 데이터 크기 절약을 여전히 달성할 수 있습니다.