meshopt-rs 크레이트 설명
요약
meshopt-rs 크레이트는 meshoptimizer C/C++ 라이브러리를 위한 Rust 래퍼와 FFI 레이어를 제공합니다. 이 라이브러리는 GPU 파이프라인에서 삼각형 메쉬를 효율적으로 렌더링하기 위해 정점 및 인덱스 데이터를 최적화하는 알고리즘을 제공합니다. 사용자는 다양한 패킹, 인코딩, 재인덱싱 등의 단계를 거쳐 메시 복잡성과 저장 오버헤드를 줄일 수 있습니다.
핵심 포인트
- GPU 렌더링 효율성을 위한 메쉬 데이터 최적화 도구입니다.
- 정점 및 인덱스 데이터를 처리하는 다양한 알고리즘을 제공합니다 (예: 패킹, 양자화).
- Rust 환경에서 C/C++ 기반의 meshoptimizer를 쉽게 사용할 수 있습니다.
- 최적화 과정은 순서가 중요하며 여러 단계를 거쳐야 합니다.
meshopt
이 크레이트는 훌륭한 meshoptimizer C/C++ 라이브러리를 위한 FFI 레이어와 관용적인 rust 래퍼를 제공합니다.
목적 (Purpose)
GPU가 삼각형 메쉬를 렌더링할 때, GPU 파이프라인의 여러 단계는 정점(vertex) 및 인덱스(index) 데이터를 처리해야 합니다. 이러한 단계들의 효율성은 해당 단계에 공급되는 데이터에 달려 있으며, 이 라이브러리는 이러한 단계들을 위해 메쉬 최적화에 도움을 주는 알고리즘과 메쉬 복잡성 및 저장 오버헤드를 줄이는 알고리즘을 제공합니다.
사용법 (Usage)
Cargo.toml에 다음 내용을 추가하세요:
[dependencies]
meshopt = "0.7"
예제 (Example)
demo
이것은 거의 전체 기능 매트릭스를 실행하는 단일 모놀리식 demo 예제입니다. demo에서 opt_complete 루틴은 100% 최적의 GPU 성능을 얻는 접근 방식입니다. 추가적인 CPU 개선은 다양한 패킹(packing) 및 인코딩(encoding) 루틴을 통해 선택할 수 있습니다.
cargo run --release --example demo
encoder
encoder 예제는 일반적인 게임 엔진 파이프라인에서 메쉬 최적화를 수행하기 위한 최소한의 호출을 보여주며, meshoptimizer 저장소에 존재하는 WebAssembly 로더 및 THREE.js 뷰어 실험과 호환되는 형식으로 메쉬를 직렬화합니다.
- https://github.com/zeux/meshoptimizer/blob/master/tools/OptMeshLoader.js
- https://github.com/zeux/meshoptimizer/blob/master/demo/index.html
cargo run --release --example encoder -- --input pirate.obj --output pirate.optmesh
파이프라인 (Pipeline)
메쉬를 최적화할 때, 일반적으로 일련의 최적화를 거쳐야 합니다 (순서가 중요합니다!):
- 인덱싱
- 정점 캐시 최적화
- 오버드로우 최적화
- 정점 페치 최적화
- 정점 양자화
- (선택 사항) 정점/인덱스 버퍼 압축
인덱싱
이 라이브러리의 대부분의 알고리즘은 메시가 정점 버퍼(vertex buffer)와 인덱스 버퍼(index buffer)를 가지고 있다고 가정합니다. 알고리즘이 잘 작동하고 GPU가 메시를 효율적으로 렌더링하려면, 정점 버퍼에는 중복된 정점이 없어야 합니다. 비인덱싱된 정점 버퍼에서 인덱스 버퍼를 생성하거나 generate_vertex_remap을 사용하여 기존의 (잠재적으로 중복된) 인덱스 버퍼를 재인덱싱할 수 있습니다.
리맵 테이블(remap table)을 생성한 후, remap_index_buffer와 remap_vertex_buffer를 사용하여 리매핑을 수행할 수 있습니다.
그 다음으로 다른 함수들을 해당 버퍼들에 인플레이스(in-place)로 호출하여 결과 버퍼를 더욱 최적화할 수 있습니다.
정점 캐시 최적화
GPU가 메시를 렌더링할 때, 각 정점에 대해 정점 셰이더(vertex shader)를 실행해야 합니다. 일반적으로 GPU에는 변환된 정점(정점 셰이더 실행 결과)을 저장하는 내장 고정 크기 캐시가 있으며, 이 캐시를 사용하여 정점 셰이더 호출 횟수를 줄입니다. 이 캐시는 보통 작고 (16~32개의 정점), 다양한 교체 정책을 가질 수 있습니다. 이 캐시를 효율적으로 사용하려면, 재사용되는 정점 참조의 지역성(locality)을 최대화하도록 삼각형 순서를 재배열해야 합니다. 이러한 재배열은 optimize_vertex_cache를 사용하여 수행할 수 있습니다.
오버드로우 최적화
정점을 변환한 후, GPU는 래스터화(rasterization)를 위해 삼각형을 전송하며, 이 과정에서 일반적으로 깊이 테스트(depth test)를 거치는 픽셀들이 생성되고, 이를 통과한 픽셀에 대해 최종 색상을 생성하기 위한 픽셀 셰이더(pixel shader)가 실행됩니다. 픽셀 셰이더가 점점 더 비용이 많이 들면서, 오버드로우(overdraw)를 줄이는 것이 점점 더 중요해지고 있습니다. 일반적으로 오버드로우 개선에는 뷰 의존적 작업(view-dependent operations)이 필요하지만, 이 라이브러리는 모든 방향에서 오버드로우를 최소화하도록 삼각형 순서를 재배열하는 알고리즘을 제공합니다. 이는 정점 캐시 최적화 후에 실행해야 하며, 해당 루틴은 optimize_overdraw입니다.
오버드로(overdraw) 최적화를 수행할 때 부동소수점 임계값(floating-point threshold) 매개변수를 지정해야 합니다. 이 알고리즘은 정점 캐시 효율성(vertex cache efficiency)과 오버드로 사이의 균형을 유지하려고 시도하며, 이 임계값은 알고리즘이 얼마나 많이 정점 캐시 적중률(vertex cache hit ratio)을 저하시킬 수 있는지를 결정합니다. 1.05는 최적화 전보다 결과 비율이 최대 5% 나빠야 함을 의미합니다.
정점 가져오기 최적화 (Vertex fetch optimization)
최종 삼각형 순서가 확립된 후에도 메모리 효율성을 위해 정점 버퍼(vertex buffer)를 최적화할 수 있습니다. 정점 셰이더(vertex shader)를 실행하기 전에 GPU는 정점 버퍼에서 정점 속성(vertex attributes)을 가져와야 하며, 이 가져오기 작업은 보통 메모리 캐시(memory cache)에 의해 지원되므로, 메모리 접근의 지역성(locality of memory access)을 위해 데이터를 최적화하는 것이 중요합니다. 다음 코드를 실행하여 이를 수행할 수 있습니다:
[IMG:N]
정점 가져오기 효율성을 위해 인덱스/정점 버퍼를 최적화하려면 optimize_vertex_fetch를 호출하십시오.
이 함수는 참조 지역성(locality of reference)을 개선하기 위해 정점 버퍼의 정점을 재배열하고, 이에 맞춰 인덱스를 제자리에서 다시 작성합니다. 만약 정점 데이터가 여러 스트림으로 저장되는 경우, 대신 optimize_vertex_fetch_remap을 사용해야 합니다. 이 최적화는 최적의 정점 순서가 삼각형 순서에 의존하기 때문에 최종 인덱스 버퍼(final index buffer)에서 수행되어야 합니다.
알고리즘이 캐시 교체(cache replacement)를 정확하게 모델링하려고 시도하지 않으며, 대신 사용 순서대로 정점을 배열하여 일반적으로 최적에 가까운 결과를 산출한다는 점에 유의하십시오.
정점 양자화 (Vertex quantization)
정점 데이터를 가져올 때 메모리 대역폭(memory bandwidth)을 더욱 최적화하고 메시를 저장하는 데 필요한 메모리 양을 줄이려면, 정점 속성을 더 작은 타입으로 양자화(quantize)하는 것이 종종 유익합니다. 이 최적화는 기술적으로 파이프라인의 어느 부분에서든 실행될 수 있으며 (그리고 때로는 거의 동일한 정점을 병합하여 인덱싱을 개선하기 위해 첫 번째 단계로 양자화를 수행하는 것이 좋을 수도 있지만), 일부 최적화가 float3 위치에 대한 접근을 요구하므로, 일반적으로 다른 모든 최적화 후에 이 작업을 수행하는 것이 더 쉽습니다.
양자화(Quantization)는 보통 도메인 특화적입니다. 노멀(normals)을 3개의 8비트 정수(integer)로 양자화하는 것이 일반적이지만, 더 높은 정밀도의 양자화(예를 들어 10_10_10_2 형식에서 구성 요소당 10비트를 사용)나 단지 2개의 구성 요소만 사용하는 다른 인코딩을 사용할 수도 있습니다. 위치(positions) 및 텍스처 좌표(texture coordinate) 데이터의 경우, 가장 일반적인 두 가지 저장 형식은 하프 정밀도 부동 소수점(half precision floats)과 메시의 AABB 또는 UV 경계 사각형에 상대적인 위치를 인코딩하는 16비트 정규화된 정수입니다.
여기서 가능한 조합의 수는 매우 크지만, 이 라이브러리는 빌딩 블록을 제공합니다. 구체적으로 부동 소수점 값을 정규화된 정수 및 하프 정밀도 부동 소수점으로 양자화하는 함수들을 제공합니다.
관련 루틴:
quantize_unormquantize_snormquantize_halfquantize_float
정점/인덱스 버퍼 압축
위의 모든 최적화가 끝난 후, 지오메트리 데이터는 GPU가 소비하기에 최적입니다. 하지만 데이터를 있는 그대로 저장할 필요는 없습니다. 만약 저장 크기나 전송 대역폭이 중요하다면, 정점(vertex) 및 인덱스(index) 데이터를 압축하고 싶을 수 있습니다. Google Draco와 같은 여러 메시 압축 라이브러리가 있지만, 이들은 일반적으로 정점/인덱스 순서 유지라는 비용으로 압축률 극대화에 초점을 맞추고 있어 (이는 GPU에서 메시를 렌더링하기 비효율하게 만듭니다) 또는 디압축 성능을 희생합니다. 또한 이러한 라이브러리들은 커스텀 게임 준비 양자화된 정점 형식을 지원하지 않는 경우가 많아, 로드한 후 데이터를 재양자화해야 하며, 이는 추가적인 양자화 오차를 유발하고 디코딩 속도를 늦춥니다.
대안으로 zstd나 Oodle과 같은 범용 압축 라이브러리를 사용하여 정점/인덱스 데이터를 압축할 수 있습니다. 하지만 이러한 압축기는 정점/인덱스 데이터의 중복성을 활용하도록 설계되지 않았기 때문에, 압축률이 만족스럽지 않을 수 있습니다.
이를 위해 이 라이브러리는 정점(vertex) 및 인덱스(index) 데이터를 "인코딩"하는 알고리즘을 제공합니다. 인코딩의 결과는 일반적으로 초기 데이터보다 훨씬 작으며, 일반 목적 압축기(general purpose compressors)로도 여전히 압축이 가능합니다. 따라서 인코딩된 데이터를 직접 저장할 수도 있고 (적당한 압축률과 최대 디코딩 성능을 위해), zstd 등 다른 도구를 사용하여 추가로 압축하여 압축률을 극대화할 수도 있습니다.
인코딩을 위해서는 encode_vertex_buffer와 encode_index_buffer 루틴을 사용할 수 있습니다. 인코딩된 데이터는 그대로 직렬화(serialized)하거나 추가로 압축할 수 있습니다. 런타임에 디코딩은 decode_vertex_buffer와 decode_index_buffer 루틴으로 수행할 수 있습니다.
정점 인코딩은 정점 버퍼가 정점 가져오기(vertex fetch)에 최적화되었고, 정점이 양자화(quantized)되었다는 것을 가정하며, 인덱스 인코딩은 정점/인덱스 버퍼가 정점 캐시(vertex cache) 및 정점 가져오기에 최적화되었다는 것을 가정한다는 점에 유의하십시오. 최적화되지 않은 데이터를 인코더에 공급하면 낮은 압축률이 나올 것입니다. 두 코덱 모두 무손실(lossless)이며, 유일한 손실 과정은 인코딩 전에 발생하는 양자화입니다.
디코딩 함수는 고도로 최적화되어 있으며 쓰기 결합 메모리(write-combined memory)를 직접 대상으로 할 수 있습니다. 따라서 두 디코더 모두 최신 데스크톱 CPU에서 12 GB/s로 작동할 것으로 예상할 수 있습니다.4배(이미 양자화된 데이터를 비교했을 때)이며, 인덱스 데이터의 압축률은 약 5~6배(원시 16비트 인덱스 데이터를 비교했을 때)입니다. 일반 목적 무손실 압축기로는 이 결과를 더욱 개선할 수 있습니다.
압축률은 데이터에 따라 달라집니다. 정점 데이터의 압축률은 일반적으로 2
삼각형 스트립 변환 (Triangle strip conversion)
대부분의 하드웨어에서 인덱싱된 삼각형 목록(indexed triangle lists)이 GPU를 구동하는 가장 효율적인 방법입니다. 하지만 경우에 따라 삼각형 스트립(triangle strips)이 유용할 수 있습니다:
- 일부 구형 GPU에서는 삼각형 스트립을 렌더링하는 것이 약간 더 효율적일 수 있음
- 메모리가 극도로 제한된 시스템에서는 삼각형 스트립의 인덱스 버퍼가 약간의 메모리를 절약해 줄 수 있음
이 라이브러리는 버텍스 캐시가 최적화된 삼각형 리스트를 삼각형 스트립(triangle strip)으로 변환하는 stripify 루틴을 제공하며, 그 역변환은 unstripify 루틴으로 수행할 수 있습니다.
일반적으로 삼각형 스트립은 삼각형 리스트에 비해 인덱스가 약 5060% 적고 (삼각형당 약 1.51.8개 인덱스), ACMR(average cache miss ratio)이 약 5% 더 나쁜 경향이 있습니다. 삼각형 스트립을 사용하려면 렌더링 시 리스타트 인덱스(restart index) 지원이 필요하며, 스트립 연결을 위해 퇴화된 삼각형(degenerate triangles)을 사용하는 것은 지원되지 않습니다.
효율성 분석기 (Efficiency analyzers)
정확한 성능 데이터를 얻는 유일한 방법은 대상 GPU에서 성능을 측정하는 것이지만, 이 최적화들의 영향을 GPU 독립적인 방식으로 측정하는 것도 가치가 있습니다. 이를 위해 라이브러리는 세 가지 주요 최적화 루틴에 대한 분석기를 제공합니다. 각 최적화에는 analyze_overdraw와 같이 통계가 포함된 구조체를 반환하는 해당 분석 함수가 있습니다.
analyze_vertex_cache는 버텍스 캐시 통계를 반환합니다. 사용되는 일반적인 지표는 ACMR(average cache miss ratio)로, 이는 총 버텍스 호출 횟수를 삼각형 개수로 나눈 비율입니다. 최악의 경우 ACMR은 3 (GPU가 삼각형당 3개의 버텍스를 처리해야 함)이며, 규칙적인 그리드에서는 최적의 ACMR이 0.5에 접근합니다. 실제 메쉬의 경우 일반적으로 버텍스 분할(vertex splits) 정도에 따라 [0.5..1.5] 범위에 있습니다. 또 다른 유용한 지표는 ATVR(average transformed vertex ratio)로, 이는 버텍스 셰이더 호출 횟수를 전체 버텍스 수로 나눈 비율을 나타내며, 메쉬 토폴로지(mesh topology)와 관계없이 최상의 경우는 1.0입니다 (각 버텍스는 한 번 변환됨).
analyze_vertex_fetch는 버텍스 페치 통계를 반환합니다. 여기서 사용되는 주요 지표는 오버페치(overfetch)로, 이는 버텍스 버퍼에서 읽은 바이트 수를 버텍스 버퍼의 총 바이트 수로 나눈 비율입니다. 비중복적인 버텍스 버퍼를 가정할 때 최상의 경우는 1.0입니다 (각 바이트가 한 번 페치됨).
analyze_overdraw는 오버드로(overdraw) 통계를 반환합니다. 여기서 주요 측정 지표는 오버드로로, 여러 개의 다른 직교 카메라(orthographic cameras)에서 측정된 픽셀 셰이더 호출 횟수와 총 커버된 픽셀 수의 비율입니다. 오버드로가 가장 좋은 경우는 1.0이며, 이는 각 픽셀이 한 번만 셰이딩된다는 것을 의미합니다.
모든 분석기는 관련 GPU 유닛에 대한 근사 모델을 사용하므로, 결과로 얻게 되는 수치는 실제 성능의 대략적인 추정치일 뿐이라는 점에 유의하십시오.
라이선스 (License)
다음 중 하나에 따라 라이선스가 부여됩니다:
- Apache License, Version 2.0 (LICENSE-APACHE 또는 http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT 또는 http://opensource.org/licenses/MIT)
선택에 따라.
크레딧 및 특별 감사 (Credits and Special Thanks)
AI 자동 생성 콘텐츠
본 콘텐츠는 개발 자료 GitHub · 8bfcc14c의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기