Android ARCore demo 簡(jiǎn)析

1. 簡(jiǎn)介

ARCore 是 google 官方出的一款 AR SDK,其基本原理為:

ARCore 使用手機(jī)攝像頭來辨識(shí)特征點(diǎn),并跟蹤這些特征點(diǎn)的移動(dòng)軌跡。結(jié)合特征點(diǎn)的移動(dòng)軌跡和手機(jī)的慣性傳感器,ARCore 就可以在手機(jī)移動(dòng)時(shí)判定它的位置、角度等信息。識(shí)別出特征點(diǎn),就能在特征點(diǎn)的基礎(chǔ)上,偵測(cè)平面,如地板、桌子等。另外能 ARCore 也支持估測(cè)周圍的平均光照強(qiáng)度。有了手機(jī)自身的位置角度信息和周圍的光照強(qiáng)度信息,ARCore 就可以構(gòu)建周邊世界的模型。

  1. 運(yùn)動(dòng)跟蹤

    它利用 IMU 傳感器和設(shè)備的相機(jī)來發(fā)現(xiàn)空間的特征點(diǎn),由此確定 Android 設(shè)備的位置和方向。此外,使用 VPS,可以讓 AR 物體每次看起來似乎都在同一位置。

  2. 環(huán)境感知

    虛擬物體一般都是放置于平坦平面上的,用 ARCore 可以檢測(cè)物體的水平表面,建立環(huán)境認(rèn)知感,以保證虛擬的對(duì)象可以準(zhǔn)確放置,然后讓您看到放置在這些表面上的 AR 物體。

  3. 光線預(yù)測(cè)

    ARCore 根據(jù)環(huán)境的光強(qiáng)度,使開發(fā)人員可以與周圍環(huán)境相匹配的方式點(diǎn)亮虛擬對(duì)象。此外,最近的一個(gè)實(shí)驗(yàn)發(fā)現(xiàn),虛擬陰影在真實(shí)環(huán)境光照下的調(diào)整功能也是如此,這樣就可以使 AR 物體的外觀更為逼真。

取自 ARCore ——移動(dòng)AR的浪潮

2. Android Studio 工程配置

  1. 安裝 Android Studio 2.3 及以上,使用 Android SDK Platform 版本 7.0
  2. 一臺(tái)支持的 Android 設(shè)備 (暫時(shí)僅支持 Google Pixel 和 Samsung Galaxy S8 的2款設(shè)備)
  3. 獲取 ARCore SDK

鑒于支持 ARCore 的 Android 設(shè)備太少,為此可通過修改 ARCore 的設(shè)備支持接口,修改方式如下:

  1. Open a command line interface
  2. Unzip the AAR to a temporary directory: unzip arcore_client-original.aar -d aar-tmp
  3. Enter the temporary aar directory: cd aar-tmp
  4. Unzip classes.jar to a temporary directory: unzip classes.jar -d classes-tmp
  5. Enter the temporary classes directory: cd classes-tmp
  6. Enter the directory containing the SupportedDevices class: cd com/google/atap/tangoservice
  7. Decompile the SupportedDevices class: java -jar /path/to/cfr.jar SupportedDevices.class > SupportedDevices.java
  8. Open a text editor and delete return false from the end of isSupported()
  9. Compile the modified SupportedDevice class: javac -cp /path/to/sdk/platform/android.jar -source 1.7 -target 1.7 SupportedDevices.java
  10. Delete the Java source: rm SupportedDevices.java
  11. Change directory back to aar-tmp: cd ../../../../../
  12. Create a JAR from the modified classes directory: jar cvf classes.jar -C classes-tmp .
  13. Change directory back to repo root: cd ..
  14. Create an AAR from the modified aar directory: jar cvf arcore_client.aar -C aar-tmp .

取自 arcore-for-all

強(qiáng)行修改 ARCore 的支持函數(shù)判斷后,各機(jī)型的支持程度如下:

