D3DXLoadMeshFromX 사용하기


계층적인 데이터를 사용할 때는 D3DXLoadMeshHierarchyFromX()를 사용해야 되지만 간단한 메쉬를 렌드링 할 때는
D3DXLoadMeshFromX()를 사용한다.

LoadMesh.h 파일을 보면

bool LoadMesh(LPDIRECT3DDEVICE9 pDevice);
bool RnderMesh(LPDIRECT3DDEVICE9 pDevice, float timeDelta);
void CleanupMesh();

3개의 파일이 특별한 설명이 없더라도 함수이름에서 기능을 유추 해낼 수 있을 것이다.
하나씩 차근차근 알아보자.

메쉬 로딩

bool LoadMesh(LPDIRECT3DDEVICE9 pDevice)
{
	HRESULT hr = 0;

	ID3DXBuffer* adjBuffer  = 0;
	ID3DXBuffer* mtrlBuffer = 0;
	DWORD        numMtrls   = 0;

	if(FAILED(D3DXLoadMeshFromX("bigship1.x", D3DXMESH_MANAGED, pDevice,
			&adjBuffer, &mtrlBuffer, 0, &numMtrls, &g_mesh)))
							return false;

	if( mtrlBuffer != 0 && numMtrls != 0 )
	{
		D3DXMATERIAL* mtrls = (D3DXMATERIAL*)mtrlBuffer->GetBufferPointer();

		for(int i = 0; i < numMtrls; i++)
		{
			//MatD3D 속성은 ambient 값을 가지지 않으므로 지금 이를 저장한다.
			mtrls[i].MatD3D.Ambient = mtrls[i].MatD3D.Diffuse;
			g_mtrls.push_back( mtrls[i].MatD3D );

			if( mtrls[i].pTextureFilename != 0 )
			{
				IDirect3DTexture9* tex = 0;
				D3DXCreateTextureFromFile(pDevice, 
				             mtrls[i].pTextureFilename, &tex);

				g_textures.push_back( tex );
			}
			else
				g_textures.push_back( 0 );
		}
	}

	_RELEASE_(mtrlBuffer); 

	// Optimize the mesh.
	hr = g_mesh->OptimizeInplace(
	        D3DXMESHOPT_ATTRSORT | D3DXMESHOPT_COMPACT | D3DXMESHOPT_VERTEXCACHE,
		(DWORD*)adjBuffer->GetBufferPointer(), 0, 0, 0);

	_RELEASE_(adjBuffer); 

	if(FAILED(hr))
		return false;

	return true;
}
http://www.moon-labs.com

ID3DXBuffer :
D3DX가 연속적인 메모리 블록에 데이터를 저장하기 위해 이용하는 범용 데이터 구조체이다.
ID3DXBuffer는 COM 객체이므로 사용이 끝난 뒤에는 메모리 누출을 막기 위해 객체를 해제해야 한다.

LPVOID GetBufferPointer() - 데이터의 시작을 가리키는 포인터를 리턴한다.
DWORD GetBufferSize() - 버퍼 크기를 바이트 수로 리턴한다.

ID3DXBuffer 사용방법은 캐스팅 연산자로 변환시키면 된다.

D3DXMATERIAL* mtrls = (D3DXMATERIAL*)mtrlBuffer->GetBufferPointer();

HRESULT WINAPI D3DXLoadMeshFromX(
    LPCTSTR pFilename,
    DWORD Options,
    LPDIRECT3DDEVICE9 pD3DDevice,
    LPD3DXBUFFER *ppAdjacency,
    LPD3DXBUFFER *ppMaterials,
    LPD3DXBUFFER *ppEffectInstances,
    DWORD *pNumMaterials,
    LPD3DXMESH *ppMesh );

ppMaterials의 버퍼는 아래와 구조체로 이뤄져 있다.

typedef struct D3DXMATERIAL 
{
         D3DMATERIAL9 MatD3D;
         LPSTR pTextureFilename;
} D3DXMATERIAL;

ppMaterials 버퍼의 값을 D3DMATERIAL9 객체와 IDirect3DTexture9* 객체의 배열에 저장하면 된다.
여기서는 vector를 사용하고 있다.
ppEffectInstances는 잘모르니 그냥 0을 넣자.
pNumMaterials는 D3DXMATERIAL의 갯수이다. 
ppMesh로 ID3DXMesh 객체의 포인터를 받는다.(ID3DXMesh객체가 아니라 ID3DXMesh객체의 포인터라는 것을 명심하자.)

MatD3D 필드는 Ambient 값은 셋팅을 하지 않기 때문에, 보통 Diffuse 값을 넣는다.
Mat3D는 전역변수 g_mtrls 벡터에 넣고, pTextureFilenamed은 D3DXCreateTextureFromFile()을 이용하여 IDirect3DTexture9* 핸들을 구하여 g_textures 벡터에 넣는다.

