如何在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




