3D 엔진에서 EasyAR 사용하기
3D 엔진에서 EasyAR를 사용하려면 카메라 화면과 가상 오브젝트를 렌더링해야 합니다. 가상 오브젝트 렌더링은 카메라 화면과 정렬되어야 합니다. 카메라 화면 렌더링 시, 이미지 생성 시점과 디스플레이 시점의 매개변수(물리 카메라의 위치, 방향, 화면 비율, 종횡비 등)가 일치하지 않을 수 있으며, 렌더링 시 이를 고려해야 합니다. EasyAR를 지원되지 않는 3D 엔진에 통합해야 하는 경우, 다음 세부 사항에 특히 주의해야 합니다.
카메라 화면 경계 패딩의 크롭
이미지 크롭, 전치 및 인코딩은 상당한 계산량이 필요합니다. 계산을 줄이고 지연 시간을 낮추기 위해 일반적으로 더 원시적인 형식을 사용합니다. 비디오 인코딩을 용이하게 하기 위해, 물리 카메라가 출력하는 이미지는 종종 8x8, 16x16, 32x32, 64x64와 같은 그리드에 정렬됩니다. 예를 들어, 일부 휴대폰에서 1920x1080 해상도를 선택했을 때 출력 이미지가 1920x1088이 되는 경우가 있는데, 이는 1080이 64의 배수가 아니기 때문입니다.

