You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何在Android Studio中为AR应用的相机视图添加模型或图片?

嘿,这个需求我刚好折腾过!不用Vuforia这类第三方AR库的话,咱们完全可以用Android原生的CameraX(适配性更强的Jetpack相机组件)结合OpenGL ES来实现基础的AR叠加效果——核心逻辑就是把相机预览画面作为底层渲染层,在它上面绘制2D图片或3D模型。下面给你一步步拆解实操方案:

核心思路总览

不用第三方AR库的本质是「相机流实时渲染 + 自定义内容叠加」:

  • 用CameraX获取稳定的相机预览画面
  • 叠加2D图片可以用简单的FrameLayout层级实现
  • 要实现3D模型的空间感(伪AR),就得用OpenGL ES把相机画面作为纹理背景,再在上面渲染3D内容

步骤1:配置项目依赖与权限

首先在Module级别的build.gradle里添加CameraX和OpenGL相关依赖:

dependencies {
    // CameraX核心依赖
    implementation "androidx.camera:camera-core:1.2.3"
    implementation "androidx.camera:camera-camera2:1.2.3"
    implementation "androidx.camera:camera-lifecycle:1.2.3"
    // 基础AppCompat库
    implementation "androidx.appcompat:appcompat:1.6.1"
}

然后在AndroidManifest.xml里添加相机权限和OpenGL声明:

<!-- 相机权限 -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera.any" />
<!-- OpenGL ES 2.0声明(渲染3D需要) -->
<uses-feature android:glEsVersion="0x00020000" android:required="true" />

步骤2:用CameraX实现相机预览

先写布局,用PreviewView显示相机画面,外层套FrameLayout方便叠加内容:

<FrameLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <!-- 相机预览层 -->
    <androidx.camera.view.PreviewView
        android:id="@+id/previewView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
    <!-- 叠加层会放在这里,后面根据需求添加 -->
</FrameLayout>

然后在Activity里初始化CameraX:

class ARDemoActivity : AppCompatActivity() {
    private lateinit var previewView: PreviewView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_ar_demo)
        previewView = findViewById(R.id.previewView)
        
        // 先请求相机权限
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
            != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA), 100)
            return
        }
        
        // 启动相机
        startCamera()
    }

    private fun startCamera() {
        val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
        cameraProviderFuture.addListener({
            val cameraProvider = cameraProviderFuture.get()
            
            // 配置预览用例
            val preview = Preview.Builder().build().also {
                it.setSurfaceProvider(previewView.surfaceProvider)
            }
            
            // 选择后置相机
            val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
            
            try {
                // 解绑之前的用例,再绑定新的
                cameraProvider.unbindAll()
                cameraProvider.bindToLifecycle(this, cameraSelector, preview)
                
                // 相机启动成功后初始化叠加内容
                initOverlayContent()
            } catch(exc: Exception) {
                Log.e("ARDemo", "相机绑定失败", exc)
            }
        }, ContextCompat.getMainExecutor(this))
    }

    private fun initOverlayContent() {
        // 这里后面会添加叠加2D/3D内容的逻辑
    }
}

步骤3:叠加静态2D图片(最简方案)

如果只是需要在相机画面上固定位置放一张图片,不用复杂的空间效果,直接在FrameLayout里加ImageView就行:
修改布局,添加叠加的ImageView:

<FrameLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <androidx.camera.view.PreviewView
        android:id="@+id/previewView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
    <!-- 叠加的图片,这里居中显示 -->
    <ImageView
        android:id="@+id/overlayImage"
        android:layout_width="150dp"
        android:layout_height="150dp"
        android:src="@drawable/your_custom_image"
        android:layout_gravity="center"
        android:background="#80000000" /> <!-- 加半透明背景,避免和相机画面混淆 -->
</FrameLayout>

这种方式零成本,但缺点是图片固定在屏幕位置,不能和真实空间联动。


步骤4:叠加3D模型(伪AR效果)

如果要实现模型跟着相机视角移动的效果,就得用OpenGL ES把相机画面作为纹理背景,再渲染3D模型。

第一步:添加GLSurfaceView到布局

把之前的ImageView换成GLSurfaceView,用来渲染OpenGL内容:

<FrameLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <androidx.camera.view.PreviewView
        android:id="@+id/previewView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
    <!-- OpenGL渲染层,设0.99的透明度让相机画面透出来 -->
    <android.opengl.GLSurfaceView
        android:id="@+id/glSurfaceView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:alpha="0.99" />
</FrameLayout>

第二步:实现OpenGL Renderer类

