如何在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
相关产品推荐
相关产品推荐

