android filament入门,GLB和GLTF模型查看器

filament入门挺难的,主要是因为受干扰的信息太多了,有arCore的干扰,也有scenceform的干扰。这里通过制作3D模型查看器的方式,理清他们之间的关系。
有用的信息来源主要有3个:
1,https://medium.com/@philiprideout/getting-started-with-filament-on-android-d10b16f0ec67
Android上的Filament入门
2,https://github.com/thomasgorisse/sceneform-android-sdk
适用于Android的Sceneform SDK-维护
3,https://github.com/Sergiioh/android-model-viewer
适用于Android的GLB和GLTF模型查看器
整体表现为:1实现了页面查看3D模型,2实现了ar查看3D模型,3提供了将1和2集成到你应用的一种思路。依照他们说的一步步来做,就可以实现这样的效果。

球状元素周期表

想更换ktx资源文件的可以转到上一篇文章。
//www.greatytc.com/p/bbc83a93d0b1
gltf版本的3D模型文件可以从这里得到
https://github.com/KhronosGroup/glTF-Sample-Models

为了防止页面走丢,这里把他们的内容复制进来。

class gltfActivity : Activity()

    companion object {
        init {
            Utils.init()
        }
    }
    private lateinit var surfaceView: SurfaceView
    private lateinit var choreographer: Choreographer
    private lateinit var modelViewer: ModelViewer

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        surfaceView = SurfaceView(this).apply { setContentView(this) }
        choreographer = Choreographer.getInstance()
        modelViewer = ModelViewer(surfaceView)
        surfaceView.setOnTouchListener(modelViewer)

        loadGltf("BoomBox")
        loadEnvironment("courtyard_8k")
    }

    private fun loadGltf(name: String) {
        val buffer = readAsset("models/BoomBox/${name}.gltf")
        modelViewer.loadModelGltf(buffer) { uri -> readAsset("models/BoomBox/$uri") }
        modelViewer.transformToUnitCube()
    }

    private fun readAsset(assetName: String): ByteBuffer {
        val input = assets.open(assetName)
        val bytes = ByteArray(input.available())
        input.read(bytes)
        return ByteBuffer.wrap(bytes)
    }

    private val frameCallback = object : Choreographer.FrameCallback {
        private val startTime = System.nanoTime()
        override fun doFrame(currentTime: Long) {
            val seconds = (currentTime - startTime).toDouble() / 1_000_000_000
            choreographer.postFrameCallback(this)
            modelViewer.animator?.apply {
                if (animationCount > 0) {
                    applyAnimation(0, seconds.toFloat())
                }
                updateBoneMatrices()
            }
            modelViewer.render(currentTime)
        }
    }
    private fun Int.getTransform(): Mat4 {
        val tm = modelViewer.engine.transformManager
        return Mat4.of(*tm.getTransform(tm.getInstance(this), null))
    }

    private fun Int.setTransform(mat: Mat4) {
        val tm = modelViewer.engine.transformManager
        tm.setTransform(tm.getInstance(this), mat.toFloatArray())
    }


    private fun loadEnvironment(ibl: String) {
        // Create the indirect light source and add it to the scene.
        var buffer = readAsset("envs/$ibl/${ibl}_ibl.ktx")
        KtxLoader.createIndirectLight(modelViewer.engine, buffer).apply {
            intensity = 50_000f
            modelViewer.scene.indirectLight = this
        }

        // Create the sky box and add it to the scene.
        buffer = readAsset("envs/$ibl/${ibl}_skybox.ktx")
        KtxLoader.createSkybox(modelViewer.engine, buffer).apply {
            modelViewer.scene.skybox = this
        }
    }

    override fun onResume() {
        super.onResume()
        choreographer.postFrameCallback(frameCallback)
    }

    override fun onPause() {
        super.onPause()
        choreographer.removeFrameCallback(frameCallback)
    }

    override fun onDestroy() {
        super.onDestroy()
        choreographer.removeFrameCallback(frameCallback)
    }
}
class arActivity : AppCompatActivity() {

    private var arFragment: ArFragment? = null
    private var renderable: Renderable? = null

    private class AnimationInstance internal constructor(
        var animator: Animator,
        index: Int,
        var startTime: Long
    ) {
        var duration: Float
        var index: Int

        init {
            duration = animator.getAnimationDuration(index)
            this.index = index
        }
    }

    private val animators: MutableSet<AnimationInstance> = ArraySet()
    private val colors = Arrays.asList(
        Color(0F, 0F, 0F, 1F),
        Color(1F, 0F, 0F, 1F),
        Color(0F, 1F, 0F, 1F),
        Color(0F, 0F, 1F, 1F),
        Color(1F, 1F, 0F, 1F),
        Color(0F, 1F, 1F, 1F),
        Color(1F, 0F, 1F, 1F),
        Color(1F, 1F, 1F, 1F)
    )
    private var nextColor = 0

