ARCore:相機(jī)預(yù)覽背景繪制

在完成了OpenGL ES和ARCore基本的實現(xiàn)后,下一步肯定就是圖像繪制了。在Google為ARCore提供的Demo中,主要進(jìn)行了相機(jī)預(yù)覽、檢測點(diǎn)云、檢測平面和Android小機(jī)器人的繪制。本節(jié)我們將跟大家介紹相機(jī)預(yù)覽背景的繪制過程和原理。

一、OpenGL ES渲染管線


OpenGL ES渲染管線其實就是渲染流水線,實質(zhì)上指的是一系列的繪制過程。這些過程輸入的是待渲染3D物理的相關(guān)描述信息數(shù)據(jù),經(jīng)過渲染管線,輸出一幀想要的圖像。

OpenGL ES渲染管線,如下圖所示:


OpenGL ES渲染管線

1.基本處理
在該階段設(shè)定3D空間中物體的頂點(diǎn)坐標(biāo)、頂點(diǎn)對應(yīng)的顏色、頂點(diǎn)紋理坐標(biāo)等屬性,并指定繪制方式,如點(diǎn)繪制、線段繪制或者三角形繪制等。

OpenGL ES支持2D和3D圖形的繪制。當(dāng)你希望在2D界面繪制一條任意的曲線的時候,把這邊曲線放的足夠大來看,會發(fā)現(xiàn)這條曲線其實是由許多足夠短的直線連接起來的。那么繪制3D圖形,即使你看見的是一個“圓滑曲面”的3D圖形,實際上足夠放大它依然是由多個小平面組成的。所以對于2D圖形,可能是由很多“曲線”組成;而3D圖形可能是由很多“曲面”組成。


3D圖形

2.頂點(diǎn)緩沖對象
該階段在應(yīng)用程序中是可選的。在整個場景中頂點(diǎn)的基本數(shù)據(jù)不變的情況。可以在初始化階段將頂點(diǎn)數(shù)據(jù)經(jīng)過基本處理后送入頂點(diǎn)緩沖對象,在繪制每一幀想要的圖像就省去了頂點(diǎn)IO的麻煩,直接從頂點(diǎn)緩沖對象中獲取頂點(diǎn)數(shù)據(jù)即可。

3.頂點(diǎn)著色器
頂點(diǎn)著色器是一個可編程處理單元,為執(zhí)行頂點(diǎn)變換、光照、材質(zhì)的應(yīng)用與計算等頂點(diǎn)相關(guān)的操作,每個頂點(diǎn)執(zhí)行一次。工作過程首先將原始的幾何信息及其他屬性傳送到頂點(diǎn)著色器中,經(jīng)過自己開發(fā)的頂點(diǎn)著色器處理后產(chǎn)生紋理坐標(biāo)、顏色、點(diǎn)位置等后流程需要的各項頂點(diǎn)屬性信息,然后將其傳遞給圖元裝配階段。

頂點(diǎn)著色器輸入主要為待處理頂點(diǎn)相應(yīng)的attribute(屬性)變量、uniform(一致)變量、采樣器以及臨時變量;輸出主要為經(jīng)過頂點(diǎn)著色器后生成的varying(易變)變量以及一些內(nèi)建輸出變量。


頂點(diǎn)著色器

4.圖元裝配
該階段主要任務(wù)是圖元組裝和圖元處理。

圖元組裝是指頂點(diǎn)數(shù)據(jù)根據(jù)設(shè)置的繪制方式被結(jié)合成完整的圖元。如點(diǎn)繪制每個頂點(diǎn)為一個圖元;線段繪制方式每個圖元則為兩個頂點(diǎn);三角形繪制方式下需要3個頂點(diǎn)構(gòu)造成一個圖元。

圖元處理最重要的工作是剪裁,其任務(wù)是消除位于半空間之外的部分幾何圖元。之所以進(jìn)行剪裁,是因為隨著觀察位置、角度不同,并不能總看到特定3D物體某個圖元的全部。剪裁時,若圖元完全位置視景體以及自定義裁剪平面的內(nèi)部,則將圖元傳遞到后面步驟進(jìn)行處理;如果其完全位于視景體或者自定義剪裁平面的外部,則丟棄該圖元。


圖片處理剪裁

5.光柵化
虛擬3D世界中的物體的幾何信息一般采用連續(xù)的數(shù)學(xué)向量來表示,因此投影的平面結(jié)果也是用連續(xù)的數(shù)學(xué)向量表示的。但目前的顯示設(shè)備都是離散化的(由一個一個的像素組成),因此還需要將投影的結(jié)果離散化。將其分解為一個一個離散化的小單元。