렌더링 시 이 여분의 패딩 부분을 제거해야 합니다. 여러 가지 방법이 가능합니다: 이미지를 그래픽 메모리에 업로드할 때 너비를 지정하는 방법(예: OpenGL에서 glPixelStorei(GL_PACK_ROW_LENGTH, ...) 사용)이나, 프래그먼트 셰이더에서 수동으로 UV 좌표를 계산하고 이미지를 샘플링할 때 초과 부분을 잘라내는 방법 등이 있습니다.
화면 회전 방향에 따른 렌더링
휴대폰에서 물리 카메라가 기록한 이미지는 일반적으로 기기 방향에 고정되어 있으며 화면 표시 방향 변경에 따라 변하지 않습니다. 그러나 기기 자체의 방향 변화는 이미지의 상하좌우 방향 정의에 영향을 미칩니다. 렌더링 시 현재 화면 표시 방향도 표시되는 이미지의 방향에 영향을 미칩니다.
일반적으로 렌더링 시, 화면 표시 방향에 대한 카메라 이미지의 상대적인 회전 각도 $\theta$를 결정해야 합니다.
$\theta_{screen}$을 화면 이미지가 화면의 자연 방향(보통 세로 방향)에 대해 시계 방향으로 회전한 라디안 값, $\theta_{phycam}$을 물리 카메라 이미지가 자연 방향 화면에 올바르게 표시되기 위해 필요한 시계 방향 회전 라디안 값, $\theta$를 물리 카메라 이미지가 현재 화면에 표시되기 위해 필요한 시계 방향 회전 라디안 값으로 정의합니다.
후면 카메라의 경우:
예를 들어, Android 휴대폰에서 자연 방향(세로 방향)으로 사용할 때 \(\theta_{screen} = 0, \theta_{phycam} = \frac{\pi}{2}\) 이므로 \(\theta = \frac{\pi}{2}\) 입니다.
전면 카메라의 경우, 회전 완료 후 좌우 방향으로 반전(미러링)을 수행한다면:
참고
화면 이미지가 회전할 때, $\theta$는 회전이 발생한 후 첫 번째 프레임에서 즉시 재계산되어야 합니다. 그렇지 않으면 순간적으로 화면 이미지 방향이 비정상적으로 나타날 수 있습니다.
카메라 배경과 가상 오브젝트 렌더링
휴대폰에서 가상 오브젝트를 렌더링하려면 가상 오브젝트를 카메라 화면과 정렬해야 합니다. 이는 가상 오브젝트와 렌더링 카메라를 실제 공간과 완전히 대응되는 가상 공간에 배치하고, 물리 카메라와 동일한 시야각(FOV) 및 종횡비를 사용하여 렌더링해야 함을 의미합니다. 카메라 화면과 가상 오브젝트가 거치는 원근 투영 변환은 거의 동일하지만, 한 가지 중요한 차이점이 있습니다: 카메라 화면의 원근 투영 변환의 대부분은 물리 카메라 내부에서 발생하는 반면, 가상 오브젝트의 원근 투영 변환은 완전히 계산 과정입니다.
이하 설명은 OpenGL 관례를 따릅니다. 다른 관례를 사용하는 경우 적절한 좌표축 매핑이 필요합니다. 카메라 좌표계의 축 정의는 다음과 같다고 가정합니다: x축 오른쪽, y축 위쪽, z축 화면 밖으로 나오는 방향. 클리핑 좌표계의 축 정의는 다음과 같습니다: x축 오른쪽, y축 위쪽, z축 화면 밖으로 나오는 방향, w축은 가상 축.
이때, 카메라 화면 렌더링에 필요한 원근 투영 변환 행렬은 다음과 같습니다:
여기서:
flip: 화면 좌우 반전 여부 (반전 시 1, 미반전 시 0)- \(\theta\): 이미지의 시계 방향 회전 각도 (라디안 단위)
- \(s_x\), \(s_y\): $\theta$에 따라 변화하는, 등비 스케일링 또는 등비 패딩을 위한 스케일 계수
이 변환 행렬은 먼저 카메라 이미지를 스케일링한 다음 회전시키고, 마지막으로 반전시킵니다. 렌더링 시 화면을 채우는 사각형을 사용해야 합니다. 예를 들어 OpenGL에서는 사각형의 정점을 \((-1, -1, 0)\), \((1, -1, 0)\), \((1, 1, 0)\), $(-1, 1, 0)$에 위치시키고, 해당 코너에 UV 좌표를 설정한 후 이 원근 투영 행렬을 사용하여 렌더링합니다.
가상 오브젝트 렌더링에 필요한 원근 투영 행렬은 다음과 같습니다:
여기서:
- \(n\), \(f\): 일반적인 3D 렌더링 원근 투영 행렬의 근거리 및 원거리 클리핑 평면 매개변수
- \(w\), \(h\): 카메라 이미지의 픽셀 너비 및 높이
- \(f_x\), \(f_y\), \(c_x\), \(c_y\): 카메라 모델에서 흔히 사용되는 내부 매개변수 (Intrinsic Parameters). \(f_x\), $f_y$는 픽셀 단위 초점 거리, \(c_x\), $c_y$는 주점(Principal Point)의 픽셀 위치입니다.
이 투영 행렬은 다음과 같은 변환을 순차적으로 수행합니다:
- 카메라 내부 매개변수에 의한 원근 투영 변환 (OpenCV의 이미지 좌표계 y, z축 방향이 OpenGL 카메라 좌표계와 반대이므로 두 번의 좌표계 변환 수행)
- 이미지 픽셀 좌표계에서 이미지 사각형 좌표계로의 변환
- 근거리 및 원거리 클리핑 평면에 의한 변환
- 카메라 화면 렌더링에 사용된 원근 투영 변환
정리하면 다음과 같습니다:
위 과정에서 알 수 있듯이, 렌더링은 일반적으로 두 단계로 나누어 수행됩니다: 한 번은 카메라 화면을 렌더링하고, 다음으로 가상 오브젝트를 카메라 화면 위에 오버레이하여 렌더링합니다.
일부 3D 엔진에서는 원근 투영 행렬을 수평 시야각(\(\alpha\)), 종횡비(\(r\)) 등의 매개변수로 표현합니다. 회전과 반전을 고려하지 않고 주점 오프셋을 무시한다면, 이 값들을 계산할 수 있습니다: 수평 시야각 \(\alpha=2 \arctan{\frac{w}{2 f_x}}\), 종횡비 \(r=\frac{w}{h}\).
이 과정에서는 카메라 왜곡을 고려하지 않았음에 유의하십시오. 현재 대부분의 휴대폰 카메라의 왜곡은 매우 미미하기 때문입니다.