首页 > 其他分享 >【Android】我用 ARCore 做了一个 1:1 高达

【Android】我用 ARCore 做了一个 1:1 高达

时间:2023-06-22 22:08:25浏览次数:41  
标签:node Sceneform ARCore AR 我用 Android arFragment anchor 3D


最近看到一个新闻,一个 1: 1 的自由高达落户在上海金桥。

【Android】我用 ARCore 做了一个 1:1 高达_android

作为高达爱好者的我一直想去现场感受一下高达真实的压迫感,无奈一直没机会去上海。不过这难不倒我,借助 AR 技术自己动手做了一个 1:1 的高达

【Android】我用 ARCore 做了一个 1:1 高达_3D_02

怎么样,这效果不比上海金桥的差吧 ~

什么是 AR (Augemented Reality)

AR(增强现实)是近几年新兴的技术,他可以将3D模型等虚拟元素模拟仿真后应用到现实世界中,虚拟与现实,两种信息互为补充,从而实现对真实世界的“增强”。

不少人会将 AR(增强现实) 与 VR(虚拟现实)相混淆,两者的区别在于虚拟元素的占比:

  • VR:看到的场景和人物全是假的,是把你的意识代入一个虚拟的世界。
  • AR:看到的场景和人物一部分是真一部分是假,是把虚拟的信息带入到现实世界中。

【Android】我用 ARCore 做了一个 1:1 高达_android_03

VR 技术的研究已经有30多年的历史了,而 AR 则年轻得多,随着智能手机以及智能穿戴设备的普及而逐渐被人们熟知。相对于 VR,AR 的开发门槛低得多,只要有一台智能手机,借助 Google 提供的 ARCore,人人都可以开发出自己的 AR 应用。

Google ARCore

ARCore 是 Google 提供的 AR 解决方案,为开发者提供 API,可以通过 Android , iOS 等手机平台感知周边环境,打造沉浸式的 AR 体验。

ARCore 为开发者提供了三大能力:

  • 动作追踪 : 通过识别相机图像中的可视特征点来跟踪拍摄者的位置,从而决定虚拟元素的相对位置变化
  • 【Android】我用 ARCore 做了一个 1:1 高达_ar_04

  • 环境理解 :识别常见水平或垂直表面(如表格或墙壁)上的特征点群集,还可以确定平面边界,将虚拟对象放置在平面上
  • 【Android】我用 ARCore 做了一个 1:1 高达_加载_05

  • 光线预测 : 预测当前场景的光照条件,可以使用此照明信息来照亮虚拟 AR 对象,模拟出物体在现实世界的影子
  • 【Android】我用 ARCore 做了一个 1:1 高达_ar_06

Sceneform SDK

ARCore 为 AR 提供了周边环境的感知能力,但一个完整的 AR 应用还需要处理 3D 模型的渲染,这要借助 OpenGL ES 来完成,学习成本很高。官方意识到这个问题,在 2017 年推出 ARCore 之后,紧跟着 2018 年的 IO 大会上推出了 Sceneform 这个在 Android 上的 3D 图像渲染库。

.obj , .fbx.gltf 等常见的 3D 模型文件格式,虽然可以在主流的 3D 软件中通用,但在 Android 中,我们只能通过 OpenGL 代码对其进行渲染。而 Sceneform 可以将这些格式的模型文件,连同所依赖的资源文件(.mtl, .bin, .png 等) 转换为 .sfa.sfb 格式。 后者是可供 Sceneform 渲染的二进制模型文件, 前者是具有可读性的摘要文件,用来描述后者。

相比于 OpenGL , Sceneform 的使用要简单得多,而且 sfb 还可以通过 Sceneform 提供的 AS 插件在 IDE 中进行模型预览。

【Android】我用 ARCore 做了一个 1:1 高达_android_07

接下来,通过 Sceneform 和 ARCore 来实现我的 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 申请权限

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

<!-- 此 App 在 GooglePlay 中只会对支持 ARCore 的设备可见 -->
<uses-feature android:name="android.hardware.camera.ar" android:required="true"/>

<application …>
    …

  <!-- 当安装 App 时,如果设备没有安装 ARCore,GooglePlay 会自动为其安装 -->
  <meta-data android:name="com.google.ar.core" android:value="required" />

</application>

3. 布局文件

ARFragment 可以用来承载 AR 场景、响应用户行为,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 等专业软件制作的,不少 3D 建模爱好者会将自己的作品上传到一些设计网站供大家免费或有偿下载。

【Android】我用 ARCore 做了一个 1:1 高达_android_08

我们可以在网站上下载常见格式的 3D 模型文件。以 .obj 为例,obj 文件中描述了多边形的顶点和片段信息等, 此外还有颜色、材质等信息存储在配套的 .mtl 文件中 , 我们将下载的 obj/mtl/png 等模型文件拷贝到非 assets 目录下,这样可以避免打入 apk。

例如 app/sampledata

【Android】我用 ARCore 做了一个 1:1 高达_加载_09

我们在 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 目标路径,我们将目标文件生成在 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 能够响应在 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 是一个树形数据结构,作为 AR 场景的根节点,各种虚拟元素将作为其子节点被添加到场景中进行渲染

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

所以,渲染 3D 模型,其实就是添加一个 Node 并为其设置 Renderable 的过程。

