androidengineers.Book a session

Media & Graphics

ARCore Primer

article20 minHard

ARCore is Google's platform for building augmented reality experiences on Android. It uses the device's camera and IMU to understand the physical world, allowing virtual 3D content to appear anchored in the real environment.

Three Foundational Capabilities

  1. Motion tracking: tracks the device's position and orientation relative to the world using Visual Inertial Odometry (VIO)
  2. Environmental understanding: detects flat surfaces (floor, table, walls) and estimates their boundaries
  3. Light estimation: measures ambient light intensity and direction so virtual objects can be lit realistically

Sceneform vs ARSceneView (Alternatives)

Sceneform was deprecated. In 2024, the recommended approach is:

  • ARCore SDK directly for custom rendering (OpenGL / Vulkan)
  • io.github.sceneview:arsceneview (community maintained Sceneform successor) for simpler 3D object placement

Basic ARCore Session

// implementation("com.google.ar:core:1.43.0")

class ArActivity : AppCompatActivity() {
    private var arSession: Session? = null
    private lateinit var surfaceView: GLSurfaceView

    override fun onResume() {
        super.onResume()

        // Check ARCore availability
        when (ArCoreApk.getInstance().checkAvailability(this)) {
            ArCoreApk.Availability.SUPPORTED_INSTALLED -> createSession()
            ArCoreApk.Availability.SUPPORTED_NOT_INSTALLED,
            ArCoreApk.Availability.SUPPORTED_APK_TOO_OLD -> {
                // Prompt user to install/update ARCore via Play Store
                ArCoreApk.getInstance().requestInstall(this, true)
            }
            else -> {
                Toast.makeText(this, "ARCore not supported", Toast.LENGTH_LONG).show()
                finish()
            }
        }
    }

    private fun createSession() {
        try {
            val session = Session(this)
            val config = Config(session).apply {
                planeFindingMode = Config.PlaneFindingMode.HORIZONTAL_AND_VERTICAL
                lightEstimationMode = Config.LightEstimationMode.ENVIRONMENTAL_HDR
                updateMode = Config.UpdateMode.LATEST_CAMERA_IMAGE
            }
            session.configure(config)
            arSession = session
            session.resume()
        } catch (e: UnavailableException) {
            handleArException(e)
        }
    }

    override fun onPause() {
        super.onPause()
        arSession?.pause()
    }
}

Per-Frame Processing

The AR update loop runs every frame in the GL thread:

// Called from GLSurfaceView.Renderer.onDrawFrame()
fun onDrawFrame() {
    val frame = arSession?.update() ?: return
    val camera = frame.camera

    if (camera.trackingState != TrackingState.TRACKING) return  // not localized yet

    // Process newly detected planes
    for (plane in arSession!!.getAllTrackables(Plane::class.java)) {
        if (plane.trackingState == TrackingState.TRACKING) {
            drawPlaneIndicator(plane)
        }
    }

    // Handle user tap — hit test against detected planes
    pendingTap?.let { tap ->
        val hitResults = frame.hitTest(tap)
        hitResults.firstOrNull { it.trackable is Plane }?.let { hit ->
            val anchor = hit.createAnchor()
            placeObject(anchor)
        }
        pendingTap = null
    }
}

Anchors and Trackables

// Anchor: a fixed point in the physical world
// Creating an anchor from a hit test result:
val anchor = hitResult.createAnchor()

// Placing content at an anchor:
val pose = anchor.pose  // Pose = position + rotation in world space
val modelMatrix = FloatArray(16)
pose.toMatrix(modelMatrix, 0)
// Use modelMatrix to position your 3D object in OpenGL

// Persistent anchors (Cloud Anchors) — shared across devices
val hostListener = object : Session.HostCloudAnchorCallback {
    override fun onHostComplete(cloudAnchorId: String, state: CloudAnchorState) {
        if (state == CloudAnchorState.SUCCESS) {
            saveToServer(cloudAnchorId)  // share this ID with other devices
        }
    }
}
arSession.hostCloudAnchorWithTtl(anchor, 1, hostListener)  // TTL in days

Depth API

On supported devices, ARCore provides a depth map — a 16-bit depth image at the resolution of the color camera:

val config = Config(session).apply {
    depthMode = if (session.isDepthModeSupported(Config.DepthMode.AUTOMATIC)) {
        Config.DepthMode.AUTOMATIC
    } else {
        Config.DepthMode.DISABLED
    }
}

// Per frame: get depth image
val depthImage = frame.acquireDepthImage16Bits()
// depthImage is a 16-bit depth map in millimeters
// Use for occlusion rendering: virtual objects hidden behind real-world surfaces
depthImage.close()  // always close!

Key Takeaways

ConceptRule
Session lifecycleresume() in onResume, pause() in onPause
Tracking stateCheck camera.trackingState == TRACKING before rendering
Hit testingTap → frame.hitTest() → filter for PlanecreateAnchor()
AnchorsAnchor positions virtual objects to real-world coordinates
Depth APIUse for occlusion; check isDepthModeSupported before enabling
ARCore installCheck and prompt for ARCore installation before creating a session

YOUR LEARNING JOURNEY

0 of 177 available lessons completed

Progress saved in this browser. No account needed.
ARCore Primer | Android System Design | Android Engineers