OptimizeInplace()를 위하여 ppAdjacency의 값을 임시로 저장한다.



렌드링
 
bool RnderMesh(LPDIRECT3DDEVICE9 pDevice, float timeDelta)
{
	if( pDevice )
	{
		static float y = 0.0f;
		D3DXMATRIX yRot;
		D3DXMatrixRotationY(&yRot, y);
		y += timeDelta;

		if( y >= 6.28f )
			y = 0.0f;

		D3DXMATRIX World = yRot;
		pDevice->SetTransform(D3DTS_WORLD, &World);

		//pDevice->Clear(0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0xffffffff, 1.0f, 0);
		//pDevice->BeginScene();

		for(int i = 0; i < g_mtrls.size(); i++)
		{
			pDevice->SetMaterial( &g_mtrls[i] );
			pDevice->SetTexture(0, g_textures[i]);
			g_mesh->DrawSubset(i);
		}	

		//pDevice->EndScene();
		//pDevice->Present(0, 0, 0, 0);
	}
	return true;
}

y값을 스택틱 변수로 비행기를 timeDelta 시간에 따라 회전시키고 있다.

화면 클리어와 갱신은 RenderMesh() 밖에서 하고 있기 때문에 주석처리하였다.

size()만큼 SetMaterial(), SetTexture(), DrawSubset() 하여 렌드링하고 있다.

 


메쉬 소멸

void CleanupMesh()
{
	_RELEASE_(g_mesh);

	for(int i = 0; i < g_textures.size(); i++)
		_RELEASE_( g_textures[i] );
}

g_mesh 객체를 해제한다.
g_textures는 각각 해제 시킨다.
g_mtrls는 해제시킬 필요가 없다.

객체들을 vector를 사용하지 않고 배열을 사용할 때는 어떻게 할지 참고하기 위한 생성과 소멸코드이다.

LPD3DXMESH m_pMesh;
LPD3DXBUFFER m_pMaterialsBuffer;
D3DMATERIAL9* m_pMaterials;
LPDIRECT3DTEXTURE9* m_pTextures;

D3DXMATERIAL* pMaterials = (D3DXMATERIAL*)m_pMaterialsBuffer->GetBufferPointer();

// Create 2 arrays, 1 for textures and 1 for materials. D3DXLoadMeshFromX puts the
// number of materials/textures (always the same) into m_dwNumMaterials above.

m_pMaterials = new D3DMATERIAL9[m_dwNumMaterials];
m_pTextures = new LPDIRECT3DTEXTURE9[m_dwNumMaterials];

for(int iCount = 0; iCount < (int)m_dwNumMaterials; iCount++)
{
    // For each material buffer element, copy the D3DMATERIAL into this class' array.
    m_pMaterials[iCount] = pMaterials[iCount].MatD3D;
    m_pMaterials[iCount].Ambient = m_pMaterials[iCount].Diffuse;
    rslt=D3DXCreateTextureFromFile(pDevice, pMaterials[iCount].pTextureFilename, &m_pTextures[iCount]);
    if(FAILED(rslt)) { return Error(rslt, __LINE__, __FILE__, "D3DXCreateTextureFromFile() failed."); }


HRESULT CD3DMesh::Shutdown()
{
    // Free the materials array
    if(m_pMaterials)
        delete [] m_pMaterials;

    // If there are textures...
    if(m_pTextures)
    {
        // ...index through the texture array & release the texture object
        for(int iCount = 0; iCount < (int)m_dwNumMaterials; iCount++)
        {
            m_pTextures[iCount]->Release();
            m_pTextures[iCount] = NULL;
        }

        // Don't forget to delete the array as well
        delete [] m_pTextures;
    }

    // Release the mesh object
    if(m_pMesh)
        m_pMesh->Release();

    m_dwNumMaterials = 0;
    m_pMaterialsBuffer = NULL;
    m_pMaterials = NULL;
    m_pTextures = NULL;
    m_pMesh = NULL;

    return S_OK;
}



시간 함수 사용하기

test.cpp RenderLoop()를 보면 비행기를 회전시키기 위해 시간을 구하는 함수를 사용하고 있다.

        static float lastTime = (float)timeGetTime();  

RenderLoop()를 통과한 최초의 시간을 저장한다.

        .......

        float currTime  = (float)timeGetTime();
        float timeDelta = (currTime - lastTime)*0.001f;
        lastTime = currTime; 

        // Render the scene as normal
       if( FAILED( hr = Render3D(timeDelta)))
            return hr;

현재 시간과 마지막 시간차에 0.001를 곱하여 시간차를 구한다.

다음시간에는 버텍스 법선이 존재하지 않는 메쉬에 법선을 만드는 방법에 대해서 알아보자.

 

[펌]
http://www.moon-labs.com
DirectX 9를 이용한 3D GAME 프로그래밍 입문 - 정보 문화사
http://www.thehavok.co.uk