【Android】我用 ARCore 做了一個 1:1 高達

最近看到一個新聞,一個 1: 1 的自由高達落戶在上海金橋。

shanghai_gundam

作為高達愛好者的我一直想去現(xiàn)場感受一下高達真實的壓迫感,無奈一直沒機會去上海。不過這難不倒我,借助 AR 技術(shù)自己動手做了一個 1:1 的高達

怎么樣,這效果不比上海金橋的差吧 ~

什么是 AR (Augemented Reality)


AR(增強現(xiàn)實)是近幾年新興的技術(shù),他可以將3D模型等虛擬元素模擬仿真后應(yīng)用到現(xiàn)實世界中,虛擬與現(xiàn)實,兩種信息互為補充,從而實現(xiàn)對真實世界的“增強”。

不少人會將 AR(增強現(xiàn)實) 與 VR(虛擬現(xiàn)實)相混淆,兩者的區(qū)別在于虛擬元素的占比:

  • VR:看到的場景和人物全是假的,是把你的意識代入一個虛擬的世界。
  • AR:看到的場景和人物一部分是真一部分是假,是把虛擬的信息帶入到現(xiàn)實世界中。
image

VR 技術(shù)的研究已經(jīng)有30多年的歷史了,而 AR 則年輕得多,隨著智能手機以及智能穿戴設(shè)備的普及而逐漸被人們熟知。相對于 VR,AR 的開發(fā)門檻低得多,只要有一臺智能手機,借助 Google 提供的 ARCore,人人都可以開發(fā)出自己的 AR 應(yīng)用。

Google ARCore


ARCore 是 Google 提供的 AR 解決方案,為開發(fā)者提供 API,可以通過 Android , iOS 等手機平臺感知周邊環(huán)境,打造沉浸式的 AR 體驗。

ARCore 為開發(fā)者提供了三大能力:

  • 動作追蹤 : 通過識別相機圖像中的可視特征點來跟蹤拍攝者的位置,從而決定虛擬元素的相對位置變化
  • 環(huán)境理解 :識別常見水平或垂直表面(如表格或墻壁)上的特征點群集,還可以確定平面邊界,將虛擬對象放置在平面上
  • 光線預(yù)測 : 預(yù)測當(dāng)前場景的光照條件,可以使用此照明信息來照亮虛擬 AR 對象,模擬出物體在現(xiàn)實世界的影子

Sceneform SDK


ARCore 為 AR 提供了周邊環(huán)境的感知能力,但一個完整的 AR 應(yīng)用還需要處理 3D 模型的渲染,這要借助 OpenGL ES 來完成,學(xué)習(xí)成本很高。官方意識到這個問題,在 2017 年推出 ARCore 之后,緊跟著 2018 年的 IO 大會上推出了 Sceneform 這個在 Android 上的 3D 圖像渲染庫。

.obj , .fbx.gltf 等常見的 3D 模型文件格式,雖然可以在主流的 3D 軟件中通用,但在 Android 中,我們只能通過 OpenGL 代碼對其進行渲染。而 Sceneform 可以將這些格式的模型文件,連同所依賴的資源文件(.mtl, .bin, .png 等) 轉(zhuǎn)換為 .sfa.sfb 格式。 后者是可供 Sceneform 渲染的二進制模型文件, 前者是具有可讀性的摘要文件,用來描述后者。

相比于 OpenGL , Sceneform 的使用要簡單得多,而且 sfb 還可以通過 Sceneform 提供的 AS 插件在 IDE 中進行模型預(yù)覽。

image

接下來,通過 Sceneform 和 ARCore 來實現(xiàn)我的 1:1 高達

1. Gradle 添加依賴

新建 AndroidStudio 工程,在 root 的 build.gradle 中添加 Sceneform 插件

dependencies { 
    classpath 'com.google.ar.sceneform:plugin:1.17.1' 
}

接著在 app 的 build.gradle 中依賴 ARCore 和 Sceneform 的 aar

dependencies {
    ...
    implementation 'com.google.ar:core:1.26.0'
    implementation 'com.google.ar.sceneform.ux:sceneform-ux:1.17.1'
    implementation 'com.google.ar.sceneform:core:1.17.1'
}

