레이블이 인덱스 버퍼인 게시물을 표시합니다. 모든 게시물 표시
레이블이 인덱스 버퍼인 게시물을 표시합니다. 모든 게시물 표시

2015. 3. 8.

[DirectX9]지형 생성

 정점 버퍼와 인덱스 버퍼를 활용하여 지형을 생성해보자. 먼저 가장 기본적인 평평한 지형을 만들고, 그 다음 그 지형을 활용하여 굴곡이 있는 지형을 생성해보도록 하겠다. 이번 포스트에는 DirectX9에 대한 새로운 지식이 필요하다기 보다는 정점 버퍼와 인덱스 버퍼를 맵핑하는 알고리즘이 추가된 정도이다. 이 알고리즘 부분만 이해한다면 나머지 부분은 이미 여러 번 코드이므로 쉬울 것이다.


전체적인 과정
  1. 평면을 생성하는 정점을 선언하고, 정점 버퍼에 저장한다.
  2. 정점 버퍼에 저장된 정점을 활용하는 인덱스 버퍼를 만들어 평면을 생성하는 정보를 저장한다.
  3. 생성된 평면에 텍스처를 입힌다.
 크게 이 세 과정을 거쳐서 간단한 지형을 생성하고자 한다. 정점 버퍼, 인덱스 버퍼, 텍스처에 대한 간단한 지식이 한 번에 사용되는 예제가 될 것이다.


사용될 정점을 저장하는 정점 버퍼

가로 길이가 100이고, 세로 길이가 100인 평면 지형을 만들기 위해서는 몇 개의 정점이 있으면 되겠는가? 최소로 생각하면 4개만 있어도 충분하다. 평면 지형은 하나의 사각형으로 표현할 수도 있기 때문이다. 하지만 우리는 나중에 평면 지형을 활용하여 지형의 높이 굴곡도 표현하고자 하기 때문에 지금은 길이 1당 정점이 하나씩 필요하다고 생각하고 설명을 진행하도록 하겠다. 길이 1당 정점이 하나씩 필요하다면 100 * 100개의 정점이 필요하다.

 먼저 사용자 정의 정점을 선언하자. 
 사용자 정의 정점 구조체를 기반으로 100 * 100개의 정점을 위한 정점 버퍼를 먼저 생성해보자.
 가로 길이와 세로 길이 만큼 정점 버퍼의 크기를 설정하여 정점 버퍼를 생성하였다. 그리고 Lock() 함수를 통해 정점 정보를 저장할 주소값을 얻어왔다. 포인터 변수 pV에 그 주소값을 저장하였다. 정점의 위치, 수직 벡터, 텍스처 좌표를 설정한 후, 이 값을 정점 버퍼에 하나씩 저장하고 있다. 저장을 완료한 후에 Unlock() 함수를 호출하였다.

 우리는 현재 총 100 * 100개의 정점을 정점 버퍼에 저장하였다. 저장된 정점들과 인덱스 버퍼를 활용하여 지형을 생성하여야 한다. 그러기 위해서는 먼저 지형을 만들 수 있는 인덱스 정보들을 인덱스 버퍼에 넣어야 한다.


지형을 형성하는 인덱스 정보를 저장하는 인덱스 버퍼

 인덱스 버퍼의 장점에 대해서는 이전 포스트에서 언급한 적이 있다. 그리고자 하는 물체가 복잡하여 사용되는 정점의 수가 많을수록 정점 인덱스를 사용하는 효과는 더욱 커진다. 만약 인덱스 버퍼를 사용하지 않고, 가로 100, 세로 100의 평면을 삼각형으로 표현하기 위해서는 몇 개의 정점이 필요할까? 간단하게 계산할 수 있다. 총 100 * 100개의 작은 사각형이 그려져야하고, 이 사각형을 그리기 위한 2개의 작은 삼각형을 그려야 한다. 결국 100 * 100 * 2개의 삼각형을 그려야 한다. 정점 인덱스를 사용하지 않으면 좌표값이 동일한 정점의 중복을 허용해야 한다. 즉 하나의 삼각형을 그리는데 3개의 정점이 고정적으로 사용된다. 그래서 총 필요한 정점의 수는 100 * 100 * 2 * 3개가 된다. 이에 반해 인덱스 버퍼를 사용하면 필요한 정점의 수는 100 * 100으로 고정된다. 이 경우 인덱스 버퍼를 사용하면 정점 버퍼에 소비하는 메모리의 크기는 1/6로 줄일 수 있다. 여튼 좋으니깐 사용하도록 하자.

 인덱스 버퍼를 생성하는 코드를 보도록 하자.
 방금 전 평면 지형을 생성하기 위한 작은 사각형의 갯수에 대해 설명하였다. 그리고 그 사각형의 갯수의 두 배만큼의 삼각형이 필요하다. 그러므로 주석에서 설명하고 있는 것처럼 인덱스 버퍼의 크기는 코드에서처럼 설정된다.

 다음은 인덱스 버퍼에 작은 삼각형들을 생성하는 알고리즘이다. 코드를 보기 전에 정점 버퍼에 저장된 정점의 인덱스를 살펴보자. 아래 그림을 보자.


 우리가 정점 버퍼를 생성하면서 저장한 정점은 위의 그림처럼 저장되어 있다. 위의 숫자는 바로 정점의 인덱스를 의미한다. 위 그림은 가로 4, 세로 4인 경우 정점 버퍼에 저장된는 정점과 인덱스를 보여주고 있다. 가로 100, 세로 100의 경우는 아래와 같을 것이다.(화살표는 무시하도록 하자)


 첫 번째 행는 0 ~ 99, 두 번째 행은 100 ~ 199, 100번째 행은 99900 ~ 99999 인덱스가 부여될 것이다. 이 인덱스를 기반으로 평면을 형성하는 인덱스 버퍼를 만들어 보자.
 알고리즘에 따르면 첫 번째 삼각형은 [0, 1, 100], 두 번째 삼각형은 [100, 1, 101]로 인덱스 버퍼에 저장된다. 그리고 이 두 개의 삼각형으로 평면을 구성하는 작은 사각형을 표현할 수 있다. 이런 삼각형을 표현하는 MYINDEX 구조체가 100 * 100 * 2개만큼 인덱스 버퍼에 저장된다. 그리고 주목할만한 사실은 MYINDEX에 저장되는 인덱스가 반시계 방향(CCW)인지 시계 방향(CW)인지 살펴보아야 한다. 우리가 알고리즘을 통해 생성한 MYINDEX는 시계 방향으로 구성된다. SetRenderState() 함수를 통해 컬링 모드를 시계 방향으로 설정했다면 아무것도 그려지지 않게된다. 이 점을 유의하도록 해서 인덱스 버퍼와 컬링 모드를 설정하도록 하자.


생성한 지형에 텍스처를 입히자

 다음 이미지는 텍스처로 사용할 파일이다. 다운받아서 사용하도록 하자.

 텍스처를 초기화하였다.
 그리고 이 텍스처를 0번으로 설정하고, 확대 필터, 텍스처 좌표계를 설정하였다.


전체 코드

실행 결과


정리하면

  • 정점 버퍼, 인덱스 버퍼, 텍스처를 활용하여 지형을 생성할 수 있다.
  • 인덱스 버퍼를 사용하면 정점 버퍼에 저장하는 정점의 개수를 줄일 수 있다.

다음 주제: [DirectX9]높이맵

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]지형 생성