Manufacturer Device Model GPU 64-bit? Official Support? Functional?
Google Pixel All Adreno 530
Google Pixel XL All Adreno 530
Samsung Galaxy S6 G920 Mali-T760MP8 × ××
Samsung Galaxy S7 G930F Mali-T880 MP12 × ×
Samsung Galaxy S7 Edge G9350 (Hong Kong) Adreno 530 × ?
Samsung Galaxy S7 Edge G935FD, G935F, G935W8 Mali-T880 MP12 × ×
Samsung Galaxy S8 USA & China Adreno 540
Samsung Galaxy S8 EMEA Mali-G71 MP20 ?
Samsung Galaxy S8+ USA & China Adreno 540 ×
Samsung Galaxy S8+ G955F (EMEA) Mali-G71 MP20 ×
HTC HTC 10 All Adreno 530 × ×
Huawei Nexus 6P All Adreno 430 ×
Huawei P9 Lite All Mali-T830MP2 × ×
Huawei P10 All Mali-G71 MP8 × ×
LG G2 All Adreno 330 × × ×
LG V20 US996 Adreno 530 × ×
LG Nexus 5 All Adreno 330 × ×
LG Nexus 5X All Adreno 418 × ×
OnePlus 3 All Adreno 530 × ×
OnePlus 3T All Adreno 530 × ×
OnePlus X All Adreno 330 × × ×
OnePlus 5 All Adreno 540 ×
Nvidia Shield K1 All ULP GeForce Kepler × ×
Xiaomi Redmi Note 4 All Adreno 506 × ×
Xiaomi Mi 5s capricorn Adreno 530 × ×
Xiaomi Mi Mix All Adreno 530 × ×
Motorola Moto G4 All Adreno 405 × ×
Motorola Nexus 6 All Adreno 420 × × ×
ZTE Axon 7 A2017 Adreno 530 × ×
Sony Xperia XZs All Adreno 530 × ×

取自 arcore-for-all Device Research

實(shí)際使用 Nexus 6P 運(yùn)行 arcore-for-all sample.apk 效果并不如意,平面監(jiān)測(cè)效果較差,較難形成平面

3. demo 簡(jiǎn)析

3.0 demo 效果

gif

3.1 配置工程

  1. 配置 sdk 版本信息

    compileSdkVersion 25
    buildToolsVersion "25.0.0"
    
    defaultConfig {
        applicationId "com.google.ar.core.examples.java.helloar"
        minSdkVersion 19
        targetSdkVersion 25
        versionCode 1
        versionName "1.0"
    }
    

    其中配置的 minSdkVersion 最小為 19

  2. 引入需要的第三方庫

    dependencies {
        compile (name: 'arcore_client', ext: 'aar')
        compile (name: 'obj-0.2.1', ext: 'jar')
        ...
    }
    

    其中 obj-0.2.1.jar 包用于加載解析 obj 文件為模型數(shù)據(jù)

3.2 顯示 Activity 布局和準(zhǔn)備對(duì)象

  1. 布局

    <android.opengl.GLSurfaceView
        android:id="@+id/surfaceview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="top"/>
    
  2. 渲染封裝對(duì)象

    • ObjectRenderer mVirtualObject

      google 機(jī)器人模型

    • ObjectRenderer mVirtualObjectShadow

      google 機(jī)器人陰影模型

    • PointCloudRenderer mPointCloud

      平面監(jiān)測(cè)特征點(diǎn)

    • PlaneRenderer mPlaneRenderer

      平面識(shí)別成功之后的網(wǎng)格模型

    • BackgroundRenderer mBackgroundRenderer

      相機(jī)視頻流數(shù)據(jù)顯示至紋理

3.3 onCreate 初始化

setContentView(R.layout.activity_main);
mSurfaceView = (GLSurfaceView) findViewById(R.id.surfaceview);

// 1. 
mSession = new Session(/*context=*/this);

// 2. 
// Create default config, check is supported, create session from that config.
mDefaultConfig = Config.createDefaultConfig();
if (!mSession.isSupported(mDefaultConfig)) {
    Toast.makeText(this, "This device does not support AR", Toast.LENGTH_LONG).show();
    finish();
    return;
}

// 3. 
// 創(chuàng)建并設(shè)置 SurfaceView Tap 事件
...