光柵化

6.片元著色器
片元著色器是用于處理片元值以及相關(guān)數(shù)據(jù)的可編程單元,其可以執(zhí)行紋理的采樣、顏色的匯總、計算霧顏色等操作,每片元執(zhí)行一次。片元著色器通過重復(fù)執(zhí)行,將3D物體中的圖元光柵化后產(chǎn)生的每個片元的顏色等屬性計算出來后送入后續(xù)階段,如剪裁測試、深度測試及模板測試等。

片元著色器輸入是從頂點(diǎn)著色器傳遞到片元著色器的易變變量(Varying0~n),輸出為內(nèi)建變量(gl_FragColor)是片元的最終顏色。


片元著色器

7.剪裁測試
OpenGL ES會檢查每個片元在幀緩沖中對應(yīng)的位置,若對應(yīng)位置在剪裁窗口中則將此片元送入下一階段,否則丟棄此片元。

8.深度測試和模板測試
深度測試指將輸入片元的深度值與緩沖區(qū)中存儲的對應(yīng)位置的深度值進(jìn)行比較,若輸入片元的深度值小于則將輸入片元送入下一個階段準(zhǔn)備覆蓋幀緩沖中的原片元或幀緩沖中的原片元緩沖,否則丟棄輸入片元。

模板測試的主要功能是為將繪制區(qū)域限定在一定范圍內(nèi),一般應(yīng)用在湖面倒影、鏡像等場合。

9.顏色緩沖混合
若程序開啟的Alpha混合,則根據(jù)混合因子將上一階段送來的片元幀緩沖對應(yīng)的位置的片元進(jìn)行Alpha混合,否則送入的片元將覆蓋幀緩沖中對應(yīng)位置的片元。

10.抖動
抖動是一種簡單的操作,允許只使用少量的顏色模擬出更寬的顏色顯示范圍,從而使顏色視覺更豐富。

11.幀緩沖
OpenGL ES中的物體繪制并不是直接繪制在屏幕上進(jìn)行的,而是預(yù)先在幀緩沖區(qū)中進(jìn)行繪制,每繪制完一幀再將繪制的結(jié)果交換到屏幕上。因此,在每次繪制更新一幀都需要清除緩沖區(qū)中的相關(guān)數(shù)據(jù)。

二、紋理映射


除了基本基本圖形的繪制,如果想要繪制更加真實、炫酷的3D物體,就需要用到紋理映射。它就是把一幅紋理應(yīng)用到相應(yīng)的幾何圖元,告知渲染系統(tǒng)如何進(jìn)行紋理映射。告知的方式就是為圖元中的每個頂點(diǎn)指定恰當(dāng)紋理坐標(biāo),然后通過紋理坐標(biāo)在紋理圖中可以確定選中的紋理區(qū)域,最后將選中的紋理區(qū)域中的內(nèi)容根據(jù)紋理坐標(biāo)映射到指定的圖元上。


紋理映射

三 、相機(jī)預(yù)覽背景繪制步驟


1.繪制背景準(zhǔn)備
在該過程,完成了頂點(diǎn)和紋理坐標(biāo)數(shù)據(jù)的提供,紋理id的生成獲取、綁定和設(shè)置,創(chuàng)建頂點(diǎn)坐標(biāo)和紋理坐標(biāo)頂點(diǎn)緩沖對象,加載并綁定背景頂點(diǎn)和片元著色器,獲取頂點(diǎn)和紋理坐標(biāo)位置屬性等。

2.背景紋理和Session攝像頭紋理綁定
在該過程中,將背景紋理id和Session攝像頭紋理id進(jìn)行綁定。

3.執(zhí)行背景繪制
該階段處理顯示旋轉(zhuǎn)紋理坐標(biāo)的變換,設(shè)置深度測試,告知OpenGL應(yīng)用的紋理id和使用的程序,將頂點(diǎn)和紋理坐標(biāo)傳送渲染管線,啟動頂點(diǎn)和紋理id數(shù)據(jù),執(zhí)行背景繪制和禁用頂點(diǎn)數(shù)據(jù)等。

四、案例源碼分析


1.繪制背景準(zhǔn)備
com\google\ar\core\examples\java\helloar\rendering\BackgroundRender.java

public class BackgroundRenderer {
    private static final String TAG = BackgroundRenderer.class.getSimpleName();
    private static final int COORDS_PER_VERTEX = 3;
    private static final int TEXCOORDS_PER_VERTEX = 2;
    private static final int FLOAT_SIZE = 4;
   