    // CompletableFuture requires api level 24
    // FutureReturnValueIgnored is not valid
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        if (!checkIsSupportedDeviceOrFinish(this)) {
            return
        }
        setContentView(R.layout.activity_gltf)
        arFragment = supportFragmentManager.findFragmentById(R.id.ux_fragment) as ArFragment?
        val weakActivity = WeakReference(this)
        ModelRenderable.builder()
            .setSource(
                this,
                R.raw.xxxx
                // /*   Uri.parse(
                //"https://storage.googleapis.com/ar-answers-in-search-models/static/Tiger/model.glb")*/
            )
            .setIsFilamentGltf(true)
            .build()
            .thenAccept { modelRenderable: ModelRenderable? ->
                val activity = weakActivity.get()
                if (activity != null) {
                    activity.renderable = modelRenderable
                }
            }
            .exceptionally { throwable: Throwable? ->
                val toast =
                    Toast.makeText(this, "Unable to load Tiger renderable", Toast.LENGTH_LONG)
                toast.setGravity(Gravity.CENTER, 0, 0)
                toast.show()
                null
            }
        arFragment!!.setOnTapArPlaneListener { hitResult: HitResult, plane: Plane?, motionEvent: MotionEvent? ->
            if (renderable == null) {
                return@setOnTapArPlaneListener
            }

            // Create the Anchor.
            val anchor = hitResult.createAnchor()
            val anchorNode =
                AnchorNode(anchor)
            anchorNode.setParent(arFragment!!.arSceneView.scene)
            // Create the transformable model and add it to the anchor.
            val model =
                TransformableNode(arFragment!!.transformationSystem)
             model.setParent(anchorNode)
            model.renderable = renderable
            model.select()
            val filamentAsset =
                model.renderableInstance!!.filamentAsset
            if (filamentAsset!!.animator.animationCount > 0) {
                animators.add(
                    AnimationInstance(
                        filamentAsset.animator,
                        0,
                        System.nanoTime()
                    )
                )
            }
            val color = colors[nextColor]
            nextColor++
            for (i in 0 until renderable!!.submeshCount) {
                val material =
                    renderable!!.getMaterial(i)
                material.setFloat4("baseColorFactor", color)
            }
            val tigerTitleNode = Node()
            tigerTitleNode.setParent(model)
            tigerTitleNode.isEnabled = false
            tigerTitleNode.localPosition = Vector3(0.0f, 1.0f, 0.0f)
            ViewRenderable.builder()
                .setView(this, R.layout.tiger_card_view)
                .build()
                .thenAccept { renderable: ViewRenderable? ->
                    tigerTitleNode.renderable = renderable
                    tigerTitleNode.isEnabled = true
                }
                .exceptionally { throwable: Throwable? ->
                    throw AssertionError(
                        "Could not load card view.",
                        throwable
                    )
                }
        }
        arFragment!!
            .getArSceneView()
            .scene
            .addOnUpdateListener { frameTime: FrameTime? ->
                val time = System.nanoTime()
                for (animator: AnimationInstance in animators) {
                    animator.animator.applyAnimation(
                        animator.index, ((time - animator.startTime) / TimeUnit.SECONDS.toNanos(
                            1
                        ).toDouble()).toFloat()
                                % animator.duration
                    )
                    animator.animator.updateBoneMatrices()
                }
            }
    }

    companion object {
        private val TAG = arActivity::class.java.simpleName
        private const val MIN_OPENGL_VERSION = 3.0

        fun checkIsSupportedDeviceOrFinish(activity: Activity): Boolean {
            val openGlVersionString =
                (activity.getSystemService(ACTIVITY_SERVICE) as ActivityManager)
                    .deviceConfigurationInfo
                    .glEsVersion
            if (openGlVersionString.toDouble() < MIN_OPENGL_VERSION) {
                Log.e(TAG, "Sceneform requires OpenGL ES 3.0 later")
                Toast.makeText(
                    activity,
                    "Sceneform requires OpenGL ES 3.0 or later",
                    Toast.LENGTH_LONG
                )
                    .show()
                activity.finish()
                return false
            }
            return true
        }
    }
}
dependencies {

    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    implementation 'androidx.core:core-ktx:1.3.2'
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'com.google.android.material:material:1.2.1'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
    testImplementation 'junit:junit:4.+'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'

    implementation 'com.gorisse.thomas.sceneform:sceneform:1.18.3'

    implementation 'com.google.android.filament:filament-android:1.9.4'
    implementation 'com.google.android.filament:filament-utils-android:1.9.4'
    implementation 'com.google.android.filament:gltfio-android:1.9.4'
}

最后,附上工程目录截图,具体的实现细节,请自行摸索。


002.png

这个工程可用,但是实现不是很好,因为同样的3D模型,R.raw和assets/models要各复制一份,我没找到更好的实现方式,不知道你有没有找到呢?找到了,要留言告诉我喔~~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容