Android ARCore demo 簡析

1. 簡介

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

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

  1. 運(yùn)動跟蹤

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

  2. 環(huán)境感知

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

  3. 光線預(yù)測

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

取自 ARCore ——移動AR的浪潮

2. Android Studio 工程配置

  1. 安裝 Android Studio 2.3 及以上,使用 Android SDK Platform 版本 7.0
  2. 一臺支持的 Android 設(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)測效果較差三娩,較難形成平面

3. demo 簡析

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)備對象

  1. 布局

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

    • ObjectRenderer mVirtualObject

      google 機(jī)器人模型

    • ObjectRenderer mVirtualObjectShadow

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

    • PointCloudRenderer mPointCloud

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

    • PlaneRenderer mPlaneRenderer

      平面識別成功之后的網(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 對象

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

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

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

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

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

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

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

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 時調(diào)用 mSession.resume(mDefaultConfig)

  2. onPause

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

    在頁面 onPause 時調(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. 初始化背景紋理對象(后續(xù)詳細(xì)介紹),并將創(chuàng)建的紋理 id 綁定給 mSession 的相機(jī)紋理對象蜻韭,用于顯示相機(jī)產(chǎn)生的視頻內(nèi)容
  3. 初始化 Android 機(jī)器人顯示對象悼尾,并設(shè)置材料反射光照(環(huán)境光無, 漫反射光 3.5, 鏡面光 1.0, 光照聚焦 6.0)
  4. 初始化 Android 機(jī)器人陰影顯示對象,設(shè)置顯示混合模式(開啟透明度肖方,GLES20.GL_ONE_MINUS_SRC_ALPHA)闺魏,設(shè)置顯示材料反射光照信息(1.0f, 0.0f, 0.0f, 1.0f)
  5. 初始化平面監(jiān)測結(jié)果顯示對象
  6. 初始化特征點(diǎn)云顯示對象

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 中的顯示視口大小(和后續(xù)計算 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)俯画,垂直向下的一條射線)析桥,計算射線是否和 frame 中的平面有交點(diǎn),且交點(diǎn)是否處理平面監(jiān)測得到的區(qū)域里面艰垂。若是泡仗,則保存平面信息和 pose 信息(可得到模型的位置和轉(zhuǎn)向信息)
  4. 繪制相機(jī)背景視圖
  5. 判斷是否已經(jīng)檢測到平面,沒有的話猜憎,不在顯示后續(xù)內(nèi)容
  6. 從 frame 中得到投影矩陣
  7. 從 frame 中得到模型變換矩陣
  8. 得到預(yù)計的環(huán)境光照強(qiáng)度
  9. 更新檢測的特征點(diǎn)云的坐標(biāo)并繪制
  10. 取消顯示正在檢測中的 toast(和 AR 無關(guān))
  11. 繪制檢測到的平面(顯示為網(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 初始化背景紋理對象
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)建紋理對象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 中計算得到

  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 錯誤

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

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ā)生變化肾扰,重新計算背景的紋理 uv 坐標(biāo)

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

  2. 關(guān)閉深度測試和深度緩沖區(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. 重新打開深度測試和深度緩沖區(qū)的可讀性

  7. 檢查錯誤

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

4. 總結(jié)

由上其實(shí)可以看到場景的顯示等蒋院,其實(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)測出來的平面辞友,用于后續(xù)業(yè)務(wù)開發(fā)顯示場景
  4. 提供接口獲取估計光照強(qiáng)度
  5. 提供相機(jī)幀中的特征點(diǎn)和檢測得到的平面數(shù)據(jù)
  6. 提供接口計算點(diǎn)擊操作和場景是否相交,和交點(diǎn)信息(包括交點(diǎn)垂直平面的位置和方向)
  7. 暫時未見如果識別特定圖片震肮,顯示模型的 demo

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末侈询,一起剝皮案震驚了整個濱河市匙奴,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌妄荔,老刑警劉巖泼菌,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異啦租,居然都是意外死亡哗伯,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進(jìn)店門篷角,熙熙樓的掌柜王于貴愁眉苦臉地迎上來焊刹,“玉大人,你說我怎么就攤上這事恳蹲∨翱椋” “怎么了?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵嘉蕾,是天一觀的道長贺奠。 經(jīng)常有香客問我,道長错忱,這世上最難降的妖魔是什么儡率? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任挂据,我火速辦了婚禮,結(jié)果婚禮上儿普,老公的妹妹穿的比我還像新娘崎逃。我一直安慰自己,他們只是感情好眉孩,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布个绍。 她就那樣靜靜地躺著,像睡著了一般浪汪。 火紅的嫁衣襯著肌膚如雪巴柿。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天吟宦,我揣著相機(jī)與錄音,去河邊找鬼涩维。 笑死殃姓,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的瓦阐。 我是一名探鬼主播蜗侈,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼睡蟋!你這毒婦竟也來了踏幻?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤戳杀,失蹤者是張志新(化名)和其女友劉穎该面,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體信卡,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡隔缀,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了傍菇。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片猾瘸。...
    茶點(diǎn)故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖丢习,靈堂內(nèi)的尸體忽然破棺而出牵触,到底是詐尸還是另有隱情,我是刑警寧澤咐低,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布揽思,位于F島的核電站,受9級特大地震影響见擦,放射性物質(zhì)發(fā)生泄漏绰更。R本人自食惡果不足惜瞧挤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望儡湾。 院中可真熱鬧特恬,春花似錦、人聲如沸徐钠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽尝丐。三九已至显拜,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間爹袁,已是汗流浹背远荠。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留失息,地道東北人譬淳。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像盹兢,于是被迫代替她去往敵國和親邻梆。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內(nèi)容

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