Table of Contents

在 3D 引擎中使用 EasyAR

在 3D 引擎中使用 EasyAR,需要渲染相機畫面與虛擬物體。渲染虛擬物體需要和相機畫面對準。相機畫面渲染時,影像生成時和顯示時的一些參數可能不匹配,例如實體相機的位置、朝向、畫幅、寬高比等和顯示器畫面可能不同,在渲染時需要考慮。如果需要將 EasyAR 接到沒有支援的 3D 引擎上,需要特別注意以下細節。

相機畫面邊界填充的剪裁

影像的剪裁、轉置和編碼都需要較大的計算量,出於減少計算和降低延遲的考慮,一般會使用比較原始的格式。為了方便視訊編碼,實體相機輸出的影像經常會對齊到 8x8、16x16、32x32、64x64 這樣的方格上,例如有些手機上選擇 1920x1080 的解析度,輸出的影像可能變成 1920x1088,就是由於 1080 不是 64 的倍數。

image with padding

這就要求在渲染的時候去掉這些多餘的填充部分。有多種可能的做法:一種是在將影像上傳到顯存時指定寬度,例如 OpenGL 中可以使用glPixelStorei(GL_PACK_ROW_LENGTH, ...)實現;一種是在 fragment shader 中手動計算 UV 座標,並在從影像取樣時將超過的部分截斷。

跟隨螢幕旋轉方向渲染

在手機上,實體相機記錄的影像通常相對於機身固定,不隨螢幕顯示方向變化而變化。但手機機身朝向的變化會影響到我們對於影像的上下左右方向的定義。渲染時,當前螢幕顯示方向也會影響到顯示的影像的方向。

通常在渲染時,需要確定一個相機影像相對於螢幕顯示方向的旋轉角。

如果我們用 \(\theta_{screen}\) 表示螢幕影像相對於螢幕自然方向順時針旋轉的弧度,\(\theta_{phycam}\) 表示實體相機影像要正確顯示在自然方向的螢幕上需要順時針旋轉的弧度,\(\theta\) 表示實體相機影像顯示在當前螢幕上需要順時針旋轉的弧度。

對於後置鏡頭,有

\[ \theta = \theta_{phycam} - \theta_{screen} \]

例如,Android 手機上,在自然方向使用手機時,\(\theta_{screen} = 0, \theta_{phycam} = \frac{\pi}{2}\),則 \(\theta = \frac{\pi}{2}\)

對於前置鏡頭,如果在旋轉完成後,進行左右方向翻轉,則有

\[ \theta = \theta_{phycam} + \theta_{screen} \]
附註

當螢幕影像旋轉時,需要在旋轉發生後的第一幀立刻重新計算 \(\theta\),否則可能出現瞬間的螢幕影像方向不正常。

相機背景和虛擬物體的渲染

在手機上渲染虛擬物體,需要將虛擬物體和相機畫面對準。這要求我們將渲染相機和物體都放置在和真實空間完全對應的虛擬空間中,並使用實體相機相同的視場角、寬高比來進行渲染。相機畫面和虛擬物體經過的透視投影變換幾乎一模一樣,只有一點區別,即相機畫面的透視投影變換大部分是發生在實體相機中,而虛擬物體的透視投影變換完全是一個計算過程。

以下均採用 OpenGL 慣例,使用其他慣例,需要進行相應的座標軸對映。假設相機座標系的座標軸定義如下:x 軸指向右,y 軸指向上,z 軸出螢幕向外。剪裁座標系的座標軸定義如下:x 軸指向右,y 軸指向上,z 軸出螢幕向外,w 軸為虛擬軸。

此時,渲染相機畫面需要的透視投影變換矩陣如下:

\[ P_i=\left( \begin{array}{cccc} (-1)^{\text{flip}} & \phantom{0} & \phantom{0} & \phantom{0} \\ \phantom{0} & 1 & \phantom{0} & \phantom{0} \\ \phantom{0} & \phantom{0} & 1 & \phantom{0} \\ \phantom{0} & \phantom{0} & \phantom{0} & 1 \\ \end{array} \right)\left( \begin{array}{cccc} \cos (-\theta ) & -\sin (-\theta ) & \phantom{0} & \phantom{0} \\ \sin (-\theta ) & \cos (-\theta ) & \phantom{0} & \phantom{0} \\ \phantom{0} & \phantom{0} & 1 & \phantom{0} \\ \phantom{0} & \phantom{0} & \phantom{0} & 1 \\ \end{array} \right)\left( \begin{array}{cccc} s_x & \phantom{0} & \phantom{0} & \phantom{0} \\ \phantom{0} & s_y & \phantom{0} & \phantom{0} \\ \phantom{0} & \phantom{0} & 1 & \phantom{0} \\ \phantom{0} & \phantom{0} & \phantom{0} & 1 \\ \end{array} \right) \]

