밉맵(Mipmap)과 LOD
Mipmap
텍스쳐링 기술의 하나로
‘텍스쳐’를 효율적으로 표시하기 위한 기술
- 하나의 텍스쳐의
여러 해상도
를 미리 만들어 두고
관측자(카메라)와의 거리를 계산하여 사용할 해상도의 텍스쳐를 선택하는 방식
(언리얼 같은 엔진은 이러한 해상도에 따른 축소본들을 자동으로 생성해줌)
(DirectX에서도 준비할 수 있다)
ex)
레벨 | 해상도 | 설명 |
---|---|---|
Level 0 | 1024×1024 | 원본 텍스처 |
Level 1 | 512×512 | 절반 크기 |
Level 2 | 256×256 | 1/4 크기 |
Level 3 | 128×128 | … |
… | … | 마지막은 1×1까지 |
(위 사진처럼 점점 이미지가 절반이 되기에
이미지 피라이드 라는 단어로도 표현됨)
- 왜 사용하는가?
- 멀리 있는 물체에 고해상도를 쓰는 것은 ‘낭비’인 동시에
무늬가 깜빡이거나 패턴이 깨지는원근 감쇠(Distance Attenuation) 현상
이
발생 가능하며, 이를 예방하기 위함
(사진처럼 Mipmap을 사용하는 쪽이 끝 부분이 깔끔함) - 또한 작은 텍스쳐를 사용함으로서 메모리 및 캐시에 대한 이점을 가지게 됨
- 멀리 있는 물체에 고해상도를 쓰는 것은 ‘낭비’인 동시에
- 어디에 활용하는가
- Level Of Detail (LOD)
- PBR
- Level Of Detail (LOD)
예제 - Mipmap
- Mipmap Level을 수정함으로서 텍스쳐의 디테일이 달라지는 모습
Detail이 높은 텍스쳐에서 낮은 텍스쳐로 갈수록
텍스쳐보단 ‘하나의 색’에 가까워지는 모습
Mipmap 구현 방식
- 임시 사용할 스테이징 텍스쳐를 만들기
- Cpu에서 스테이징 텍스쳐로 복사
- 스테이징 텍스쳐에서 진짜로 사용할 텍스쳐로 복사
void BasicMeshGroup::Initialize(ComPtr<ID3D11Device> &device,
ComPtr<ID3D11DeviceContext> &context,
const std::vector<MeshData> &meshes) {
// Sampler 만들기
D3D11_SAMPLER_DESC sampDesc;
ZeroMemory(&sampDesc, sizeof(sampDesc));
sampDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
// sampDesc.Filter = D3D11_FILTER_MIN_MAG_LINEAR_MIP_POINT;
...
}
- D3D11_FILTER_MIN_MAG_LINEAR_MIP_POINT 옵션 사용시
해상도가 낮아지는 것이 더 직관적으로 보인다
(변화의 중간이 없이 계단처럼 뚝뚝 끊기도록 설정)
(위에서 본 사진처럼 조건에 맞는 텍스쳐를 하나만 골라 바로 적용)
(Linear는 거리에 따라 더 세부적인 보간 처리를 해줌)
Min Mag
- Mag : 텍스쳐를 원본보다 크게 그릴 때
- Min : 텍스쳐를 원본보다 작게 그릴 때
각각 적용되는 필터 방식을 설정하는 것이
위에서 본 MIN_MAG 옵션
(각각 다르게 설정도 가능하다)
- ‘텍스쳐’를 이루는 각 픽셀을
'Texel'(텍셀)
이라 함
Subresources
- SubResource의 일종의 ‘좌표축’들
(Array Slice, Mip Slice)
- 지정된 각각의 서브리소스들
📦 Texture2DArray 리소스
├─ [0] 텍스처
│ ├─ Mip 0 → Sub 0
│ ├─ Mip 1 → Sub 1
│ ├─ Mip 2 → Sub 2
│ └─ Mip 3 → Sub 3
└─ [1] 텍스처
├─ Mip 0 → Sub 4
├─ Mip 1 → Sub 5
├─ Mip 2 → Sub 6
└─ Mip 3 → Sub 7
-
GPU에 올려져 있는 메모리 묶음
-
텍스쳐 자체는 ‘리소스’ 중 하나 이지만
각각의 ‘해상도’가 ‘달라지는’ 경우
이를 ‘구분’해야 하기에
이러한 ‘각각’을 표현하기 위해
서브리소스
라는 표현을 사용
용어 | 설명 |
---|---|
Resource (리소스) | GPU 메모리 안의 큰 데이터 단위 (예: Texture, Buffer 등) |
Subresource (서브리소스) | 그 내부의 “부분 단위” (예: 특정 밉맵, 특정 배열 요소) |
-
특정한 Mipmap만 업데이트할 수 있으며
특정한 요소의 인덱스를 통해 지정이 가능하기에
‘일부’만 제어할 때 특히 유용한 단위 -
Mipmap은 서브리소스를 통해 ‘표현’될 수 있음
-
예시 코드
ComPtr<ID3D11Texture2D>
CreateStagingTexture(ComPtr<ID3D11Device> &device,
ComPtr<ID3D11DeviceContext> &context, const int width,
const int height, const std::vector<uint8_t> &image,
const int mipLevels = 1, const int arraySize = 1) {
// 스테이징 텍스춰 만들기
D3D11_TEXTURE2D_DESC txtDesc;
ZeroMemory(&txtDesc, sizeof(txtDesc));
txtDesc.Width = width;
txtDesc.Height = height;
txtDesc.MipLevels = mipLevels;
txtDesc.ArraySize = arraySize;
txtDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
txtDesc.SampleDesc.Count = 1;
txtDesc.Usage = D3D11_USAGE_STAGING;
txtDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE | D3D11_CPU_ACCESS_READ;
ComPtr<ID3D11Texture2D> stagingTexture;
if (FAILED(device->CreateTexture2D(&txtDesc, nullptr,
stagingTexture.GetAddressOf()))) {
cout << "Failed()" << endl;
}
// CPU에서 이미지 데이터 복사
D3D11_MAPPED_SUBRESOURCE ms;
context->Map(stagingTexture.Get(), NULL, D3D11_MAP_WRITE, NULL, &ms);
uint8_t *pData = (uint8_t *)ms.pData;
for (UINT h = 0; h < UINT(height); h++) { // 가로줄 한 줄씩 복사
memcpy(&pData[h * ms.RowPitch], &image[h * width * 4],
width * sizeof(uint8_t) * 4);
}
context->Unmap(stagingTexture.Get(), NULL);
return stagingTexture;
}
---
void D3D11Utils::CreateTexture(
ComPtr<ID3D11Device> &device, ComPtr<ID3D11DeviceContext> &context,
const std::string filename, ComPtr<ID3D11Texture2D> &texture,
ComPtr<ID3D11ShaderResourceView> &textureResourceView) {
int width, height;
std::vector<uint8_t> image;
ReadImage(filename, image, width, height);
// 스테이징 텍스춰 만들고 CPU에서 이미지를 복사합니다.
ComPtr<ID3D11Texture2D> stagingTexture =
CreateStagingTexture(device, context, width, height, image);
// 실제로 사용할 텍스춰 설정
D3D11_TEXTURE2D_DESC txtDesc;
ZeroMemory(&txtDesc, sizeof(txtDesc));
txtDesc.Width = width;
txtDesc.Height = height;
txtDesc.MipLevels = 0; // 밉맵 레벨 최대
txtDesc.ArraySize = 1;
txtDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
txtDesc.SampleDesc.Count = 1;
txtDesc.Usage = D3D11_USAGE_DEFAULT; // 스테이징 텍스춰로부터 복사 가능
txtDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET;
txtDesc.MiscFlags = D3D11_RESOURCE_MISC_GENERATE_MIPS; // 밉맵 사용
txtDesc.CPUAccessFlags = 0;
// 초기 데이터 없이 텍스춰 생성 (전부 검은색)
device->CreateTexture2D(&txtDesc, nullptr, texture.GetAddressOf());
// 실제로 생성된 MipLevels를 확인해보고 싶을 경우
// texture->GetDesc(&txtDesc);
// cout << txtDesc.MipLevels << endl;
// 스테이징 텍스춰로부터 가장 해상도가 높은 이미지 복사
context->CopySubresourceRegion(texture.Get(), 0, 0, 0, 0,
stagingTexture.Get(), 0, nullptr);
// ResourceView 만들기
device->CreateShaderResourceView(texture.Get(), 0,
textureResourceView.GetAddressOf());
// 해상도를 낮춰가며 밉맵 생성
context->GenerateMips(textureResourceView.Get());
// HLSL 쉐이더 안에서는 SampleLevel() 사용
}
- 스테이징 텍스쳐를 만들기
- D3D11_USAGE_STAGING : CPU와의 통신을 위하여 임시로 데이터를 담아둠
그렇기에 CPU 접근 Access 플래그가
D3D11_CPU_ACCESS_WRITE | D3D11_CPU_ACCESS_READ
(1번 단계)
- D3D11_USAGE_STAGING : CPU와의 통신을 위하여 임시로 데이터를 담아둠
-
이후 Map,Unmap을 통해 CPU로부터 텍스쳐 데이터를 읽어들임
(해상도가 일치하지 않기에 memcpy로 일일이 복사)
(2번 단계) - 이후 실제 사용할 텍스쳐를 구현하기
- MipLevel = 0 일 경우, 가능한 모든 Mipmap을 만듦
- Usage를 Defalut로 잡아 스테이징으로부터 복사 가능하도록
- BindFlag는 D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET 를 사용
(GenerateMips를 사용하려면 ‘읽기 + 쓰기’가 모두 가능해야 함) - MiscFlag를 D3D11_RESOURCE_MISC_GENERATE_MIPS로 설정(밉맵 만들려면 설정해야함)
- GPU로부터 복사해오기에 CpuFlag는 0으로 설정
- 이후 생성할 때, nullptr을 통해 검은색으로 밀어버림
- MipLevel = 0 일 경우, 가능한 모든 Mipmap을 만듦
- 스테이징 텍스쳐로부터 해상도가 가장 높은걸 복사
쉐이더 코드 (Hlsl)
PixelShaderOutput main(PixelShaderInput input)
{
float3 toEye = normalize(eyeWorld - input.posWorld);
float3 color = float3(0.0, 0.0, 0.0);
...
if (useTexture)
{
// diffuse *= g_texture0.Sample(g_sampler, input.texcoord);
diffuse *= g_texture0.SampleLevel(g_sampler, input.texcoord, mipmapLevel);
// Specular texture를 별도로 사용할 수도 있습니다.
}
PixelShaderOutput output;
output.pixelColor = diffuse + specular;
output.indexColor = indexColor;
return output;
}
- SampleLevel 을 통해 mipmapLevel에 따른 텍스쳐를 사용
(Sample은 적절한 Lod Level을 내부적으로 계산하여 샘플링함)
(위의 SubResource를 통해 만들어진 Mipmap중 하나를 선택하며
mipmapLevel이 그 중간값이면 내부적으로 보간한 값을 선택)
예제 - Hlsl 코드를 수정하여 거리에 따른 Mipmap 변화
if (useTexture)
{
// diffuse *= g_texture0.Sample(g_sampler, input.texcoord);
float dist = length(eyeWorld - input.posWorld);
float distMin = 5.0;
float distMax = 10.0;
float lod = 10.0 * saturate((dist - distMin) / (distMax - distMin));
diffuse *= g_texture0.SampleLevel(g_sampler, input.texcoord, lod);
// Specular texture를 별도로 사용할 수도 있습니다.
}
- Mipmap Level이 상승할수록 텍스쳐 품질이 낮아짐
-
거리가 가까울수록 mipmap 값이 낮아지도록
- 결과
거리가 멀어지니 지구본의 텍스쳐가
다소 흐려진 모습이다
이미지 출처
- Wiki
댓글남기기