DX9을 이용한 3D Game 프로그래밍 입문 - Part.2 Direct3D 기초 3장

3장 Direct3D에서의 드로잉

3.1 버텍스/인덱스 버퍼

버텍스 버퍼 : 버텍스 데이터를 보관하는 연속적인 메모리 덩어리
인덱스 버퍼 : 인덱스 데이터를 보관하는 연속적인 메모리 덩어리

배열이 아닌 버퍼에 데이터를 보관하는 이유는 버퍼를 비디오 메모리에 저장할 수 있기 때문으로, 시스템 메모리의 데이터를 렌더링하는 것보다는 비디오 메모리의 데이터를 렌더링 하는 것이 훨씬 빠르기 때문이다.

코드에서 버텍스 버퍼는 IDirect3DVertexBuffer9 인터페이스로 나타내며,
인덱스 버퍼는 IDirect3DIndexBuffer9 인터페이스로 나타낸다.

3.1.1 버텍스와 인덱스 버퍼 만들기

다음의 두 가지 메서드를 이용해 버텍스 버퍼와 인덱스 버퍼를 만들 수있다.

HRESULT IDirect3DDevice9::CreateVertexBuffer(
UINT Length,
DWORD Usage,
DWORD FVF,
D3DPOOL Pool,
IDirect3DVertexBuffer9* ppVertexBuffer,
HANDLE
pSharedHandle
);

HRESULT IDirect3DDevice9::CreateIndexBuffer(
UINT Length,
DWORD Usage,
D3DFORMAT Format
D3DPOOL Pool,
IDirect3DIndexBuffer9* ppIndexBuffer,
HANDLE
pSharedHandle
);

두 메서드의 인자들은 대부분 동일하므로, 두 메서드의 인자를 함께 살펴보기로 하자.

Length - 버퍼에 할당하 바이트 수. 만약 8개의 버텍스를 보관할 수 있는 만큼의 버텍스 버퍼를 할당하고자 한다면 8 sizeof(Vertex)로 인자로 지정하면 된다. Vertex는 이용할 버텍스 구조체를 나타낸다.

*Usage - 버퍼가 이용되는 방법을 결정하는 몇 가지 부가적인 특성을 지정한다. 이 값에는 부가적인 특성이 없음을 의미하는 0을 지정하거나 다음의 플래그들을 하나 이상 지정할 수 있다.

-D3DUSAGE_DYNAMIC - 이 플래그를 설정하면 버퍼를 동적으로 만든다. 정적 버퍼와 동적 버퍼에 대해서는 이어지는 페이지를 참고한다.

-D3DUSAGE_POINTS - 이 플래그는 버퍼가 포인트 기본형을 보관할 것임을 지정한다. 포인트 기본형에 대해서는 14장 파티클 시스템에서 다루어진다. 이 플래그는 버텍스 버퍼에서만 이용된다.

-D3DUSAGE_SOFTWAREPROCESSING - 버텍스 프로세싱을 소프트웨어로 처리한다.

-D3DUSAGE_WRITEONLY - 애플리케이션이 버퍼에 쓰기만을 수행할 것임을 지정한다. 이 플래그를 설정하면 드라이버가 쓰기 작업에 최적화된 메모리 위치에 버퍼를 옮길 수 있다. 이 플래그가 설정된 버퍼에 읽기를 수행하려고 시도하면 오류가 발생한다.

*FVF - 버텍스 버퍼에 보관될 버텍스의 유연한 버텍스 포맷

*Pool - 버퍼가 위치할 메모리 풀

*ppVertexBuffer - 만들어질 버텍스 버퍼를 받을 포인터

*pSharedHandle - 이용되지 않는다 . 0으로 지정한다.

*Format - 인덱스의 크기를 지정한다.

*ppIndexBuffer - 만들어질 인덱스 버퍼를 받을 포인터

-NOTE-