其中:flip是指畫面是否左右翻轉,翻轉時值為 1,不翻轉時值為 0;\(\theta\) 是影像沿順時針旋轉角,單位為弧度; \(s_x\)\(s_y\) 是縮放係數,用於進行等比縮放或等比填充,它們隨 \(\theta\) 變化。此變換矩陣首先對相機影像進行縮放,然後進行旋轉,最後進行翻轉。渲染時應使用一個矩形鋪滿螢幕,例如在 OpenGL 中,可以將矩形的頂點放在 \((-1, -1, 0)\)\((1, -1, 0)\)\((1, 1, 0)\)\((-1, 1, 0)\) ,UV 座標設定在對應的四個角上,然後使用此透視投影矩陣渲染。

渲染虛擬物體需要的透視投影矩陣如下:

\[ P=P_i\left( \begin{array}{cccc} 1 & \phantom{0} & \phantom{0} & \phantom{0} \\ \phantom{0} & 1 & \phantom{0} & \phantom{0} \\ \phantom{0} & \phantom{0} & -\frac{f+n}{f-n} & -\frac{2 f n}{f-n} \\ \phantom{0} & \phantom{0} & -1 & \phantom{0} \\ \end{array} \right)\left( \begin{array}{cccc} \frac{2}{w} & \phantom{0} & \phantom{0} & \phantom{0} \\ \phantom{0} & \frac{2}{h} & \phantom{0} & \phantom{0} \\ \phantom{0} & \phantom{0} & 1 & \phantom{0} \\ \phantom{0} & \phantom{0} & \phantom{0} & 1 \\ \end{array} \right)\left( \begin{array}{cccc} 1 & \phantom{0} & \phantom{0} & \phantom{0} \\ \phantom{0} & -1 & \phantom{0} & \phantom{0} \\ \phantom{0} & \phantom{0} & -1 & \phantom{0} \\ \phantom{0} & \phantom{0} & \phantom{0} & 1 \\ \end{array} \right)\left( \begin{array}{cccc} f_x & \phantom{0} & c_x & \phantom{0} \\ \phantom{0} & f_y & c_y & \phantom{0} \\ \phantom{0} & \phantom{0} & 1 & \phantom{0} \\ \phantom{0} & \phantom{0} & \phantom{0} & 1 \\ \end{array} \right)\left( \begin{array}{cccc} 1 & \phantom{0} & \phantom{0} & \phantom{0} \\ \phantom{0} & -1 & \phantom{0} & \phantom{0} \\ \phantom{0} & \phantom{0} & -1 & \phantom{0} \\ \phantom{0} & \phantom{0} & \phantom{0} & 1 \\ \end{array} \right) \]

其中: \(n\)\(f\) 是通常 3D 渲染的透視投影矩陣中使用的近裁和遠裁參數; \(w\)\(h\) 是相機影像的像素寬高; \(f_x\)\(f_y\)\(c_x\)\(c_y\) 為相機模型中常用的內參,其中 \(f_x\)\(f_y\) 是像素焦距, \(c_x\)\(c_y\) 為主點像素位置。此投影投影矩陣依次進行如下變換:相機內參的透視投影變換(由於 OpenCV 中影像座標系 y、z 軸方向和 OpenGL 相機座標系相反,進行了兩次座標系變換),從影像像素座標系轉換到影像矩形座標系的變換,近裁和遠裁的變換,渲染相機畫面時的透視投影變換。

經過整理,可得

\[ P=P_i\left( \begin{array}{cccc} \frac{2 f_x}{w} & \phantom{0} & 1-\frac{2 c_x}{w} & \phantom{0} \\ \phantom{0} & \frac{2 f_y}{h} & -1+\frac{2 c_y}{h} & \phantom{0} \\ \phantom{0} & \phantom{0} & -\frac{f+n}{f-n} & -\frac{2 f n}{f-n} \\ \phantom{0} & \phantom{0} & -1 & \phantom{0} \\ \end{array} \right) \]

從上述過程可知,渲染通常需要分兩次進行,一次渲染相機畫面,一次渲染虛擬物體,虛擬物體覆蓋在相機畫面之上。

有些 3D 引擎中將透視投影矩陣表示為橫向視場角、寬高比等參數,如果不考慮旋轉、翻轉,忽略主點偏移,是可以計算的,其中橫向視場角 \(\alpha=2 arctan{\frac{w}{2 f_x}}\) ,寬高比 \(r=\frac{w}{h}\)

需要注意這個過程中沒有考慮相機畸變的情況,因為目前大部分手機的相機畸變非常輕微。