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资源文件的可以转到上一篇文章。
https://www.jianshu.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 = 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要各复制一份,我没找到更好的实现方式,不知道你有没有找到呢?找到了,要留言告诉我喔~~

你可能感兴趣的:(android filament入门,GLB和GLTF模型查看器)