티스토리 뷰
기존 포스팅 내용 요약
[Graphics] 복셀 렌더링 - 1 <복셀과 자료구조>
본격적인 이야기에 앞서, 지난 포스트에서 다루었던 '복셀(Voxel) 데이터를 메모리에 욱여넣는 방법'을 짧게 되짚어 보겠습니다. 거대한 3D 공간에서 텅 빈 공간(Empty Space)을 쳐내기 위해 우리는 다음과 같은 트리 기반의 자료구조들을 살펴봤습니다.
- SVO (Sparse Voxel Octree): 공간을 8등분하며 파고드는 직관적인 구조. 동적 업데이트와 LOD에 유리하지만, 노드를 찾기 위해 메모리 주소를 계속 추적해야 하는 '포인터 체이싱(Pointer Chasing)' 때문에 GPU가 제 속도를 내지 못하는 치명적인 단점이 있었습니다.
- SVDAG (Sparse Voxel Directed Acyclic Graph): SVO에서 더 나아가, 똑같이 생긴 덩어리(중복 데이터)들마저 하나로 병합해 버리는 극한의 메모리 압축 기술입니다.
- NanoVDB: GPU가 가장 싫어하는 '포인터'를 아예 없애버린 혁명적인 구조. 트리의 논리적 형태는 유지하되, 물리적으로는 거대한 1차원 배열(Flat Array)에 데이터를 빽빽하게 세워두고 '비트 연산'과 '점프(Offset)'만으로 데이터를 빛의 속도로 읽어냅니다. (구름, 연기 등 정적인 데이터 읽기에 적합)
그렇다면 NanoVDB가 게임 엔진의 최종 정답일까?
지난 포스트의 마지막에서 우리는 한 가지 의문을 던졌습니다. "블록이 실시간으로 박살 나는 마인크래프트는 SVO를 쓸까? 언리얼 엔진은 NanoVDB를 그대로 쓸까?"
정답은 "아니오"입니다.
NanoVDB는 완벽에 가까운 구조지만, '게임 엔진(실시간 렌더링)'이라는 가혹한 환경에서는 역부족이었습니다.
이에 이번 포스트에서는 다음 두 가지 새로운 패러다임을 이야기하고자 합니다.
- 언리얼 엔진의 SVT (Sparse Volume Texture): NanoVDB는 데이터를 읽어올 때 부드러운 혼합(보간)을 위해 셰이더 연산(ALU)을 너무 많이 씁니다. 언리얼 엔진은 GPU에 공짜로 탑재된 '텍스처 하드웨어 필터링'을 써먹기 위해, 1차원 배열을 억지로 3D 텍스처 형태로 비틀어버렸습니다.
- 마인크래프트의 Spatial Hash Map (공간 해시 맵): 곡괭이질 한 번에 지형이 무너지는 환경에서는 트리를 타고 내려갈 시간조차 아깝습니다. 트리를 아예 부숴버리고, 좌표를 부르면 즉시 데이터가 튀어나오는 $O(1)$의 마법, 공간 해싱을 선택했습니다.
언리얼 엔진의 SVT (Sparse Volume Texture)
지난 포스트에서 우리는 NanoVDB가 포인터를 없애고 1차원 배열(Linear Buffer)을 통해 극강의 탐색 속도를 이끌어낸 과정을 보았습니다. 그렇다면 언리얼 엔진 5의 나이아가라(Niagara) 유체 시뮬레이션이나 Heterogeneous Volumes는 NanoVDB를 그대로 사용할까요? 정답은 아래와 같습니다.
"데이터를 들고 올 때는 VDB를 쓰지만, GPU로 렌더링할 때는 SVT(Sparse Volume Texture) 사용"
완벽해 보이는 NanoVDB를 굳이 다른 형태로 변환하는 이유, 그것은 바로 GPU 하드웨어의 구조적 특징 때문입니다.
1. NanoVDB의 뼈아픈 약점: 소프트웨어 보간과 ALU 병목
구름이나 연기 같은 볼류메트릭 데이터를 렌더링할 때, 마인크래프트처럼 각지게 보이지 않으려면 복셀과 복셀 사이의 중간 밀도값을 아주 부드럽게 섞어주어야 합니다. 이를 삼선형 보간(Trilinear Interpolation)이라고 합니다.
* 쌍선형 보간은 2차원 평면 상에서 한 점의 위치를 보간을 통해 구하기 위해 사용하는 방법으로
흔히 우리가 사용하는 1차원 보간인 Lerp를 X축으로 두번, Y축으로 한 번 (반대로 Y축 2번 X축 1번이어도 됩니다.) 수행하여 구할 수 있습니다.