// 4.
// Set up renderer.
mSurfaceView.setPreserveEGLContextOnPause(true);
mSurfaceView.setEGLContextClientVersion(2);
mSurfaceView.setEGLConfigChooser(8, 8, 8, 8, 16, 0); // Alpha used for plane blending.
mSurfaceView.setRenderer(this);
mSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
  1. 創(chuàng)建 Session 對(duì)象

    Session 用于處理 ARCore 狀態(tài),處理當(dāng)前 AR 的生命周期(resume,pause),綁定背景相機(jī)圖像紋理,設(shè)置視口顯示大小,從視圖中獲取 frame 數(shù)據(jù)(可得到估計(jì)光照強(qiáng)度、投影矩陣、模型變換矩陣、圖像特征點(diǎn)數(shù)據(jù)和模型矩陣、監(jiān)測(cè)平面數(shù)據(jù)等)

  2. 創(chuàng)建 mDefaultConfig 并判斷當(dāng)前機(jī)型是否支持 ARCore

    暫時(shí)支持的機(jī)型,見 Supported Devices

  3. 創(chuàng)建并設(shè)置 SurfaceView Tap 事件

    記錄用戶在 Surface Tap 的位置信息,用于創(chuàng)建 Google 機(jī)器人

  4. SurfaceView 相關(guān)設(shè)置

    設(shè)置在 Pause 時(shí)保留 GL 上下文環(huán)境,設(shè)置 EGL 版本為 2.0,設(shè)置各個(gè)通道的大小,設(shè)置渲染監(jiān)聽實(shí)現(xiàn),設(shè)置為主動(dòng)渲染

3.4 處理生命周期

  1. onResume

    @Override
    protected void onResume() {
        super.onResume();
    
        if (CameraPermissionHelper.hasCameraPermission(this)) {
            showLoadingMessage();
            // Note that order matters - see the note in onPause(), the reverse applies here.
            mSession.resume(mDefaultConfig);
            mSurfaceView.onResume();
        } else {
            CameraPermissionHelper.requestCameraPermission(this);
        }
    }
    

    在頁面 onResume 時(shí)調(diào)用 mSession.resume(mDefaultConfig)

  2. onPause

    @Override
    public void onPause() {
        super.onPause();
        mSurfaceView.onPause();
        mSession.pause();
    }
    

    在頁面 onPause 時(shí)調(diào)用 mSession.pause(),停止頁面查詢 Session

    注意:mSession.pause() 必須在 mSurfaceView.onPause() 后面執(zhí)行,否則 可能發(fā)生在 mSession.pause() 之后繼續(xù)調(diào)用 mSession.update(),進(jìn)而發(fā)生 SessionPausedException 異常

3.5 SurfaceView 顯示準(zhǔn)備

@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
    // 1.
    GLES20.glClearColor(0.1f, 0.1f, 0.1f, 1.0f);

    // 2.
    mBackgroundRenderer.createOnGlThread(/*context=*/this);
    mSession.setCameraTextureName(mBackgroundRenderer.getTextureId());

    try {
        // 3.
        mVirtualObject.createOnGlThread(/*context=*/this, "andy.obj", "andy.png");
        mVirtualObject.setMaterialProperties(0.0f, 3.5f, 1.0f, 6.0f);

        // 4.
        mVirtualObjectShadow.createOnGlThread(/*context=*/this,
                "andy_shadow.obj", "andy_shadow.png");
        mVirtualObjectShadow.setBlendMode(BlendMode.Shadow);
        mVirtualObjectShadow.setMaterialProperties(1.0f, 0.0f, 0.0f, 1.0f);
    } catch (IOException e) {
        Log.e(TAG, "Failed to read obj file");
    }
    try {
        // 5.
        mPlaneRenderer.createOnGlThread(/*context=*/this, "trigrid.png");
    } catch (IOException e) {
        Log.e(TAG, "Failed to read plane texture");
    }
    
    // 6.
    mPointCloud.createOnGlThread(/*context=*/this);
}
  1. 設(shè)置 opengl 幀緩存清空顏色為灰白色
  2. 初始化背景紋理對(duì)象(后續(xù)詳細(xì)介紹),并將創(chuàng)建的紋理 id 綁定給 mSession 的相機(jī)紋理對(duì)象,用于顯示相機(jī)產(chǎn)生的視頻內(nèi)容
  3. 初始化 Android 機(jī)器人顯示對(duì)象,并設(shè)置材料反射光照(環(huán)境光無, 漫反射光 3.5, 鏡面光 1.0, 光照聚焦 6.0)
  4. 初始化 Android 機(jī)器人陰影顯示對(duì)象,設(shè)置顯示混合模式(開啟透明度,GLES20.GL_ONE_MINUS_SRC_ALPHA),設(shè)置顯示材料反射光照信息(1.0f, 0.0f, 0.0f, 1.0f)
  5. 初始化平面監(jiān)測(cè)結(jié)果顯示對(duì)象
  6. 初始化特征點(diǎn)云顯示對(duì)象