    //頂點(diǎn)坐標(biāo)數(shù)據(jù)
    public static final float[] QUAD_COORDS = new float[]{
            -1.0f, -1.0f, 0.0f,     //左下頂點(diǎn)
            -1.0f, +1.0f, 0.0f,    //左上頂點(diǎn)
            +1.0f, -1.0f, 0.0f,    //右下頂點(diǎn)
            +1.0f, +1.0f, 0.0f,   //右上頂點(diǎn)
    };

    //紋理坐標(biāo)數(shù)據(jù)
    public static final float[] QUAD_TEXCOORDS = new float[]{
            0.0f, 1.0f,
            0.0f, 0.0f,
            1.0f, 1.0f,
            1.0f, 0.0f,
    };

    //頂點(diǎn)坐標(biāo),紋理坐標(biāo)和頂點(diǎn)變換數(shù)據(jù)內(nèi)存
    private FloatBuffer mQuadVertices;
    private FloatBuffer mQuadTexCoord;
    private FloatBuffer mQuadTexCoordTransformed;
   
    //背景著色器程序
    private int mQuadProgram;
    //背景繪制頂點(diǎn)和紋理屬性
    private int mQuadPositionParam;
    private int mQuadTexCoordParam;

    //背景紋理渲染id
    private int mTextureId = -1;
    private int mTextureTarget = GLES11Ext.GL_TEXTURE_EXTERNAL_OES;

    public BackgroundRenderer() {
    }

    ... ... 
   
    /**
    * 分配和初始化背景渲染器的需要的OpenGL資源。必須在OpenGL線程中調(diào)用,通常在GLSurfaceView.Render
    * (GL10,EGLConfig)
    */
    public void createOnGlThread(Context context) {
        //生成背景紋理
        int textures[] = new int[1];
        GLES20.glGenTextures(1, textures, 0);
        //獲取加載圖像后OpenGL紋理id
        mTextureId = textures[0];

        //告訴OpenGL后面的紋理調(diào)用應(yīng)該應(yīng)用mTextureId這個紋理對象
        GLES20.glBindTexture(mTextureTarget, mTextureId);
        
        //當(dāng)紋理大小被擴(kuò)大或者縮小的時候,我們就會使用到紋理過濾
        //為mTextureTarget紋理設(shè)置屬性,紋理放大和縮小的濾波方式,橫向和縱向平鋪方式
        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.");
        }

        //OpenGL作為本地系統(tǒng)直接運(yùn)行在硬件上,沒有虛擬機(jī)。所以需要把Java虛擬機(jī)中的內(nèi)存復(fù)制到本地堆中
        //分配一塊本地內(nèi)存bbVertices,告訴緩沖區(qū)按照本地字節(jié)組織它的內(nèi)容
        ByteBuffer bbVertices = ByteBuffer.allocateDirect(QUAD_COORDS.length * FLOAT_SIZE);
        bbVertices.order(ByteOrder.nativeOrder());
        //獲取一個可以反映底層字節(jié)的FloatBuffer類實例,把頂點(diǎn)QUAD_COORDS數(shù)據(jù)放到本地內(nèi)存中
        mQuadVertices = bbVertices.asFloatBuffer();
        mQuadVertices.put(QUAD_COORDS);
        mQuadVertices.position(0);
        
        //同上
        ByteBuffer bbTexCoords = ByteBuffer.allocateDirect(numVertices * TEXCOORDS_PER_VERTEX * FLOAT_SIZE);
        bbTexCoords.order(ByteOrder.nativeOrder());
        mQuadTexCoord = bbTexCoords.asFloatBuffer();
        mQuadTexCoord.put(QUAD_TEXCOORDS);
        mQuadTexCoord.position(0);
        
        //同上
        ByteBuffer bbTexCoordsTransformed = ByteBuffer.allocateDirect(numVertices * TEXCOORDS_PER_VERTEX * FLOAT_SIZE);
        bbTexCoordsTransformed.order(ByteOrder.nativeOrder());
        mQuadTexCoordTransformed = bbTexCoordsTransformed.asFloatBuffer();
        
        //加載背景頂點(diǎn)和片元著色器
        int vertexShader = ShaderUtil.loadGLShader(TAG, context,GLES20.GL_VERTEX_SHADER, R.raw.screenquad_vertex);
        int fragmentShader = ShaderUtil.loadGLShader(TAG, context,GLES20.GL_FRAGMENT_SHADER, R.raw.screenquad_fragment_oes);
        