* 삼선형 보간은 이런 보간 방식을 3차원상에서 수행하는 것을 의미합니다.
- NanoVDB의 딜레마: NanoVDB는 근본적으로 1차원 버퍼입니다. 따라서 셰이더에서 임의의 위치에 있는 부드러운 값을 얻으려면, 주변 8개의 복셀 데이터를 버퍼에서 직접 찾아 읽어온 뒤, 셰이더 코드 안에서 복잡한 수학 공식으로 직접 섞어주어야 합니다.
- 화면의 수백만 픽셀마다 레이(Ray)를 쏘며 매 스텝 이 무거운 연산을 반복한다면? GPU의 연산 유닛(ALU)이 비명을 지르며 프레임이 곤두박질치게 됩니다.
2. SVT의 핵심: 가상 메모리(Virtual Memory)의 3D화

그래픽스 프로그래머들은 생각했습니다.
"GPU 안에는 텍스처를 기가 막히게 섞어주는 전용 하드웨어인 TMU(Texture Mapping Unit)가 놀고 있잖아? 데이터를 무식하게 버퍼에 넣지 말고, 3D 텍스처에 구겨 넣으면 TMU가 공짜로 보간을 해주지 않을까?"_
이 발상에서 탄생한 것이 SVT입니다. 언리얼 엔진은 5.3 버전부터 SVT 개념을 도입했으며(https://www.youtube.com/watch?v=QtJzitZweWM), SVT는 컴퓨터 운영체제의 '가상 메모리 페이징' 기법을 3D 그래픽스에 그대로 이식한 구조로, 두 개의 거대한 3D 텍스처로 이루어집니다.
- Page Table Texture (가상 주소록):
전체 월드 공간을 덮고 있는 저해상도 3D 텍스처입니다.- 실제 밀도 데이터가 아니라, "진짜 데이터가 있는 물리적 주소(인덱스)"만 적혀 있습니다.
- 텅 빈 공기(Empty Space)라면 이곳에 0이 적혀 있습니다.
- Physical Texture Atlas (물리 데이터 창고):
빈 공간은 싹 쳐내고, 실제 데이터가 존재하는 $8 \times 8 \times 8$ 크기의 '페이지(Page)'들만 테트리스처럼 빽빽하게 모아둔 거대한 고해상도 3D 텍스처입니다. (기존 VDB의 Leaf Data에 해당)

이 이원화된 구조 덕분에 빈 공간의 메모리 낭비는 완벽하게 제거(NanoVDB의 장점)하면서도, 최종 데이터는 3D 텍스처 형태에 보관(TMU 활용 가능) 할 수 있게 되었습니다.
| 특징 | OS 가상 메모리 페이징 | 언리얼 SVT |
|---|---|---|
| 가상공간 | Virtual Memory (연속된 거대한 주소 공간) | 렌더링할 구름의 전체 3D Bounding Box |
| 매핑 테이블 | Page Table (가상 $\rightarrow$ 물리 주소록) | Page Table Texture (저해상도 3D 주소록 텍스처) |
| 물리 공간 | Physical RAM (파편화된 실제 메모리 조각, Frames) | Physical Texture Atlas (데이터가 꽉 찬 타일들의 모음) |
| 블록 단위 | 보통 4KB 단위의 Page/Frame | 보통 $8 \times 8 \times 8$ 단위의 Voxel Brick (타일) |
| 빈 공간 처리 | 페이지 테이블에 '할당 안 됨(Null)' 표시 | Page Table Texture에 0 기록 (Empty Space Skipping) |
| 데이터 스트리밍 | 디스크(SSD)에서 RAM으로 Swap In | 디스크/CPU에서 VRAM 물리 텍스처로 Tile Streaming |
3. 실무에서 SVT 제작 및 임포트하는 방법
4. 실무 구현의 최대 난제: 경계면 아티팩트와 패딩(Padding)
이론은 완벽하지만, SVT를 실제 엔진에 구현할 때 그래픽스 엔지니어들의 뒤통수를 치는 엄청난 문제가 하나 발생합니다. 바로 경계면 필터링(Boundary Filtering) 문제입니다.
Physical Texture 안에는 서로 다른 공간에서 온 $8 \times 8 \times 8$ 페이지들이 순서 없이 다닥다닥 붙어있습니다. 만약 하드웨어 텍스처 유닛(TMU)이 '페이지의 끝자락(경계면)'에 있는 복셀을 삼선형 보간하려고 하면 어떤 일이 벌어질까요?
전혀 상관없는 옆 페이지의 데이터까지 스며들어와 같이 섞여버립니다(Bleeding). 결과적으로 렌더링 된 구름 표면에 끔찍한 격자무늬(Artifact)가 생겨버리죠. (텍스쳐 아틀라스에서도 쉽게 겪는 이슈입니다.)
언리얼 엔진의 해결책: 1-Voxel Padding
이 문제를 해결하기 위해 언리얼 엔진의 SVT는 물리 텍스처에 데이터를 올릴 때 $8 \times 8 \times 8$ 원본 크기 그대로 저장하지 않습니다.
사방으로 1복셀씩 이웃 페이지의 데이터를 복사해 와서 덧붙인(Padding) $10 \times 10 \times 10$ 크기로 뻥튀기하여 저장합니다.
이렇게 여분의 보호막(Padding)을 둘러치면, TMU가 경계면에서 보간 연산을 하더라도 항상 올바른 이웃 복셀 데이터와 섞이게 되어 아티팩트가 완벽하게 사라진다고 설명합니다. (이로 인해 메모리를 약간 더 쓰게 되는 Trade-off를 감수하는 것입니다.)
하지만, 이는 전통적인 아틀라스 관리방법에서도 사용했던 솔루션이고 우리는 이에 상응하는 대가가 무엇인지 알고 있습니다.
1. 밉맵 블리딩 (Mipmap Bleeding): 하드웨어 밉맵의 사용 불가
2D 아틀라스에서 가장 아티팩트가 심하게 터지는 순간은, 카메라가 멀어져서 밉맵(Mipmap)이 작동할 때입니다. 해상도가 절반씩 줄어들다 보면 결국 패딩 공간마저 뭉개지면서 옆 텍스처의 색상이 스며들어옵니다(Bleeding).
- SVT의 한계:
이 때문에 SVT의 Physical Texture는 GPU 하드웨어가 자동으로 만들어주는 밉맵 생성 기능(GenerateMips)을 절대 사용할 수 없습니다. 만약 3D 아틀라스를 그대로 축소해 버리면, 전혀 상관없는 5번 구역 연기와 100번 구역 구름이 섞여버리는 대참사가 일어납니다. - SVT의 해결책 (비용 증가):
언리얼 엔진은 이 블리딩을 막기 위해 각 밉맵 레벨(LOD)마다 별도의 SVT(Page Table + Physical Texture 세트)를 아예 새로 만듭니다. 즉, Mip 0용 아틀라스, Mip 1용 아틀라스, Mip 2용 아틀라스를 CPU나 Compute Shader에서 각각 따로 계산해서 메모리에 올려야 합니다. 유지보수와 메모리 관리 비용이 기하급수적으로 증가합니다.
2. 메모리 뻥튀기 (The Curse of Volume): 3D 패딩의 끔찍한 나비효과
2D 텍스처에서 1픽셀 패딩을 두르는 것은 전체 용량에 큰 타격을 주지 않습니다. 하지만 3D 공간인 복셀에서는 '부피(Volume)'의 저주가 시작됩니다.
- $8 \times 8 \times 8$ 크기의 원본 데이터 블록은 512개의 복셀을 가집니다.
- 여기에 사방으로 1복셀씩만 패딩을 둘러도 $10 \times 10 \times 10$이 되어 1000개의 복셀이 됩니다.
- 결과: 고작 1복셀 두께의 경계면 아티팩트를 막겠다고, 메모리를 무려 95%나 더 쓰게 되는 미친 낭비가 발생합니다.
SVT의 해결책: 이 메모리 폭발을 줄이기 위해, 실제 상용 엔진에서는 블록(Page)의 기본 크기를 훨씬 크게 잡습니다. 예를 들어 $32 \times 32 \times 32$ (32,768 복셀)로 잡고 패딩을 둘러 $34 \times 34 \times 34$ (39,304 복셀)로 만들면, 오버헤드 비율을 약 20% 수준으로 억누를 수 있습니다. (대신 블록이 너무 커지면 빈 공간을 쳐내는 효율이 떨어지는 또 다른 딜레마에 빠집니다.)
3. 부동소수점 정밀도 한계 (Floating-Point Precision Seams)
패딩을 완벽하게 둘렀다고 해도 시각적 아티팩트가 생길 수 있습니다. 셰이더에서 레이마칭을 할 때, 3D 월드 좌표를 물리 텍스처(Physical Texture) 내부의 아주 미세한 UVW 좌표로 변환하는 수학적 계산을 거칩니다.
- 문제점: GPU의 float 부동소수점 연산은 소수점 아래로 내려갈수록 미세한 오차가 발생합니다. 이 오차 때문에 텍스처 샘플러가 우리가 의도한 '안전한 1복셀 패딩 구역'을 아주 미세하게(0.0001 픽셀만큼) 벗어나서 샘플링하게 되면, 구름이나 연기 표면에 아주 얇고 날카로운 경계선 틈새(Seam)가 깜빡거리며 보이게 됩니다.
- 해결책: 셰이더 프로그래머들은 이 아티팩트를 없애기 위해 UVW 좌표가 절대 패딩 바깥으로 넘어가지 않도록, 수식 끝에 미세한 오프셋을 더하고 빼거나 clamp() 함수를 걸어주는 등 피눈물 나는 예외 처리 코드를 추가해야 합니다.
5. 셰이더(HLSL) 레벨의 SVT 샘플링 (수도 코드)
실제 언리얼 엔진 내부의 셰이더 코드가 레이마칭을 수행할 때, SVT를 어떻게 읽어오는지 그 논리적 흐름을 단순화한 수도 코드(Pseudo Code)입니다.
// SVT 텍스처 자원들
Texture3D<uint> PageTableTex; // 주소록 (가상 공간)
Texture3D<float> PhysicalTex; // 실제 데이터 (물리 공간 아틀라스)
SamplerState TrilinearSampler; // 하드웨어 보간 샘플러
float SampleSVT(float3 WorldPos)
{
// 1. 월드 좌표를 Page Table의 3D 가상 UVW 좌표 [0~1]로 변환
float3 VirtualUVW = WorldToVirtualUVW(WorldPos);
// 2. 주소록(Page Table) 읽기: 현재 좌표가 속한 페이지의 물리적 주소를 가져옴
// (Point 샘플링: 섞이지 않은 정확한 정수 인덱스만 획득)
uint PhysicalPageIndex = PageTableTex.Load(PointSampler, int(floor(VirtualUVW)), 0);
// 3. 빈 공간(Empty Space) 스킵 로직
// 주소가 0이면 텅 빈 공기! 무거운 연산 즉시 종료. (NanoVDB의 Bitmask 역할)
if (PhysicalPageIndex == 0)
{
return 0.0f;
}
// 4. 가상 좌표 안에서 나의 디테일한 위치(Local UVW) 및 패딩(Padding) 보정 계산
float3 PhysicalUVW = ConvertToPhysicalUVW(PhysicalPageIndex, VirtualUVW);
// 5. 하드웨어 삼선형 보간의 마법!
// GPU의 TMU가 알아서 8개 복셀을 부드럽게 섞어 반환함. (ALU 연산 거의 제로)
return PhysicalTex.SampleLevel(TrilinearSampler, PhysicalUVW, 0);
}텍스처를 두 번 읽는 것(Indirection)은 GPU 입장에서 부담일 수 있습니다.
하지만 코드의 3번 단계를 보세요. PhysicalPageIndex == 0일 경우 두 번째 텍스처 읽기(PhysicalTex 샘플링)를 아예 생략해 버립니다. 구름이 없는 90%의 허공을 렌더링할 때 엄청난 연산 스킵이 발생하므로 전체적인 성능은 압도적으로 유리해집니다.
아래는 이제 실제 언리얼 엔진 내 구현 사용부입니다.
(SparseVolumeTextureCommon.ush)
// PageTable에서부터 Voxel의 UVW 좌표값을 가져오는 과정입니다.
float3 SparseVolumeTextureGetVoxelCoord(Texture3D<uint> PageTable, FSparseVolumeTextureUniforms Uniforms, float3 PageTableCoord, int MipLevel)
{
// Load from page table texture
// 아래 보면 PageTable (가상 테이블)로부터 주소값을 가져올 때는 MipLevel을 사용하는 것을 알 수 있습니다.
const uint PackedPhysicalTileCoord = PageTable.Load(int4(floor(PageTableCoord), MipLevel)).x;
const int3 PhysicalTileCoord = int3(PackedPhysicalTileCoord & 0xFFu, (PackedPhysicalTileCoord >> 8u) & 0xFFu, (PackedPhysicalTileCoord >> 16u) & 0xFFu);
const uint TileMipLevel = PackedPhysicalTileCoord >> 24u;
// Scale TileCoord by 1 / exp2(ActualMipLevel - DesiredMipLevel) to get correct UVs when sampling from a higher mip as fallback
const float TileRcpMipLevelFactor = rcp(float(1u << (TileMipLevel - uint(MipLevel))));
const float3 FracTileCoord = frac(PageTableCoord * TileRcpMipLevelFactor);
const float3 VoxelCoord = float3(PhysicalTileCoord) * float(SPARSE_VOLUME_TILE_RES_PADDED) + (FracTileCoord * float(SPARSE_VOLUME_TILE_RES) + float(SPARSE_VOLUME_TILE_BORDER));
return VoxelCoord;
}
float3 SparseVolumeTextureSamplePageTable(Texture3D<uint> PageTable, FSparseVolumeTextureUniforms Uniforms, float3 UVW, uint AddressU, uint AddressV, uint AddressW, int MipLevel = 0)
{
// Apply address mode to UVW and clamp mip level to resident levels
UVW = SparseVolumeTextureApplyAddressMode(UVW, AddressU, AddressV, AddressW);
MipLevel = clamp(MipLevel, 0, Uniforms.HighestMipLevel);
const float RcpMipLevelFactor = rcp(float(1u << (uint)MipLevel));
const float3 VolumePageCoord = UVW * Uniforms.VolumePageResolution;
const float3 MipPageTableOffset = floor(Uniforms.PageTableOffset * RcpMipLevelFactor);
const float3 PageTableCoord = VolumePageCoord * RcpMipLevelFactor - MipPageTableOffset;
const float3 VoxelCoord = SparseVolumeTextureGetVoxelCoord(PageTable, Uniforms, PageTableCoord, MipLevel);
const float3 VoxelUVW = VoxelCoord * Uniforms.TileDataTexelSize;
return VoxelUVW;
}
// 위에서 얻은 UVW를 아래 PhysicalTileData 샘플링 함수에 사용함으로써 실제 저장된 값을 가져올 수 있습니다.
float4 SparseVolumeTextureSamplePhysicalTileData(Texture3D PhysicalTileDataA, Texture3D PhysicalTileDataB, SamplerState TileDataSampler, float3 VoxelUVW, int PhysicalTileDataIndex)
{
// SampleLevel을 할 때 보면 Mip Bleeding을 해소하기 위해 MipLevel을 0.0f로 고정하고 있는 것을 알 수 있습니다.
switch (PhysicalTileDataIndex)
{
case 0: return PhysicalTileDataA.SampleLevel(TileDataSampler, VoxelUVW, 0.0f);
case 1: return PhysicalTileDataB.SampleLevel(TileDataSampler, VoxelUVW, 0.0f);
default: return 0.0f;
}
}- 경계면 패딩(Padding)
이전에 "패딩 때문에 메모리 낭비가 있을 것이다."라고 했습니다. 이에 엔진에서는 아래와 같이 로직을 명시하고 있습니다.
// UE 코드 내부 (SparseVolumeTextureCommon.ush Line 8)
#define SPARSE_VOLUME_TILE_RES 16 // 순수 데이터 타일 크기 (예: 16)
#define SPARSE_VOLUME_TILE_RES_PADDED (SPARSE_VOLUME_TILE_RES + 2 * SPARSE_VOLUME_TILE_BORDER) // 패딩이 포함된 타일 크기
#define SPARSE_VOLUME_TILE_BORDER 1 // 경계면 패딩 두께 (예: 1)- 수학적 계산: 물리적 타일 시작 위치(Padded 기준) + (타일 내 비율 * 순수 해상도) + 테두리 두께(Border)
이 수식이 바로, 하드웨어 삼선형 보간(Trilinear Interpolation) 시 블리딩(Bleeding) 아티팩트를 막기 위해 1복셀씩 안쪽으로 밀어 넣어서 샘플링하는 정확한 오프셋 계산입니다. 우리가 논의했던 이론이 코드 한 줄로 완벽히 구현되어 있습니다.
- Page Table의 비트 패킹 (데이터 압축)
수도 코드에서는 단순히 uint PhysicalPageIndex 하나만 가져온다고 쳤지만, 언리얼은 32비트 정수 하나(PackedPhysicalTileCoord)에 4가지 정보를 욱여넣었습니다.
// 32비트를 8비트씩 쪼개서 X, Y, Z, MipLevel을 추출함 int3 PhysicalTileCoord = int3(PackedPhysicalTileCoord & 0xFFu, (PackedPhysicalTileCoord >> 8u) & 0xFFu, (PackedPhysicalTileCoord >> 16u) & 0xFFu); uint TileMipLevel = PackedPhysicalTileCoord >> 24u;- 물리적 텍스처(Physical Texture)가 1차원 배열이 아니라 3D 텍스처이기 때문에, 1D 인덱스가 아닌 3D 타일 좌표 $(X, Y, Z)$가 필요합니다. 이를 8비트씩 잘라서 쓰고, 남은 상위 8비트는 해당 타일의 밉맵(MipLevel) 정보를 담는 데 사용하여 메모리 대역폭을 극한으로 아꼈습니다.
- 분기문(if) 없는 Empty Space 처리 (Branchless Optimization)
수도 코드에서는 빈 공간을 만나면 렌더링을 멈추기 위해 if (Index == 0) return 0; 이라는 분기문을 넣었습니다. 하지만 언리얼 코드에는 if문이 보이지 않습니다. 왜 그럴까요?
언리얼의 꼼수 (Tile 0 예약)
GPU는 if문을 만날 때 성능이 저하될 수 있습니다(Warp Divergence). 언리얼은 이를 막기 위해, Physical Texture의 가장 첫 번째 타일(0, 0, 0 위치)을 '모든 값이 0(투명)으로 꽉 찬 블랙 타일'로 만들어 둡니다.
Page Table에서 빈 공간을 만나 PackedPhysicalTileCoord가 0이 나오면, if문으로 끊지 않고 그냥 0번 타일(투명한 블랙 타일)의 좌표를 계산해서 샘플링하게 둡니다. 결과적으로 투명한 값이 반환되므로 시각적 결과는 같으면서 GPU 파이프라인이 멈추지 않게(Branchless) 최적화한 것입니다.
- 하드웨어 삼선형 보간 (Trilinear Filter)
이 부분이 우리가 NanoVDB 대신 SVT를 쓰는 궁극적인 이유였죠.복잡한 수학 공식 하나 없이, SampleLevel 함수와 TileDataSampler (여기에 Trilinear 필터 세팅이 들어있음)를 호출하여 GPU 텍스처 유닛(TMU)에 보간을 완전히 떠넘겼습니다.
PhysicalTileDataA.SampleLevel(TileDataSampler, VoxelUVW, 0.0f);- 밉맵 폴백 (Mipmap Fallback) 로직
언리얼 코드의 아주 특별한 부분입니다.
const float TileRcpMipLevelFactor = rcp(float(1u << (TileMipLevel - uint(MipLevel))));이 폴백 함수를 디테일하게 살펴보면 아래와 같습니다.
// TileMipLevel(가진 것) = 2, MipLevel(원하는 것) = 0 이라고 가정해 봅시다.
// 1단계: 해상도 차이 계산 (비트 시프트)
(TileMipLevel - uint(MipLevel)) // 2 - 0 = 2 (2단계 차이남)
// 2단계: 크기 비율 계산 (2의 거듭제곱)
1u << 2 // 이것은 2^2 와 같습니다. 즉, 값이 '4'가 됩니다.
// (Mip 2 타일은 1D 축을 기준으로 Mip 0 타일보다 4배 더 넓은 공간을 포괄합니다)
// 3단계: 역수(Reciprocal) 취하기
rcp(4.0f) // 1.0f / 4.0f = 0.25f언리얼 월드는 방대해서, 특정 구역의 고해상도(Mip 0) 데이터가 메모리에 아직 덜 로딩(Streaming)되었을 수 있습니다. 이때 코드가 터지지 않고, "원하는 밉맵이 없으면, 현재 메모리에 올라와 있는 더 낮은 해상도(TileMipLevel)의 데이터를 스케일링해서 일단 보여줘!"라는 강력한 예외 처리 로직이 들어있습니다.
원래 이 포스트를 통해 Spatial Hash Map에 대한 이야기까지 나눈 뒤 렌더링 쪽으로 넘어가고자 했으나, 생각보다 발표 시간이 촉박하여 다음 포스팅에서 이어 설명한 뒤 렌더링으로 이어갑니다.
'개발' 카테고리의 다른 글
| [Graphics] 복셀 렌더링 - 3 <Spatial Hash Map> (0) | 2026.03.19 |
|---|---|
| [Graphics] 복셀 렌더링 - 1 <복셀과 자료구조> (2) | 2026.02.20 |
| [C/C++] 조건문 컴파일러 최적화 (0) | 2025.05.22 |
| [스크랩] C++ 비트필드 선언 (0) | 2023.02.02 |
| [네트워크/SMTP] G-Mail의 SMTP 보안정책 대응 [22.05.31] (0) | 2023.01.11 |
- Total
- Today
- Yesterday
- Substance
- Unreal
- 노영태
- ue4
- designer
- Graphics
- 네트워크
- 유니티
- 유니티 셰이더
- Substance Designer
- shader
- 소프트웨어공학
- normal
- ImageEffect
- 정리
- Voxel
- unity
- 이미지이펙트
- MotionBlur
- 컴퓨터네트워크
- 이종식
- HLSL
- 법선
- 인하대
- 소공
- 컴네
- 컴퓨터구조론
- #Shader #셰이더 #Tessellator #눈발자국 #발자국
- 모션블러
- 블러효과
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 |