메쉬 파트 1
ID3DXMesh 인터페이스는 기능의 상당 부분을 부모인 ID3DXBaseMesh에서 상속받는다.ID3DXPMesh(프로그레시브 메쉬) 와 같은 다른 인터페이스 역시 ID3DXBaseMesh에서 상속받는다.
10.1 기하 정보
ID3DXBaseMesh 인터페이스는 메쉬의 버텍스를 보관하는 버텍스 버퍼와 이들 버텍스가 어떻게 메쉬의 삼각형들을 구성하는가를 정의하는 인덱스 버퍼를 포함한다.다음의 두 가지 메서드를 이용해 이들 버퍼로의 포인터를 얻을 수 있다.
HRESULT ID3DXMesh::GetVertexBuffer(LPDIRECT3DVERTEXBUFFER9 ppVB);
HRESULT ID3DXMesh::GetIndexBuffer(LPDIRECT3DINDEXBUFFER9 ppIB);
다음은 이들 메서드를 이용하는 예이다.
IDirect3DVertexBuffer9* vb = 0;
Mesh->GetVertexBuffer(&vb);
IDirect3DIndexBuffer9* ib = 0
Mesh->GetIndexBuffer(&ib);
-NOTE-
ID3DXMesh 인터페이스는 순수한 정보를 제공하기 위한 목적으로 기본형 타입의 인덱스화된 삼각형 리스트를 지원한다.
만약 버퍼 쓰기나 읽기를 위해 버퍼 잠금이 필요하다면 다음 두 가지 메서드를 이용할 수 있다. 이들 메서드는 전체 VI 버퍼를 잠근다.
HRESULT ID3DMesh::LockVertexBuffer(DWORD Flags, BYTE ppData);
HRESULT ID3DMesh::LockIndexBuffer(DWORD Flags, BYTE ppData);
Flags 인자는 잠금 방식을 지정하는 데 쓰인다. ppData 인자는 함수가 리턴할 때 잠근 메모리를 가리킬 포인터의 주소이다.
버퍼를 잠그고 작업을 마친 뒤에는 대응되는 unlock 메서드를 호출해야 한다.
HRESULT ID3DXMesh::UnlockVertexBuffer();
HRESULT ID3DXMesh::UnlockIndexBuffer();
다음은 기하 관련 정보를 얻기 위한 부가적인 ID3DXMesh 메서드의 리스트이다.
DWORD GetFVF() - 버텍스의 버텍스 포맷을 나타내는 DWORD를 리턴한다.
DWORD GetNumVertices() -버텍스 버퍼 내의 버텍스의 수를 리턴한다.
DWORD GetNumBytesPerVertex() -버텍스 당 바이트의 수를 리턴한다.
DWORD GetNumFaces() - 메쉬 내의 면(삼각형)의 수를 리턴한다.
10.2 서브셋과 속성 버퍼
메쉬는 하나 이상의 서브셋으로 구성됨.
서브셋 : 동일한 속성을 이용해 렌더링할 수 있는 메쉬 내 삼각형들의 그룹
여기서 속성이란 재질과 텍스처,렌더 상태를 의미한다.
각각의 서브셋에는 DWORD 양의 정수 값을 지정하여 서로 구분한다.
메쉬 내의 삼각형들은 각 삼각형이 위치하는 서브셋을 지정하는 속성 ID를 가진다.
삼각형의 속성 ID는 메쉬의 DWORD 배열인 속성 버퍼 내에 보관된다.각각으 ㅣ면들은 속성 버퍼 내에 하나의 항목을 가지므로,속성 버퍼 내의 요소 수는 메쉬 내의 면의 수와 같게 된다. 속성 버퍼 내의 항목과 인덱스 버퍼에 정의된 삼각형들은 1:1 대응관게를 가지므로 속성 버퍼 내의 항목 i는 인덱스 버퍼 내의 삼각형 i와 대응된다. 삼각형 i는 인덱스 버퍼의 다음 세 개의 인덱스에 의해 정의된다.
A = i 3;
B = i 3 + 1;
C = i * 3 + 2;
다음의 코드에서와 같이,속성 버퍼에 접근하기 위해서는 먼저 버퍼를 잠가야 한다.
DWORD* buffer = 0;
Mesh->LockAttributeBuffer(lockingFlags,&buffer);
//속성 버퍼를 읽거나 쓰는 작업
Mesh->UnlockAttributeBuffer();
10.3 드로잉
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);
}
10.4 최적화
메쉬 최적화 : 좀더 효과적으로 메쉬를 렌더링하기 위해서는 버텍스와 인덱스를 재구성하는 것
HRESULT ID3DXMesh::OptimizeInplace(
DWORD Flags, - 수행할 최적화의 종류를 알려주는 최적화 플래그
CONST DWROD pAdjacencyIn,- 최적화되지 않은 메쉬의 인접 배열로의 포인터
DWORD pAdjacencyOut, - 최적화된 메쉬의 인접 정보로 채워질 DWORD 배열로의 포인터. 이 배열은 반드시 ID3DXMesh::GetNumFaces() 3수의 요소를 가지고 있어야 한다. 만약 이정보가 필요치 않다면 0을 전달한다.
DWORD pFaceRemap, - 면 리맵 정보로 채워질 DWORD 배열로의 포인터.이 배열은 반드시 ID3DXMesh::GetNumFaces() 크기를 가져야 한다. 메쉬를 최적화하면 메쉬의 면들이 인덱스 버퍼 내에서 이동하게 되며, 면 리맵 정보는 원래의 면이 어느 곳으로 이동했는지를 알려준다. 즉, pFaceRemap 내의 i번째 항목은 원래의 i번째 면이 어느 곳으로 이동했는지를 알려주는 면 인덱스를 포함한다. 만약 이 정보가 필요치 않다면 0을 전달한다.
LPD3DXBUFFER* ppVertexRemap - 버텍스 리맵 정보를 채워질 ID3DXBuffer 포인터의 주소. 이 버퍼는 iD3DXMesh::GetNumVertices() 만큼의 항목을 포함해야 한다. 메쉬를 최적화 하면 메쉬의 버텍스들이 버텍스 버퍼 내에서 이동하게 되며, 버텍스 리맵 정보는 원본 버텍스가 어느 곳으로 이동했는지를 알려준다.즉, ppVertexRemap 내의 i번째 항목은 i번째 원본 버텍스가 어느 곳으로 이동했는지를 알려주는 버텍스 인덱스를 퐇마한다. 만약 이 정보가 필요치 않다면 0을 전달한다.
);
호출 예:
//최적화되지 않은 메쉬의 인접 정보를 얻는다.
DWORD adjacencyInfo[Mesh->GetNumFaces() * 3];
Mesh->GenerateAdjacency(0.0f, adjacencyInfo);
//최적화된 인접 정보를 보관할 배열
DWORD optimizedAdjacencyInfo[Mesh->GetNumFaces() * 3];
Mesh->OptimizeInplace(
D3DXMESHOPT_ATTRSORT |
D3DXMESHOPT_COMPACT |
D3DXMESHOPT_VERTEXCACHE,
adjacencyInfo,
optimizedAdjacencyInfo,
0,
0);
);
비슷한 메서드로 Optimize 메서드가 있다. 이 메서드는 직접 호출 메쉬 객체를 최적화하는 대신 최적화된 버전의 메쉬 객체를 리턴한다.
10.5 속성 테이블
D3DXMESHOPT_ATTSORT 플래그를 설정하여 메쉬를 최적화하면 메쉬의 기하정보가 그 속성에 따라 정렬되므로,VI버퍼 내에 있는 특정 부분의 기하정보가 연속된 블록에 위치하게 된다.
D3DXMESHOPT_ATTSORT 최적화를 수행하면 기하정보 정렬과는 별도로 속성 테이블이 만들어진다. 속성 테이블은 D3DXATTRIBUTERANGE 구조체의 배열로, 테이블의 각 항목은 메쉬의 각 서브셋과 대응되며 서브셋의 기하정보들이 보관되는 VI버퍼 내의 메모리 블록을 지정한다.
typedef struct _D3DXATTRIBUTERANGE{
DWORD AttribID; - 서브셋 ID
DWORD FaceStart; -현재의 서브셋과 연결된 삼각형들의 시작을 지정하는 인덱스 버퍼(FaceStart * 3)로의 오프셋
DWORD FaceCount; - 서브셋 내의 면(삼각형)의 수
DWORD VertexStart; - 현재의 서브셋과 연결된 버텍스들의 시작을 지정하는 버텍스 버퍼로의 오프셋
DWROD VertexCount; - 서브셋 내의 버텍스의 수
} D3DXATTRIBUTERANGE;
일단 속성 테이블을 구성한 뒤에는 간단한 속성 테이블 검색만으로 특정한 서브셋 내의 모든 기하정보를 찾을 수 있게 되므로,서브셋 렌더링의 효율이 극적으로 향상될 수 있다.만약 속성 테이블을 이용할 수 없다면 우리가 그리고자 하는 서브셋에 존재하는 기하정보들을 찾기 위해 전체 속성 버퍼에 대한 선형 검색이 필요할것이다.
메쉬의 속성 테이블에 접근하는 데는 다음 메서드가 사용된다.
HRESULT ID3DXMesh::GetAttributeTable(
D3DXATTRIBUTERANGE pAttribTable,
DWORD pAttribTableSize
};
이 메서드는 두 가지 기능을 가지고 있다. 우선 속성 테이블 내의 속성의 수를 리턴 할 수 있으며,속성 데이터로 D3DXATTRIBUTERANGE 구조체의 배열을 채울 수 있다.
속성 테이블 내의 요소의 수를 얻기 위해서는 첫번째 인자에 0을 전달한다.
DWORD numSubsets = 0;
Mesh->GetAttributeTable(0, &numSubsets);
요소의 수를 알아내면 속성 테이블을 D3DXATTRIBUTERANGE 배열로 채울 수 있다.
D3DXATTRIBUTERANGE table = new D3DXATTRIBUTERANGE[numSubsets];
Mesh->GetAttributeTable(table, &numSubsets);
ID3DXMesh::SetAttributeTable 메서드를 이용해 속성 테이블을 직접 지정하는 것도 가능하다. 다음은 속성 테이블을 12개의 서브셋으로 지정한 예이다.
D3DXATTRIBUTERANGE attributeTable[12];
// ...attributeTable 배열을 데이터로 채운다.
Mesh->SetAttributeTable(attributeTable,12);
10.6 근접 정보
메쉬의 인접 배열은 최적화와 같은 특수한 메쉬 처리를 위해서 주어진 삼각형과 인접한 다른 삼각형에 대한 정보를 보관한다.
인접배열의 각 DWORD 항목은 메쉬 내의 삼각형을 식별하는 인덱스이다.예를 들어,항목 i를 이용해 다음과 같은 인덱스로 구성되는 삼각형을 정의할 수 있다.
A = i 3; B = i 3 + 1; C= i *3 + 2;
특정한 면이 인접한 삼각형을 가지고 있지 않음을 의미하는 항목(ULONG_MAX = 4294967295)이 있음을 기억해두자. -1을 이용할 수도 있는데, 이것은 DWORD에 -1을 할당하면 결국 ULONG_MAX가 되기 때문이다(Unsinged 자료형임)
따라서 인접 배열은 메쉬 내 모든 삼각형들의 인접한 세 삼각형을 위해 (ID3DXBaseMesh::GetNumFaces() * 3) 만큼의 요소를 가져야 한다.
D3DX의 메쉬 생성 함수들 상당수가 인접 정보를 만들어내지만 다음의 함수를 이용할 수도 있다.
HRESULT ID3DXMesh::GenerateAdjacency{
FLOAT fEpsilon,- 두 개의 포인트를 동일한 것으로 취급할 거리 근사값.예를 들어, 두 포인트 간의 거리가 근사값보다 작다면 이를 같은 것으로 취급할 수 있다.
DOWRD* pAdjacency - 인접 정보로 채워질 DWORD 배열로의 포인터
};
사용 예
DWORD adjacencyInfo[Mesh->GetNumFaces() *3]
Mesh->GenerateAdjacency(0.001f,adjacencyInfo);
10.7 복제
때로는 한 메쉬의 데이터를 다른 곳으로 복사할 필요가 있으며,ID3DXBaseMesh::CloneMeshFVF 메서드가 이러한 일을 해준다.
HRESULT ID3DXMesh::CloneMeshFVF(
DWORD Options,-복제된 메쉬를 만드는 데 이용될 하나 이상의 플래그
DWORD FVF, - 복제된 메쉬를 만드는 데 이용될 유연한 버텍스 포맷
LPDIRECT3DDEVICE9 pDevice, - 복제된 메쉬와 연계될 장치
LPD3DXMESH* ppCloneMesh - 복제된 메쉬를 출력한다.
);
이 메서드는 원본 메쉬와는 다른 유연한 버텍스 포맷의 목적지 메쉬를 위한 생성 옵션을 가지고 있다는 것을 기억하자. 예를 들어,유연한 버텍스 포맷 D3DFVF_XYZ의 메쉬를 가지고 있고, D3DFVF_XYZ | D3DFVF_NORMAL의 복제본을 만들고자 한다면, 다음과 같은 코드를 작성할 수 있다.
ID3DXMesh* clone = 0;
Mesh->CloneMeshFVF(
Mesh->GetOptions(), // 원본 메쉬와 같은 옵션을 이용한다.
D3DFVF_XYZ | D3DFVF_NORMAL, //복제본 FVF를 지정한다.
Device,
&clone);
10.8 메쉬 만들기(D3DXCreateMeshFVF)
D3DXCreateMeshFVF 메서드를 이용해 비어있는 메쉬를 만드는 것도 가능하다. 빈 메쉬를 만들기 위해서는 먼저 원하는 메쉬를 구성할 면과 버텍스의 수를 지정하고, D3DXCreatMeshFVF를 통해 적절한 크기와 버텍스,인덱스,속성 버퍼를 할당하면된다. 메쉬 버퍼가 할당된 다음에는 메쉬의 데이터를 우리가 직접 채워 넣어야 하는데, 즉 버텍스와 인덱스,속성 데이터를 버텍스 버퍼와 인덱스 버퍼,속성 버퍼에 각각 저장해야 한다.
HRESULT D3DXCreateMeshFVF{
DWORD NumFaces, -메쉬가 가질 면의 수(0보다 커야함)
DWORD NumVertices -메쉬가 가질 버텍스의수(0보다 커야함)
DWORD Options - 메쉬를 만드는 데 이용될 하나 이상의 생성 플래그
DWORD FVF,-복제된 메쉬를 만드는 데 이용될 유연한 버텍스 포맷
LPDIRECT3DDEVICE9 pDevice -복제된 메쉬와 연계될 장치
LPD3dXMESH* ppMesh - 복제된 메쉬를 출력한다.
}
FVF대신 버텍스의 포맷을 나타내는 D3DVERTEXELEMENT9 구조체의 배열을 지정하는 D3DXCreateMeshFVF 함수도 있디.
10.10 요약
*메쉬는 버텍스 버퍼와 인덱스 버퍼,속성 버퍼를 포함한다. VI버퍼는 메쉬의 기하정보(버텍스와 삼각형)를 보관한다. 속성 버퍼는 각각의 삼각형과 이 삼각형이 존재하는 서브셋을 지정한다.
*OptimizeInplace나 Optimaize 메서드를 이용해 메쉬를 최적화할 수 있다. 최적화 작업은 메쉬의 기하정보를 재구성하여 효괴적으로 렌더링을 수행할 수 있도록 해준다. D3DXMESHOPT_ATTSORT플래그를 이용한 최적화 작업을 통해 속성 테이블을 만들 수 있으며, 속성 테이블을 이용하면 테이블에 대한 간단한 조회를 통해 전체 서브셋을 렌더링할 수 있도록 해준다.
*메쉬의 인접 정보는 메쉬 내 각각의 삼각형 당 세 개의 항목을 가지는 DWORD 배열이다. 세 개의 항목은 각각의 삼각형과 인접한 세 개의 삼각형과 대응된다.
*D3DXCreateMeshFVF 함수를 이용해 빈 메쉬를 만들 수 있다. 메쉬를 만든 뒤에는 적절한 잠금 메서드를 호출하여 메쉬에 데이터를 쓸 수 있다.