# ============================================================================= # .crtex — ktx_compress.py 가 사용하는 텍스처별 계약 파일 # ============================================================================= # # ── 이 파일은 무엇인가 ───────────────────────────────────────────────────── # 소스 이미지 1개당 .crtex 1개, 같은 basename 사용: foo.png + foo.crtex # 형식은 TOML (Python tomllib 로 파싱) — 문자열은 큰따옴표, 불리언은 소문자 true/false, # 정수는 그대로, 주석은 '#', [encode] / [runtime] 은 TOML 테이블. # # ── 생명주기 (중요) ──────────────────────────────────────────────────────── # .crtex 는 저작 전용이라 assets_raw/texture/ 에만 존재하며 배포되지 않는다. # ktx_compress.py 가 png + crtex 를 읽어 assets/texture/foo.ktx2 를 만든다: # [encode] → .ktx2 컨테이너 자체에 구워짐. format / 밉 레벨 수 / 색공간을 ktx2 가 스스로 서술하므로 런타임에 따로 전달할 필요가 없다. # [runtime] → .ktx2 의 key/value 메타데이터로 PACK 됨 (키 이름: crtex_wrap_s, crtex_wrap_t, crtex_filter). 엔진 로더가 이 KV 를 읽어 GL 샘플러 상태에 적용한다. # 결과적으로 배포되는 .ktx2 는 자기완결적이며, 이 .crtex 자체는 함께 나가지 않는다. # # ── 규약 ─────────────────────────────────────────────────────────────────── # * 모든 필드 필수 — 파일 누락 / 필드 누락 / 미지의 필드는 전부 빌드 실패 (fail-fast). # * 기본값 없음 — "안 적으면 알아서" 가 없다. 전부 명시해야 한다 (알아야 다룰 수 있고, 그래야 정확히 다뤄진다). # * '_' 로 시작하는 파일은 스캔에서 제외 — 이 템플릿(_template.crtex)이 변환 대상이 아닌 이유. # # ----------------------------------------------------------------------------- # 레시피 — 텍스처 종류별로 "무엇"이 아니라 "왜". 아래는 기본값에서 "바꿀 것"만 적는다. # ----------------------------------------------------------------------------- # * 일반 스프라이트 (가장 흔함) # 기본값 그대로 — 바꿀 것 없음. 컬러 스프라이트엔 압축 + 밉 + clamp + linear + sRGB 가 무난하다. # # * UI / HUD (화면에 1:1로 그려지고 축소될 일이 없음) # mipmap = false 고정 크기라 축소가 없음 → 밉은 샘플될 일 없이 용량만 차지 # filter = "nearest" 또렷한 픽셀 UI. 부드러운 UI 면 "linear" 유지 # # * 아틀라스 시트 (.cratlas 동반, 예: builtin_atlas_0) # mipmap = false 낮은 밉에서 서브렉트 UV 가 이웃 스프라이트로 번짐 (8px gutter 는 레벨마다 반감 → mip 4에서 소멸) # wrap_s = "clamp" HW wrap 은 텍스처 전체 기준이라 서브렉트엔 적용 불가 → 아틀라스에서 REPEAT 은 무의미 # wrap_t = "clamp" # # * 타일링 텍스처 (노이즈, 스크롤 배경 — 두 축 모두 반복) # wrap_s = "repeat" 반드시 단독 텍스처 — 아틀라스에 넣지 말 것 (위 항목 참고) # wrap_t = "repeat" mipmap = true 면 툴이 이 값을 밉 엣지 모드로도 재사용 → 낮은 밉에서도 이음새 유지 # # * 한 축만 타일링 (리본 트레일 등 — 예: sq16 / 1x1gray, crGraphics::RepeatTexture) # wrap_s = "repeat" u 가 표면(트레일 길이)을 따라 타일링 # wrap_t = "clamp" v 는 폭을 한 번만 걸침 # # * 데이터 텍스처 (마스크, LUT, 하이트맵, 데이터용 노이즈) # srgb = false 셰이더는 원시 값을 기대 → sRGB 디코드가 걸리면 에러 없이 값만 조용히 틀어짐 (silent) # mipmap = false 밉 평균이 데이터의 의미를 망치는 경우가 많음 # filter = "nearest" 정확한 조회는 nearest(값 보존), 부드러운 데이터면 "linear" # # * 노멀맵 # srgb = false 색이 아니라 벡터 성분 (linear) # compress = "none" 라이팅에서 블록 압축 아티팩트가 보이면 무압축으로 (안 보이면 uastc 로도 무방) # # * 픽셀아트 / 정확한 색이 필요한 경우 # filter = "nearest" 정의적 선택 — 픽셀 단위로 또렷하게 # compress = "none" UASTC 는 손실 압축; LINEAR 는 오차를 뭉개주지만 NEAREST 는 픽셀 오차를 그대로 노출 # mipmap = false 보통 밉 블러도 원치 않음 # # * 초소형 / 솔리드 유틸 (1x1, 소형 스와치) # compress = "none" 이 크기엔 블록 압축 이득이 없음 (파일 용량은 zstd 가 처리) # mipmap = false 단색/미세 텍스처는 밉이 무의미 # # * 3D 월드 텍스처 / 빌보드 (거리에 따라 축소되어 보임) # mipmap = true 밉 없이 축소되면 시머링/에일리어싱 (기본값이 이미 true) # # srgb 판별 경험칙: "플레이어가 보는 색" -> true / "셰이더가 해석하는 숫자" -> false # # ----------------------------------------------------------------------------- # 필드 노트 — 필드별 의미와 함정 (레시피가 역할별이라면, 이건 필드별) # ----------------------------------------------------------------------------- # compress 블록 압축 = GPU/VRAM 용량 담당. 파일 용량이 아니다(그건 zstd 몫). "uastc" = 손실 블록 압축, "none" = 무압축(픽셀아트/솔리드/노멀 등). # mipmap 압축 텍스처는 GL 이 런타임에 밉을 생성할 수 없어 파일에 미리 구워야 한다. filter 와 짝을 이룬다(맨 아래 참고) — 밉 유무와 min-filter 가 일치해야 함. # srgb 틀리면 에러 없이 색/값이 조용히 어긋난다(silent failure). 소스 PNG 에서 안전하게 추론 불가라 반드시 명시. 색 = true, 데이터/노멀 = false. # quality UASTC 인코딩 effort (0..4). 크기가 아니라 "탐색 노력"이다 — 레벨과 무관하게 8bpp 고정, 높을수록 품질↑/인코딩만 느려짐. 오프라인 bake 라 4(최고) 권장. # mip_filter mipmap = false 면 무시되지만, "전 필드 명시" 규약상 값은 적는다. # zstd 파일/다운로드 용량 담당(무손실). 압축 해제는 런타임에 레벨과 거의 무관하게 일정 → 높은 레벨이 로드엔 사실상 공짜. 단 UASTC 페이로드는 고엔트로피라 ~18 위로는 거의 안 줄어든다. 더 짜내려면 zstd 레벨보다 RDO(현재 보류)가 효과적. # wrap_s/t GL 은 S(u) / T(v) 축을 독립 설정하므로 둘 다 명시한다. "mirror" = GL_MIRRORED_REPEAT. mipmap = true 면 이 값이 밉 생성 엣지 샘플 모드로도 재사용된다(타일 이음새 유지). ktx --mipmap-wrap 은 단일 모드라 두 축이 다르면 타일링 축을 우선해 하나로 도출한다 — 런타임 타일링은 KV 로 축별 정확하고, 이 도출은 생성된 밉의 경계 텍셀에만 영향. # filter "의도"만 선언한다 — 실제 GL min-filter 는 (filter × 밉 유무) 조합으로 로더가 도출한다. 그래서 "밉이 없는데 밉맵 필터를 걸어 텍스처가 incomplete" 같은 사고가 구조적으로 불가능하다. # ============================================================================= [encode] compress = "uastc" # "uastc" | "none" mipmap = true # true | false srgb = true # true | false quality = 4 # 0..4 mip_filter = "lanczos4" # box | tent | bell | b-spline | mitchell | blackman | lanczos3 | lanczos4 | lanczos6 | lanczos12 | kaiser | gaussian | catmullrom | quadratic_interp | quadratic_approx | quadratic_mix zstd = 18 # 1..22 [runtime] wrap_s = "clamp" # "clamp" | "repeat" | "mirror" (S / u 축) wrap_t = "clamp" # "clamp" | "repeat" | "mirror" (T / v 축) filter = "linear" # "linear" | "nearest"