8 게임 자산을 위한 ASTC 사용 (Using ASTC for game assets)
개발자로서 게임 자산(asset)을 압축할 때는 몇 가지 사항을 고려해야 합니다. 이러한 고려 사항은 텍스처의 사용 방식과 요구되는 이미지 품질에 따라 달라집니다.
8.1 적절한 비트레이트 선택
더 높거나 낮은 비트레이트를 선택함으로써 데이터 크기 대비 이미지 품질을 조절하여 최적의 균형을 맞출 수 있습니다. astcenc와 Mali Texture Compression Tool, 그리고 여타 도구 및 게임 엔진들은 모두 ASTC가 지원하는 전체 범위 내에서 비트레이트나 블록 크기를 지정할 수 있게 해줍니다.
다음 이미지는 서로 다른 비트레이트가 이미지 품질에 어떤 영향을 미치는지 보여줍니다.

좋은 방법은 뷰어(관찰자)와의 거리나 전반적인 가시성 및 중요도에 따라 모든 텍스처 자산을 품질 카테고리로 분류하는 것입니다. 예를 들어, 자산을 상, 중, 하 세 가지 카테고리로 나눌 수 있습니다. 개별 텍스처마다 비트레이트를 조정하는 대신, 각 카테고리에서 몇 개의 텍스처로 실험을 하여 해당 카테고리에 대한 최적의 비트레이트를 결정하십시오.
그런 다음 결정된 비트레이트를 사용하여 각 카테고리의 나머지 텍스처들을 일괄 압축(batch-compress)할 수 있습니다.
3D 콘텐츠에 사용되는 텍스처와 GUI 요소에 사용되는 텍스처는 구별해야 합니다. 때로는 원치 않는 아티팩트(화질 결함)를 피하기 위해 GUI 텍스처를 압축하지 않은 상태로 두는 것이 최선일 수 있습니다.
장치가 ASTC를 지원한다면 3D 콘텐츠의 텍스처 압축에 이를 사용하십시오. 장치가 ASTC를 지원하지 않는다면 ETC2 사용을 시도해 보십시오.
8.2 노멀 맵 (Normal maps)
노멀 맵은 원치 않는 아티팩트를 방지하기 위해 특별한 방식으로 처리되어야 합니다.
astcenc 도구는 노멀 맵 인코딩을 위한 두 가지 옵션인 -normal_psnr과 -normal_percep을 제공합니다. 두 옵션 모두 astcenc가 단위 길이 탄젠트 공간 노멀(unit-length tangent space normal)의 X와 Y 성분을 루미넌스(Luminance)와 알파(Alpha)로 저장하여 노멀 맵을 처리하도록 지정합니다. 노멀의 Z 성분은 벡터의 길이가 1이라는 점을 이용하여 셰이더 코드에서 재구성할 수 있습니다.
Z 값을 재구성하기 위한 GLSL 코드는 다음과 같습니다.
vec3 normal;
normal.xy = texture(...).ga;
normal.z = sqrt(1 - dot(normal.xy, normal.xy));
(주: 노말맵은 길이가 1이라는것을 이용해서 x와 y 데이터만 가지고 z 데이터를 뽑습니다.
단 텍스쳐에서는 보통 g와 a 를 사용하죠. 이걸 이용해서 x와 y 를 추출하고, 이를 다시 이용해서 z 를 추출하는 방법입니다. astc도 RGB와 A를 독립적으로 처리하니 여전히 이게 도움이 되네요 )
-normal_psnr 및 -normal_percep 옵션은 또한 압축기가 데이터의 절대 색상 오차(absolute color error)보다는 결과 벡터의 각도 오차(angular error)에 최적화하도록 전환합니다. 노멀 맵에서는 각도 오차가 절대 색상 오차보다 더 중요하기 때문입니다.
(주: Percep vs PSNR: percep은 지각적(perceptual) 품질을 중시하고, psnr은 수치적 신호 대 잡음비(PSNR)를 중시합니다. 일반적으로 노멀 맵은 사람 눈에 보이는 각도 차이가 중요하므로 -normal_percep이 더 자연스러운 결과를 줄 때가 많습니다.)
다음 이미지는 -normal_psnr과 -normal_percep 옵션을 사용한 결과를 보여줍니다.

