做者:李超 前「跟誰學」直播研發高級經理
其實關注 ARCore也蠻久了,但一直沒有騰出時間來寫個總結。正好應朋友之約,咱們今天就來好好聊一聊 ARCore.android
ARCore的歷史以及與蘋果ARKit的競爭我就很少講了,在網上能夠搜到一堆信息。但網上深刻講解ARCore的確實很少。git
本文主要有兩個目的,一是向你們介紹一下ARCore的基本概念,瞭解這些概念對於你們後續深刻的學習 ARCore具備關鍵的做用。二是深刻剖析一下 ARCore的工做機理,這樣可讓你們更容易理解 ARCore。github
另外,ARCore與ARKit的基本概念很接近,只要瞭解了其中的一個,基本上也就掌握了另外一個。session
ARCore工做時要作兩件事兒,首先跟蹤手機的運動軌跡,而後構建出它對現實世界的理解。併發
ARCore的運動跟蹤技術是經過 Camera 標識出特徵點,並隨着時間的推移跟蹤這些特徵點是如何移動的。經過這些特徵點的運動數據及從手機慣性傳感器讀到的信息,ARCore計算出手機移動的位置和方向,並稱其爲姿態。框架
除了識別出這些特徵點外,ARCore還能檢測出像地板、桌面等平面信息以及在某個地方的光線強度。這些信息使得ARCore可以構建出本身理解的真實世界。構建出這樣一個模型後,能夠在上面放置一些虛擬內容了。ide
ARCore是如何作到的呢?它使用三項關鍵技術將虛擬內容與真實世界整合到一塊兒,這三種技術分別是:學習
ARCore 能夠在手機移動的過程當中知道,相對於真實世界手機所在的位置和方向(姿式)。測試
當手機在真實世界移動時,ARCore使用稱爲併發測距和映射的過程來了解手機與周圍世界的相對位置。 動畫
ARCore能檢測到Camera捕獲的圖像在視覺上的不一樣特徵,稱爲特徵點。它使用這些點計算其位置變化。隨着時間的推移,經過視覺信息與來自IMU設備的慣性測量,ARCore就能夠估算出Camera相對於真實世界的姿態(位置和方向)。
經過將渲染的3D虛擬內容與物理Camera的姿式對齊,開發人員就能夠從正確的角度渲染虛擬內容。 再經過將虛擬物品的圖像渲染到從Camera得到的圖像之上,這樣看起來就好像虛擬內容是真實世界的一部分似的。
ARCore可讓手機檢測出一塊水平面的位置和大小。如地面、桌子、書架等等。這樣就能夠將虛擬物體放置到檢測出的水平面上了。
它是如何作到的呢?ARCore經過檢測特徵點和平面不斷改善對現實世界環境的理解。
ARCore會查找常見水平表面(如桌面)上的特徵點集羣,除此以外,ARCore還能夠肯定每一個平面的邊界,並將以上信息提供給您的應用程序。 這樣,開發人員就可使用這些信息,並將虛擬物體放置在平坦的表面上了。
因爲ARCore使用特徵點檢測平面,所以可能沒法正確檢測到沒有紋理的平坦表面(如白色桌面)。
ARCore 可讓手機估算出當前環境的光線強度,這樣可讓虛擬物理顯示在真實環境中更加逼真。
ARCore使用 hit testing(命中測試) 獲取與手機屏幕相對應的(x,y)座標(如經過點擊屏幕等交互方式),將其投射到 Camera 的3D座標系中,並返回與命中點射線相交的全部平面和特徵點,以及在世界座標系中該交叉點的姿態。這樣就能實現用戶與ARCore環境中的對象交互了。
ARCore能夠改變對自身位置和環境的理解來調整姿態。如咱們要在ARCore環境中放置一個虛擬對象,首先要肯定一個錨點,以確保ARCore能隨着時間的推移不斷跟蹤對象的位置。一般狀況下,會根據命中測試返回的姿式建立一個錨點。
姿式改變這項技術特別關鍵,只有獲得姿式,ARCore才能夠隨着時間的推移不斷更新環境對象(像飛機和特徵點)的位置。ARCore將平面和點認爲是可跟蹤的特殊類型的對象。您能夠將虛擬對象錨定到這些可追蹤的對象上,以確保在設備移動時,虛擬對象和可跟蹤對象之間保持穩定的關係。這就好像您在桌面上放置一個虛擬的花瓶,若是ARCore稍後調整與桌面相關的姿式,那麼花瓶仍然會保持在桌面上。
com.google.ar.core.Session
類,Session管理AR系統狀態並處理Session生命週期。 該類是ARCore API的主要入口點。 該類容許用戶建立Session,配置Session,啓動/中止Session,最重要的是接收視頻幀,以容許訪問Camera圖像和設備姿式。
com.google.ar.core.Config
類,用於保存Session的設置。
com.google.ar.core.Frame
類,該類經過調用update()方法,獲取狀態信息並更新AR系統。
com.google.ar.core.HitResult
類,該類定義了命中點射線與估算的真實幾何世界之間的交集。
com.google.ar.core.Point
類,它表明ARCore正在跟蹤的空間點。 它是建立錨點(調用createAnchor方法)時,或者進行命中檢測(調用hitTest方法)時,返回的結果。
com.google.ar.core.PointCloud
類,它包含一組觀察到的3D點和信心值。
com.google.ar.core.Plane
類,描述了現實世界平面表面的最新信息。
com.google.ar.core.Anchor
類,描述了現實世界中的固定位置和方向。 爲了保持物理空間的固定位置,這個位置的數字描述信息將隨着ARCore對空間的理解的不斷改進而更新。
com.google.ar.core.Pose
類, 姿式表示從一個座標空間到另外一個座標空間位置不變的轉換。 在全部的ARCore API裏,姿式老是描述從對象本地座標空間到世界座標空間的轉換。
隨着ARCore對環境的瞭解不斷變化,它將調整座標系模式以便與真實世界保持一致。 這時,Camera和錨點的位置(座標)可能會發生明顯的變化,以便它們所表明的物體處理恰當的位置。
這意味着,每一幀圖像都應被認爲是在一個徹底獨立的世界座標空間中。錨點和Camera的座標不該該在渲染幀以外的地方使用,若是需考慮到某個位置超出單個渲染框架的範圍,則應該建立一個錨點或者應該使用相對於附近現有錨點的位置。
com.google.ar.core.ImageMetadata
類,提供了對Camera圖像捕捉結果的元數據的訪問。
com.google.ar.core.LightEstimate
保存關於真實場景光照的估計信息。 經過 getLightEstimate()獲得。
Google發佈的 ARCore SDK 中包括了一些例子程序,有了上面的基本知識後,咱們就很容易理解他所寫的 Demo 程序的流程了。
在 Activity中的 onCreate 方法中建立 Session 和 Config是個不錯的地方。
mSession = new Session(/*context=*/this); mDefaultConfig = Config.createDefaultConfig(); if (!mSession.isSupported(mDefaultConfig)) { Toast.makeText(this, "This device does not support AR", Toast.LENGTH_LONG).show(); finish(); return; }
在 Google 提供的Demo中,AR的展現部分使用的是 GLSurfaceView。作視頻開發的同窗都清楚,Android 可使用三種View進行視頻渲染。分別是:
其中,SurfaceView最靈活,效率也最高,但使用起來比較煩鎖。而GLSurfaceView相對 SurfaceView就是簡單不少,只須要實現它的 Render 接口便可。而 TextureView使用最簡單,不少工做都由 Android 的窗口管理器幫你作了,但靈活性相對較差。
爲了渲染的高效,Google在Demo中大量使用了OpenGL技術。因爲OpenGL是圖像處理很是大的一個領域,沒法經過一兩篇文章講解清楚,同時也不是咱們本文的重點,因此咱們這裏不對它作詳細介紹,有興趣的同窗能夠到網上自行學習。
mSurfaceView = (GLSurfaceView) findViewById(R.id.surfaceview); ... mSurfaceView.setPreserveEGLContextOnPause(true); mSurfaceView.setEGLContextClientVersion(2); mSurfaceView.setEGLConfigChooser(8, 8, 8, 8, 16, 0); // Alpha used for plane blending. mSurfaceView.setRenderer(this); mSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
該段代碼首先經過資源文件建立一個GLSurfaceView對象,而後將 GLSurfaceView 與 EGL 上下文關聯。並將Activity做爲GLSurfaceView的回調對象(也就是說該Activity要實現 GLSurfaceView.Renderer中定義的接口,如onSurfaceCreated、onSurfaceChanged、onDrawFrame等),最後設置 mSurfaceView 的渲染模式爲 GLSurfaceView.RENDERMODE_CONTINUOUSLY,即對 GLSurfaceView 持續不斷的渲染。
要理解本節內容,首先你們要知道AR的詳細工做原理是怎樣的。我在這裏再向你們作個簡要的說明。
背景展現
用過AR的人都知道,AR是將一些虛擬物品放到真實的場景中。那麼這個真實的場景從哪裏來呢?固然是從手機的 Camera上獲取。
咱們把從 Camera中獲取的視頻看成 AR的背景。其實,AR 就是將虛擬物品放到視頻上,只不過不是簡單的放置,而是須要通過大量的計算,找到視頻中的平面位置再放置。
而Android中視頻的採集相對比較簡單,像直播系統,照像機都要使用該技術。
平臺檢測
上面咱們已經說了,AR就是實時視頻+虛擬物品。但虛擬物不能簡單的放到視頻上,而是先對視頻中的每一幀進行檢測,找到視頻中的平面,肯定好位置後,再將虛擬物品放置上去。這樣纔算是AR呀:)
點雲
上面咱們知道了,AR=實時視頻+平面+虛擬物品。除此以外,它還應該能對虛擬物品進行跟蹤,也就是能夠在不一樣的角度觀察同一個物品,並得出不一樣的姿態,因此就有了「點雲」 技術。那什麼是點雲呢?顧名思義,形象的說就是一堆點,這些的形狀有點像雲。點雲中的每一個點都是一個特徵點,它是經過Camera得到的。
放置虛擬物品
找到了平面,有了跟蹤手段,咱們就能夠將準備好的虛擬物品放置到平臺上,如今纔是真正的AR哈。
好,知道了這些基本原理後,咱們來看看Google Demo是如何作的呢?
建立線程
對於上面的每一點,Demo都啓動了一個線程,代碼以下:
... // Create the texture and pass it to ARCore session to be filled during update(). mBackgroundRenderer.createOnGlThread(/*context=*/this); mSession.setCameraTextureName(mBackgroundRenderer.getTextureId()); // Prepare the other rendering objects. try { mVirtualObject.createOnGlThread(/*context=*/this, "andy.obj", "andy.png"); mVirtualObject.setMaterialProperties(0.0f, 3.5f, 1.0f, 6.0f); ... } catch (IOException e) { Log.e(TAG, "Failed to read obj file"); } try { mPlaneRenderer.createOnGlThread(/*context=*/this, "trigrid.png"); } catch (IOException e) { Log.e(TAG, "Failed to read plane texture"); } mPointCloud.createOnGlThread(/*context=*/this); ...
上面的代碼中首先建立了一個背景線程,用來將從Camera中獲取的視頻渲染到屏幕上當背景。數據是從哪裏來的呢?就是經過 Session.update 獲取 Camera 數據,再經過紋理交給背景線程。
對紋理沒有概念的同窗能夠把它想像成一塊內存空間。
而後啓動虛擬物品線程,用於繪製虛擬物品,及發生角度變化時,更新虛擬物別的姿式。緊接着建立平面線程來繪製平面。最後啓動點雲線程繪製特徵點。
到此,各類線程就建立完畢了。下面咱們來講一下如何渲染。
命中檢測
當咱們要向背景繪製虛擬物品時,首先要進行命中檢測。代碼以下:
MotionEvent tap = mQueuedSingleTaps.poll(); if (tap != null && frame.getTrackingState() == TrackingState.TRACKING) { for (HitResult hit : frame.hitTest(tap)) { // Check if any plane was hit, and if it was hit inside the plane polygon. if (hit instanceof PlaneHitResult && ((PlaneHitResult) hit).isHitInPolygon()) { // Cap the number of objects created. This avoids overloading both the // rendering system and ARCore. if (mTouches.size() >= 16) { mSession.removeAnchors(Arrays.asList(mTouches.get(0).getAnchor())); mTouches.remove(0); } // Adding an Anchor tells ARCore that it should track this position in // space. This anchor will be used in PlaneAttachment to place the 3d model // in the correct position relative both to the world and to the plane. mTouches.add(new PlaneAttachment( ((PlaneHitResult) hit).getPlane(), mSession.addAnchor(hit.getHitPose()))); // Hits are sorted by depth. Consider only closest hit on a plane. break; } } }
在例子中,它查看是否有點擊事件,且圖像處理於跟蹤狀態?若是是,就對其進行命中檢測,看是否能夠找到一個平面,若是找到就建立一個錨點並將其與該平臺綁定起來。
渲染背景
// Draw background. mBackgroundRenderer.draw(frame);
經過上面的代碼就能夠將紋理中的內容推給 EGL,上面建立的渲染線程從 EGL 上下文中獲取數據,最終將視頻渲染到屏幕上。
繪製點雲
mPointCloud.update(frame.getPointCloud()); mPointCloud.draw(frame.getPointCloudPose(), viewmtx, projmtx);
同理,經過上面的代碼,就能夠將數據傳給點雲線程進行點雲的繪製。
繪製平面
// Visualize planes. mPlaneRenderer.drawPlanes(mSession.getAllPlanes(), frame.getPose(), projmtx);
經過上面代碼將數據傳給平面線程進行平面的繪製。
繪製虛擬物品
for (PlaneAttachment planeAttachment : mTouches) { if (!planeAttachment.isTracking()) { continue; } // Get the current combined pose of an Anchor and Plane in world space. The Anchor // and Plane poses are updated during calls to session.update() as ARCore refines // its estimate of the world. planeAttachment.getPose().toMatrix(mAnchorMatrix, 0); // Update and draw the model and its shadow. mVirtualObject.updateModelMatrix(mAnchorMatrix, scaleFactor); mVirtualObjectShadow.updateModelMatrix(mAnchorMatrix, scaleFactor); }
最後,遍歷全部的錨點,在每一個錨點上繪製虛擬物品。
至此,咱們對ARCore的分析就告一段落了。
ARCore相對於初學者來講仍是有很多難度的。由於裏面有不少新概念須要你們消化吸取。
另外一方面,ARCore目前只有幾款機型可能作測試,而這幾款機型在國內用的人很少,因此對於大多數人來講無法作實驗,這也增長了學習的難度。
除了以上兩點外,ARCore中大量使用了 OpenGL的相關知識。而OpenGL又是一門很深的學問,因此學習的難度更加陡峭了。
經過以上三點,能夠說目前學習ARCore的門檻相較於蘋果的ARKit要難很多。
但願本文能對您有所幫助。
其實,AR在直播場景下已經有不少應用,好比在一些直播中關注能夠往主播臉上的墨鏡或其它AR動畫配飾。
至此,咱們已經分享過了ARKit與ARCore的基礎知識,咱們將在後續進一步分享基於ARKit、ARCore與直播結合的實踐案例。歡迎持續關注!