        //把頂點(diǎn)和Fragment著色器綁定放在一個單個的程序中一起工作:
        //新建程序?qū)ο髆QuadProgram,并把vertexShader和fragmentShader附加到程序中
        mQuadProgram = GLES20.glCreateProgram();
        GLES20.glAttachShader(mQuadProgram, vertexShader);
        GLES20.glAttachShader(mQuadProgram, fragmentShader);
        //把這些著色器聯(lián)合起來,并且告訴OpenGL在繪制背景的時候使用這個定義的程序
        GLES20.glLinkProgram(mQuadProgram);
        GLES20.glUseProgram(mQuadProgram);

        //獲取頂點(diǎn)位置紋理坐標(biāo)屬性
        ShaderUtil.checkGLError(TAG, "Program creation");
        mQuadPositionParam = GLES20.glGetAttribLocation(mQuadProgram, "a_Position");
        mQuadTexCoordParam = GLES20.glGetAttribLocation(mQuadProgram, "a_TexCoord");
        ShaderUtil.checkGLError(TAG, "Program parameters");
    }
 
    ... ... 
}

com\google\ar\core\examples\java\helloar\rendering\ShaderUtil.java

public class ShaderUtil {
    /**
    * 將原始文本文件(保存為資源)轉(zhuǎn)換為OpenGL ES著色程序。
    */
    public static int loadGLShader(String tag, Context context, int type, int resId) {
        //通過著色器代碼原始資源文件id,讀取代碼為字符串
        String code = readRawTextFile(context, resId);
       
        //創(chuàng)建一個類型的著色器對象
        int shader = GLES20.glCreateShader(type);
        //把著色器源代碼上傳上著色器對象里,它與shader引用的著色器對象關(guān)聯(lián)起來
        GLES20.glShaderSource(shader, code);
        //編譯著色器
        GLES20.glCompileShader(shader);

        //取出編譯狀態(tài),檢查OpenGL是否能成功地編譯這個著色器,如果編譯失敗則刪除著色器
        final int[] compileStatus = new int[1];
        GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compileStatus, 0);
        if (compileStatus[0] == 0) {
            Log.e(tag, "Error compiling shader: " + GLES20.glGetShaderInfoLog(shader));
            GLES20.glDeleteShader(shader);
            shader = 0;
        }

        //編譯成功,返回著色器引用id
        if (shader == 0) {
            throw new RuntimeException("Error creating shader.");
        }
        return shader;
    }

    /**
    * 將原始文本文件轉(zhuǎn)換為字符串:從原始資源中通過流讀取字符串
    */
    private static String readRawTextFile(Context context, int resId) {
        InputStream inputStream = context.getResources().openRawResource(resId);
        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
            StringBuilder sb = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                sb.append(line).append("\n");
            }
            reader.close();
            return sb.toString();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}

java_arcore_hello_ar\app\src\main\res\raw\screenquad_vertex.shader

attribute vec4 a_Position;    //3D物理每個頂點(diǎn)各自不同的頂點(diǎn)位置信息
attribute vec2 a_TexCoord; //3D物理每個頂點(diǎn)各自不同的紋理坐標(biāo)信息
varying vec2 v_TexCoord;   //從頂點(diǎn)著色器計算產(chǎn)生并傳遞到片元著色器的紋理坐標(biāo)信息

void main() {
   gl_Position = a_Position;    //頂點(diǎn)著色器從渲染管線中獲得原始頂點(diǎn)信息a_Position,寫入gl_Position內(nèi)建變量傳遞到渲染管線的后階段繼續(xù)處理
   v_TexCoord = a_TexCoord;     //頂點(diǎn)著色器從渲染管線中獲得紋理坐標(biāo)信息a_TexCoord,傳遞給片元著色器繼續(xù)處理
}

java_arcore_hello_ar\app\src\main\res\raw\screenquad_fragment_oes.shader

precision mediump float;  //給出默認(rèn)的浮點(diǎn)精度
varying vec2 v_TexCoord;  //從頂點(diǎn)著色器傳來的紋理坐標(biāo)
uniform samplerExternalOES sTexture;  //紋理內(nèi)容數(shù)據(jù)


void main() {
    gl_FragColor = texture2D(sTexture, v_TexCoord);  //根據(jù)紋理坐標(biāo)采樣出顏色值
}

2.背景紋理和Session攝像頭紋理綁定
com\google\ar\core\examples\java\helloar\HelloArActivity.java

public class HelloArActivity extends AppCompatActivity implements GLSurfaceView.Renderer {
    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        //OpenGL環(huán)境被設(shè)置的時候,調(diào)用一次
        //設(shè)置背景幀的顏色
        GLES20.glClearColor(0.1f, 0.1f, 0.1f, 1.0f);