3.6 SurfaceView 大小改變

@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
    // 1. 設(shè)置 opengl 的視口大小
    GLES20.glViewport(0, 0, width, height);
    // 2. 設(shè)置 mSession 中的顯示視口大?。ê秃罄m(xù)計(jì)算 frame 有關(guān))
    mSession.setDisplayGeometry(width, height);
}

3.7 SurfaceView 繪制

@Override
public void onDrawFrame(GL10 gl) {
    // 1.
    GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);

    try {
        // 2.
        Frame frame = mSession.update();

        // 3. 
        MotionEvent tap = mQueuedSingleTaps.poll();
        if (tap != null && frame.getTrackingState() == TrackingState.TRACKING) {
            for (HitResult hit : frame.hitTest(tap)) {
                // Check if any plane was hit, and if it was hit inside the plane polygon.
                if (hit instanceof PlaneHitResult && ((PlaneHitResult) hit).isHitInPolygon()) {
                    // Cap the number of objects created. This avoids overloading both the
                    // rendering system and ARCore.
                    if (mTouches.size() >= 16) {
                        mSession.removeAnchors(Arrays.asList(mTouches.get(0).getAnchor()));
                        mTouches.remove(0);
                    }
                    // Adding an Anchor tells ARCore that it should track this position in
                    // space. This anchor will be used in PlaneAttachment to place the 3d model
                    // in the correct position relative both to the world and to the plane.
                    mTouches.add(new PlaneAttachment(
                            ((PlaneHitResult) hit).getPlane(),
                            mSession.addAnchor(hit.getHitPose())));

                    // Hits are sorted by depth. Consider only closest hit on a plane.
                    break;
                }
            }
        }

        // 4.
        mBackgroundRenderer.draw(frame);

        // 5.
        if (frame.getTrackingState() == TrackingState.NOT_TRACKING) {
            return;
        }

        // 6.
        float[] projmtx = new float[16];
        mSession.getProjectionMatrix(projmtx, 0, 0.1f, 100.0f);

        // 7.
        float[] viewmtx = new float[16];
        frame.getViewMatrix(viewmtx, 0);

        // 8.
        // Compute lighting from average intensity of the image.
        final float lightIntensity = frame.getLightEstimate().getPixelIntensity();

        // 9.
        // Visualize tracked points.
        mPointCloud.update(frame.getPointCloud());
        mPointCloud.draw(frame.getPointCloudPose(), viewmtx, projmtx);

        // 10. 
        // Check if we detected at least one plane. If so, hide the loading message.
        ...

        // 11. Visualize planes.
        mPlaneRenderer.drawPlanes(mSession.getAllPlanes(), frame.getPose(), projmtx);

        // 12. Visualize anchors created by touch.
        float scaleFactor = 1.0f;
        for (PlaneAttachment planeAttachment : mTouches) {
            if (!planeAttachment.isTracking()) {
                continue;
            }
            // Get the current combined pose of an Anchor and Plane in world space. The Anchor
            // and Plane poses are updated during calls to session.update() as ARCore refines
            // its estimate of the world.
            planeAttachment.getPose().toMatrix(mAnchorMatrix, 0);

            // Update and draw the model and its shadow.
            mVirtualObject.updateModelMatrix(mAnchorMatrix, scaleFactor);
            mVirtualObjectShadow.updateModelMatrix(mAnchorMatrix, scaleFactor);
            mVirtualObject.draw(viewmtx, projmtx, lightIntensity);
            mVirtualObjectShadow.draw(viewmtx, projmtx, lightIntensity);
        }

    } catch (Throwable t) {
        // Avoid crashing the application due to unhandled exceptions.
        Log.e(TAG, "Exception on the OpenGL thread", t);
    }
}
  1. 清除顏色和深度緩沖區(qū)
  2. 從 mSession 得到最新的 frame
  3. 遍歷前面 tap 操作得到的點(diǎn)列表(可以理解以此為起點(diǎn),垂直向下的一條射線),計(jì)算射線是否和 frame 中的平面有交點(diǎn),且交點(diǎn)是否處理平面監(jiān)測(cè)得到的區(qū)域里面。若是,則保存平面信息和 pose 信息(可得到模型的位置和轉(zhuǎn)向信息)
  4. 繪制相機(jī)背景視圖
  5. 判斷是否已經(jīng)檢測(cè)到平面,沒有的話,不在顯示后續(xù)內(nèi)容
  6. 從 frame 中得到投影矩陣
  7. 從 frame 中得到模型變換矩陣
  8. 得到預(yù)計(jì)的環(huán)境光照強(qiáng)度
  9. 更新檢測(cè)的特征點(diǎn)云的坐標(biāo)并繪制
  10. 取消顯示正在檢測(cè)中的 toast(和 AR 無關(guān))
  11. 繪制檢測(cè)到的平面(顯示為網(wǎng)格狀)
  12. 遍歷第 3 步記錄的 PlaneAttachment 列表,得到投影矩陣和模型變換矩陣,并結(jié)合第 8 步得到的光照強(qiáng)度,繪制 Android 機(jī)器人和陰影