hitResult 是用户点击的位置信息,Anchor基于 hitResult 创建锚点,这个锚点作为子节点被添加到 Scene 根节点中,同时又作为 TransformableNode 的父节点。 TransformableNode 用来承载 3D 模型, 它可以接受手势进行拖拽或者放大缩小, 添加到 Archor 就相当于把 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 用来检测可运行环境,通过实现可知, Sceneform 的运行条件是 AndroidN 以及 OpenGL 3.0 以上。

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

【Android】我用 ARCore 做了一个 1:1 高达_ar_10

最后

Sceneform 配合 ARCore 可以快速搭建 AR 应用,除了加载静态的 3D 模型以外,Sceneform 还可以加载带动画的模型。

随着 “元宇宙” 概念的兴起,Google,Facebook 等巨头必将加大在 AR 乃至 VR 技术上的研究投入,虚拟现实技术或将成为移动互联网之后的新一代社交、娱乐场景,想象空间巨大。

标签:node,Sceneform,ARCore,AR,我用,Android,arFragment,anchor,3D
From: https://blog.51cto.com/u_16163442/6535739

相关文章

  • 腾讯技术团队最新出品,Android Framework系统框架底层原理解密
    今年以来,Android就业形势愈发严峻,各公司对开发人员的要求也是逐渐提高,在筛选Android程序员的时候也越来越看重其对于底层的理解和思考。作为一个AndroidAPP开发者,我们也不能当温水里的青蛙,必须对Android系统的组成和AndroidFramework的层次架构有所了解,才能突破和进阶。在这里就......
  • 爆火的2022版腾讯Android面试手册,最新最细致,终于拿到手了
    据腾讯HR部门6月8号发布的最新信息,2022年6月Android开发岗位数将同比增长21%,伴随应届生求职季的到来,想进腾讯的小伙伴竞争会异常激烈。面试的深度和难度将不断增加,很多想进腾讯的朋友都在问,如何准备才能顺利拿下offer?第一章Java基础静态内部类和非静态内部类的比较多态的理解与应......
  • 钉钉和抖音Android岗面筋,阿里挂了HR面,抖音通过收获Offer
    前言这一次的话,主要就是只投了钉钉和抖音两个部门,然后为了保险起见,让指导老师给我推荐了一个小公司,因为实在太想实习了,想着如果面试不上,总要有一个保底的机会。当然那家公司也挺nice的,我跟老总说了来意之后,老总直说让我全力冲,位置给我留着,所以在这里非常感谢吴总您对我的支持。阿里......
  • 盘点2021Android框架百大排行榜 附:《Android百大框架源码解析》
    一.榜单介绍排行榜包括四大类:单一框架:仅提供路由、网络层、UI层、通信层或其他单一功能的框架混合开发框架:提供开发hybridapp、h5与webview结合能力、webapp能力的框架企业级开源项目:可以独立运行的app,有极高的学习价值、思路借鉴意义书籍类开源项目:类似Open-sourc-project这样的......
  • 八年腾讯T4老开发对Android Framework的解密总结
    前言在Android开发者技能中,如果想进大厂,一般拥有较好的学历可能有优势一些。但是如果你靠硬实力也是有机会的,例如死磕Framework。Framework知识广泛应用在Android各个领域中,重要性显而易见。成为一名AndroidFramework高手,也是目前招聘过程中非常稀缺的人才,可以成为你的敲门砖。......
  • 腾讯资深Android开发带你入门面试重点Framework,掌握更加核心的技术
    前言今天,想跟大家聊聊,Framework开发的那些事。系统应用开发,现在来说,已经开始脱离系统,单独拿出来开发,系统定制接口,已提供给应用调用,用来增强功能。原生的桌面,拨号,设置,已经没法做出差异化优势,因此都费尽心机,来进行应用深度开发。对于之前维护系统应用模块的人来讲,修修补补,真的没有什......
  • 七年音视频开发呕心沥血之作:《Android音视频开发进阶指南》开源分享
    前言前两天在脉脉看到这么一条动态:作为Android开发者,真的是深有感触,Android开发越来越卷,越来越多人唱衰Android,还有很多人一直在思考是否要转行,大家都越来越焦虑。。。但与其深陷焦虑,**我们更应该积极寻求出路,通过提升自己来更好地端好Android这碗饭:5G的浪潮全面袭来,其实为Android......
  • Android应用签名
    为了要签名?   开发Android的人这么多,完全有可能大家都把类名,包名起成了一个同样的名字,这时候如何区分?签名这时候就是起区分作用的。   由于开发商可能通过使用相同的PackageName来混淆替换已经安装的程序,签名可以保证相当名字,但是签名不同的包不被替换。   APK如果使......
  • Android四种Activity的加载模式
    建议首先阅读下面两篇文章,这样才可以更好的理解Activity的加载模式:Android的进程,线程模型其中对“Android的单线程模型”的描述,明白Activity的一些注意事项。AndroidApplicationTaskActivities的关系尤其要明白Task是啥。 一个Activty的生命周期Activty的生命周期的也......
  • Android 的Margin和Padding属性以及支持的长度单位
    Android的Margin和Padding跟Html的是一样的。如下图所示:黄色部分为Padding,灰色部分为Margin。通俗的理解Padding为内边框,Margin为外边框对应的属性为android:layout_marginBottom="25dip"android:layout_marginLeft="10dip"android:layout_marginTop="10dip"an......