You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Android原生环境中使用ARCore渲染2D图像(替代已归档的SceneForm方案)

我懂你现在的困扰——SceneForm归档之后,之前用ViewRenderable在ARCore里放2D图像的路子走不通了,还想要实现类似ARKit那种把图片完美叠加在现实场景里的效果对吧?别担心,我给你整理了一套官方推荐的替代方案,用Kotlin实现,完全能达到你要的效果:

核心方案选择:ARCore + Filament 实现空间2D图像显示

因为SceneForm已经停止维护,现在官方推荐用ARCore结合Filament(Google的实时渲染引擎)来做AR内容渲染,这套组合能原生支持3D空间里的2D纹理展示,和ARKit的效果对齐。


1. 先把依赖配好

在Module级的build.gradle里添加这些依赖(用最新版本就行):

dependencies {
    // ARCore核心SDK
    implementation 'com.google.ar:core:1.42.0'
    // Filament渲染引擎及工具库
    implementation 'com.google.android.filament:filament-android:1.32.0'
    implementation 'com.google.android.filament:filament-utils-android:1.32.0'
    // ARCore和Filament的绑定工具
    implementation 'com.google.ar.core:filament-helper:1.42.0'
}

2. 配置Manifest权限

AR功能离不开相机权限,把这些加到AndroidManifest.xml里:

<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera.ar" android:required="true" />

<application ...>
    <meta-data
        android:name="com.google.ar.core"
        android:value="required" />
</application>

3. 布局文件:用ARView承载AR内容

布局里直接用ARCore提供的ArView,它会帮我们处理相机预览和AR渲染:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.google.ar.core.ArView
        android:id="@+id/arView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</FrameLayout>

4. 核心代码实现

在Activity里完成初始化、平面检测、2D图像放置的逻辑:

import android.os.Bundle
import android.view.MotionEvent
import androidx.appcompat.app.AppCompatActivity
import com.google.ar.core.Anchor
import com.google.ar.core.HitResult
import com.google.ar.core.Plane
import com.google.ar.core.Session
import com.google.ar.core.ArView
import com.google.android.filament.Engine
import com.google.android.filament.Material
import com.google.android.filament.RenderableManager
import com.google.android.filament.Texture
import com.google.android.filament.android.UiHelper
import com.google.android.filament.utils.ModelViewer
import com.google.android.filament.utils.TextureHelper
import java.nio.ByteBuffer

