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`를 선택하더라도 인덱스 버퍼를 사용하면 상당한 데이터 크기 절약을 여전히 달성할 수 있습니다.

GitHub에 의견 남기기