DirectX11 3D - 3D 맵 지형 렌더링(Terrain) 높이맵(Height Map) 적용
Terrian(지형) 렌더링
지형 렌더링에서는 높이 맵을 사용 한다.
1. 높이 맵
높이 맵이란 지형의 높이를 나타내는 y값을 텍스쳐로 저장한 텍스쳐를 뜻한다.
Byte로 저장된 이미지는 0~255로 표현하는데,
그림판에서 흰색을 표현할 때 최댓값 255를 가지는 것을
볼 수가 있다. 흰색일수록 높은 높이값을 갖는다는 의미다.
그렇다면 위 높이 맵을 보면, 가운데는 평지고,
바깥쪽은 높은 고지를 이룬다는 점을 미루어 짐작할 수 있겠다.
하지만 이런 높이 맵은 지형의 기울기에 따라, 폴리곤의 증가나 감소가 어렵다.
이는 쿼트 트리, 옥트리, BSP 공간분할 기법을 이용해 LOD 같은 기술을
사용해 보완하기도 한다.
2. 텍스쳐 접근 및 제한
CPU나 GPU가 리소스에 접근할 수 있는지는 개발자가 직접 D3D11_USAGE를
사용해서 지정해야 한다.
typedef
enum D3D11_USAGE
{
D3D11_USAGE_DEFAULT = 0,
D3D11_USAGE_IMMUTABLE = 1,
D3D11_USAGE_DYNAMIC = 2,
D3D11_USAGE_STAGING = 3
} D3D11_USAGE;
일반적으로 GPU가 접근할 수 있다. (D3D11_USAGE_DEFAULT)
높이 맵 텍스쳐를 읽어와서 높이 맵 정보를 토대로
높이 맵을 적용하기 위해(Vertex생성) CPU가 접근해야 한다.
리소스 사용 방법 | DEFAULT | DYNAMIC | IMMUTABLE | STAGING |
GPU 읽기 | 가능 | 허용1 | 가능 | 허용 1,2 |
GPU 쓰기 | 허용1 | 허용 1,2 | ||
CPU 읽기 | 허용 1,2 | |||
CPU 쓰기 | 가능 | 허용 1,2 |
허용은 예외 사항을 갖고 있다는 뜻이다.
CPU는 ID3D11DeviceContext::Map으로 접근할 수 있다.
GPU는 CopySubresourceResource나 CopyResource, UpdateSubresource로 접근 가능하다.
3. 높이 맵 적용
높이 맵 텍스쳐를 로드해 STAGING 권한을 갖고
ID3D11DeviceContext::Map API를 통해 높이 정보를 받아 오면 된다.
Map은 크리티컬 섹션처럼 공유 자원의 독점을 보장해준다.
Unmap 하기 전까지 아무도 접근을 못한다.
HRESULT hr;
ID3D11ShaderResourceView* pSRV = nullptr;
wrl::ComPtr<ID3D11Resource> pTexture;
size_t maxsize = 0;
if (FAILED(hr = CreateWICTextureFromFileEx(g_pd3dDevice,
heightmap.c_str(),
maxsize,
D3D11_USAGE_STAGING,
NULL,
D3D11_CPU_ACCESS_WRITE|D3D11_CPU_ACCESS_READ,
NULL,
WIC_LOADER_DEFAULT,
pTexture.GetAddressOf(), nullptr)))
{
return false;
}
WICTextureLoader DXToolkit으로 텍스쳐를 로드한다.
CPU Access Flag를 D3D11_USAGE_STAGING
misc Flags를 D3D11_CPU_ACCESS_WRITE|D3D11_CPU_ACCESS_READ로
CPU가 접근해서 쓰고 읽기 가능하게 한다.
ID3D11Texture2D* pTexture2D = NULL;
if (FAILED(pTexture->QueryInterface(__uuidof(ID3D11Texture2D), (LPVOID*)&pTexture2D)))
{
return false;
}
그런 후에 인터페이스를 얻기 위해 사용하는 QueryInterface 함수를 사용한다.
위에서 얻는 텍스쳐 리소스를 ID3D11Texture2D* 포인터 변수에 가상 포인터를 넣어준다.
D3D11_TEXTURE2D_DESC desc;
pTexture2D->GetDesc(&desc);
이제 이 pTexture2D 변수로 파일의 정보를 얻을 수 있다.
m_HeightList.resize(desc.Height * desc.Width);
if (pTexture2D)
{
D3D11_MAPPED_SUBRESOURCE MappedFaceDest;
//크리티칼 섹션처럼 unmap 하기전까지 접근 못함
if (SUCCEEDED(m_pContext->Map((ID3D11Resource*)pTexture2D,
D3D11CalcSubresource(0, 0, 1), D3D11_MAP_READ, 0, &MappedFaceDest)))
{
UCHAR* pTexels = (UCHAR*)MappedFaceDest.pData;
PNCT_VERTEX v;
for (UINT row = 0; row < desc.Height; row++)
{
UINT rowStart = row * MappedFaceDest.RowPitch;
for (UINT col = 0; col < desc.Width; col++)
{
UINT colStart = col * 4;
UINT byte_height = pTexels[rowStart + colStart + 0];
//byte에 저장할수있는 최대값은 0~255
//따라서 높이를 조절하려면 나눗셈
m_HeightList[row * desc.Width + col] = (static_cast<float>(byte_height)/8.0f)-4.0f; /// DWORD이므로 pitch/4
}
}
m_pContext->Unmap(pTexture2D, D3D11CalcSubresource(0, 0, 1));
}
}
m_num_row = desc.Height;
m_num_col = desc.Width;
if (pTexture2D) pTexture2D->Release();
HRESULT Map(
[in] ID3D11Resource *pResource,//읽을 리소스 포인터
[in] UINT Subresource,//인덱스 번호
[in] D3D11_MAP MapType,//읽기 쓰기 권한
[in] UINT MapFlags,//CPU 수행 작업 플래그
[out, optional] D3D11_MAPPED_SUBRESOURCE *pMappedResource//깊이,크기,사이즈 조회 가능
);
Map함수를 호출하여 텍스쳐 정보에 액세스 할 수 있는
D3D11_MAPPED_SUBRESOURCE 포인터를 받아온다.
참고로 2번째 인자는 D3D11SubCalcResource 함수로 계산되는데
인덱스 번호를 지정하는 것으로 [리소스의 밉맵 레벨 X 리소스 번호 + 서브 리소스 번호]
로 지정이 된다.
UINT rowStart = row * MappedFaceDest.RowPitch;
for (UINT col = 0; col < desc.Width; col++)
{
UINT colStart = col * 4;
UINT byte_height = pTexels[rowStart + colStart + 0];
//byte에 저장할수있는 최대값은 0~255
//따라서 높이를 조절하려면 나눗셈
m_HeightList[row * desc.Width + col] = (static_cast<float>(byte_height)/8.0f)-4.0f;
}
나머지는 파싱 해서 높이 값 정보 m_HeightList에 저장을 한다.
오브젝트 위치에 y축 0에 맞추고 싶어서 적당히 나눠준 모습이다.
https://docs.microsoft.com/en-us/windows/win32/api/d3d11/ns-d3d11-d3d11_mapped_subresource
이제 버텍스를 찍는 함수에서 Y값을 높이맵 정보를 저장한 HeightLIst로 만들어 낸다.
나머지는 저번에 노말 맵 적용하면서 퐁 셰이딩 적용했기 때문에,
오브젝트와 마찬가지로 노말, 바이 노말, 탄젠트 값을 버텍스 정보에 넘겨주었다.
나중에 Flat Shading, Gouraud Shading, Phong Shading에 대해서도 정리해야겠다.
4. 결과