2. Manifest 申請權(quán)限


<uses-permission android:name="android.permission.CAMERA"/>

<!-- 此 App 在 GooglePlay 中只會對支持 ARCore 的設(shè)備可見 -->
<uses-feature android:name="android.hardware.camera.ar" android:required="true"/>

<application …>
    …

  <!-- 當(dāng)安裝 App 時,如果設(shè)備沒有安裝 ARCore,GooglePlay 會自動為其安裝 -->
  <meta-data android:name="com.google.ar.core" android:value="required" />

</application>

3. 布局文件

ARFragment 可以用來承載 AR 場景、響應(yīng)用戶行為,Android 中顯示虛擬元素的最簡答的方法就是在布局中添加一個 ARFRagment :

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
  
<fragment
        android:id="@+id/ux_fragment"
        android:name="com.google.ar.sceneform.ux.ArFragment"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="9" />

</FrameLayout>

4. 制作 sfb 模型文件

3D 模型一般是通過 Maya 或 3D Max 等專業(yè)軟件制作的,不少 3D 建模愛好者會將自己的作品上傳到一些設(shè)計網(wǎng)站供大家免費或有償下載。

sketchfab_gundam

我們可以在網(wǎng)站上下載常見格式的 3D 模型文件。以 .obj 為例,obj 文件中描述了多邊形的頂點和片段信息等, 此外還有顏色、材質(zhì)等信息存儲在配套的 .mtl 文件中 , 我們將下載的 obj/mtl/png 等模型文件拷貝到非 assets 目錄下,這樣可以避免打入 apk。

例如 app/sampledata

image

我們在 build.gtadle 通過 sceneform.asset(...) 添加 obj > sfb 的配置如下

sceneform.asset('sampledata/msz-006_zeta_gundam/scene.obj',
        'default',
        'sampledata/msz-006_zeta_gundam/scene.sfa',
        'src/main/assets/scene')

sampledata/msz-006_zeta_gundam/scene.obj 是 obj 源文件位置, src/main/assets/scene 是生成的 sfb 目標(biāo)路徑,我們將目標(biāo)文件生成在 assets/ 中,打入 apk ,便于在運行時加載。

gradle 配置完后,sync 并 build 工程,build 過程中,會在 assets/ 中生成同名 sfb 文件

5. 加載、渲染模型

//MainActivity.kt

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        arFragment = supportFragmentManager.findFragmentById(R.id.ux_fragment) as ArFragment
        arFragment.setOnTapArPlaneListener { hitResult, plane, motionEvent ->
        
            if (plane.type != Plane.Type.HORIZONTAL_UPWARD_FACING ||
                    state != AnchorState.NONE) {
                return@setOnTapArPlaneListener
            }

            val anchor = hitResult.createAnchor()
            placeObject(ux_fragment, anchor, Uri.parse("scene.sfb"))

        }
        
    }
        

ARFragment 能夠響應(yīng)在 AR 場景中的用戶點擊行為,在點擊的位置中添加虛擬元素,
Uri.parse("scene.sfb") 用來獲取 assets 中生成的模型文件。

    private fun placeObject(fragment: ArFragment, anchor: Anchor, model: Uri) {
        ModelRenderable.builder()
                .setSource(fragment.context, model)
                .build()
                .thenAccept {
                    addNodeToScene(fragment, anchor, it)
                }
                .exceptionally { throwable : Throwable ->
                   Toast.makeText(arFragment.getContext(), "Error:$throwable.message", Toast.LENGTH_LONG).show();
                   return@exceptionally null
                }
    }
    

Sceneform 提供 ModelRenderable 用于模型渲染。 通過 setSource 加載 sfb 模型文件

   private fun addNodeToScene(fragment: ArFragment, anchor: Anchor, renderable: Renderable) {
        val anchorNode = AnchorNode(anchor)
        val node = TransformableNode(fragment.transformationSystem)
        node.renderable = renderable
        node.setParent(anchorNode)
        fragment.arSceneView.scene.addChild(anchorNode)
        node.select()
    }

ARSceneView 持有一個 Scene, Scene 是一個樹形數(shù)據(jù)結(jié)構(gòu),作為 AR 場景的根節(jié)點,各種虛擬元素將作為其子節(jié)點被添加到場景中進行渲染

