10장 메쉬 파트 I
ID3DXMesh ID3DXMesh 인터페이스 기능의 상당부분은 부모인 ID3DXBaseMesh 에서 상속 받는다. 10장 내용 ID3DXMesh 객체의 내부 데이터 구성 ID3DXMesh 를 만드는 방법 ID3DXMesh 를 최적화하는 방법 ID3DXMesh 를 렌더링하는 방법 2009-1학기 컴퓨터게임(DirectX)
기하정보 ID3DXBaseMesh 인터페이스는 메쉬의 버텍스를 보관하는 버텍스 버퍼와 이들 버텍스가 어떻게 메쉬의 삼각형들을 구성하는가를 정의하는 인덱스 버퍼를 포함 다음 두 가지 메서드를 이용해 이들 버퍼로의 포인터를 얻음 HRESULT ID3DXMesh::GetVertexBuffer(LPDIRECT3DVERTEXBUFFER9* ppVB); HRESULT ID3DXMesh::GetIndexBuffer(LPDIRECT3DINDEXBUFFER9* ppIB); 2009-1학기 컴퓨터게임(DirectX)
IDirect3DVertexBuffer9* vb=0; Mesh->GetVertexBuffer(&vb); 다음은 이들 메서드를 이용한 예 IDirect3DVertexBuffer9* vb=0; Mesh->GetVertexBuffer(&vb); IDirect#3DIndexBuffer9* ib=0; Mesh->GetIndexBuffer(&ib); 2009-1학기 컴퓨터게임(DirectX)
ID3DXMesh 인터페이스는 순수한 정보를 제공하기 위한 목적으로 기본형 타입의 인덱스화된 삼각형 리스트를 지원 만약 버퍼 쓰기나 읽기를 위해 버퍼 잠금이 필요하다면 다음의 두 가지 메서드를 이용할 수 있음. 이들 메서드는 전체 버텍스/인덱스 버퍼를 잠근다는 데 주의 HRESULT ID3DXMesh ::LockVertexBuffer(DWORD Flags, BYTE** ppData); HRESULT ID3DXMesh::LockIndexBuffer(DWORD Flags, BYTE** ppData); Flags 인자는 잠금 방식을 지정하는데 쓰이고, ppData인자는 함수가 리턴할 때 잠근 메모리를 가리킬 포인터의 주소 2009-1학기 컴퓨터게임(DirectX)
HRESULT ID3DXMesh::UnlockVertexBuffer(); HRESULT ID3DXMesh::UnlockIndexBuffer(); 다음은 기하 관련 정보를 얻기 위한 부가적인 ID3DXMesh 메서드의 리스트임 DWORD GetFVF() – 버텍스의 버텍스 포맷을 나타내는 DWORD를 리턴 DWORD GetNumVertices(); - 버텍스 버퍼 내의 버텍스의 수를 리턴 DWORD GetNumBytesPerVertex(); - 버텍스 당 바이트의 수를 리턴 DWORD GetNumFaces(); - 메쉬 내의 면(삼각형)의 수를 리턴 2009-1학기 컴퓨터게임(DirectX)
서브셋과 속성 버퍼 메쉬는 하나 이상의 서브셋으로 구성. 서브셋은 동일한 속성을 이용해 렌더링할수 있는 메쉬 내 삼각형들의 그룹으로, 여기에서 말하는 속성이란 재질과 텍스처, 렌더 상태를 의미 집 서브셋 3: 창문,창 문 속성을 이용해 서브셋 내의 삼각 형들을 렌더링 서브셋 2: 천정,천 정속성을 이용해 서브셋 1:벽,벽속 성을 이용해 서브 셋 내의 삼각형 들을 렌더링 서브셋 0 : 바닥, 바닥 속성을 이용 해 서브셋 내의 삼 각형들을 렌더링 (그림1) 서브셋들로 분리된 하나의 집 2009-1학기 컴퓨터게임(DirectX)
각각의 서브셋에는 독특한 양의 정수 값을 지정하여 서로 구분 각각의 서브셋에는 독특한 양의 정수 값을 지정하여 서로 구분. DWORD로 보관할 수 있는 값이라면 어떤 것이든 이 값으로 사용될 수 있는데, 예를 들어 그림에서는 각각의 서브셋에 0,1,2,3 값을 붙였다 메쉬 내의 삼각형들은 각 삼각형이 위치하는 서브셋을 지정하는 속성 ID를 가짐. 예를 들어, 집의 바닥을 구성하는 삼각형들은 속성 ID로 0을 가지고, 벽을 구성하는 삼각형들은 속성 ID로 1을 가짐 삼각형의 속성 ID는 메쉬의 DWORD 배열인 속성 버퍼 내에 보관. 각각의 면들은 속성 버퍼 내에 하나의 항목을 가지므로, 속성 버퍼 내의 요소 수는 메쉬 내의 면의 수와 같음 속성 버퍼내의 항목과 인덱스 버퍼에 정의된 삼각형들은 1:1 대응관계를 가지므로 속성 버퍼 내의 항목 i는 인덱스 버퍼 내의 삼각형 i와 대응 2009-1학기 컴퓨터게임(DirectX)
삼각형 i는 인덱스 버퍼의 다음 세 개의 인덱스에 의해 정의 A=i·3 B=i·3+1 C=i·3+2 각각의 관계는 다음 그림과 같음 4 2 3 1 20 23 22 항목들은 삼각형이 존재하는 서브셋을 지정 삼각형0 삼각형1 삼각형n 인덱스 버퍼 속성 버퍼 (그림2) 인덱스 버퍼에 정의된 삼각형과 속성 버퍼 내의 항목들과의 대응 관계 2009-1학기 컴퓨터게임(DirectX)
삼각형 0은 서브셋 0에 존재하며, 삼각형 1은 서브셋 4에 존재하고, 삼각형 n은 서브셋 2에 존재 다음의 코드에서와 같이, 속성 버퍼에 접근하기 위해서는 먼저 버퍼를 잠가야 함 DWORD* buffer=0; Mesh->LockAttributeBuffer(lockingFlags, &buffer); //속성 버퍼를 읽거나 쓰는 작업 Mesh->UnlockAttributeBuffer(); 2009-1학기 컴퓨터게임(DirectX)
드로잉 ID3DXMesh 인터페이스는 AttribId 인자로 지정한 특정 서브셋의 삼각형을 그리는 DrawSubset(DWORD AttribId) 메서드를 제공 예를 들어, 서브셋 0에 존재하는 모든 삼각형을 그리고자 한다면 다음과 같이 메서드를 호출 Mesh->DrawSubset(0); 전체 메쉬를 그리기 위해서는 메쉬의 모든 서브셋을 그려야 함. 따라서 전체 서브셋의 수가 n이라 할 때 각각의 서브셋 번호를 0, 1, 2, … , n-1 식으로 붙이고 대응되는 재질과 텍스처 배열에서도 인덱스 i가 서브셋 i와 대응되도록 구성하는 것이 편리 전체 메쉬를 다음과 같이 렌더링 할 수 있음 for(int i=0 ; i< numSubsets ; i++) { Device->SetMaterial(mtrls[i]); Device->SetTexture(0, textures[i]); Mesh->DrawSubset(i); } 2009-1학기 컴퓨터게임(DirectX)
최적화 좀더 효과적으로 메쉬를 렌더링하기 위해서는 버텍스와 인덱스를 재구성하는 것이 좋은데, 이와 같은 작업을 최적화라 하고, 다음과 같은 메서드를 이용 HRESULT ID3DXMesh::OptimizeInplace( DWORD Flags, Const DWORD* pAdjacencyIn, DWORD* pAdjacencyOut, DWORD* pFaceRemap, LPD3DXBUFFER* ppVertexRemap ); Flags – 수행할 최적화의 종류를 알려주는 최적화 플래그. 다음의 플래그들을 한 개 이상 지정할 수 있음 D3DXMESHOPT_COMPACT – 메쉬에서 이용되지 않은 인덱스와 버텍스를 제거 2009-1학기 컴퓨터게임(DirectX)
D3DXMESHOPT_VERTEXCACHE – 버텍스 캐시의 히트율을 높임 D3DXMESHOPT_ATTRSORT – 속성으로 삼각형을 정렬하고 속성 테이블을 생성. 이 플래그는 DrawSubset의 효율을 높여줌 D3DXMESHOPT_VERTEXCACHE – 버텍스 캐시의 히트율을 높임 D3DXMESHOPT_STRIPREORDER – 삼각형 스트립이 가능한 길어지도록 인덱스를 재구성 D3DXMESHOPT_IGNOREVERTS – 버텍스를 무시하고 인덱스 정보만을 최적화 D3DXMESHOPT_VERTEXCACHE와 D3DXMESHOPT_ STRIPREORDER 플래그는 함께 사용할 수 없음 pAdjacencyIn – 최적화되지 않은 메쉬의 인접 배열로의 포인터 pAdjacencyOut – 최적화된 인접 정보로 채워질 DWORD 배열로의 포인터. 이 배열은 반드시ID3DXMEsh::GetNumFaces() * 3수의 요소를 가지고 있어야 함. 만약 이 정보가 필요치 않다면 0을 전달 2009-1학기 컴퓨터게임(DirectX)
pFaceRemap – 면 리맵 정보로 채워질 DWORD 배열로의 포인터 pFaceRemap – 면 리맵 정보로 채워질 DWORD 배열로의 포인터. 이 배열은 반드시 ID3DXMesh::GetNumFaces()크기를 가져야 함. 메쉬를 최적화하면 메쉬의 면들이 인덱스 버퍼 내에서 이동하게 되며, 면 리맵 정보는 원래의 면이 어느 곳으로 이동했는지를 알려줌. 즉, pFaceRemap내의 i번째 항목은 원래의 i번째 면이 어느 곳으로 이동했는지를 알려주는 면 인덱스를 포함. 만약 이 정보도 필요치 않다면 0을 전달 ppVertexRemap – 버텍스 리맵 정보로 채워질 ID3DXBuffer 포인터의 주소. 이 버퍼는 ID3DXMesh::GetNumVertices() 만큼의 항목을 포함해야 함. 메쉬를 최적화하면 메쉬의 버텍스들이 버텍스 버퍼 내에서 이동하게 되며, 버텍스 리맵 정보는 원본 버텍스가 어느 곳으로 이동했는지를 알려줌. 즉, ppVertexRemap 내의 i번째 항목은 i번째 원본 버텍스가 어느 곳으로 이동했는지를 알려주는 버텍스 인덱스를 포함. 만약 이정보가 필요치 않다면 0을 전달 2009-1학기 컴퓨터게임(DirectX)
호출 예: // 최적화되지 않은 메쉬의 인접 정보를 얻는다. DWORD adjacencyInfo(Mesh->GetNumFaces() * 3); Mesh->GenerateAdjacency(0.0f, &adjacencyInfo); // 최적화된 인접 정보를 보관할 배열 Mesh->OptimizeInplace( D3DXMESHOPT_ATTRSORT | D3DXMESHOPT_COMPACT | D3DXMESHOPT_VERTEXCACHE, adjacencyInfo, optimizedAdjacencyInfo, 0, 0); 2009-1학기 컴퓨터게임(DirectX)
HRESULT ID3DXMesh::Optimize( DWORD Flags, CONST DWORD* pAdjacencyIn, DWORD* pAdjacencyOut, DWORD* pFaceRemap, LPD3DXBUFFER* ppVertexRemap, LPD3DXMESH* ppOptMesh //최적화된 메쉬 ); 2009-1학기 컴퓨터게임(DirectX)
속성 테이블 D3DXMESHOPT_ATTSORT 플래그를 설정하여 메쉬를 최적화하면 메쉬의 기하정보가 그 속성에 따라 정렬되므로, 버텍스/인덱스 버퍼 내에 있는 특정 부분의 기하정보가 연속된 블록을 위치하게 됨 2009-1학기 컴퓨터게임(DirectX)
기하정보와 속성 버퍼가 속성에 따라 정렬되었음. 이제 하나의 서브셋 기하정보 시작과 끝을 간단하게 표시할 수 1 2 Tri:0 Tri:1 Tri:2 Tri:3 Tri:5 Tri:7 Tri:6 Tri:4 Tri:9 Tri:8 서브셋0 서브셋1 서브셋2 속성버퍼: 인덱스 버퍼: 시작: 카운트 (그림10.3) 특정 서브셋의 기하정보가 연속적으로 위치하도록 기하정보와 속성 버퍼가 속성에 따라 정렬되었음. 이제 하나의 서브셋 기하정보 시작과 끝을 간단하게 표시할 수 있음. 인덱스 버퍼 내의 각 “Tri”는 세 개의 인덱스를 나타냄 2009-1학기 컴퓨터게임(DirectX)
typedef struct _D3DXATTRIBUTERANGE { DWORD AttribId; DWORD FaceStart; D3DXMESHOPT_ATTRSORT 최적화를 수행하면 기하정보 정렬과는 별도로 속성 테이블이 만들어짐. 속성 테이블은 D3DXATTRIBUTERANGE 구조체의 배열로, 테이블의 각 항목은 메쉬의 각 서브셋과 대응되며 서브셋의 기하정보들이 보관되는 버텍스/인덱스 버퍼 내의 메모리 블록을 지정 D3DXATTRIBUTERANGE 구조체는 다음과 같이 정의 되어 있음 typedef struct _D3DXATTRIBUTERANGE { DWORD AttribId; DWORD FaceStart; DWORD FaceCount; DWROD VertexStart; DWORD VertexCount; } D3DXATTRIBUTERANGE; 2009-1학기 컴퓨터게임(DirectX)
FaceStart – 현재의 서브셋과 연결된 삼각형들의 시작을 지정하는 인덱스 버퍼(FaceStart * 3)로의 오프셋 AttribId – 서브셋 ID FaceStart – 현재의 서브셋과 연결된 삼각형들의 시작을 지정하는 인덱스 버퍼(FaceStart * 3)로의 오프셋 FaceCount – 서브셋 내의 면(삼각형)의 수 VertexStart – 현재의 서브셋과 연괼된 버텍스들의 시작을 지정하는 버텍스 버퍼로의 오프셋 VertexCount – 서브셋 내의 버텍스의 수 일단 속성 테이블을 구성한 뒤에는 간단한 속성 테이블 검색만으로 특정한 서브셋 내의 모든 기하정보를 찾을 수 있게 되므로, 서브셋 렌더링의 효율이 극적으로 향상될 수 있음 2009-1학기 컴퓨터게임(DirectX)
메쉬의 속성 테이블에 접근하는 데는 다음 메서드가 사용 HRESULT ID3DXMesh::GetAttributeTable( 만약 속성 테이블을 이용할 수 없다면 우리가 그리고자 하는 서브셋에 존재하는 기하정보들을 찾기 위해 전체 속성 버퍼에 대한 선형 검색이 필요함 메쉬의 속성 테이블에 접근하는 데는 다음 메서드가 사용 HRESULT ID3DXMesh::GetAttributeTable( D3DXATTRIBUTERANGE* pAttribTable, DWORD* pAttribTableSize ); 이 메서드는 두 가지 기능을 가지고 있는데 우선 속성 테이블 내의 속성의 수를 리턴할 수 있으며, 속성 데이터로 D3DXATTRIBUTERANGE 구조체의 배열을 채울 수 있음 속성 테이블 내의 요소의 수를 얻기 위해서는 첫 번째 인자에 0을 전달 2009-1학기 컴퓨터게임(DirectX)
Mesh->GetAttributeTable(0,&numSubsets); DWORD numSubsets=0; Mesh->GetAttributeTable(0,&numSubsets); 요소의 수를 알아내면, 다음과 같은 코드를 이용해 속성 테이블을 D3DXATTRIBUTERANGE배열로 채울 수 있음 D3DXATTRIBUTERANGE table = new D3DXATTRIBUTERANGE [numSubsets]; Mesh->GetAttributeTable(table,&numSubsets); ID3DXMesh::SetAttributeTable 메서드를 이용해 속성 테이블을 직접 지정하는 것도 가능 2009-1학기 컴퓨터게임(DirectX)
다음은 속성 테이블을 12개의 서브셋으로 지정한 예 D3DXATTRIBUTERANGE attributeTable[12]; Mesh->SetAttributeTable(attributeTable,12); 2009-1학기 컴퓨터게임(DirectX)
근접 정보 최적화와 같은 특수한 메쉬 처리를 위해서는 주어진 삼각형과 인접한 다른 삼각형에 대한 정보가 필요 메쉬의 인접 배열은 바로 이러한 정보를 보관 인접 배열은 DWORD의 배열이며 각 항목은 메쉬 내의 삼각형을 식별하는 인덱스임. 예를 들어, 항목 i를 이용해 다음과 같은 인덱스로 구성되는 삼각형을 정의할 수 있음 A=i·3 B=i·3+1 C=i·3+2 2009-1학기 컴퓨터게임(DirectX)
삼각형은 세 개의 변을 가지므로 결국 세 개의 인접한 삼각형을 가질 수 있음 특정한 면이 인접한 삼각형을 가지고 있지 않음을 의미하는 항목(ULONG_MAX = 4294967295)이 있음. -1을 이용할 수도 있는데. 이것은 DWORD에 -1을 할당하면 결국 ULONG_MAX가 되기 때문. DWORD는 부호를 갖지 않은 32비트 정수임을 주의 삼각형은 세 개의 변을 가지므로 결국 세 개의 인접한 삼각형을 가질 수 있음 2009-1학기 컴퓨터게임(DirectX)
Tri: 1은 두 개의 인접 삼각형(Tri: 0과 Tri:2)을 가짐 Tri: 1은 두 개의 인접 삼각형(Tri: 0과 Tri:2)을 가짐. 즉, Tri: 1과 대응되는 인접 항목에는 0, 2, -1이 포함되어 있을 것이고, 0과 2는 각각 Tri: 0과 Tri: 2를 의미하며, -1은 Tri: 1의 한 변에 인접 삼각형이 없음을 의미 Tri:0 Tri:1 Tri:2 -1 1 2 Adjacency: 인덱스버퍼: (그림 10.4) 각각의 삼각형 배열은 인접 배열에 인접한 삼각형을 식별하는 세 개의 항목을 가지고 있음 2009-1학기 컴퓨터게임(DirectX)
D3DX의 메쉬 생성 함수들 상당수가 인접 정보를 만들어내지만 다음의 함수를 이용할 수도 있음 HRESULT ID3DXMesh::GenerateAdjacency( FLOAT fEpsilon, DWORD* pAdjacency ); fEpsilon – 두 개의 포인트를 동일한 것으로 취급할 거리 근사값 pAdjacency – 인접 정보로 채워질 DWORD 배열로의 포인터 예를 들어 DWORD adjacencyInfo[Mesh->GetNumFaces()*3]; Mesh->GenerateAdjacecy(0.001f, adjacencyInfo); 2009-1학기 컴퓨터게임(DirectX)
복제 때로는 한 메쉬의 데이터를 다른 곳으로 복사할 필요가 있으며, ID3DXBaseMesh::CloneMeshFVF메서드가 이러한 일을 해줌 HRESULT ID3DXMesh::CloneMeshFVF( DWORD Options, DWORD FVF, LPDIRECT3DDEVICE9 pDevice, LPD3DXMESH* ppCloneMesh ); Options – 복제된 메쉬를 만드는 데 이용될 하나 이상의 플래그 D3DXMESH_32BIT – 메쉬는 32비트 인덱스를 이용하게 됨 D3DXMESH_MANAGED – 메쉬는 관리 메모리 풀 내에 보관 D3DXMESH_WRITEONLY – 메쉬 데이터에는 쓰기만 허용 D3DXMESH_DYNAMIC – 메쉬 버퍼는 동적으로 만들어짐 2009-1학기 컴퓨터게임(DirectX)
FVF – 복제된 메쉬를 만드는데 이용될 유연한 버텍스 포맷 pDevice – 복제된 메쉬와 연계될 장치 ppCloneMesh – 복제된 메쉬를 출력 이 메서드는 원본 메쉬와는 다른 유연한 버텍스 포맷의 목적지 메쉬를 위한 생성 옵션을 가지고 있음 예를들어, 유연한 버텍스 포맷 D3DFVF_XYZ의 메쉬를 가지고 있고, D3DFVF_XYZ | D3DFVF_NORMAL의 복제본을 만들고자 한다면, 다음과 같은 코드를 작성할 수 있음 ID3DXMesh* clone=0; Mesh->CloneMeshFVF( Mesh->GetOptions(), // 원본 메쉬와 같은 옵션 사용 D3DFVF_XYZ | D3DFVF_NORMAL, // 복제본 FVF 지정 Device, &clone); 2009-1학기 컴퓨터게임(DirectX)
메쉬 만들기(D3DXCreateMeshFVF) 빈 메쉬를 만들기 위해서는 먼저 원하는 메쉬를 구성할 면과 버텍스의 수를 지정하고, D3DXCreateMeshFVF를 통해 적절한 크기와 버텍스, 인덱스, 속성 버퍼를 할당하면 됨 메쉬 버퍼가 할당된 다음에는 메쉬의 데이터를 우리가 직접 채워 넣어야 하는데, 버텍스와 인덱스, 속성 데이터를 버텍스 버퍼와 인덱스 버퍼, 속성 버퍼에 각각 저장해야 함 D3DXCreateMeshFVF 함수는 다음과 같이 정의 HRESULT D3DXCreateMeshFVF( DWORD NumFaces, DWORD NumVertices, DWORD Options, DWORD FVF, 2009-1학기 컴퓨터게임(DirectX)
LPDIRECT3DDEVICE9 pDevice, LPD3DXMESH* ppMesh ); NumFaces – 메쉬가 가질 면의 수, 이 값은 반드시 0보다 커야 함 NumVertices – 메쉬가 가질 버텍스의 수. 이 값은 반드시 0보다 커야 함 Options – 메쉬를 만드는데 이용될 하나 이상의 생성 플래그. D3DXMESH_32BIT – 메쉬는 32비트 인덱스를 이용 D3DXMESH_MANAGED – 메쉬는 관리 메모리 풀 내에 보관 D3DXMESH_WRITEONLY – 메쉬 데이터에는 쓰기만 허용 DF3DXMESH_DYNAMIC – 메쉬 버퍼는 동적으로 만들어짐 FVF – 복제된 메쉬를 만드는 데 이용될 유연한 버텍스 포맷 pDevice – 복제된 메쉬와 연계될 장치 ppMesh – 복제된 메쉬를 출력 2009-1학기 컴퓨터게임(DirectX)
이 외에도 다음과 같은 프로토타입을 가진 D3DXCreateMesh 함수를 이용해 빈 메쉬를 만들 수 있음 HRESULT D3DXCreateMesh( DWORD NumFaces, DWORD NumVertices, DWORD Options, CONST LPD3DVERTEXELEMENT9* pDeclaration, LPDIRECT3DDEVICE9 pDevice, LPD3DXMESH* ppMesh ); 네 번째 인자를 제외한 나머지 인자들은 D3DXCreateMeshFVF 메서드와 비슷함. 이 메서드는 FVF 대신 버텍스의 포맷을 나타내는 D3DVERTEXELEMENT9 구조체의 배열을 지정하는데, 여기에 사용되는 함수는 다음과 같다 2009-1학기 컴퓨터게임(DirectX)
HRESULT D3DXDeclaratorFromFVF( DWORD FVF, // 입력 포맷 D3DVERTEXELEMENT9 Declaration[MAX_FVF_DECL_SIZE] // 출력 포맷 ); 이 함수는 입력받는 FVF에 대응되는 D3DVERTEXELEMENT9 구조체의 배열을 리턴. MAX_FVF_DECL_SIZE는 다음과 같이 정의 typedef enum { MAX_FVF_DECL_SIZE = 18 } MAX_FVF_DECL_SIZE; 2009-1학기 컴퓨터게임(DirectX)
예제 애플리케이션: 메쉬 생성과 렌더링 상자의 메쉬를 렌더링하는데 필요한 작업 빈 메쉬 만들기 상자의 기하정보로 메쉬를 채운다 메쉬의 각 면이 존재하는 서브셋을 지정 메쉬의 인접 정보를 만든다 메쉬를 최적화 메쉬를 그려냄 2009-1학기 컴퓨터게임(DirectX)
부가적으로 메쉬 요소의 디버깅과 검사를 위해서 메쉬의 내용을 파일로 덤프하는 다음과 같은 함수들을 구현 void dumpVertices(std::ofstream& outFile, ID3DXMesh* mesh); void dumpIndices(std::ofstream& outFile, ID3DXMesh* mesh); void dumpAttributeBuffer(std::ofstream& outFile, ID3DXMesh* mesh); void dumpAdjacencyBuffer(std::ofstream& outFile, ID3DXMesh* mesh); void dumpAttributeTable(std::ofstream& outFile, ID3DXMesh* mesh); 각 함수들의 역할은 함수의 이름으로 쉽게 짐작할 수 있을 것임 2009-1학기 컴퓨터게임(DirectX)
예제 코드 예제 코드는 다음과 같은 전역 변수 인스턴스화로 시작 ID3DXMesh* Mesh = 0; const DWORD NumSubsets = 3; IDirect3DTexture9* Textures[3]={0, 0, 0}; // 각 서브셋을 위한 텍스쳐 std::ofstream OutFile; // 메쉬 데이터를 파일에 덤프하는데 이용 2009-1학기 컴퓨터게임(DirectX)
Texutures 배열은 각 서브셋을 위한 텍스처를 보관하며 텍스처 배열의 i번째 인덱스는 메쉬의 i번째 서브셋과 대응 이후에 만들 메쉬를 위한 포인터를 인스턴스화 하였고, 메쉬가 가질 서브셋의 수를 세 개로 정의. 이 예제에서 각 서브셋은 서로 다른 텍스트로 렌더링 됨 Texutures 배열은 각 서브셋을 위한 텍스처를 보관하며 텍스처 배열의 i번째 인덱스는 메쉬의 i번째 서브셋과 대응 OutFile 변수는 메쉬의 내용을 텍스트 파일로 출력하는 데 이용. 이 객체를 dump* 함수로 전달 이 예제에서 수행하는 대부분의 작업들은 Setup함수에서 이루어지는데 이 함수는 먼저 빈 메쉬를 만들어 냄 2009-1학기 컴퓨터게임(DirectX)
hr = D3DXCreateMeshFVF( 12, 24, D3DXMESH_MANAGED, Vertex::FVF, Device, bool Setup() { HRESULT hr = 0; hr = D3DXCreateMeshFVF( 12, 24, D3DXMESH_MANAGED, Vertex::FVF, Device, &Mesh); 상자를 표현하는 데 필요한 12개의 면과 24개의 버텍스를 가진 메쉬를 할당 2009-1학기 컴퓨터게임(DirectX)
Mesh->LockVertexBuffer(0, (void**)&v); 현재 시점에서 메쉬는 빈 상태이므로 상자를 표현하기 위한 버텍스와 인덱스를 버텍스 버퍼와 인덱스 버퍼에 저장하는 작업이 필요. 다음 코드는 버텍스/인덱스 버퍼를 잠그고 직접 데이터를 쓰는 과정을 담당 Vertex* v = 0; Mesh->LockVertexBuffer(0, (void**)&v); // fill in the front face vertex data v[0] = Vertex(-1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f); … v[23] = Vertex( 1.0f, -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f); Mesh->UnlockVertexBuffer(); WORD* i = 0; Mesh->LockIndexBuffer(0, (void**)&i); 2009-1학기 컴퓨터게임(DirectX)
// fill in the front face index data i[0] = 0; i[1] = 1; i[2] = 2; … // 우측 인덱스 데이터를 채운다. i[30] = 20; i[31] = 21; i[32] = 22; i[33] = 20; i[34] = 22; i[35] = 23; Mesh->UnlockIndexBuffer(); 메쉬의 기하정보를 쓴 다음에는 각각의 삼각형이 존재하는 서브셋을 지정하는 과정을 잊어서는 안됨 메쉬 내의 각 삼각형이 속한 서브셋은 속성 버퍼에 보관됨 2009-1학기 컴퓨터게임(DirectX)
DWORD* attributeBuffer = 0; 이 예제에서는 인덱스 내에 정의된 처음 네 개의 삼각형을 서브셋 0으로 지정했으며, 다음 네 개의 삼각형을 서브셋 1로, 마지막 네 개의 삼각형을 서브셋 2로 지정. 코드로 나타내면 다음과 같음 DWORD* attributeBuffer = 0; Mesh->LockAttributeBuffer(0, &attributeBuffer); for(int a = 0; a < 4; a++) attributeBuffer[a] = 0; for(int b = 4; b < 8; b++) attributeBuffer[b] = 1; for(int c = 8; c < 12; c++) attributeBuffer[c] = 2; Mesh->UnlockAttributeBuffer(); 2009-1학기 컴퓨터게임(DirectX)
std::vector<DWORD> adjacencyBuffer(Mesh->GetNumFaces() * 3); 이제 올바른 데이터를 포함하는 메쉬를 만들었으므로 바로 렌더링이 가능하지만, 먼저 최적화를 수행하면, 우선 메쉬의 인접 정보를 계산해야 함 std::vector<DWORD> adjacencyBuffer(Mesh->GetNumFaces() * 3); Mesh->GenerateAdjacency(0.0f, &adjacencyBuffer[0]); 이제는 다음과 같이 최적화할 수 있음 hr = Mesh->OptimizeInplace( D3DXMESHOPT_ATTRSORT | D3DXMESHOPT_COMPACT | D3DXMESHOPT_VERTEXCACHE, &adjacencyBuffer[0], 0, 0, 0); 2009-1학기 컴퓨터게임(DirectX)
메쉬를 구성하는 과정은 모두 끝났으며 이를 렌더링하는 과정만이 남아있음 Setup 함수에 관련된 코드가 약간 남았는데. 이 코드는 앞서 언급한 dump* 함수를 이용해 메쉬의 내부 데이터 내용을 파일로 출력하는 부분임. 메쉬의 데이터를 직접 확인할 수 있다면 디버깅이나 메쉬의 구조를 배우는 과정이 훨씬 수월해짐 OutFile.open("Mesh Dump.txt"); dumpVertices(OutFile, Mesh); dumpIndices(OutFile, Mesh); dumpAttributeTable(OutFile, Mesh); dumpAttributeBuffer(OutFile, Mesh); dumpAdjacencyBuffer(OutFile, Mesh); OutFile.close(); 2009-1학기 컴퓨터게임(DirectX)
예를 들어, 속성 테이블 데이터를 파일로 출력하는 dumpAttributeTable 함수는 다음과 같이 구현 void dumpAttributeTable(std::ofstream& outFile, ID3DXMesh* mesh) { outFile << "Attribute Table:" << std::endl; outFile << "----------------" << std::endl << std::endl; // number of entries in the attribute table DWORD numEntries = 0; mesh->GetAttributeTable(0, &numEntries); std::vector<D3DXATTRIBUTERANGE> table(numEntries); mesh->GetAttributeTable(&table[0], &numEntries); 2009-1학기 컴퓨터게임(DirectX)
for(int i = 0; i < numEntries; i++) { outFile << "Entry " << i << std::endl; outFile << "-----------" << std::endl; outFile << "Subset ID: " << table[i].AttribId << std::endl; outFile << "Face Start: " << table[i].FaceStart << std::endl; outFile << "Face Count: " << table[i].FaceCount << std::endl; outFile << "Vertex Start: " << table[i].VertexStart << std::endl; outFile << "Vertex Count: " << table[i].VertexCount << std::endl; outFile << std::endl; } outFile << std::endl << std::endl; } 2009-1학기 컴퓨터게임(DirectX)
Attribute Table: ---------------- Entry 0 ----------- Subset ID: 0 다음의 텍스트는 예제 프로그램이 출력한 dump.txt 파일의 내용 중 dumpAttributeTable함수가 만들어낸 데이터임 Attribute Table: ---------------- Entry 0 ----------- Subset ID: 0 Face Start: 0 Face Count: 4 Vertex Start: 0 Vertex Count: 8 2009-1학기 컴퓨터게임(DirectX)
Entry 1 ----------- Subset ID: 1 Face Start: 4 Face Count: 4 Vertex Start: 8 Vertex Count: 8 Entry 2 Subset ID: 2 Face Start: 8 Vertex Start: 16 2009-1학기 컴퓨터게임(DirectX)
이 데이터를 보면 우리가 메쉬에 지정했던 세 개의 서브셋과 각 서브셋 당 네 개의 삼각형이 포함되어 있음을 알 수 있음 다음과 같은 코드를 이용해 간단하게 메쉬를 렌더링함. 근복적으로 이 코드는 각 서브셋을 대상으로 반복하여 연결된 텍스처를 지정하고 서브셋을 드로잉하는데, 이렇게 손쉽게 드로잉이 수행될 수 있는 이유는 0,1,2,…,n-1 구조로 서브셋을 구성했기 때문 bool Display(float timeDelta) { if( Device ) //…프레임 갱신 코드는 생략 Device->Clear(0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0x00000000, 1.0f, 0); Device->BeginScene(); 2009-1학기 컴퓨터게임(DirectX)
for(int i = 0; i < NumSubsets; i++) { Device->SetTexture( 0, Textures[i] ); Mesh->DrawSubset( i ); } Device->EndScene(); Device->Present(0, 0, 0, 0); return true; 2009-1학기 컴퓨터게임(DirectX)
2009-1학기 컴퓨터게임(DirectX)
#include <fstream> #include <vector> #include "d3dUtility.h" #include <fstream> #include <vector> IDirect3DDevice9* Device = 0; const int Width = 640; const int Height = 480; ID3DXMesh* Mesh = 0; const DWORD NumSubsets = 3; IDirect3DTexture9* Textures[3] = {0, 0, 0};// texture for each subset std::ofstream OutFile; // used to dump mesh data to file void dumpVertices(std::ofstream& outFile, ID3DXMesh* mesh); 2009-1학기 컴퓨터게임(DirectX)
void dumpIndices(std::ofstream& outFile, ID3DXMesh* mesh); void dumpAttributeBuffer(std::ofstream& outFile, ID3DXMesh* mesh); void dumpAdjacencyBuffer(std::ofstream& outFile, ID3DXMesh* mesh); void dumpAttributeTable(std::ofstream& outFile, ID3DXMesh* mesh); struct Vertex { Vertex(){} Vertex(float x, float y, float z, float nx, float ny, float nz, float u, float v) _x = x; _y = y; _z = z; _nx = nx; _ny = ny; _nz = nz; _u = u; _v = v; } float _x, _y, _z, _nx, _ny, _nz, _u, _v; 2009-1학기 컴퓨터게임(DirectX)
static const DWORD FVF; }; const DWORD Vertex::FVF = D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX1; bool Setup() { HRESULT hr = 0; hr = D3DXCreateMeshFVF( 12, 24, D3DXMESH_MANAGED, Vertex::FVF, Device, &Mesh); 2009-1학기 컴퓨터게임(DirectX)
::MessageBox(0, "D3DXCreateMeshFVF() - FAILED", 0, 0); return false; } if(FAILED(hr)) { ::MessageBox(0, "D3DXCreateMeshFVF() - FAILED", 0, 0); return false; } Vertex* v = 0; Mesh->LockVertexBuffer(0, (void**)&v); // fill in the front face vertex data v[0] = Vertex(-1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f); v[1] = Vertex(-1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f); v[2] = Vertex( 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f); v[3] = Vertex( 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f); // fill in the back face vertex data v[4] = Vertex(-1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f); 2009-1학기 컴퓨터게임(DirectX)
v[5] = Vertex( 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f); // fill in the top face vertex data v[8] = Vertex(-1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f); v[9] = Vertex(-1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f); v[10] = Vertex( 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f); v[11] = Vertex( 1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f); // fill in the bottom face vertex data v[12] = Vertex(-1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f); v[13] = Vertex( 1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f); v[14] = Vertex( 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f); v[15] = Vertex(-1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f); // fill in the left face vertex data v[16] = Vertex(-1.0f, -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f); 2009-1학기 컴퓨터게임(DirectX)
v[17] = Vertex(-1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f); // fill in the right face vertex data v[20] = Vertex( 1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f); v[21] = Vertex( 1.0f, 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f); v[22] = Vertex( 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f); v[23] = Vertex( 1.0f, -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f); Mesh->UnlockVertexBuffer(); WORD* i = 0; Mesh->LockIndexBuffer(0, (void**)&i); // fill in the front face index data i[0] = 0; i[1] = 1; i[2] = 2; i[3] = 0; i[4] = 2; i[5] = 3; 2009-1학기 컴퓨터게임(DirectX)
// fill in the back face index data i[6] = 4; i[7] = 5; i[8] = 6; // fill in the top face index data i[12] = 8; i[13] = 9; i[14] = 10; i[15] = 8; i[16] = 10; i[17] = 11; // fill in the bottom face index data i[18] = 12; i[19] = 13; i[20] = 14; i[21] = 12; i[22] = 14; i[23] = 15; // fill in the left face index data i[24] = 16; i[25] = 17; i[26] = 18; i[27] = 16; i[28] = 18; i[29] = 19; // fill in the right face index data 2009-1학기 컴퓨터게임(DirectX)
Mesh->UnlockIndexBuffer(); DWORD* attributeBuffer = 0; i[30] = 20; i[31] = 21; i[32] = 22; i[33] = 20; i[34] = 22; i[35] = 23; Mesh->UnlockIndexBuffer(); DWORD* attributeBuffer = 0; Mesh->LockAttributeBuffer(0, &attributeBuffer); for(int a = 0; a < 4; a++) attributeBuffer[a] = 0; for(int b = 4; b < 8; b++) attributeBuffer[b] = 1; for(int c = 8; c < 12; c++) attributeBuffer[c] = 2; Mesh->UnlockAttributeBuffer(); 2009-1학기 컴퓨터게임(DirectX)
std::vector<DWORD> adjacencyBuffer(Mesh->GetNumFaces() * 3); Mesh->GenerateAdjacency(0.0f, &adjacencyBuffer[0]); hr = Mesh->OptimizeInplace( D3DXMESHOPT_ATTRSORT | D3DXMESHOPT_COMPACT | D3DXMESHOPT_VERTEXCACHE, &adjacencyBuffer[0], 0, 0, 0); OutFile.open("Mesh Dump.txt"); dumpVertices(OutFile, Mesh); dumpIndices(OutFile, Mesh); dumpAttributeTable(OutFile, Mesh); dumpAttributeBuffer(OutFile, Mesh); dumpAdjacencyBuffer(OutFile, Mesh); 2009-1학기 컴퓨터게임(DirectX)
D3DXCreateTextureFromFile( Device, "brick0.jpg", &Textures[0]); OutFile.close(); D3DXCreateTextureFromFile( Device, "brick0.jpg", &Textures[0]); "brick1.jpg", &Textures[1]); "checker.jpg", &Textures[2]); 2009-1학기 컴퓨터게임(DirectX)
Device->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR); Device->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR); Device->SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_POINT); Device->SetRenderState(D3DRS_LIGHTING, false); D3DXVECTOR3 pos(0.0f, 0.f, -4.0f); D3DXVECTOR3 target(0.0f, 0.0f, 0.0f); D3DXVECTOR3 up(0.0f, 1.0f, 0.0f); D3DXMATRIX V; D3DXMatrixLookAtLH( &V, &pos, &target, &up); 2009-1학기 컴퓨터게임(DirectX)
Device->SetTransform(D3DTS_VIEW, &V); D3DXMATRIX proj; D3DXMatrixPerspectiveFovLH( &proj, D3DX_PI * 0.5f, // 90 - degree (float)Width / (float)Height, 1.0f, 1000.0f); Device->SetTransform(D3DTS_PROJECTION, &proj); return true; } void Cleanup() { 2009-1학기 컴퓨터게임(DirectX)
d3d::Release<ID3DXMesh*>(Mesh); d3d::Release<IDirect3DTexture9*>(Textures[0]); d3d::Release<IDirect3DTexture9*>(Textures[1]); d3d::Release<IDirect3DTexture9*>(Textures[2]); } bool Display(float timeDelta) { if( Device ) D3DXMATRIX xRot; D3DXMatrixRotationX(&xRot, D3DX_PI * 0.2f); static float y = 0.0f; D3DXMATRIX yRot; D3DXMatrixRotationY(&yRot, y); y += timeDelta; 2009-1학기 컴퓨터게임(DirectX)
D3DXMATRIX World = xRot * yRot; if( y >= 6.28f ) y = 0.0f; D3DXMATRIX World = xRot * yRot; Device->SetTransform(D3DTS_WORLD, &World); Device->Clear(0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0x00000000, 1.0f, 0); Device->BeginScene(); for(int i = 0; i < NumSubsets; i++) { Device->SetTexture( 0, Textures[i] ); Mesh->DrawSubset( i ); } Device->EndScene(); 2009-1학기 컴퓨터게임(DirectX)
Device->Present(0, 0, 0, 0); } return true; LRESULT CALLBACK d3d::WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch( msg ) case WM_DESTROY: ::PostQuitMessage(0); break; case WM_KEYDOWN: if( wParam == VK_ESCAPE ) ::DestroyWindow(hwnd); 2009-1학기 컴퓨터게임(DirectX)
return ::DefWindowProc(hwnd, msg, wParam, lParam); } return ::DefWindowProc(hwnd, msg, wParam, lParam); int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE prevInstance, PSTR cmdLine, int showCmd) { if(!d3d::InitD3D(hinstance, Width, Height, true, D3DDEVTYPE_HAL, &Device)) ::MessageBox(0, "InitD3D() - FAILED", 0, 0); return 0; if(!Setup()) 2009-1학기 컴퓨터게임(DirectX)
::MessageBox(0, "Setup() - FAILED", 0, 0); return 0; } d3d::EnterMsgLoop( Display ); Cleanup(); Device->Release(); void dumpVertices(std::ofstream& outFile, ID3DXMesh* mesh) { outFile << "Vertices:" << std::endl; outFile << "---------" << std::endl << std::endl; 2009-1학기 컴퓨터게임(DirectX)
mesh->LockVertexBuffer(0, (void**)&v); Vertex* v = 0; mesh->LockVertexBuffer(0, (void**)&v); for(int i = 0; i < mesh->GetNumVertices(); i++) { outFile << "Vertex " << i << ": ("; outFile << v[i]._x << ", " << v[i]._y << ", " << v[i]._z << ", "; outFile << v[i]._nx << ", " << v[i]._ny << ", " << v[i]._nz << ", "; outFile << v[i]._u << ", " << v[i]._v << ")" << std::endl; } mesh->UnlockVertexBuffer(); outFile << std::endl << std::endl; void dumpIndices(std::ofstream& outFile, ID3DXMesh* mesh) outFile << "Indices:" << std::endl; 2009-1학기 컴퓨터게임(DirectX)
outFile << "--------" << std::endl << std::endl; WORD* indices = 0; mesh->LockIndexBuffer(0, (void**)&indices); for(int i = 0; i < mesh->GetNumFaces(); i++) { outFile << "Triangle " << i << ": "; outFile << indices[i * 3 ] << " "; outFile << indices[i * 3 + 1] << " "; outFile << indices[i * 3 + 2] << std::endl; } mesh->UnlockIndexBuffer(); outFile << std::endl << std::endl; 2009-1학기 컴퓨터게임(DirectX)
void dumpAttributeBuffer(std::ofstream& outFile, ID3DXMesh* mesh) { outFile << "Attribute Buffer:" << std::endl; outFile << "-----------------" << std::endl << std::endl; DWORD* attributeBuffer = 0; mesh->LockAttributeBuffer(0, &attributeBuffer); // an attribute for each face for(int i = 0; i < mesh->GetNumFaces(); i++) outFile << "Triangle lives in subset " << i << ": "; outFile << attributeBuffer[i] << std::endl; } mesh->UnlockAttributeBuffer(); outFile << std::endl << std::endl; 2009-1학기 컴퓨터게임(DirectX)
void dumpAdjacencyBuffer(std::ofstream& outFile, ID3DXMesh* mesh) { outFile << "Adjacency Buffer:" << std::endl; outFile << "-----------------" << std::endl << std::endl; // three enttries per face std::vector<DWORD> adjacencyBuffer(mesh->GetNumFaces() * 3); mesh->GenerateAdjacency(0.0f, &adjacencyBuffer[0]); for(int i = 0; i < mesh->GetNumFaces(); i++) outFile << "Triangle's adjacent to triangle " << i << ": "; outFile << adjacencyBuffer[i * 3 ] << " "; outFile << adjacencyBuffer[i * 3 + 1] << " "; outFile << adjacencyBuffer[i * 3 + 2] << std::endl; } 2009-1학기 컴퓨터게임(DirectX)
outFile << std::endl << std::endl; } void dumpAttributeTable(std::ofstream& outFile, ID3DXMesh* mesh) { outFile << "Attribute Table:" << std::endl; outFile << "----------------" << std::endl << std::endl; // number of entries in the attribute table DWORD numEntries = 0; mesh->GetAttributeTable(0, &numEntries); std::vector<D3DXATTRIBUTERANGE> table(numEntries); mesh->GetAttributeTable(&table[0], &numEntries); 2009-1학기 컴퓨터게임(DirectX)
for(int i = 0; i < numEntries; i++) { outFile << "Entry " << i << std::endl; outFile << "-----------" << std::endl; outFile << "Subset ID: " << table[i].AttribId << std::endl; outFile << "Face Start: " << table[i].FaceStart << std::endl; outFile << "Face Count: " << table[i].FaceCount << std::endl; outFile << "Vertex Start: " << table[i].VertexStart << std::endl; outFile << "Vertex Count: " << table[i].VertexCount << std::endl; outFile << std::endl; } outFile << std::endl << std::endl; 2009-1학기 컴퓨터게임(DirectX)