        //準(zhǔn)備背景渲染對象
        mBackgroundRenderer.createOnGlThread(this);
        //創(chuàng)建紋理,并且將它傳遞給ARCore Session,在update()的時候填充,允許GPU訪問相機(jī)圖像
        mSession.setCameraTextureName(mBackgroundRenderer.getTextureId());
    }
}

com\google\ar\core\examples\java\helloar\rendering\BackgroundRender.java

public class BackgroundRenderer {
    ... ...

    //背景紋理id
    private int mTextureId = -1;
    private int mTextureTarget = GLES11Ext.GL_TEXTURE_EXTERNAL_OES;
    ... ... 

    //獲取背景紋理id
    public int getTextureId() {
        return mTextureId;
    }
    ... ... 
}

3.執(zhí)行背景繪制
com\google\ar\core\examples\java\helloar\rendering\BackgroundRender.java

public class BackgroundRenderer {
    private static final String TAG = BackgroundRenderer.class.getSimpleName();
    private static final int COORDS_PER_VERTEX = 3;
    private static final int TEXCOORDS_PER_VERTEX = 2;
    private static final int FLOAT_SIZE = 4;

    private FloatBuffer mQuadVertices;
    private FloatBuffer mQuadTexCoord;
    private FloatBuffer mQuadTexCoordTransformed;

    private int mQuadProgram;
    private int mQuadPositionParam;
    private int mQuadTexCoordParam;

    private int mTextureId = -1;
    private int mTextureTarget = GLES11Ext.GL_TEXTURE_EXTERNAL_OES;
 
     /**
     * 繪制AR背景圖片。這個圖片將會使用Frame.getViewMatrix(float[], int)和Session.getProjectionMatrix(
     * float[], int, float, float)提供的矩陣?yán)L制,將準(zhǔn)確的跟蹤靜態(tài)物理對象,它必須在繪制虛擬對象之前調(diào)用
     * @param frame 通過Session.update()返回的最新Frame
     */
    public void draw(Frame frame) {
        //如果顯示旋轉(zhuǎn)(也包括尺寸的改變),我們需要重新查詢屏幕rect的uv坐標(biāo),因為它們可能發(fā)生了變化
        if (frame.isDisplayRotationChanged()) {
            frame.transformDisplayUvCoords(mQuadTexCoord, mQuadTexCoordTransformed);
        }

        //不需要測試或者寫入深度,屏幕頂點(diǎn)具有任意深度,預(yù)計將首先繪制
        GLES20.glDisable(GLES20.GL_DEPTH_TEST);
        GLES20.glDepthMask(false);

        //告訴OpenGL紋理調(diào)用應(yīng)該應(yīng)用mTextureId這個紋理對象
        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureId);
        //告訴OpenGL在繪制背景的時候使用這個定義的程序
        GLES20.glUseProgram(mQuadProgram);
        
        //將頂點(diǎn)位置數(shù)據(jù)傳送進(jìn)渲染管線
        GLES20.glVertexAttribPointer(mQuadPositionParam, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, 0, mQuadVertices);
        //將頂點(diǎn)紋理坐標(biāo)數(shù)據(jù)傳送進(jìn)渲染管線
        GLES20.glVertexAttribPointer(mQuadTexCoordParam, TEXCOORDS_PER_VERTEX,GLES20.GL_FLOAT, false, 0, mQuadTexCoordTransformed);
        
        //啟動頂點(diǎn)位置和著色數(shù)據(jù)
        GLES20.glEnableVertexAttribArray(mQuadPositionParam);
        GLES20.glEnableVertexAttribArray(mQuadTexCoordParam);
        
        //執(zhí)行背景繪制
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);

        //禁用頂點(diǎn)數(shù)組
        GLES20.glDisableVertexAttribArray(mQuadPositionParam);
        GLES20.glDisableVertexAttribArray(mQuadTexCoordParam);

        //恢復(fù)深度狀態(tài)以作進(jìn)一步繪圖。
        GLES20.glDepthMask(true);
        GLES20.glEnable(GLES20.GL_DEPTH_TEST);
        ShaderUtil.checkGLError(TAG, "Draw");
    }
}

1.新技術(shù),新未來!盡在1024工場。時刻關(guān)注最前沿技術(shù)資訊,發(fā)布最棒技術(shù)博文!(甭客氣!盡情的掃描或者長按?。?/em>

1024工場服務(wù)號

2.完整和持續(xù)更新的《使用Android打開AR的開發(fā)大門—ARCore》文檔,歡迎大家閱讀!
https://www.kancloud.cn/p3243986735/arcore_develop/457951

這里寫圖片描述

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

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

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