D3DUSAGE_DYNAMIC 플래그를 지정하지 않고 만들어진 버퍼를 정적 버퍼라 한다. 효율적으로 내용물을 처리할 수있는 비디오 메모리 내에 보관됨. 하지만 접근속도가 비교적 느리기때문에 정적버퍼의 메모리에서 메모리를 읽고 쓰는 작업 또한 느려지게 된다. 이런 특성 때문에 정적 버퍼는 정적인 데이터를 보관하는 데 적합하다(자주 바뀌지 않을 데이터)
-NOTE-
D3DUSAGE_DYNAMIC 플래그를 지정하고 만들어진 버퍼를 동적 버퍼라 한다. 동적 버퍼는 일반젹으로 매우 빠른 속도로 갱신이 가능한 AGP 메모리 내에 보관된다. 동적 버퍼를 처리하기 위해서는 먼저 비디오 메모리로 전송하는 과정이 필요하므로 정적 버퍼만큼 빠를 수는 없지만, 버퍼의 갱신이 상당히 빠르다는 장점을 가지고 있다. 따라서 버퍼의 내용을 자주 갱신해야 하는 경우에는 동적 버퍼를 이용해야 한다. 예를 들어, 파이틀 시스템은 매 프레임마다 기하정보를 변환해야 하므로 동적 버퍼가 어울리는 좋은 예이다.
-NOTE-
비디오 메모리나 AGP 메모리에서의 읽기 작업은 상당히 느리므로, 런타임시에 기하정보를 자주 읽어 들여야 한다면 지역 시스템 메모리에 복사본을 남겨두고 이를 읽어들이는 것이 바람직 하다.

3.1.2 버퍼 메모리에 접근하기

VI 버퍼 메모리에 접근하기 위해서는 내부 메모리 컨텐트로의 포인터가 필요하다. 포인터를 얻기 위해서는 Lock 메소드를 이용하며, 버퍼의 정보를 읽고 쓸수 있게 된다.이용이 끝난 뒤에는 반드시 Unlock 메소드를 이용해 버퍼의 잠금을 해제해야 한다. 메모리로의 포인터를 얻은 뒤에는 버퍼의 정보를 읽고 쓸 수 있게 된다.

-NOTE-
D3DUSAGE_WRITEONLY 플래그를 설정하고 만들어진 VI 버퍼의 경우에는 절대로 버퍼 읽기를 시도해서는 안된다. 읽기 시도는 오류로 이어진다.

HRESULT IDirect3DVertexBuffer9::Lock(
UINT OffsetToLock, //잠금을 시작할 버퍼 위치의 오프셋
UINT SizeToLock, //잠금 바이트의 수
BYTE** ppbData, //잠근 메모리의 시작을 가리키는 포인터
DWORD Flags //잠금이 이루어지는 방법을 지정한다.0을 지정하거나 플래그를 조합해서 지정한다.
);

HRESULT IDirect3DVertexBuffer9::UnLock(
UINT OffsetToLock,
UINT SizeToLock,
BYTE** ppbData,
DWORD Flags
);

락과 언락 두 메서드의 인자는 완전히 동일하다.

플래그의 D3DLOCK_DISCARD와 D3DLOCK_NOOVERWRITE 플래그는 Lock 호출이 있을 때 버퍼 메모리의 일부가 이용중인(렌더링중인) 상황을 해결하는 데 도움을 준다 만약 이들 플래그를 이용할 수 있는 상황이라면 버퍼를 잠글 때도 렌더링이 정지되는 것을 막을 수 있다.

3.1.3 버텍스 버퍼와 인덱스 버퍼에 대한 정보 얻기

떄로는 버텍스/인덱스 버퍼에 대한 정보를 얻을 필요가 있다.

_vertecBuffer->GetDesc(&vbDescritpon); 버텍스 버퍼 정보를 얻는다.

3.2 렌더 상태

Direct3D는 기하정보가 렌더링되는 방식을 결정하는 다양한 렌더링 상태를 캡슐화하고 있다. 렌더 상태는 모두 디폴트 값을 가지고 있으므로, 여러분의 애플리케이션에서 디폴트가 아닌 다른 동작을 필요로 하는 경우에만 이를 변경하면 된다. 렌더 상태는 상태를 다시 바꾸기 전까지 계속 효과를 유지한다. 렌더 상태를 변경하기 위해서는 다음의 메서드를 이용한다.
HRESULT IDirect3DDevice9::SetRenderState(
D3DRENDERSTATETYPE State //변경할 상태
DWORD Value //새로운 상태값
);

3.3 드로잉 준비

남은 세가지의 단계

  1. 스트림 소스 지정. 스트림 소스를 버텍스 버퍼와 연결하여 버퍼의 기하정보르 렌더링 파이프라인에 보낼 수 있도록 한다.