val node = TransformableNode(fragment.transformationSystem)
node.renderable = renderable
node.setParent(anchorNode)

所以,渲染 3D 模型,其實就是添加一個 Node 并為其設(shè)置 Renderable 的過程。

hitResult 是用戶點擊的位置信息,Anchor基于 hitResult 創(chuàng)建錨點,這個錨點作為子節(jié)點被添加到 Scene 根節(jié)點中,同時又作為 TransformableNode 的父節(jié)點。 TransformableNode 用來承載 3D 模型, 它可以接受手勢進行拖拽或者放大縮小, 添加到 Archor 就相當(dāng)于把 3D 模型放置到點擊的位置上。

6. 完整代碼

class MainActivity : AppCompatActivity() {
    lateinit var arFragment: ArFragment
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        if (!checkIsSupportedDeviceOrFinish(this)) return
        setContentView(R.layout.activity_main)
        
        arFragment = supportFragmentManager.findFragmentById(R.id.ux_fragment) as ArFragment
        arFragment.setOnTapArPlaneListener { hitresult: HitResult, plane: Plane, motionevent: MotionEvent? ->
            if (plane.type != Plane.Type.HORIZONTAL_UPWARD_FACING)
                return@setOnTapArPlaneListener
            val anchor = hitresult.createAnchor()
            placeObject(arFragment, anchor, R.raw.cube)
        }
    }

    private fun placeObject(arFragment: ArFragment, anchor: Anchor, uri: Int) {
        ModelRenderable.builder()
                .setSource(arFragment.context, uri)
                .build()
                .thenAccept { modelRenderable: ModelRenderable -> addNodeToScene(arFragment, anchor, modelRenderable) }
                .exceptionally { throwable: Throwable ->
                   Toast.makeText(arFragment.getContext(), "Error:$throwable.message", Toast.LENGTH_LONG).show();
                    return@exceptionally null
                }
    }

    private fun addNodeToScene(arFragment: ArFragment, anchor: Anchor, renderable: Renderable) {
        val anchorNode = AnchorNode(anchor)
        val node = TransformableNode(arFragment.transformationSystem)
        node.renderable = renderable
        node.setParent(anchorNode)
        arFragment.arSceneView.scene.addChild(anchorNode)
        node.select()
    }

    private fun checkIsSupportedDeviceOrFinish(activity: Activity): Boolean {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
            Toast.makeText(activity, "Sceneform requires Android N or later", Toast.LENGTH_LONG).show()
            activity.finish()
            return false
        }
        val openGlVersionString = (activity.getSystemService<Any>(Context.ACTIVITY_SERVICE) as ActivityManager)
                .deviceConfigurationInfo
                .glEsVersion
        if (openGlVersionString.toDouble() < MIN_OPENGL_VERSION) {
            Toast.makeText(activity, "Sceneform requires OpenGL ES 3.0 or later", Toast.LENGTH_LONG)
                    .show()
            activity.finish()
            return false
        }
        return true
    }

    companion object {
        private const val MIN_OPENGL_VERSION = 3.0
    }
}

checkIsSupportedDeviceOrFinish 用來檢測可運行環(huán)境,通過實現(xiàn)可知, Sceneform 的運行條件是 AndroidN 以及 OpenGL 3.0 以上。

以上就是全部代碼了,雖然代碼很少,效果很哇塞

ar zeta gundam

最后

Sceneform 配合 ARCore 可以快速搭建 AR 應(yīng)用,除了加載靜態(tài)的 3D 模型以外,Sceneform 還可以加載帶動畫的模型。

隨著 “元宇宙” 概念的興起,Google,F(xiàn)acebook 等巨頭必將加大在 AR 乃至 VR 技術(shù)上的研究投入,虛擬現(xiàn)實技術(shù)或?qū)⒊蔀橐苿踊ヂ?lián)網(wǎng)之后的新一代社交、娛樂場景,想象空間巨大。

今天就寫到這里吧, 我要和剛認(rèn)識的小姐姐玩耍去了 ??

ar girl

最后推薦一個網(wǎng)站,大家可以在那里下載一些有趣的 3D 模型 ~

https://sketchfab.com/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容