这个类负责渲染相机纹理和3D模型:

class ARRenderer : GLSurfaceView.Renderer {
    private var cameraTextureId = -1
    // 3D模型的着色器程序
    private lateinit var modelProgram: Int

    override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
        // 初始化OpenGL环境
        GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f)
        // 创建相机外部纹理(用来绑定相机预览帧)
        cameraTextureId = createExternalTexture()
        // 加载3D模型的着色器程序
        modelProgram = createShaderProgram(vertexShaderCode, fragmentShaderCode)
    }

    override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
        // 设置渲染视口
        GLES20.glViewport(0, 0, width, height)
    }

    override fun onDrawFrame(gl: GL10?) {
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT or GLES20.GL_DEPTH_BUFFER_BIT)
        
        // 1. 先渲染相机预览纹理作为背景
        drawCameraBackground()
        
        // 2. 再渲染3D模型
        draw3DModel()
    }

    // 创建相机外部纹理的工具方法
    private fun createExternalTexture(): Int {
        val textureIds = IntArray(1)
        GLES20.glGenTextures(1, textureIds, 0)
        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureIds[0])
        // 设置纹理过滤参数
        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR.toFloat())
        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR.toFloat())
        GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE)
        GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE)
        return textureIds[0]
    }

    // 渲染相机背景的方法(需要把CameraX的预览帧绑定到纹理)
    private fun drawCameraBackground() {
        // 这里需要结合CameraX的ImageAnalysis用例,把相机帧数据传递到OpenGL纹理
        // 核心是使用GL_TEXTURE_EXTERNAL_OES扩展来渲染相机的YUV帧
    }

    // 渲染3D模型的方法(示例用简单立方体)
    private fun draw3DModel() {
        // 1. 启用模型着色器程序
        GLES20.glUseProgram(modelProgram)
        // 2. 加载模型的顶点数据、MVP矩阵(模型、视图、投影)
        // 3. 执行绘制命令
        // 注:如果要加载OBJ等格式的模型,需要自己实现模型解析器或者用轻量的第三方解析库(比如objloader-android)
    }

    // 相机纹理渲染的着色器代码
    private val vertexShaderCode = """
        attribute vec4 vPosition;
        attribute vec2 vTexCoord;
        varying vec2 texCoord;
        void main() {
            gl_Position = vPosition;
            texCoord = vTexCoord;
        }
    """.trimIndent()

    private val fragmentShaderCode = """
        #extension GL_OES_EGL_image_external : require
        precision mediump float;
        varying vec2 texCoord;
        uniform samplerExternalOES sTexture;
        void main() {
            gl_FragColor = texture2D(sTexture, texCoord);
        }
    """.trimIndent()

    // 创建着色器程序的工具方法
    private fun createShaderProgram(vertexCode: String, fragmentCode: String): Int {
        val vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexCode)
        val fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentCode)
        val program = GLES20.glCreateProgram()
        GLES20.glAttachShader(program, vertexShader)
        GLES20.glAttachShader(program, fragmentShader)
        GLES20.glLinkProgram(program)
        return program
    }

    private fun loadShader(type: Int, shaderCode: String): Int {
        val shader = GLES20.glCreateShader(type)
        GLES20.glShaderSource(shader, shaderCode)
        GLES20.glCompileShader(shader)
        return shader
    }
}

第三步:在Activity里初始化GLSurfaceView

修改initOverlayContent()方法:

private fun initOverlayContent() {
    val glSurfaceView = findViewById<GLSurfaceView>(R.id.glSurfaceView)
    // 使用OpenGL ES 2.0
    glSurfaceView.setEGLContextClientVersion(2)
    glSurfaceView.setRenderer(ARRenderer())
    // 持续渲染(保证模型实时更新)
    glSurfaceView.renderMode = GLSurfaceView.RENDERMODE_CONTINUOUSLY
}

关键注意事项
  • 相机帧与OpenGL纹理同步:需要用CameraX的ImageAnalysis用例获取预览帧,把帧数据绑定到OpenGL的外部纹理上,这个过程要注意线程同步(OpenGL在GL线程,相机帧在后台线程)。
  • 空间感模拟:要让模型看起来在真实空间里,需要用相机的参数(焦距、传感器尺寸)构建投影矩阵,再通过手势识别(比如触摸移动)更新模型的视图矩阵,模拟3D空间交互。
  • 性能优化:相机和OpenGL都比较耗资源,要记得在Activity销毁时释放纹理、着色器等资源,避免内存泄漏。

内容的提问来源于stack exchange,提问作者Ahsan Azwar

火山引擎 最新活动