왼쪽의 노멀 맵은 기본 설정으로 압축된 것입니다. 가운데 노멀 맵은 -normal_psnr을 사용했습니다. 오른쪽 노멀 맵은 -normal_percep을 사용했습니다.
8.3 마스크 (Masks)
-mask 옵션은 입력 텍스처의 각 채널에 완전히 관계없는 콘텐츠가 들어있음을 명시합니다. 이 옵션은 한 채널의 오차가 다른 채널에 영향을 미치는 것이 바람직하지 않다는 것을 astcenc에 알립니다.
다음 이미지는 비트맵 폰트의 예를 보여줍니다.

빨간색(Red) 채널은 글자를, 파란색(Blue) 채널은 후광(rear glow)을, 초록색(Green) 채널은 드롭 섀도우(drop shadow)를 나타냅니다.
왼쪽 이미지는 압축되지 않은 데이터입니다. 가운데 이미지는 기본 설정으로 압축된 것이며, 오른쪽 이미지는 -mask 인수를 사용한 것입니다.
(주: 게임 개발에서는 이를 흔히 채널 패킹(Channel Packing)이라고 부릅니다. 예를 들어 R채널엔 메탈릭, G채널엔 러프니스, B채널엔 앰비언트 오클루전을 넣는 경우입니다. 이때 서로 다른 정보가 섞이지 않도록(Crosstalk 방지) -mask 옵션을 켜주는 것이 품질 유지에 중요합니다.)
8.4 sRGB
ASTC는 압축 및 압축 해제 시점 모두에서 비선형 sRGB 색 공간 변환을 지원합니다.
이미지를 사용할 시점까지 sRGB 색 공간으로 유지하려면:
1. 평소와 같은 방식으로 이미지를 압축합니다.
2. 이미지를 로드할 때, 일반 텍스처 포맷 대신 sRGB 텍스처 포맷을 사용합니다.
sRGB 텍스처 포맷은 이름에 SRGB8_ALPHA8을 포함합니다. 예를 들어 COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR와 같습니다. 모든 RGBA 포맷에는 그에 상응하는 sRGB 포맷이 존재합니다.
런타임에 sRGB 텍스처 타입을 사용하는 것에 대한 대안으로, 압축 전에 이미지를 선형(linear) RGBA로 변환하는 압축기용 명령줄 인수가 있습니다. -srgb 인수는 색 공간을 변환하고 텍스처를 선형 공간에서 압축하여, (런타임에는) 일반적인 RGBA 텍스처 포맷으로 로드할 수 있도록 준비해 줍니다.
(역주 : 원래 텍스쳐는 sRGB 옵션으로 저장합니다. 그리고 셰이더가 텍스쳐를 읽을때 GPU 하드웨어가 자동으로 sRGB -> Linear 변환을 수행하여 셰이더에 전달합니다)
9. 그래픽스 API와 함께 ASTC 사용하기 (Using ASTC with graphics APIs)
(역주 : 게임엔진을 이용하는 차원에서는 사실 아래 내용은 신경쓸 일이 없습니다만... 하드웨어에서 지원 불가 이슈가 나올때 참고할 수는 있겠네요)
OpenGL(확장 기능 사용)과 Vulkan 모두 ASTC를 지원합니다. 이러한 지원 덕분에 쉽게 통합하고 하드웨어를 활용할 수 있습니다.
ASTC 기능은 다음 세 가지 기능 프로파일 세트로 규정됩니다.
- 2D LDR 프로파일
- 2D LDR + HDR 프로파일
- 2D LDR + HDR 및 3D LDR + HDR 프로파일
2D LDR 프로파일은 OpenGL ES 3.2에서 필수 사항이며, Vulkan에서는 표준화된 선택적(optional) 기능입니다. 이는 2D LDR 프로파일이 최신 모바일 장치에서 널리 지원됨을 의미합니다. 2D HDR 프로파일은 필수 사항은 아니지만 널리 지원되고 있습니다.
9.1 Khronos OpenGL ES 확장 (Khronos OpenGL ES extensions)
ASTC의 기능 프로파일에 대한 공식 Khronos 확장(extension)들이 존재합니다.
- KHR_texture_compression_astc_ldr: 2D LDR 지원
- KHR_texture_compression_astc_sliced_3d: 2D + 슬라이스 된(sliced) 3D LDR 지원
- KHR_texture_compression_astc_hdr: 2D + 슬라이스 된 3D HDR 지원 및 2D LDR 지원
또한, 세 가지 Khronos 확장을 모두 지원하는 것과 같은 전체 기능 세트를 제공하는 편의성 확장도 있습니다.
- OES_texture_compression_astc: 2D + 3D, LDR + HDR 지원
각 슬라이스를 독립적으로 압축할 수 있는 '슬라이스 된 3D'와 4x4x4와 같은 '진정한 3D 포맷' 사이에는 차이가 있습니다. OES_texture_compression_astc는 3D 포맷을 가져오는 유일한 확장이며, KHR 확장들의 상위 집합(superset)입니다.
ASTC는 텍셀(texel)을 fp16(16비트 부동소수점) 중간값으로 압축 해제합니다. 단, sRGB는 항상 8비트 UNORM 중간값으로 압축 해제합니다. 많은 사용 사례에서 fp16은 필요 이상의 동적 범위(dynamic range)와 정밀도를 제공하며, 데이터 크기가 커서 텍스처링 효율성을 저하시킬 수 있습니다.
역자 주: 여기서 '데이터 크기가 커서 효율성이 떨어진다'는 말은 압축 파일 크기가 아니라, GPU 내부 캐시 대역폭이나 전력 소모 측면에서의 효율성을 말합니다. fp16 데이터는 8비트 데이터보다 처리가 무겁기 때문입니다.
최신 모바일 GPU에서 지원되는 다음 확장들을 사용하면, 애플리케이션이 중간 정밀도를 UNORM8 또는 RGB9e5로 줄일 수 있습니다.
- OES_texture_compression_astc_decode_mode: UNORM8 중간값 허용
- OES_texture_compression_astc_decode_mode_rgb9e5: RGB9e5 중간값 허용
LDR 텍스처에는 UNORM8이 권장되며, HDR 텍스처에는 RGB9e5가 권장됩니다.
ASTC 이미지 블록을 디코딩할 때 디코딩 정밀도를 선택하기 위해 ASTC 디코드 모드 확장을 사용하는 방법에 대한 자세한 내용은 'OpenGL ES SDK for Android: ASTC low precision tutorial'을 참조하십시오.
9.2 ASTC 헤더 (ASTC header)
OpenGL과 Vulkan 모두에서, ASTC 데이터를 처리하기 전에 특정 텍스처 이미지 속성을 알아야 합니다. 예를 들어, 텍스처의 크기와 압축된 데이터 크기를 계산하려면 ASTC 블록의 차원(dimensions)과 크기를 알아야 합니다. 이 정보는 압축된 데이터의 시작 부분에 있는 ASTC 헤더에서 찾을 수 있습니다.
ASTC 헤더는 다음과 같은 형식을 가집니다.
/* ASTC 헤더 선언. */
typedef struct
{
unsigned char magic[4]; // 매직 넘버
unsigned char blockdim_x; // 블록 X 차원
unsigned char blockdim_y; // 블록 Y 차원
unsigned char blockdim_z; // 블록 Z 차원
unsigned char xsize[3]; // 텍스처 전체 X 크기 (3바이트)
unsigned char ysize[3]; // 텍스처 전체 Y 크기 (3바이트)
unsigned char zsize[3]; // 텍스처 전체 Z 크기 (3바이트)
} astc_header;
9.3 OpenGL ES
ASTC 포맷을 사용하려면 해당 OpenGL ES 확장을 지원하는 하드웨어에서 애플리케이션을 실행해야 합니다.
- GL_KHR_texture_compression_astc_ldr
- GL_KHR_texture_compression_astc_hdr
하드웨어가 이 확장들을 지원하지 않는 경우, glCompressedTex* 함수를 통해 기본 그래픽 드라이버로 전달될 때 잘못된 포맷이나 내부 포맷에 대한 메시지를 보게 됩니다.
텍스처를 준비하려면 다음 단계를 수행하십시오.
- 텍스처 이미지 파일을 열고 로컬 메모리 버퍼에 로드합니다.
- 로컬 버퍼를 ASTC 헤더 구조체에 매핑합니다.
- ASTC 헤더에서 텍스처 속성을 읽고 glCompressedTexImage2D 함수에 전달할 인수들의 필요한 값을 계산합니다.
이 호출에 사용될 총바이트 수는 다음 코드와 같이 블록당 바이트 수와 블록 수를 곱하여 계산됩니다.
/* 3개의 char로 된 x, y, z 크기를 하나의 정수 값으로 병합. */
xsize = astc_data_ptr->xsize[0] + (astc_data_ptr->xsize[1] << 8) + (astc_data_ptr->xsize[2] << 16);
ysize = astc_data_ptr->ysize[0] + (astc_data_ptr->ysize[1] << 8) + (astc_data_ptr->ysize[2] << 16);
zsize = astc_data_ptr->zsize[0] + (astc_data_ptr->zsize[1] << 8) + (astc_data_ptr->zsize[2] << 16);
/* 각 방향의 블록 수 계산. */
// 역자 주: (전체 크기 + 블록 크기 - 1) / 블록 크기 공식은 올림 나눗셈을 위한 것입니다.
xblocks = (xsize + astc_data_ptr->blockdim_x - 1) / astc_data_ptr->blockdim_x;
yblocks = (ysize + astc_data_ptr->blockdim_y - 1) / astc_data_ptr->blockdim_y;
zblocks = (zsize + astc_data_ptr->blockdim_z - 1) / astc_data_ptr->blockdim_z;
/* 각 블록은 16바이트로 인코딩되므로, 총 압축 이미지 데이터 크기를 계산. */
// 역자 주: << 4는 16을 곱하는 것과 같습니다.
n_bytes_to_read = xblocks * yblocks * zblocks << 4;
4. 다음 코드와 같이 glCompressedTexImage2D를 호출합니다.
glGenTextures(1, &to_id);
glBindTexture(GL_TEXTURE_2D, to_id);
/* 텍스처 데이터를 ES로 업로드. */
glCompressedTexImage2D(GL_TEXTURE_2D,
0,
compressed_data_internal_format,
xsize,
ysize,
0,
n_bytes_to_read,
(const GLvoid*) astc_data_ptr); // 주의: 실제로는 헤더 다음의 데이터 포인터여야 할 수 있음 (구현에 따라 다름)
5. GL_TEXTURE_2D 타겟에 대한 텍스처 파라미터를 설정합니다.
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
더 자세한 정보는 Arm OpenGL ES SDK 웹사이트의 전체 예제를 참조하십시오.
9.4 Vulkan
ASTC는 Vulkan의 핵심(core) 기능에 포함되어 있지만, 포맷 지원 자체는 선택적(optional)입니다. textureCompressionASTC_LDR 물리 장치 기능(physical device feature)이 활성화되어 있는지 확인하십시오.
GPU에서 ASTC가 지원되는지 감지하려면 다음 코드를 사용하십시오.
VkFormatProperties properties;
vkGetPhysicalDeviceFormatProperties(pContext->getPhysicalDevice(),
VK_FORMAT_ASTC_4x4_UNORM_BLOCK, &properties);
bool supports_astc = (properties.optimalTilingFeatures &
VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT) != 0;
ASTC 텍스처 업로드는 다른 이미지를 업로드하는 것과 동일한 메커니즘을 사용합니다. 압축된 텍스처와 압축되지 않은 텍스처를 업로드하는 것의 유일한 실질적인 차이는 ASTC 텍스처가 다른 포맷인 VK_FORMAT_ASTC_*를 사용한다는 점입니다.
다음 코드와 같이 vkCmdCopyBufferToImage를 사용하여 ASTC 데이터를 텍스처로 복사합니다.
VkBufferImageCopy region = {};
region.bufferOffset = 0;
region.bufferRowLength = 0; // Tightly packed. (빈틈없이 채워짐)
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
region.imageSubresource.layerCount = 1;
region.imageExtent.width = width;
region.imageExtent.height = height;
region.imageExtent.depth = 1;
// 버퍼를 최적화된 타일링 이미지로 복사.
// ASTC와 비압축 텍스처 간에 차이 없음.
vkCmdCopyBufferToImage(cmd, stagingBuffer.buffer, image,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion);
더 자세한 정보는 Arm Vulkan SDK 웹사이트의 전체 예제를 참조하십시오.
'자료는 자료지 > 외부에서 퍼온자료' 카테고리의 다른 글
| 실시간 렌더링 파이프라인 기초 (6) | 2026.01.02 |
|---|---|
| ASTC_User_Guide 번역 6 (0) | 2025.12.15 |
| ASTC_User_Guide 번역 4 (0) | 2025.12.14 |
| ASTC_User_Guide 번역 3 (0) | 2025.12.09 |
| [번역]양식화된 잔디 렌더링 (0) | 2025.12.09 |
댓글