티스토리 뷰
Jittering
- Jittering이란 3D 그래픽에서 부동소수점 정밀도 부족으로 인해 오브젝트가 미세하게 흔들리거나 깨지는 현상을 의미한다.
- 대부분의 GPU는 64비트가 아닌 32비트 부동 소수점 값을 지원 및 최적화
- 반면, CPU는 64비트 Double Precision(배정밀도) 사용
- CPU에서 Double Precision 연산을 활용
- GPU에서 지터링 제거
- RTC 사용, 이는 Double Precision 보다 메모리를 절약한다.
- ⇒ 지터링을 제거하는 접근 방식
IEEE 754
: 컴퓨터에서 부동 소수점을 표현하는 가장 널리 쓰이는 표준. 다음과 같은 항목들을 정의.- 산술 형식: 유한한 수들(0을 포함한)과 무한대와 NaN(Not a number)값으로 구성된 2진수와 10진수의 부동소수점 데이터 집합
- 형식의 교환: 부동소수점 데이터를 효율적이고 압축적으로 전환할 수도 있는 인코딩
- 반올림 규칙: 산수와 전환의 과정에서 반올림을 할 때의 성질
- 작동: 산수와 산술 형식의 처리 방법 형식
- 예외 처리: 예외적인 조건의 표기 (0으로의 나누는 작업, 오버플로 등)
Jittering이 발생하는 근본적인 원인: 부동 소수점 오차
부동 소수점 오차가 발생하는 예시
- 월드 좌표가 매우 클 때 (ECEF 같은 지구 좌표계)
- 지구 반지름이 $x ≈ 6,371,000m$라고 가정을 하자. 이때 나는 지구를 $1cm$ 단위로 표현을 하고 싶다.
- m단위를 cm로 변환하면 $x ≈ 637,100,000cm$ 이다.
- $637,100,000 = 6.371000000 \times 10^{8}$
- 하지만 float은 소수점 7자리만 정밀도를 유지할 수 있기 때문에, 지구를 $1cm$ 단위로 정밀하게 표현할 수 없다.
- 해결 방법
- RTC (Relative to Center) 좌표 변환
- 더 높은 정밀도의 데이터 타입 사용 (
double
)
- 카메라가 너무 가까워지거나 너무 멀어질 때
- 해결 방법
- View Matrix 조정 (카메라 근처로 변환)
- Near/Far Plane 조절 (너무 작은 값은 피하기)
- 해결 방법
- 매우 큰 객체를 렌더링할 때
- 해결 방법
- 타일링(Tiling) 기법 (큰 오브젝트를 작은 조각으로 나눠서 렌더링)
- LOD (Level of Detail) 적용 (멀리 있는 객체는 정밀도를 낮추어 표시)
- 해결 방법
- FOV(시야각)가 좁을수록 각 픽셀이 차지하는 월드 공간이 작아져 Jitter 가 더 심해진다.
- 해결 방법
- FOV 값을 너무 작게 설정하지 않기
- Stabilization 알고리즘 적용 (예: Temporal AA, Motion Vector 보정)
- 해결 방법
Scaling이 Jittering 해결을 못하는 이유
- “큰 숫자의 반올림 오류는 지터링을 발생시키므로 큰 숫자를 제거하면 되지 않을까?”라는 생각을 할 수 있다.
- 지구를 m 단위로 사용하지 말고, 훨 씬 더 큰 단위를 사용하여 스케일링은 값 자체를 더 작게 만들 수 있지만,
- 표현 가능한 값 사이의 간격이 더 작아야 하므로 Jitter에 대해 효과적이지 않다.
RTC(Rendering Relative to Center)
RTC는 객체의 모든 좌표를 특정 중심점 $center_{WGS84}$ 을 기준으로 상대적인 좌표로 변환하여 다루는 방식
- $center_{WGS84} =$ 지구 중심(WGS 84기준)에서의 절대 좌표
- $center_{eye}$ = WGS84를 기준으로 카메라(viewer) 좌표계로 변환된 중심점 좌표
- WGS84 좌표계를 그대로 쓰면 부동소수점 연산 오차가 크므로
- ⇒ 특정 중심점인 $center_{eye}$을 기준으로 모든 객체의 좌표를 상대적으로 표현할 수 있다.
- 즉, RTC는 모든 좌표를 특정 중심점으로부터 얼마나 떨어져 있는지 기준으로 변환하는 것
$MV_{RTC}$ 변환 과정
- $MVP$(Model-View-Projection) 행렬에서 중요한 것은 네 번째 열(Translation, 이동 변환) 이다.
- $center_{(WGS84)}$를 $MV$ 행렬로 변환하여 $center_{(eye)}$ 값을 구한다.
- $center_{(eye)} = MV \times center_{(WGS84)}$
- 이 과정은 CPU에서 double precision(64비트 부동소수점) 연산을 사용하여 수행
- MV 행렬의 네 번째 열을 $center_{(eye)}$ 값으로 교체하여 $MV_{(RTC)}$를 생성한다.
- 이렇게 변환하면, 카메라(viewer)가 중심점($center_{(WGS84)}$)으로 이동할수록 $cneter_{(eye)}$값이 작아지고,
- 값이 작은 값일수록 32비트 부동소수점 정밀도로도 충분히 표현이 가능해진다.
$MV_{(RTC)} = \begin{pmatrix}
MV_{00} & MV_{01} & MV_{02} & center_{eye}x \\
MV_{10} & MV_{11} & MV_{12} & center_{eye}y \\
MV_{20} & MV_{21} & MV_{22} & center_{eye}z \\
MV_{30} & MV_{31} & MV_{32} & MV_{33} \\
\end{pmatrix}$
RTC 핵심
- 모든 좌표를 중심점($center_{(WGS84)}$) 기준으로 상대 좌표로 변환하여 float 정밀도를 유지하는 것
- MV 행렬의 네 번째 열을 $cneter_{(eye)}$로 교체하여 RTC 변환을 적용하는 것
- CPU에서 double precision(64비트) 연산을 사용해 $cneter_{(eye)}$를 계산한 후, GPU에서 32비트 연산을 수행하는 것
RTC 문제점
- 렌더링 하는 객체 크기가 너무 클 경우
- 객체가 너무 크면 카메라 중심을 기준으로 하더라도 ECEF 점을 기준으로 한 것과 차이가 없다.
- ex) 카메라가 높이가 1,000,000m인 벽 바로 앞에 있든, 하늘 상공 1,000,000m에있든 상대적으로 변환된 좌표의 크기는 여전히 크다.
- 벽의 위쪽과 아래쪽 좌표 값 차이가 너무 커서 RTC를 적용해도 float 정밀도 한계를 극복하지 못함.
- ex) 카메라가 높이가 1,000,000m인 벽 바로 앞에 있든, 하늘 상공 1,000,000m에있든 상대적으로 변환된 좌표의 크기는 여전히 크다.
- 그럼 지터링이 발생하지 않게 하기 위해, 물체의 크기를 최대 얼마나 크게 할 수 있을까?
- 32비트 부동 소수점 숫자는 7 자리 십진수를 표현할 수 있으므로
- Ohlarik은 1cm 정확도의 경우 경계 반경이 131,071m 를 초과하지 않을 것을 권장한다.
- $131,071 = 1.31071 \times 10^7$
- 32-bit 부동소수점이 1cm 정밀도를 유지할 수 있는 한계가 131,071m ≈ 130km
- 그 이상의 거리에서 부동소수점 오차로 인해 정밀도가 낮아짐.
- 객체가 너무 크면 카메라 중심을 기준으로 하더라도 ECEF 점을 기준으로 한 것과 차이가 없다.
- 카메라가 여러 개의 매우 먼 오브젝트를 렌더링 할 경우
- ex) 여러 개의 행성을 RTC로 표현한다면 한 행성을 기준으로 상대 좌표를 변환하더라도, 다른 행성은 여전히 상대 좌표가 크다.
RTC 문제점을 극복하는 방법
- Tiled Rendering
- 지형이나 거대한 오브젝트를 여러 개의 타일(Tile)로 분할하여 렌더링하면, 개별 타일에서 상대 좌표 변환을 수행하여 정밀도를 높일 수 있다
- Multiple Origin RTC
- 하나의 $cneter_{(WGS84)}$만을 사용하지 않고,씬(Scene) 내에서 여러 개의 기준점을 설정하여 상대 좌표 변환을 수행하는 방법
- 64-bit Precision 사용
- GPU에서 64비트(double precision)를 지원하는 경우, RTC를 사용할 필요 없음
RTE(Rendering Relative to Eye)
RTC의 객체 크기 한계를 극복하기 위해 객체를 카메라 시점(Viewer 또는 Eye) 기준으로 렌더링 하는 기법
- RTC(Relative to Center) → 특정 중심점 $center_{(WGS84)}$을 설정하고, 모든 좌표를 그 기준으로 상대적으로 변환
- RTE(Relative to Eye) → 카메라 자체를 중심점으로 사용하여 모든 객체를 변환
- CPU RTE는 모델 좌표(Model Coordinates)를 카메라 기준 좌표(RTE, Rendering Relative to Eye)로 변환하는 과정을,
- CPU에서 Double Precision(64 비트 연산)을 이용하여 변환하는 방식이다.
- 카메라 이동 시마다 $eye_{(WGS84)}$가 바뀌므로 CPU에서 연산을 다시 수행해야 함
- 카메라 이동 시마다 $eye_{(WGS84)}$가
Dynamic Vertex Buffer
에 기록 됨
- 카메라 이동 시마다 $eye_{(WGS84)}$가
- 하지만 GPU에서는 모든 오브젝트가 동일한 MV 행렬을 사용할 수 있어 최적화 가능
RTE 변환 과정
$MV_{RTE}$ 변환 과정
MVP 행렬에서 translation 값을 제거하여 카메라를 중심으로 장면(Scene)을 변환
- MV 행렬의 네 번째 열(Translation) 값을
0
으로 ****설정한다.
$MV_{RTE} = \begin{pmatrix}
MV_{00} & MV_{01} & MV_{02} & 0 \\
MV_{10} & MV_{11} & MV_{12} & 0\\
MV_{20} & MV_{21} & MV_{22} & 0 \\
MV_{30} & MV_{31} & MV_{32} & MV_{33} \\
\end{pmatrix}$
⇒ 이 행렬을 사용하면 장면(Scene)의 중심이 카메라 위치가 됨
⇒ 즉, 모든 객체가 카메라를 중심으로 배치되도록 변환됨
RTE 좌표 변환 (모델 좌표 → 카메라 상대 좌표)
각 객체의 모델 좌표를 $eye_{(WGS84)}$를 기준으로 변환하여 RTE 좌표 $p_{(RTE)}$를 생성한다.
- $p_{(RTE)} = p_{(WGS84)} - eye_{(WGS84)}$
- $p_{(WGS84)}$ = 원래의 모델 좌표(ex: 지구 좌표)
- $eye_{(WGS84)}$ = 현재 카메라의 위치
- $p_{(RTE)}$ = 카메라를 기준으로 변환된 좌표
- 이 뺄셈 연산은 CPU에서 Double Precision 연산을 수행 한 후, $p_{(RTE)}$를 32비트 실수로 캐스팅하고,
dynamic vertex buffer
에 기록 후 draw call을 실행한다.- CPU 에서 연산하기 때문에 이를 CPU RTE라고 부른다.
- 그렇다면 Double Preicision을 지원하지 않는 32-bit 환경에서는 어떻게 연산 할 까? 아래 참고
- Double Precision 연산을 하기 때문에, 지터링이 비교적 많이 제거된다.
- 하지만, 단점은 성능이다.
CPU RTE
RTE 좌표 변환을 CPU에서 수행
→ $p_{(RTE)} = p_{(WGS84)} - eye_{(WGS84)}$를 CPU에서 Double Precision 연산으로 계산한 후, 변환된 좌표 $P_{(RTE)}$를 32비트 float로 변환 후 GPU로 전송
CPU RTE 장점
- 부동 소수점 정밀도 문제 해결
- CPU는 64-bit 연산(double precision)이 가능하다 ⇒ Float Precision 문제 해결 가능
- 부동소수점 오차(Jitter)가 거의 없음
- ⇒ 가상 지구 렌더링에서 정밀도 극대화 및 높은 품질 렌더링
CPU RTE 문제점
- 성능 저하
- CPU 부하 증가
- 매 프레임마다 모든 오브젝트의 좌표를 변환해야 하므로, CPU가 많은 연산을 수행해야 한다.
- 특히, 오브젝트가 많을 경우 CPU가 병목(bottleneck)이 되어 성능이 저하될 수 있다.
- 시스템 버스(System Bus) 트래픽 증가
- CPU에서 변환된 좌표를 GPU로 전송해야 하기 때문에 시스템 버스의 데이터 전송량이 증가한다.
- 대량의 오브젝트를 렌더링할 때 병목이 발생할 수 있다.
- 정적인(Vertex Buffer 사용) 오브젝트의 비효율성
- 정적인 오브젝트(움직이지 않는 오브젝트, 예: 산, 건물 등)는 한 번만 좌표 변환을 수행하고 고정된 Vertex Buffer에 저장하는 것이 이상적이다.
- 하지만 CPU RTE 방식에서는 뷰어(Viewer, 즉 카메라)가 움직일 때마다 모든 오브젝트의 좌표를 다시 계산해야 한다.
- 즉, 정적인 오브젝트도 동적으로 처리해야 하므로 비효율적이다.
- CPU 부하 증가
- CPU RTE 연산이 64-bit 연산자에 의존
- CPU RTE는 성능 저하를 발생한다. 그리고 CPU RTE 연산은 64-bit 연산에 의존한다.
- 그렇다면 32-bit 환경에서는 어떻게 연산 할 까?
- 또한, 이를 가능하게 한다면 32-bit를 지원하는 코어가 수천 개 인 GPU에서 해당 연산을 수행한다면?
- ⇒ 성능 저하의 문제점을 해결 할 수 있다.
GPU RTE
카메라 이동 시마다 $eye_{(WGS84)}$가 바뀌므로 매 프레임마다 CPU에서 좌표 변환(Double Precision 연산)을 수행해야 한다.
근데 만약 GPU의 Vertex Shader
에서 좌표 변환(Float Precision연산)을 하면 더 빠르지 않을까?
- GPU는 수천 개의 코어가 병렬 연산(연산 속도가 CPU 보다 훨씬 빠름)
- Vertex Shader에서 좌표 변환을 수행하면
- CPU 부하 감소 (CPU가 좌표 변환을 하지 않아도 됨)
- 시스템 버스 트래픽 감소 (CPU가 변환한 좌표를 매번 GPU로 전송할 필요가 없음)
- Static Vertex Buffer 사용 가능 (정적인 오브젝트는 한 번만 버퍼에 저장하고 계속 사용할 수 있음)
GPU에서 처리할 때의 문제: 32-bit 연산 한계
- CPU 대신 GPU의 Vertex Shader에서 좌표 변환을 수행하려면 CPU에서 수행했던 64-bit 연산(double precision)을 흉내 내야(emulate)한다.
- 일반적으로 대부분의 GPU는 32-bit 부동소수점 연산(FP32, Floating Point 32-bit)만 지원하므로, 64-bit 좌표를 다룰 수 없는 문제가 발생한다
- 이를 해결하는 방법
- 64-bit를 지원하는 최신 GPU 사용 (예: NVIDIA RTX 시리즈는 일부 지원함)
- 64-bit 값을 2개의 32-bit 값으로 나누어 표현 (High/Low Split Encoding) => 일반적인 해결책
32-bit 환경에서는 64-bit 자료형 연산하는 방법
$p_{(RTE)} = p_{(WGS84)} - eye_{(WGS84)} ≈ (p^{high} - eye^{high}) + (p^{low} - eye^{low})$
- Double Precision을 두 개의 Single Precision로 표현한다.
- 이런 방식을 High-Low Encoding 또는 Fixed-Point Encoding 라고 함
- 이 방식은 하나의 Double Precision을 사용하는 CPU RTE 보다는정확하지 않지만, 가상지구를 구현하기에는 충분하다.
요약
'3D Engine > Graphics' 카테고리의 다른 글
컴퓨터는 다각형을 삼각형으로 쪼갠다 (feat. Ear Clipping) (1) | 2025.03.28 |
---|---|
컴퓨터로 원근감을 표현하는 방법 (feat. Depth Buffer) (10) | 2025.03.05 |
- Total
- Today
- Yesterday
- the unix timesharing system
- z-order curve
- b3dm
- redis bgsave
- Kafka
- bgsave
- cpu i/o
- z-order
- user space
- tile availability
- append only
- sendfile()
- kernel space
- 3d tiles
- 3d tiles 1.0
- b3dm to glb
- morton order
- event srource
- implict titling
- content availability
- .b3dm
- event streaming
- zero-copy
- child subtree availability
- transferto()
- Cesium
- * to gltf
- 오차 최소화
- implicit tiling
- explicit tiling
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
31 |