2015. 3. 7.

[DirectX9]인덱스 버퍼(Index Buffer)

 인덱스 버퍼는 정점 버퍼와 함께 사용하면 효과적이다. 이는 정점 버퍼에 대한 이해가 필요하다는 의미이다. 실제로 DirectX9에서 정점 버퍼를 사용하는 방법과 인덱스 버퍼를 사용하는 방법은 매우 유사하다. 정점 버퍼를 모르는 사람은 정점 버퍼의 내용을 살펴보고 난 후, 인덱스 버퍼를 공부하도록 하자.


인덱스 버퍼

 인덱스 버퍼는 정점을 저장하기 위한 정점 버퍼와 마찬가지로 정점의 인덱스를 보관하기 위한 전용 버퍼를 말한다. 인덱스 버퍼를 사용하면 다음과 같은 장점이 있다.

  • 정점을 여러 번 나열하는 것보다 메모리 소모량이 적다.
  • 자주 사용되는 정점을 캐시에 저장해서 성능 상 유리하다.
 글보다 다음 그림으로 먼저 이해해보자.

 왼쪽의 평행사변형은 인덱스를 사용하지 않은 경우이다. 평행사변형 즉, 사각형을 그리기 위해서는 삼각형 2개가 필요하다. [v0, v1, v2], [v3, v4, v5] 2개의 삼각형으로 사각형을 그렸다. 그런데 v1과 v4, v2와 v3는 동일한 좌표를 나타내는 정점이다. 동일한 좌표를 나타내는 정점이 중복된다는 문제가 발생한다. 이런 중복을 방지할 수 있는 방법이 바로 정점에 대한 인덱스를 부여하는 방법이다. 오른쪽의 경우를 살펴보자. 정점을 보관하기 위한 버퍼에 [v0, v1, v2, v3]가 저장되어 있다. 보관된 정점에 대해서 차례로 인덱스를 부여하면 [0-v0, 1-v1, 2-v2, 3-v3]가 될 것이다. 우리는 이제 정점의 인덱스를 활용하여 사각형을 그릴 것이다. 먼저 평행사변형의 [v0, v1, v2] 삼각형을 그리기 위해서 인덱스 [0, 1, 2]를 활용하면 된다. 다음 [v1, v2, v3] 삼각형을 그리기 위해서 인덱스 [2, 1, 3]를 활용하면 된다.

 인덱스가 없다면 왼쪽의 경우처럼 중복된 정점때문에 소모되는 메모리를 줄일 수 없다. 하지만 인덱스 버퍼를 활용하면 정점을 저장할 필요없이 인덱스만 보관하면 된다. 인덱스 버퍼를 사용하지 않으면 v1, v4는 동일한 위치를 나타내는 정점임에도 불구하고 정점 버퍼에 따로 저장되어야 한다. 하지만 인덱스 버퍼를 활용하면 중복된 정점을 보관할 필요없이 인덱스만 활용하면 된다. 그리고 평행사변형을 그리기 위해서 정점 버퍼에 4개의 정점을 캐시에 저장하는 것이 6개의 정점을 저정하는 것보다 캐시 힛(Cache Hit) 확률을 높힐 수 있다. 500개의 정점으로 구성된 물체가 있다고 가정하자. 이를 인덱스 버퍼를 사용하였더지 400개의 정점만 정점 버퍼에 보관하면 된고 가정하자. 만약 캐시에 100개의 정점 정보만 보관할 수 있다면 500개의 정점보다는 400개의 정점을 대상으로 연산하는 것이 유리하다. 특히 그래픽스에서 물체를 그릴 때에는 근처에 존재하는 정점이 다시 사용될 확률이 높다. 캐시 힛 확률을 높이는 것은 성능을 향상시키는데 도움이 된다.


인덱스 버퍼를 사용하여 육면체를 그려보자


 우리는 위와 같은 육면체를 그리고자 한다. 필요한 최소 정점은 8개이다. 하지만 인덱스를 사용하지 않으면 필요한 정점의 갯수는 증가할 것이다. 만약 인덱스 버퍼를 사용한다면 정점 8개와 이를 나타내는 8개의 인덱스로 우리가 구성하고자 하는 물체를 마음껏 표현할 수 있다. 그럼 먼저 8개의 정점을 부여해보자.


 아랫면을 구성하는 4개의 정점과 윗면을 구성하는 4개의 정점만 있으면 총 12개의 삼각형을 그릴 수 있고, 이를 통해 육면체를 완성할 수 있다. 그러면 이를 구현하기 위한 방법을 생각해보자. 확실한 것은 8개의 정점을 먼저 정점 버퍼에 보관해야 한다는 사실이다. 정점 버퍼에 정점이 보관되면 그 순서대로 인덱스가 부여된다. 그래서 따로 정점과 인덱스를 맵핑하는 과정은 고려하지 않아도 된다. 정점 버퍼에 정점들이 저장된 후, 인덱스를 나열하여 물체를 그리면 된다. 만약 위의 그림에서 육면체의 윗면을 그린다고 생각해보자. [4, 5, 7], [5, 7, 6]으로 인덱스를 나열하면 윗면이 완성된다. 이런 방식으로 6개 면을 구성하는 인덱스들의 모임을 나열하고, 이를 인덱스 버퍼에 저장하면 우리가 할 일은 끝난다.
 코드로 살펴보자. 먼저 8개의 정점을 정점 버퍼에 보관하는 코드이다.(참고로 위 그림의 인덱스와 코드의 인덱스는 다르다는 것을 참고하자)
 정점 버퍼를 이미 알고 있는 사람이라면 쉽게 이해할 수 있는 코드이다. 8개의 정점을 보관하기 위한 정점 버퍼를 만들고, Lock() 함수를 통해 정점을 저장할 수 있는 포인터값을 얻어, 정점을 저장한다. 그리고 Unlock() 함수를 호출하여 마무리하였다.

 다음은 정점 버퍼에 저장된 정점을 활용한 인덱스 버퍼를 설정하는 코드이다.
 코드를 보면 알겠지만 정점 버퍼를 사용하는 코드와 인덱스 버퍼를 사용하는 코드는 매우 유사하다. Lock(), Unlock() 함수를 사용하는 것은 동일하다. 정점 버퍼를 이해한 내용 그대로 인덱스 버퍼를 이해해도 무방하다.

 이제 우리는 육면체를 그릴 때, 인덱스 버퍼에 보관된 인덱스를 통해 정점 버퍼의 정점을 읽어오게 된다. 만약 인덱스 버퍼를 사용하지 않고, 정점 버퍼만 사용했다면 필요한 정점의 갯수는 증가했을 것이다.


전체 코드

실행 결과


정리하면

  • 인덱스 버퍼를 사용하면 정점 버퍼에 소모되는 메모리를 줄일 수 있다.
  • DirectX9에서 인덱스 버퍼는 정점 버퍼와 유사한 방법으로 활용할 수 있다.

다음 주제: [DirectX9]지형 생성

댓글 1개:

  1. vertex buffer 설정하는곳 x, y, z 좌표값을 -1.0, 1.0 이런식으로 바꿔주는 역할은 어디서 해주신건가요?

    답글삭제