3.8 顯示相機(jī)捕捉的視頻內(nèi)容

OpenGL 相關(guān),和 AR 無關(guān)

3.8.1 初始化背景紋理對(duì)象
public void createOnGlThread(Context context) {
    // 1.
    int textures[] = new int[1];
    GLES20.glGenTextures(1, textures, 0);
    mTextureId = textures[0];
    GLES20.glBindTexture(mTextureTarget, mTextureId);
    GLES20.glTexParameteri(mTextureTarget, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
    GLES20.glTexParameteri(mTextureTarget, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
    GLES20.glTexParameteri(mTextureTarget, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
    GLES20.glTexParameteri(mTextureTarget, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST);

    int numVertices = 4;
    if (numVertices != QUAD_COORDS.length / COORDS_PER_VERTEX) {
      throw new RuntimeException("Unexpected number of vertices in BackgroundRenderer.");
    }

    // 2.
    ByteBuffer bbVertices = ByteBuffer.allocateDirect(QUAD_COORDS.length * FLOAT_SIZE);
    bbVertices.order(ByteOrder.nativeOrder());
    mQuadVertices = bbVertices.asFloatBuffer();
    mQuadVertices.put(QUAD_COORDS);
    mQuadVertices.position(0);

    // 3.
    ByteBuffer bbTexCoords = ByteBuffer.allocateDirect(
            numVertices * TEXCOORDS_PER_VERTEX * FLOAT_SIZE);
    bbTexCoords.order(ByteOrder.nativeOrder());
    mQuadTexCoord = bbTexCoords.asFloatBuffer();
    mQuadTexCoord.put(QUAD_TEXCOORDS);
    mQuadTexCoord.position(0);

    // 4.
    ByteBuffer bbTexCoordsTransformed = ByteBuffer.allocateDirect(
        numVertices * TEXCOORDS_PER_VERTEX * FLOAT_SIZE);
    bbTexCoordsTransformed.order(ByteOrder.nativeOrder());
    mQuadTexCoordTransformed = bbTexCoordsTransformed.asFloatBuffer();

    // 5.
    int vertexShader = ShaderUtil.loadGLShader(TAG, context,
            GLES20.GL_VERTEX_SHADER, R.raw.screenquad_vertex);
    // 6.
    int fragmentShader = ShaderUtil.loadGLShader(TAG, context,
            GLES20.GL_FRAGMENT_SHADER, R.raw.screenquad_fragment_oes);

    // 7.
    mQuadProgram = GLES20.glCreateProgram();
    GLES20.glAttachShader(mQuadProgram, vertexShader);
    GLES20.glAttachShader(mQuadProgram, fragmentShader);
    GLES20.glLinkProgram(mQuadProgram);
    GLES20.glUseProgram(mQuadProgram);

    // 8.
    ShaderUtil.checkGLError(TAG, "Program creation");

    // 9.
    mQuadPositionParam = GLES20.glGetAttribLocation(mQuadProgram, "a_Position");
    mQuadTexCoordParam = GLES20.glGetAttribLocation(mQuadProgram, "a_TexCoord");

    ShaderUtil.checkGLError(TAG, "Program parameters");
}
  1. 創(chuàng)建紋理對(duì)象id,并綁定到 GL_TEXTURE_EXTERNAL_OES,設(shè)置紋理貼圖的效果和縮放效果

    綁定的紋理不是 GL_TEXTURE_2D,而是 GL_TEXTURE_EXTERNAL_OES,是因?yàn)?Camera 使用的輸出 texture 是一種特殊的格式。同樣的,在 shader 中也必須使用 SamperExternalOES 的變量類型來訪問該紋理

    #extension GL_OES_EGL_image_external : require
    
    precision mediump float;
    varying vec2 v_TexCoord;
    uniform samplerExternalOES sTexture;
    
    void main() {
        gl_FragColor = texture2D(sTexture, v_TexCoord);
    }
    

    片元顯示器

  2. 設(shè)置紋理幾何的頂點(diǎn)坐標(biāo)

  3. 設(shè)置紋理的初始貼圖坐標(biāo)

  4. 設(shè)置紋理最終的貼圖坐標(biāo),可從 Frame 中計(jì)算得到

  5. 加載頂點(diǎn)顯示器

    attribute vec4 a_Position;
    attribute vec2 a_TexCoord;
    
    varying vec2 v_TexCoord;
    
    void main() {
       gl_Position = a_Position;
       v_TexCoord = a_TexCoord;
    }
    
  6. 加載片元顯示器

  7. opengl 程序連接編譯

  8. 檢查 opengl 錯(cuò)誤

  9. 初始化背景矩形的頂點(diǎn)坐標(biāo)對(duì)象,和紋理坐標(biāo)對(duì)象

3.8.2 繪制相機(jī)捕獲的視頻幀至紋理

3.7 SurfaceView 繪制 的第 4 步調(diào)用了 mBackgroundRenderer.draw(frame) 其內(nèi)容如下:

public class BackgroundRenderer {

    ...

    public void draw(Frame frame) {
        // 1.
        if (frame.isDisplayRotationChanged()) {
            frame.transformDisplayUvCoords(mQuadTexCoord, mQuadTexCoordTransformed);
        }

        // 2.
        GLES20.glDisable(GLES20.GL_DEPTH_TEST);
        GLES20.glDepthMask(false);

        // 3.
        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureId);

        // 4.
        GLES20.glUseProgram(mQuadProgram);

        // 5.
        GLES20.glVertexAttribPointer(
            mQuadPositionParam, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, 0, mQuadVertices);
        GLES20.glVertexAttribPointer(mQuadTexCoordParam, TEXCOORDS_PER_VERTEX,
                GLES20.GL_FLOAT, false, 0, mQuadTexCoordTransformed);
        GLES20.glEnableVertexAttribArray(mQuadPositionParam);
        GLES20.glEnableVertexAttribArray(mQuadTexCoordParam);
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
        GLES20.glDisableVertexAttribArray(mQuadPositionParam);
        GLES20.glDisableVertexAttribArray(mQuadTexCoordParam);

        // 6.
        GLES20.glDepthMask(true);
        GLES20.glEnable(GLES20.GL_DEPTH_TEST);

        // 7.
        ShaderUtil.checkGLError(TAG, "Draw");
    }
}
  1. 當(dāng)顯示角度發(fā)生變化或者 SurfaceView 大小發(fā)生變化,重新計(jì)算背景的紋理 uv 坐標(biāo)

    是否發(fā)生變化,如何計(jì)算,均有 frame 接口提供

  2. 關(guān)閉深度測(cè)試和深度緩沖區(qū)的可讀性為不可讀

    相機(jī)視頻幀數(shù)據(jù)是在所有模型的后面

  3. 綁定 mTextureId 至 GLES11Ext.GL_TEXTURE_EXTERNAL_OES

  4. 設(shè)置使用繪制背景的 shader 程序

  5. 將背景四邊形頂點(diǎn)數(shù)據(jù)和紋理貼圖的 uv 數(shù)據(jù)設(shè)置給綁定的 shader 變量,設(shè)置數(shù)據(jù)在著色器端可見,繪制矩形內(nèi)容,關(guān)閉數(shù)據(jù)在著色器端可見

  6. 重新打開深度測(cè)試和深度緩沖區(qū)的可讀性

  7. 檢查錯(cuò)誤

其他內(nèi)容顯示類似,不再贅述

4. 總結(jié)

由上其實(shí)可以看到場(chǎng)景的顯示等,其實(shí)并不是 ARCore 關(guān)心的,ARCore 提供給我們的數(shù)據(jù)或幫我們完成的功能有:

  1. 判斷當(dāng)前機(jī)型是否支持 ARCore
  2. 提供接口判斷當(dāng)前角度是否發(fā)生變化,轉(zhuǎn)換相機(jī)幀紋理的 uv 坐標(biāo)
  3. 提供接口提取可用的 frame,得到投影矩陣和模型變換矩陣,得到特征點(diǎn)云,監(jiān)測(cè)出來的平面,用于后續(xù)業(yè)務(wù)開發(fā)顯示場(chǎng)景
  4. 提供接口獲取估計(jì)光照強(qiáng)度
  5. 提供相機(jī)幀中的特征點(diǎn)和檢測(cè)得到的平面數(shù)據(jù)
  6. 提供接口計(jì)算點(diǎn)擊操作和場(chǎng)景是否相交,和交點(diǎn)信息(包括交點(diǎn)垂直平面的位置和方向)
  7. 暫時(shí)未見如果識(shí)別特定圖片,顯示模型的 demo

相比其他第三方庫,如 EasyAR 收費(fèi)版本,支持估計(jì)周圍環(huán)境光照強(qiáng)度,slam 算法更加穩(wěn)定強(qiáng)大,效果更好;相比 Vuforia 支持平面監(jiān)測(cè),但當(dāng)前可支持機(jī)型還比較少。而非支持機(jī)型,通過改 ARR 代碼強(qiáng)制支持,效果也不太理想,如 Nexus 6P。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,688評(píng)論 19 139
  • http://blog.csdn.net/wangdingqiaoit/article/details/51457...
    jerryhigh閱讀 5,571評(píng)論 0 8
  • 在Android系統(tǒng)中,有一種特殊的視圖,稱為SurfaceView,它擁有獨(dú)立的繪圖表面,即它不與其宿主窗口共享...
    一個(gè)不掉頭發(fā)的開發(fā)閱讀 11,591評(píng)論 12 74
  • 題目沒別的意思 只是正好聽這首歌而已 晚秋的天已經(jīng)熱不起來 寒意也越來越重。入夜 , 我點(diǎn)了一根煙 , 披了大衣出...
    巫小樓閱讀 200評(píng)論 1 2
  • 我剛才坐在馬桶上面,百無聊賴,開始打開手機(jī)看新聞??吹揭粋€(gè)消息就是阿里巴巴上市居然用1%的承銷費(fèi)找了六大投行來IP...
    生如如花閱讀 228評(píng)論 0 0

友情鏈接更多精彩內(nèi)容