Graphics/DirectX

Direct X - LOD (Level Of Detail)

MOLOKINI 2014. 8. 21. 20:08

LOD

메시 모델링 데이터의 정밀도를 단계별로 조정하는 기술
LOD는 크게 정적 LOD와 동적 LOD로 나눌 수 있다.
지형 처리에서 사용되는 방법은 대부분 동적 LOD이다.




LOD가 필요한 이유
LOD기술은 속도와 질 사이의 타협속에서 생겨난 기술이다.
질이 너무 좋은 화면은 속도가 느리게 되고, 반면 속도에만 너무 치중하게되면 화면의 질이 떨어지는게 당연한 것!
질을 적게 떨어뜨리면서 속도도 빠른 렌더링 기술을 개발하는 과정에서 태어난 기술이 LOD라는 기법이다.

메시 하나를 표현하는데도 메시를 몇개로 분할하느냐에 따라 성능이 달라지는데,
카메라와 물체간의 거리를 측정하여 카메라와 가까운 거리에 있는 물체는 정밀한 메시를 사용하고, 거리가 멀어질 수록 낮은 단계의 메시를 사용하는 기법이 정적 LOD이다.

정적 LOD의 특징은 처음부터 메시의 정밀도가 정해져있고, 이를 카메라와의 거리에 따라서 단계별로 바꿔치기 해가며 출력한다는 것이다. 이 방법의 장점은 연산이 간단하기 때문에 속도가 빠르다는 것, 그러나 여러단계의 메시를 추가적으로 가지고 있어야 하기 때문에 메모리의 낭비가 심하고, 거리에 따라서 메시의 단계가 급격하게 변하기 때문에 튐현상이 발생한다는 것이다.
이걸 해결한게 바로 동적 LOD이다.

동적 LOD 기법이란, 카메라와 물체의 거리에 따라서 실시간으로 메시의 정밀도를 변화시키는 기법이다. 이 방법은 여러가지 기법으로 분류되는데 일반적으로 메시 분할과 메시간략화 기법으로 나뉜다.
장점은 거리에 따라서 자연스럽게 LOD가 이루어지기 때문에 튐현상이 발생하지 않고, 낭비되는 메모리도 없다는 것이다. 그러나 메시 분할이나 간략화에 추가적인 연산이 필요하기 때문에 상대적으로 속도가 느리다는 것이 단점이다.


LOD 적용 방법
LOD를 지형처리에 적용할 경우 지형의 변화율을 사용하는 방법과 카메라와 거리를 사용하는 방법이 있는데, 우리는 카메라와의 거리만을 연구대상으로 하자

간단하게, 카메라와의 거리가 가까울 수록 정밀한 메시를 사용하고, 멀어질수록 간략화된 메시를 사용하는 것이다.
문제는, 하나의 단일 메시속에 이렇게 여러단계의 메시를 공존시켜야 한다는 것이다.
여러단계의 메시가 공존하기 때문에, 서로다른 단계의 메시와 메시간의 연결이 매끄럽지 못할 경우 균열현상이 생긴다. 일단 그건 제껴놓고 하자


LOD를 쿼드트리에 적용하기
LOD에 의해 세그먼트 분할이 이루어지는 단계를 자세히 살펴보면 이것이 쿼드트리의 자식노드 분할과 닮아있다는걸 알 수 있다. 우리는 쿼드트리를 사용하고 있기 때문에 상당히 간단하게 LOD를 구현할 수 있다.

 



쿼드트리를 사용한 LOD의 구현은 놀랍게도 ZQuadTree.h에 두개의 함수를 추가하는것으로 해결된다.

그것도 다섯줄정도밖에 안된다

 - DX 라이브러리가 지원을 잘 해줘서 그런거지 구현이 결코 간단하지만은 않다


ZQuadTree.h 수정

float _GetDistance(D3DXVECTOR3* pv1, D3DXVECTOR3* pv2)

{

return D3DXVec3Length(&(*pv2 - *pv1));

}


// 카메라와 현재 노드와의 거리값을 기준으로 LOD값을 구한다

int _GetLODLevel(TERRAINVERTEX* pHeightMap, D3DXVECTOR3* pCamera, float fLODRatio)

{

float d = _GetDistance((D3DXVECTOR3*)(pHeightMap+m_nCenter), pCamera);

return max((int)(d*fLODRatio), 1);

}


// 현재 노드가 LOD등급으로 볼 때 출력이 가능한 노드인가?

BOOL _IsVisible(TERRAINVERTEX* pHeightMap, D3DXVECTOR3* pCamera, float fLODRatio)

{

return ((m_nCorner[CORNER_TR] - m_nCorner[CORNER_TL]) <= _GetLODLevel(pHeightMap, pCamera, fLODRatio));

}


_IsVisible()함수는 카메라와 현재 노드와의 거리에 따른 LOD 등급을 구해서 쿼드트리의 출력 여부를 결정한다. 이때, LOD 등급을 구해주는 함수는 _GetLODLevel()이고, 카메라와 노드 중앙 정점과의 거리를 구해주는 함수는 _GetDistance()다. 이것을 추가하는 것만으로도 우리는 훌륭한 LOD지형을 구현할 수 있다. 