class ARImageDisplayActivity : AppCompatActivity() {
    private lateinit var arView: ArView
    private lateinit var session: Session
    private lateinit var engine: Engine
    private lateinit var modelViewer: ModelViewer
    private lateinit var uiHelper: UiHelper

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_ar_image_display)

        arView = findViewById(R.id.arView)
        engine = Engine.create()
        uiHelper = UiHelper(UiHelper.ContextSensitivity.LAZY, arView)
        modelViewer = ModelViewer(engine, uiHelper)

        // 初始化AR会话
        session = Session(this)
        arView.setupSession(session)

        // 给ARView加触摸监听:点击平面就放图片
        arView.setOnTouchListener { _, event ->
            if (event.action == MotionEvent.ACTION_UP) {
                // 检测点击位置是否在可放置的平面上
                val hitResult = arView.arFrame?.hitTest(event.x, event.y)?.firstOrNull {
                    it.trackable is Plane && (it.trackable as Plane).isPoseInPolygon(it.hitPose)
                }
                hitResult?.let { placeImageOnPlane(it) }
            }
            true
        }
    }

    private fun placeImageOnPlane(hitResult: HitResult) {
        // 在点击的平面位置创建AR锚点(固定虚拟物体的位置)
        val anchor = hitResult.createAnchor()

        // 把你的2D图片转成Filament能识别的纹理(这里用raw目录下的图片举例)
        val texture = TextureHelper.createTexture(engine, resources.openRawResource(R.raw.your_target_image))

        // 加载自定义材质(用来显示纹理)
        val material = Material.Builder()
            .package(engine, "materials/texture_mat.filamat")
            .build(engine)

        // 创建一个1x1的平面Mesh,用来承载纹理
        val planeMesh = createPlaneMesh(engine)

        // 创建可渲染实体并添加到场景
        val imageEntity = modelViewer.createEntity()
        engine.renderableManager.create(imageEntity, RenderableManager.Builder(1)
            .geometry(0, RenderableManager.PrimitiveType.TRIANGLES, planeMesh)
            .material(0, material)
            .build())

        // 把实体绑定到AR锚点,让它固定在现实空间里
        modelViewer.transformManager.setTransform(
            imageEntity,
            anchor.pose.toMatrix()
        )

        // 把实体加入AR场景
        modelViewer.scene.addEntity(imageEntity)
    }

    // 手动创建平面的Mesh数据(简单的矩形顶点和索引)
    private fun createPlaneMesh(engine: Engine): com.google.android.filament.Mesh {
        val vertices = floatArrayOf(
            -0.5f, -0.5f, 0.0f,
             0.5f, -0.5f, 0.0f,
             0.5f,  0.5f, 0.0f,
            -0.5f,  0.5f, 0.0f
        )
        val indices = shortArrayOf(0, 1, 2, 0, 2, 3)

        val vertexBuffer = ByteBuffer.allocateDirect(vertices.size * 4)
            .putFloatArray(vertices)
            .rewind()

        val indexBuffer = ByteBuffer.allocateDirect(indices.size * 2)
            .putShortArray(indices)
            .rewind()

        return com.google.android.filament.Mesh.Builder()
            .vertexBufferCount(1)
            .vertexBuffer(0, com.google.android.filament.Mesh.VertexBufferDescriptor(
                vertexBuffer,
                com.google.android.filament.Mesh.Attribute.POSITION,
                3,
                com.google.android.filament.Mesh.AttributeType.FLOAT,
                false,
                0
            ))
            .indexBuffer(indexBuffer, indices.size, com.google.android.filament.Mesh.IndexType.USHORT)
            .build(engine)
    }

    // 生命周期管理:AR会话的启停要和Activity同步
    override fun onResume() {
        super.onResume()
        session.resume()
        arView.onResume()
    }

    override fun onPause() {
        super.onPause()
        session.pause()
        arView.onPause()
    }

    override fun onDestroy() {
        super.onDestroy()
        session.close()
        engine.destroy()
    }
}

5. 准备Filament材质文件

在src/main/assets/materials目录下创建texture_mat.filamat文件,这是用来渲染2D纹理的材质定义:

material {
    name : "TextureMaterial",
    shadingModel : unlit,
    parameters : [
        {
            type : texture2d,
            name : baseColorMap
        }
    ]
}

fragment {
    void material(inout MaterialInputs material) {
        prepareMaterial(material);
        material.baseColor = texture(materialParams.baseColorMap, getUV0());
    }
}

临时过渡方案:用SceneForm最后稳定版

如果你只是想快速迁移旧代码,虽然SceneForm归档了,但1.17.1版本还能正常使用,代码逻辑和你之前的ViewRenderable用法几乎一致:

// 先加依赖
implementation 'com.google.ar.sceneform:core:1.17.1'
implementation 'com.google.ar.sceneform:ui:1.17.1'

// 核心放置图片的代码
private fun placeImageOnPlane(hitResult: HitResult) {
    val anchor = hitResult.createAnchor()
    val anchorNode = AnchorNode(anchor)
    anchorNode.setParent(arFragment.arSceneView.scene)

    // 加载自定义布局(里面放ImageView显示你的图片)
    ViewRenderable.builder()
        .setView(this, R.layout.layout_image_display)
        .build()
        .thenAccept { renderable ->
            val imageNode = Node()
            imageNode.renderable = renderable
            imageNode.setParent(anchorNode)
            // 调整缩放适配图片大小
            imageNode.localScale = Vector3(0.5f, 0.5f, 0.5f)
        }
}
效果说明

用ARCore+Filament的方案实现后,你点击现实中的平面,2D图像就会以虚拟平面的形式固定在该位置,拥有和ARKit视频里一样的透视、遮挡效果,完全符合你的需求。

内容的提问来源于stack exchange,提问作者Shankar Kakumani

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 22:32:30