스트림 소스를 지정하는 데는 다음의 메서드를 이용한다.
HRESULT IDirect3DDevice9::SetStreamSource(
UINT StreamNumber - 버텍스 버퍼를 연결할 스트림 소스를 지정한다. 이 책에서는 다중 스트림을 이용하지 않으므로 항상 스트림 0을 이용할 것이다.

IDirect3DVertexBuffer9* pStreamData - 스트림과 연결하고자 하는 버텍스 버퍼로의 포인터

UINT OffsetInBytes - 렌더링 파이프라인으로 공급될 버텍스 데이터의 시작을 지정하는 스트림의 시작 오프셋, 이 인자에 0 이외에 다른 값을 지정하기 위해서는 D3DCAPS9 구조체의 D3DDEVCAPS2_STREAMOFFSET 플래그를 확인하여 여러분의 장치가 이를 지원하고 있는지의 여부를 먼저 확인 해야 한다.

UINT Stride - 스트림에 연결하고자 하는 버텍스 버퍼 내 각 요소의 바이트 수. 예를 들어 Vertex형의 버텍스로 채워진 버텍스 버퍼 vb가 있다고 할 때

_device->SetStreamSource( 0, vb, 0, sizeof(Vertex));
2. 버텍스 포맷을 지정한다. 이후의 드로잉 호출에서 이용될 버텍스 포맷을 지정하는 단계이다.

_device->SetFVF(D3DFVF_XYZ | D3DFVF_TEX1);

  1. 인덱스 버퍼를 지정한다. 만약 인덱스 버퍼를 이용한다면 이후의 드로잉 과정에서 이용될 인덱스 버퍼를 지정해야 하는데, 한 번에 하나의 인덱스 버퍼만 이용할 수 있다. 따라서 다른 인덱스 버퍼를 가진 물체를 그려야 한다면 인덱스 버퍼를 전환하는 과정이 필요하다. 인덱스 버퍼를 지정하는 방법은 다음과 같다.
    _device->SetIndices( _ib ); //인덱스 버퍼 포인터의 복사본을 전달한다.

3.4 버텍스 /인덱스 버퍼를 이용한 드로잉

버텍스/인덱스 버퍼를 만들고 모든 준비 과정을 마친 뒤에는 DrawPrimitive나 DrawIndexedPrimitive 메소드를 이용해 기하정보를 렌더링 파이프라인으로 보내는 실질적인 드로잉을 수행할 수 있다. 이들 메서드는 버텍스 스트립에서 버텍스 정보를 얻고, 지정된 인덱스 버퍼에서 인덱스를 얻는다.

3.4.1 IDirect3DDevice9::DrawPrimitive

이 메서드는 인덱스 정보를 이용하지 않는 기본형을 그리는 데 이용된다.

HRESULT IDirect3DDevice9::DrawPrimitive(
D3DPRIMITIVETYPE PrimitiveType, - 그리고자 하는 기본형 타입. 예를 들어, 삼각형 이외에도 포인트나 선을 그릴 수 있다. 여기에서 그리고자 하는 것은 삼각형이므로 D3DPT_TRIANGLELIST를 지정한다.

StatrVertex - 버텍스 읽기를 시작할 버텍스 스트림 요소로의 인덱스.이 인자는 버텍스 버퍼 내의 데이터를 일부만 그릴 수 있도록 하는 유연성을 제공한다.

PrimitiveCount - 그리고자 하는 기본형의 수

3.4.2 IDirect3DDevice9::DrawIndexedPrimitive

이 메서드는 인덱스 정보를 이용해 기본형을 그린다.

HRESULT IDirect3DDevice9::DrawIndexedPrimitive(
D3DPRIMITIVETYPE Type , -그리고자 하는 기본형 타입
INT BaseVertexIndex, -이번 호출에 이용될 인덱스에 더해질 기반 번호를 지정한다. 이어지는 노트를 참고하자.
UINT MinIndex, -참조할 최소 인덱스 값
UINT NumVertices, - 이번 호출에 참조될 버텍스의 수
UINT StartIndex, - 인덳스 버퍼 내에서 읽기를 시작할 요소로의 인덱스
UINT PrimitiveCount - 그리고자 하는 기본형의 수
);

_device->DrawIndexedPrimitive(D3DPT_TRIANGLELIST,0,0,8,0,12);

-NOTE-

로컬 인덱스 버퍼는 대응되는 각각의 로컬 버텍스 버퍼의 버텍스들을 참조한다. 만약, 구체와 입방체, 원기둥의 버텍스를 하나의 전역 버텍스 버퍼로 결합하기를 원한다고 가정해보자. 각각의 물체에 대해 전역 버텍스 버퍼에 맞도록 인덱스를 다시 계산하는 과정이 필요할 것이다. 새로운 인덱스는 전역 버텍스 버퍼의 물체 버텍스 시작을 지정으로 하는 오프셋 값을 더하도록 계산된다. 오프셋은 바이트가 아닌 버텍스 단위로 계산된다는 것에 주의 하자.

D3D는 전역 버텍스 버퍼 내의 물체 위치에 따라 우리가 직접 인덱스를 계산하는 수고를 덜기 위해서 BaseVertexIndex 인자를 통해 버텍스-오프셋 값을 전달할 수 있도록 해주고있다. 인덱스를 다시 계산하는 과정은 D3D에 의해 내부적으로 이루어진다.

3.4.3 장면의 시작/끝

마지막으로 기억해두어야 할 사항으로는 모든 드로잉 메소드가 IDirect3DDevice9::BeginScene와 IDirect3DDevice9:EndScene 호출 내부에 포함되어야 한다는 것이다.

3.5 D3DX 기하 물체

코드 내에서 각각의 삼각형을 연결하여 3D 물체를 구성하는 작업은 상당히 지루해질 것이다. D3DX 라이브러리는 몇 가지의 간단한 3D 물체 메쉬 데이터를 생성하는 메서드를 제공한다.

D3DXCreateBox,D3DXCreateSphere....

여섯 개의 함수는 모두 비슷하며, D3DX 메쉬 데이터 구조체 ID3DXMesh와 ID3DXBuffer 인터페이스를 이용한다. 이들 인터페이스는 10장과 11장에서 다루어지므로, 일단 현재로서는 세부적인 부분은 덮어두고, 간단한 방법으로 메서드를 이용하는 방법에 초점을 맞춰보자.

HRESULT D3DXCreateTeapot(
LPDIRECT3DDEVICE9 pDevice -메쉬와 연계된 장치
LPD3DXMESH ppMesh -메쉬를 받을 포인터
LPD3DXBUFFER
ppAdjacency -현재는 0으로 지정한다.
);

3.7 요약

*버텍스 데이터는 IDirect3DVertexBuffer9 인터페이스에 보관되며, 인덱스 데이터는 iDirect3DIndexBuffer9 인터페이스에 보관된다. 버텍스와 인덱스 버퍼를 이용하는 것은 데이터를 비디오 메모리 내에 보관할 수 있기 때문이다.

*정적인(각 프레임마다 갱신할 필요가 없는) 기하정보의 경우에는 정적 버텍스/인덱스 버퍼에 보관해야 하며, 동적인 기하정보(각 프레임마다 갱신해야 하는)는 동적 VI버퍼에 보관해야 한다.

*렌더 상태는 기하정보가 렌더링 되는 방식에 영향을 주며, 한 번 지정되며 바뀔 떄까지 영향을 미친다. 모든 렌더 상태는 초기의 디폴트 값을 가진다.

*버텍스 버퍼와 인덱스 버퍼의 내용물을 그리기 위해서는 다음의 과정을 거쳐야 한다.

-IDirect3DDevice9::SetStreamSource 메서드를 호출하여 그리려는 버텍스를 포함하는 버텍스 버퍼를 스트림과 연결한다.

-IDirect3DDevice9::SetFVF 메서드를 호출하여 버텍스의 포맷을 지정한다.

-인덱스 버퍼를 이용한다면 IDirect3DDevice9::SetIndices 메서드를 호출하여 이용하려는 인덱스 버퍼를 지정한다.

-IDirect3DDevice9::BeginScene와 IDirect3DDevice9::EndScene 메서드 호출 블록 내부에서 IDirect3DDevice9::DrawPrimitive나 IDirect3DDevice9::DrawIndexedPrimitve 메서드를 호출하여 드로잉을 수행한다.

D3DXCreate 함수를 이용하면 구체나 원기둥,주전자 등과 같은 좀더 복잡한 3D 물체의 기하정보를 만들어낼 수있다.

좋은 웹페이지 즐겨찾기