문제는 LOD등급이 서로 다른 메시들끼리 공존하기 때문에 LOD등급이 다른 경계부분에서 균열이 발생하는 것을 막지 못하고 있다는 것이야.. 이 처리는 다음에~



메인도 수정해야겠죠?

인자가 수정되는 곳은 연쇄적으로 다 수정해줘야한다



ZTerrain.h 수정

HRESULT Create( LPDIRECT3DDEVICE9 pDev, D3DXVECTOR3* pvfScale, float fLODRatio, LPSTR lpBMPFilename, LPSTR lpTexFilename[MAX_TERRAIN_TEX] );


fLODRatio 인자가 생겨났다.


ZTerrain.cpp 수정

HRESULT ZTerrain::Create( LPDIRECT3DDEVICE9 pDev, D3DXVECTOR3* pvfScale, float fLODRatio, LPSTR lpBMPFilename, LPSTR lpTEXFilename[4] )

{

m_pd3dDevice = pDev;

m_vfScale = *pvfScale;

m_fLODRatio = fLODRatio;

if( FAILED( _BuildHeightMap( lpBMPFilename ) ) ) { _Destroy(); return E_FAIL; }

if( FAILED( _LoadTextures( lpTEXFilename ) ) ) { _Destroy(); return E_FAIL; }

if( FAILED( _CreateVIB() ) ) { _Destroy(); return E_FAIL; }

m_pQuadTree = new ZQuadTree( m_cxDIB, m_czDIB );

if( FAILED( _BuildQuadTree() ) ) { _Destroy(); return E_FAIL; }


return S_OK;

}


ZQuadTree.cpp 수정

int ZQuadTree::_GenTriIndex( int nTris, LPVOID pIndex, TERRAINVERTEX* pHeightMap, ZFrustum* pFrustum, float fLODRatio )

{

// 컬링된 노드라면 그냥 리턴

if( m_bCulled )

{

m_bCulled = FALSE;

return nTris;

}


// 현재 노드가 출력되어야 하는가?

if( _IsVisible( pHeightMap, pFrustum->GetPos(), fLODRatio ) )

{

#ifdef _USE_INDEX16

LPWORD p = ((LPWORD)pIndex) + nTris * 3;

#else

LPDWORD p = ((LPDWORD)pIndex) + nTris * 3;

#endif

*p++ = m_nCorner[0];

*p++ = m_nCorner[1];

*p++ = m_nCorner[2];

nTris++;

*p++ = m_nCorner[2];

*p++ = m_nCorner[1];

*p++ = m_nCorner[3];

nTris++;


return nTris;

}


// 자식 노드들 검색

if( m_pChild[CORNER_TL] ) nTris = m_pChild[CORNER_TL]->_GenTriIndex( nTris, pIndex, pHeightMap, pFrustum, fLODRatio );

if( m_pChild[CORNER_TR] ) nTris = m_pChild[CORNER_TR]->_GenTriIndex( nTris, pIndex, pHeightMap, pFrustum, fLODRatio );

if( m_pChild[CORNER_BL] ) nTris = m_pChild[CORNER_BL]->_GenTriIndex( nTris, pIndex, pHeightMap, pFrustum, fLODRatio );

if( m_pChild[CORNER_BR] ) nTris = m_pChild[CORNER_BR]->_GenTriIndex( nTris, pIndex, pHeightMap, pFrustum, fLODRatio );


return nTris;

}


// 삼각형의 인덱스를 만들고, 출력할 삼각형의 개수를 반환한다.

int ZQuadTree::GenerateIndex( LPVOID pIndex, TERRAINVERTEX* pHeightMap, ZFrustum* pFrustum, float fLODRatio )

{

// 먼저 프러스텀 컬링을 해서 컬링될 노드들을 배제한다.

_FrustumCull( pHeightMap, pFrustum );

// 출력할 폴리곤의 인덱스를 생성한뒤, 폴리곤의 개수를 리턴한다.

return _GenTriIndex( 0, pIndex, pHeightMap, pFrustum, fLODRatio );

}


HeightMap.cpp 수정

HRESULT InitObjects()

{

LPSTR tex[4] = { "tile2.tga", "", "", "" };

D3DXVECTOR3 vScale;


vScale.x = vScale.z = 1.0f; vScale.y = 0.1f;

g_pLog = new ZFLog( ZF_LOG_TARGET_WINDOW );

g_pCamera = new ZCamera;

g_pFrustum = new ZFrustum;

g_pTerrain = new ZTerrain;

g_pTerrain->Create( g_pd3dDevice, &vScale, 0.1f, BMP_HEIGHTMAP, tex );


return S_OK;

}



결과


와이어프레임으로 본 지형의 시야 

카메라와 가까운곳과 먼곳의 차이가 느껴지죠?

ㅇㅋㅇㅋ


'Graphics > DirectX' 카테고리의 다른 글

Direct X - 애니메이션 기법  (0) 2014.09.03
Direct X - 균열 방지  (0) 2014.08.27
Direct X - 쿼드트리 컬링  (0) 2014.08.16
Direct X - 쿼드트리  (0) 2014.08.14
Direct X - 절두체 컬링  (0) 2014.08.11