1. 程式人生 > >Android端使用OpenGL ES載入OBJ檔案資料

Android端使用OpenGL ES載入OBJ檔案資料

一、obj模型檔案概覽

在介紹如何用程式載入obj模型檔案之前,首先需要了解一下它的格式。
obj檔案是最簡單的一種3D模型檔案,可由3dx MAX或Maya等建模軟體匯出,廣泛應用於3D圖形應用(如遊戲)程式和3D列印等等,其本質上就是文字檔案,裡面儲存的是模型的頂點座標,頂點法向量和紋理座標資訊。

下面看一個典型的obj檔案

# 3ds Max Wavefront OBJ Exporter v0.97b - (c)2007 guruware
# 建立的檔案:01.05.2018 15:16:27

#
# object 棒球帽
#

v  22.7219 49.3250 -5.6920
v  22.7255
49.3244 -5.6873 v 24.6979 49.3887 -10.4577 v 24.4561 49.4295 -10.6732 v 22.7288 49.3238 -5.6824 v 24.9195 49.3465 -10.2052 v 26.1314 49.3264 -14.0106 v 25.7093 49.3979 -14.3621 v 26.5186 49.2526 -13.5884 v 22.7318 49.3231 -5.6773 ...... vn 0.5713 0.0798 -0.8169 vn 0.5751 0.0783 -0.8144 vn 0.1130 0.2192 -0.9691
vn 0.1180 0.2182 -0.9687 vn 0.5800 0.0763 -0.8110 vn 0.5844 0.0746 -0.8080 vn 0.1342 0.2105 -0.9683 ...... vt -0.1830 0.1242 0.0073 vt -0.1789 0.1238 0.0074 vt -0.1828 0.1220 0.0073 vt -0.1779 0.1204 0.0075 vt -0.1863 0.1216 0.0072 vt -0.1873 0.1176 0.0072 vt -0.1843 0.0892 0.0072 vt -0.1872 0.0892 0.0071 vt -0.1875 0.0867 0.0071 ...... f 238/240/239 243/245/244 244/247/245 f 244
/247/245 239/241/240 238/240/239 f 239/241/240 244/247/245 245/248/246 f 245/248/246 242/244/243 239/241/240 f 246/249/247 247/250/248 248/251/249 f 248/251/249 249/252/250 246/249/247 f 248/251/249 250/253/251 251/254/252 f 251/254/252 249/252/250 248/251/249

這裡只截取了一部分,從上述obj檔案片段中可以看出,其內容是以行為基本單位進行組織的,每種不同字首開頭的行有不同含義:

  • “#”號開頭的行為註釋,描述模型的一些基本資訊,在程式載入的過程中可以忽略。
  • “v”開頭的行用於存放頂點座標,其後面的3個數值分別表示一個頂點的X、Y、Z座標。
  • “vn”開頭的行用於存放頂點法向量,其後面的3個數值分別表示一個頂點的法向量在X軸、Y軸、Z軸上的分量。
  • “vt”開頭的行用於存放頂點紋理座標,其後面的3個數值分別表示紋理座標的S、T、W分量(S、T為紋理取樣座標,W指的是深度紋理座標,主要用於3D紋理的取樣,OpenGL ES 2.0中對3D紋理還沒有普遍支援,故這裡不使用)
  • “f”開頭的行表示一個面,如果是三角形,(由於OpenGL ES僅支援三角形,故我選擇的obj模型都是基於三角形面的)則後面有3組用空格分隔的資料,代表三角形的3個頂點,每組資料包含3個數值,用“/”分隔,依次表示頂點座標資料索引,頂點紋理座標資料索引,頂點法向量資料索引。例如有這樣一行“ f 238/240/239 243/245/244 244/247/245 ”,則表示這個三角形面中3個頂點的座標分別來自第238、243、244號“v”開頭的行,3個頂點的紋理座標分別了來自第240、245、247號“vt”開頭的行,3個頂點的法向量分別來自第239、244、245號“vn”開頭的行。

    有一點需要注意,就是就是我們載入顯示obj檔案時,頂點和麵的資料是必需的,而法向量和紋理資料是可選的。

二、載入並顯示

根據計算機圖形學中的知識,我們載入並顯示模型的步驟可分為以下幾步:

  1. 將obj檔案中的文字資訊通過檔案IO流讀進記憶體。
  2. 一行行讀取,分別用3個數組儲存頂點,紋理和法向量資料。
  3. 建立OpenGL場景(這一點Android的GLSurfaceView已經幫我們做好了)
  4. 建立著色器程式,將頂點、紋理等資料傳進渲染管線
  5. 啟用著色器程式,並設定攝像頭位置,啟用紋理,新增光照。

步驟一、建立物體類
LoadedObjectVertexNormalTexture.java

public LoadedObjectVertexNormalTexture(MySurfaceView mv,float[] vertices,float[] normals,float texCoors[])
    {
        //初始化頂點座標與著色資料
        initVertexData(vertices,normals,texCoors);
        //初始化shader
        initShader(mv);
    }

在建構函式中,根據傳入的GLSurfaceView以及頂點,紋理,法向量陣列,初始化著色器資料和shader。

//初始化頂點座標與著色資料的方法
    public void initVertexData(float[] vertices,float[] normals,float texCoors[])
    {
        //頂點座標資料的初始化================begin============================
        vCount=vertices.length/3;

        //建立頂點座標資料緩衝
        //vertices.length*4是因為一個整數四個位元組
        ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length*4);
        vbb.order(ByteOrder.nativeOrder());//設定位元組順序
        mVertexBuffer = vbb.asFloatBuffer();//轉換為Float型緩衝
        mVertexBuffer.put(vertices);//向緩衝區中放入頂點座標資料
        mVertexBuffer.position(0);//設定緩衝區起始位置
        //特別提示:由於不同平臺位元組順序不同資料單元不是位元組的一定要經過ByteBuffer
        //轉換,關鍵是要通過ByteOrder設定nativeOrder(),否則有可能會出問題
        //頂點座標資料的初始化================end============================

        //頂點法向量資料的初始化================begin============================
        ByteBuffer cbb = ByteBuffer.allocateDirect(normals.length*4);
        cbb.order(ByteOrder.nativeOrder());//設定位元組順序
        mNormalBuffer = cbb.asFloatBuffer();//轉換為Float型緩衝
        mNormalBuffer.put(normals);//向緩衝區中放入頂點法向量資料
        mNormalBuffer.position(0);//設定緩衝區起始位置
        //特別提示:由於不同平臺位元組順序不同資料單元不是位元組的一定要經過ByteBuffer
        //轉換,關鍵是要通過ByteOrder設定nativeOrder(),否則有可能會出問題
        //頂點著色資料的初始化================end============================

        //頂點紋理座標資料的初始化================begin============================
        ByteBuffer tbb = ByteBuffer.allocateDirect(texCoors.length*4);
        tbb.order(ByteOrder.nativeOrder());//設定位元組順序
        mTexCoorBuffer = tbb.asFloatBuffer();//轉換為Float型緩衝
        mTexCoorBuffer.put(texCoors);//向緩衝區中放入頂點紋理座標資料
        mTexCoorBuffer.position(0);//設定緩衝區起始位置
        //特別提示:由於不同平臺位元組順序不同資料單元不是位元組的一定要經過ByteBuffer
        //轉換,關鍵是要通過ByteOrder設定nativeOrder(),否則有可能會出問題
        //頂點紋理座標資料的初始化================end============================
    }

    //初始化shader
    public void initShader(MySurfaceView mv)
    {
        //載入頂點著色器的指令碼內容
        mVertexShader=ShaderUtil.loadFromAssetsFile("vertex.sh", mv.getResources());
        //載入片元著色器的指令碼內容
        mFragmentShader=ShaderUtil.loadFromAssetsFile("frag.sh", mv.getResources());
        //基於頂點著色器與片元著色器建立程式
        mProgram = ShaderUtil.createProgram(mVertexShader, mFragmentShader);
        //獲取程式中頂點位置屬性引用
        maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");
        //獲取程式中頂點顏色屬性引用
        maNormalHandle= GLES20.glGetAttribLocation(mProgram, "aNormal");
        //獲取程式中總變換矩陣引用
        muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
        //獲取位置、旋轉變換矩陣引用
        muMMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMMatrix");
        //獲取程式中光源位置引用
        maLightLocationHandle=GLES20.glGetUniformLocation(mProgram, "uLightLocation");
        //獲取程式中頂點紋理座標屬性引用
        maTexCoorHandle= GLES20.glGetAttribLocation(mProgram, "aTexCoor");
        //獲取程式中攝像機位置引用
        maCameraHandle=GLES20.glGetUniformLocation(mProgram, "uCamera");
    }

步驟二、編寫從obj檔案讀取資訊的工具類
LoadUtil.java
其中的LoadedObjectVertexNormalTexture方法從Resource資原始檔中載入obj格式的資料,存入陣列,並生成LoadedObjectVertexNormalTexture物件

//從obj檔案中載入攜帶頂點資訊的物體,並自動計算每個頂點的平均法向量
    public static LoadedObjectVertexNormalTexture loadFromFile
    (String fname, Resources r,MySurfaceView mv)
    {
        //載入後物體的引用
        LoadedObjectVertexNormalTexture lo=null;
        //原始頂點座標列表--直接從obj檔案中載入
        ArrayList<Float> alv=new ArrayList<Float>();
        //頂點組裝面索引列表--根據面的資訊從檔案中載入
        ArrayList<Integer> alFaceIndex=new ArrayList<Integer>();
        //結果頂點座標列表--按面組織好
        ArrayList<Float> alvResult=new ArrayList<Float>();
        //平均前各個索引對應的點的法向量集合Map
        //此HashMap的key為點的索引, value為點所在的各個面的法向量的集合
        HashMap<Integer,HashSet<Normal>> hmn=new HashMap<Integer,HashSet<Normal>>();
        //原始紋理座標列表
        ArrayList<Float> alt=new ArrayList<Float>();
        //紋理座標結果列表
        ArrayList<Float> altResult=new ArrayList<Float>();

        try
        {
            InputStream in=r.getAssets().open(fname);
            InputStreamReader isr=new InputStreamReader(in);
            BufferedReader br=new BufferedReader(isr);
            String temps=null;

            //掃面檔案,根據行型別的不同執行不同的處理邏輯
            while((temps=br.readLine())!=null)
            {
                //用空格分割行中的各個組成部分
                String[] tempsa=temps.split("[ ]+");
                if(tempsa[0].trim().equals("v"))
                {//此行為頂點座標
                    //若為頂點座標行則提取出此頂點的XYZ座標新增到原始頂點座標列表中
                    alv.add(Float.parseFloat(tempsa[1]));
                    alv.add(Float.parseFloat(tempsa[2]));
                    alv.add(Float.parseFloat(tempsa[3]));
                }
                else if(tempsa[0].trim().equals("vt"))
                {//此行為紋理座標行
                    //若為紋理座標行則提取ST座標並新增進原始紋理座標列表中
                    alt.add(Float.parseFloat(tempsa[1])/2.0f);
                    alt.add(Float.parseFloat(tempsa[2])/2.0f);
                }
                else if(tempsa[0].trim().equals("f"))
                {//此行為三角形面
                    /*
                     *若為三角形面行則根據 組成面的頂點的索引從原始頂點座標列表中
                     *提取相應的頂點座標值新增到結果頂點座標列表中,同時根據三個
                     *頂點的座標計算出此面的法向量並新增到平均前各個索引對應的點
                     *的法向量集合組成的Map中
                    */

                    int[] index=new int[3];//三個頂點索引值的陣列

                    //計算第0個頂點的索引,並獲取此頂點的XYZ三個座標
                    index[0]=Integer.parseInt(tempsa[1].split("/")[0])-1;
                    float x0=alv.get(3*index[0]);
                    float y0=alv.get(3*index[0]+1);
                    float z0=alv.get(3*index[0]+2);
                    alvResult.add(x0);
                    alvResult.add(y0);
                    alvResult.add(z0);

                    //計算第1個頂點的索引,並獲取此頂點的XYZ三個座標
                    index[1]=Integer.parseInt(tempsa[2].split("/")[0])-1;
                    float x1=alv.get(3*index[1]);
                    float y1=alv.get(3*index[1]+1);
                    float z1=alv.get(3*index[1]+2);
                    alvResult.add(x1);
                    alvResult.add(y1);
                    alvResult.add(z1);

                    //計算第2個頂點的索引,並獲取此頂點的XYZ三個座標
                    index[2]=Integer.parseInt(tempsa[3].split("/")[0])-1;
                    float x2=alv.get(3*index[2]);
                    float y2=alv.get(3*index[2]+1);
                    float z2=alv.get(3*index[2]+2);
                    alvResult.add(x2);
                    alvResult.add(y2);
                    alvResult.add(z2);

                    //記錄此面的頂點索引
                    alFaceIndex.add(index[0]);
                    alFaceIndex.add(index[1]);
                    alFaceIndex.add(index[2]);

                    //通過三角形面兩個邊向量0-1,0-2求叉積得到此面的法向量
                    //求0號點到1號點的向量
                    float vxa=x1-x0;
                    float vya=y1-y0;
                    float vza=z1-z0;
                    //求0號點到2號點的向量
                    float vxb=x2-x0;
                    float vyb=y2-y0;
                    float vzb=z2-z0;
                    //通過求兩個向量的叉積計算法向量
                    float[] vNormal=vectorNormal(getCrossProduct
                            (
                                    vxa,vya,vza,vxb,vyb,vzb
                            ));
                    for(int tempInxex:index)
                    {//記錄每個索引點的法向量到平均前各個索引對應的點的法向量集合組成的Map中
                        //獲取當前索引對應點的法向量集合
                        HashSet<Normal> hsn=hmn.get(tempInxex);
                        if(hsn==null)
                        {//若集合不存在則建立
                            hsn=new HashSet<Normal>();
                        }
                        //將此點的法向量新增到集合中
                        //由於Normal類重寫了equals方法,因此同樣的法向量不會重複出現在此點
                        //對應的法向量集合中
                        hsn.add(new Normal(vNormal[0],vNormal[1],vNormal[2]));
                        //將集合放進HsahMap中
                        hmn.put(tempInxex, hsn);
                    }

                    //將紋理座標組織到結果紋理座標列表中
                    //第0個頂點的紋理座標
                    int indexTex=Integer.parseInt(tempsa[1].split("/")[1])-1;
                    altResult.add(alt.get(indexTex*2));
                    altResult.add(alt.get(indexTex*2+1));
                    //第1個頂點的紋理座標
                    indexTex=Integer.parseInt(tempsa[2].split("/")[1])-1;
                    altResult.add(alt.get(indexTex*2));
                    altResult.add(alt.get(indexTex*2+1));
                    //第2個頂點的紋理座標
                    indexTex=Integer.parseInt(tempsa[3].split("/")[1])-1;
                    altResult.add(alt.get(indexTex*2));
                    altResult.add(alt.get(indexTex*2+1));
                }
            }

            //生成頂點陣列
            int size=alvResult.size();
            float[] vXYZ=new float[size];
            for(int i=0;i<size;i++)
            {
                vXYZ[i]=alvResult.get(i);
            }

            //生成法向量陣列
            float[] nXYZ=new float[alFaceIndex.size()*3];
            int c=0;
            for(Integer i:alFaceIndex)
            {
                //根據當前點的索引從Map中取出一個法向量的集合
                HashSet<Normal> hsn=hmn.get(i);
                //求出平均法向量
                float[] tn=Normal.getAverage(hsn);
                //將計算出的平均法向量存放到法向量陣列中
                nXYZ[c++]=tn[0];
                nXYZ[c++]=tn[1];
                nXYZ[c++]=tn[2];
            }

            //生成紋理陣列
            size=altResult.size();
            float[] tST=new float[size];
            for(int i=0;i<size;i++)
            {
                tST[i]=altResult.get(i);
            }

            //建立3D物體物件
            lo=new LoadedObjectVertexNormalTexture(mv,vXYZ,nXYZ,tST);
        }
        catch(Exception e)
        {
            Log.d("load error", "load error");
            e.printStackTrace();
        }
        return lo;
    }

方法比較長,不過總的思路也很清晰,就先開啟檔案輸入流,迴圈不斷從檔案中讀取行,根據行的型別不同執行不同的處理邏輯,比如“v”開頭的行代表頂點座標資料,直接用一個數組儲存,根據“f”開頭的面數據行查詢其3個頂點的座標,紋理索引,最終建立載入的物體物件。這裡為了求面的平均法向量,還用到了向量叉乘和求平均的方法。

//求兩個向量的叉乘
    public static float[] getCrossProduct(float x1,float y1,float z1,float x2,float y2,float z2)
    {
        //求出兩個向量叉積向量在XYZ軸的分量ABC
        float A=y1*z2-y2*z1;
        float B=z1*x2-z2*x1;
        float C=x1*y2-x2*y1;

        return new float[]{A,B,C};
    }

表示法向量的類
Normal.java

/**
 * 表示法向量的類
 */
public class Normal {
    //判斷兩個法向量是否相同的閾值
    public static final float DIFF = 0.0000001f;
    //法向量在X、Y、Z軸的分量
    float nx;
    float ny;
    float nz;
    public Normal(float nx , float ny , float nz ){
        this.nx = nx;
        this.ny = ny;
        this.nz = nz;
    }

    @Override
    public boolean equals(Object obj) {
        //若兩個法向量X、Y、Z 3個分量的差都小於指定的閾值則認為這兩個法向量相等
        if( obj instanceof Normal ){
            Normal tn = (Normal) obj;
            if( Math.abs(nx - tn.nx) < DIFF && Math.abs(ny - tn.ny) < DIFF && Math.abs(nz - tn.nz) < DIFF ){
                return true;
            }else {
                return false;
            }
        }else {
            return false;
        }
    }

    @Override
    public int hashCode() {
        return 1;
    }

    //求法向量平均值的工具方法
    public static float[] getAverage(Set<Normal> sn){
        //存放法向量X、Y、Z分量和的陣列
        float[] result = new float[3];
        for( Normal n : sn ){
            result[0] += n.nx;
            result[1] += n.ny;
            result[2] += n.nz;
        }
        return LoadUtil.vectorNormal(result);

    }
}

步驟三、接收紋理資料並啟用光源和紋理
LoadedObjectVertexNormalTexture.java

 //繪製物體的方法
    public void drawSelf(int texId)
    {
        //制定使用某套著色器程式
        GLES20.glUseProgram(mProgram);
        //將最終變換矩陣傳入著色器程式
        GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, MatrixState.getFinalMatrix(), 0);
        //將位置、旋轉變換矩陣傳入著色器程式
        GLES20.glUniformMatrix4fv(muMMatrixHandle, 1, false, MatrixState.getMMatrix(), 0);
        //將光源位置傳入著色器程式
        GLES20.glUniform3fv(maLightLocationHandle, 1, MatrixState.lightPositionFB);
        //將攝像機位置傳入著色器程式
        GLES20.glUniform3fv(maCameraHandle, 1, MatrixState.cameraFB);
        // 將頂點位置資料傳入渲染管線
        GLES20.glVertexAttribPointer
                (
                        maPositionHandle,
                        3,
                        GLES20.GL_FLOAT,
                        false,
                        3*4,
                        mVertexBuffer
                );
        //將頂點法向量資料傳入渲染管線
        GLES20.glVertexAttribPointer
                (
                        maNormalHandle,
                        3,
                        GLES20.GL_FLOAT,
                        false,
                        3*4,
                        mNormalBuffer
                );
        //為畫筆指定頂點紋理座標資料
        GLES20.glVertexAttribPointer
                (
                        maTexCoorHandle,
                        2,
                        GLES20.GL_FLOAT,
                        false,
                        2*4,
                        mTexCoorBuffer
                );
        //啟用頂點位置、法向量、紋理座標資料
        GLES20.glEnableVertexAttribArray(maPositionHandle);
        GLES20.glEnableVertexAttribArray(maNormalHandle);
        GLES20.glEnableVertexAttribArray(maTexCoorHandle);
        //繫結紋理
        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texId);
        //繪製載入的物體
        GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vCount);
    }

下面是我們用來顯示3D內容的GLSurfaceView

class MySurfaceView extends GLSurfaceView
{
    private final float TOUCH_SCALE_FACTOR = 180.0f/320;//角度縮放比例
    private SceneRenderer mRenderer;//場景渲染器

    private float mPreviousY;//上次的觸控位置Y座標
    private float mPreviousX;//上次的觸控位置X座標

    int textureId;//系統分配的紋理id

    public MySurfaceView(Context context) {
        super(context);
        this.setEGLContextClientVersion(2); //設定使用OPENGL ES2.0
        mRenderer = new SceneRenderer();    //建立場景渲染器
        setRenderer(mRenderer);             //設定渲染器
        setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);//設定渲染模式為主動渲染
    }

    //觸控事件回撥方法
    @Override
    public boolean onTouchEvent(MotionEvent e)
    {
        float y = e.getY();
        float x = e.getX();
        switch (e.getAction()) {
            case MotionEvent.ACTION_MOVE:
                float dy = y - mPreviousY;//計算觸控筆Y位移
                float dx = x - mPreviousX;//計算觸控筆X位移
                mRenderer.yAngle += dx * TOUCH_SCALE_FACTOR;//設定沿x軸旋轉角度
                mRenderer.xAngle+= dy * TOUCH_SCALE_FACTOR;//設定沿z軸旋轉角度
                requestRender();//重繪畫面
        }
        mPreviousY = y;//記錄觸控筆位置
        mPreviousX = x;//記錄觸控筆位置
        return true;
    }

    private class SceneRenderer implements GLSurfaceView.Renderer
    {
        float yAngle;//繞Y軸旋轉的角度
        float xAngle; //繞Z軸旋轉的角度
        //從指定的obj檔案中載入物件
        LoadedObjectVertexNormalTexture lovo;

        public void onDrawFrame(GL10 gl)
        {
            //清除深度緩衝與顏色緩衝
            GLES20.glClear( GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);

            //座標系推遠
            MatrixState.pushMatrix();
            MatrixState.translate(0, -2f, -25f);   //ch.obj
            //繞Y軸、Z軸旋轉
            MatrixState.rotate(yAngle, 0, 1, 0);
            MatrixState.rotate(xAngle, 1, 0, 0);

            //若載入的物體部位空則繪製物體
            if(lovo!=null)
            {
                lovo.drawSelf(textureId);
            }
            MatrixState.popMatrix();
        }

        public void onSurfaceChanged(GL10 gl, int width, int height) {
            //設定視窗大小及位置
            GLES20.glViewport(0, 0, width, height);
            //計算GLSurfaceView的寬高比
            float ratio = (float) width / height;
            //呼叫此方法計算產生透視投影矩陣
            MatrixState.setProjectFrustum(-ratio, ratio, -1, 1, 2, 500);
            //呼叫此方法產生攝像機9引數位置矩陣
            MatrixState.setCamera(0,0,50,0f,0f,-20f,0f,1.0f,0.0f);
        }

        public void onSurfaceCreated(GL10 gl, EGLConfig config)
        {
            //設定螢幕背景色RGBA
            GLES20.glClearColor(0.0f,0.0f,0.0f,1.0f);
            //開啟深度檢測
            GLES20.glEnable(GLES20.GL_DEPTH_TEST);
            //開啟背面剪裁
            //GLES20.glEnable(GLES20.GL_CULL_FACE);
            //初始化變換矩陣
            MatrixState.setInitStack();
            //初始化光源位置
            MatrixState.setLightLocation(40, 40, 40);
            //載入要繪製的物體
            lovo=LoadUtil.loadFromFile("hat.obj", MySurfaceView.this.getResources(),MySurfaceView.this);
            //載入紋理
            textureId=initTexture(R.drawable.hat_t);
        }
    }
    public int initTexture(int drawableId)//textureId
    {
        //生成紋理ID
        int[] textures = new int[1];
        GLES20.glGenTextures
                (
                        1,          //產生的紋理id的數量
                        textures,   //紋理id的陣列
                        0           //偏移量
                );
        int textureId=textures[0];
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER,GLES20.GL_NEAREST);
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,GLES20.GL_TEXTURE_MAG_FILTER,GLES20.GL_LINEAR);
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S,GLES20.GL_REPEAT);
        GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,GLES20.GL_REPEAT);

        //通過輸入流載入圖片===============begin===================
        InputStream is = this.getResources().openRawResource(drawableId);
        Bitmap bitmapTmp;
        try
        {
            bitmapTmp = BitmapFactory.decodeStream(is);
        }
        finally
        {
            try
            {
                is.close();
            }
            catch(IOException e)
            {
                e.printStackTrace();
            }
        }
        //通過輸入流載入圖片===============end=====================
        GLUtils.texImage2D
                (
                        GLES20.GL_TEXTURE_2D, //紋理型別
                        0,
                        GLUtils.getInternalFormat(bitmapTmp),
                        bitmapTmp, //紋理影象
                        GLUtils.getType(bitmapTmp),
                        0 //紋理邊框尺寸
                );
        bitmapTmp.recycle();          //紋理載入成功後釋放圖片
        return textureId;
    }

這裡面用到了一個矩陣變換和儲存矩陣狀態的類MatrixState
MatrixState.java

//儲存系統矩陣狀態的類
public class MatrixState
{
    private static float[] mProjMatrix = new float[16];//4x4矩陣 投影用
    private static float[] mVMatrix = new float[16];//攝像機位置朝向9引數矩陣
    private static float[] currMatrix;//當前變換矩陣
    public static float[] lightLocation=new float[]{0,0,0};//定位光光源位置
    public static FloatBuffer cameraFB;
    public static FloatBuffer lightPositionFB;

    public static Stack<float[]> mStack=new Stack<float[]>();//保護變換矩陣的棧

    public static void setInitStack()//獲取不變換初始矩陣
    {
        currMatrix=new float[16];
        Matrix.setRotateM(currMatrix, 0, 0, 1, 0, 0);
    }

    public static void pushMatrix()//保護變換矩陣
    {
        mStack.push(currMatrix.clone());
    }

    public static void popMatrix()//恢復變換矩陣
    {
        currMatrix=mStack.pop();
    }

    public static void translate(float x,float y,float z)//設定沿xyz軸移動
    {
        Matrix.translateM(currMatrix, 0, x, y, z);
    }

    public static void rotate(float angle,float x,float y,float z)//設定繞xyz軸移動
    {
        Matrix.rotateM(currMatrix,0,angle,x,y,z);
    }


    //設定攝像機
    public static void setCamera
    (
            float cx,   //攝像機位置x
            float cy,   //攝像機位置y
            float cz,   //攝像機位置z
            float tx,   //攝像機目標點x
            float ty,   //攝像機目標點y
            float tz,   //攝像機目標點z
            float upx,  //攝像機UP向量X分量
            float upy,  //攝像機UP向量Y分量
            float upz   //攝像機UP向量Z分量
    )
    {
        Matrix.setLookAtM
                (
                        mVMatrix,
                        0,
                        cx,
                        cy,
                        cz,
                        tx,
                        ty,
                        tz,
                        upx,
                        upy,
                        upz
                );

        float[] cameraLocation=new float[3];//攝像機位置
        cameraLocation[0]=cx;
        cameraLocation[1]=cy;
        cameraLocation[2]=cz;

        ByteBuffer llbb = ByteBuffer.allocateDirect(3*4);
        llbb.order(ByteOrder.nativeOrder());//設定位元組順序
        cameraFB=llbb.asFloatBuffer();
        cameraFB.put(cameraLocation);
        cameraFB.position(0);
    }

    //設定透視投影引數
    public static void setProjectFrustum
    (
            float left,     //near面的left
            float right,    //near面的right
            float bottom,   //near面的bottom
            float top,      //near面的top
            float near,     //near面距離
            float far       //far面距離
    )
    {
        Matrix.frustumM(mProjMatrix, 0, left, right, bottom, top, near, far);
    }

    //設定正交投影引數
    public static void setProjectOrtho
    (
            float left,     //near面的left
            float right,    //near面的right
            float bottom,   //near面的bottom
            float top,      //near面的top
            float near,     //near面距離
            float far       //far面距離
    )
    {
        Matrix.orthoM(mProjMatrix, 0, left, right, bottom, top, near, far);
    }

    //獲取具體物體的總變換矩陣
    public static float[] getFinalMatrix()
    {
        float[] mMVPMatrix=new float[16];
        Matrix.multiplyMM(mMVPMatrix, 0, mVMatrix, 0, currMatrix, 0);
        Matrix.multiplyMM(mMVPMatrix, 0, mProjMatrix, 0, mMVPMatrix, 0);
        return mMVPMatrix;
    }

    //獲取具體物體的變換矩陣
    public static float[] getMMatrix()
    {
        return currMatrix;
    }

    //設定燈光位置的方法
    public static void setLightLocation(float x,float y,float z)
    {
        lightLocation[0]=x;
        lightLocation[1]=y;
        lightLocation[2]=z;
        ByteBuffer llbb = ByteBuffer.allocateDirect(3*4);
        llbb.order(ByteOrder.nativeOrder());//設定位元組順序
        lightPositionFB=llbb.asFloatBuffer();
        lightPositionFB.put(lightLocation);
        lightPositionFB.position(0);
    }
}

最後不要忘了編寫我們的頂點著色器和片元著色器,和我文章裡之前寫過的那些著色器程式基本類似,都是正常的格式,主要注意新增光源和光照模型,比較簡單,這裡就不再贅述了。

最終的效果如下所示:
這裡寫圖片描述
是不是挺酷炫的?

相關推薦

Android使用OpenGL ES載入OBJ檔案資料

一、obj模型檔案概覽 在介紹如何用程式載入obj模型檔案之前,首先需要了解一下它的格式。 obj檔案是最簡單的一種3D模型檔案,可由3dx MAX或Maya等建模軟體匯出,廣泛應用於3D圖形應用(如遊戲)程式和3D列印等等,其本質上就是文字檔案,裡面儲存的

OpenGL ES 載入3D模型

前面繪製的矩形、立方體確實確實讓人看煩了,並且實際生活中的物體是非常複雜的,我們不可能像前面哪樣指定頂點來繪製,因此本篇部落格就說明通過OpenGL ES載入一個3D模型。這樣複雜物體的設計工作就可以交給專業的設計師來做了,進行3D建模的工具比如3dmax、maya等。 設計師通過這些軟

適用於AndroidOpenGL ES教程 - 第一部分 - 設定檢視

2009年12月3日 由每埃裡克·伯格曼 在安卓,嵌入式,Java的 | 90評論 我已經在OpenGL ES 2.0 for android上開始了一個新的更新系列教程。請檢視:OpenGL ES 2.0 我將在Android手機上編寫一些

apk解包+修改原始碼+重新打包 修改Androidapp教程 修改apk檔案教程

修改之前一定要先安裝java開發環境,不會裝的去自行百度。文章較長,耐心閱讀。 一直在做Android應用開發,但對於從自己手中輸出的apk包,瞭解並不是很深。最近想研究一下,消除下自己的一些技術盲點。 好吧,廢話少說,先切入主題。這篇文章主要介紹如何獲取apk包中的de

Android執行時ART載入OAT檔案的過程分析

                        在前面一文中,我們介紹了Android執行時ART,它的核心是OAT檔案。OAT檔案是一種Android私有ELF檔案格式,它不僅包含有從DEX檔案翻譯而來的本地機器指令,還包含有原來的DEX檔案內容。這使得我們無需重新編譯原有的APK就可以讓它正常地在ART裡

Android平臺OpenGL ES影象處理(improving)

在Android平臺一般使用OpenGL ES進行影象處理。在OpenGL ES中編寫演算法,實現效果,最後將處理的結果傳輸給 CPU,然後生成最終的照片。 直播中的美顏,對效能有很高的要求,無法使用特別複雜的演算法。我們只能在演算法和美顏效果之間找個平衡點。

android 使用OPENGL ES繪製一個圓環-三維空間

package com.scout.eeeeeee; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; import java.util.ArrayList; import javax.mi

【Qt for AndroidOpenGL ES 繪製彩色立方體

Qt 內建對OpenGL ES的支援,選用Qt進行OpenGL ES的開發是非常方便的,許多輔助類都已經具備。從Qt 5.0開始增加了一個QWindow類,該類既可以使用OpenGL繪製3D圖形,也可以使用QPainter繪製2D傳統的GDI+圖形,5.0以前的QGLWid

Android使用OpenGL ES顯示紋理(使用NDK開發)

書上第四章最後開始介紹使用OpenGL來顯示一個2D紋理,其實做音視訊2D基本滿足絕大多數要求了,下面簡單分析一下原始碼中的流程。 EGL環境初始化 首先我們需要在Java環境中初始化一個SurfaceView,然後在回撥中我們傳入surface。這裡我

android:將assets目錄下的檔案(資料夾)放置到記憶體卡指定目錄下

最近做一個OCR識別圖片文字的功能,閒暇之餘,提取專案中涉及的方法供大家參考. 這個方法是將assets下的指定檔案或資料夾,放置到sd的指定目錄下,程式碼中都有註釋. 當然我們也可以探討探討OCR.

android 使用OPENGL ES繪製一個圓柱體-三維空間

package com.scout.eeeeeee; /** * Created by liuguodong on 2017/10/29. */ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuf

android studio | openGL es 3.0增強現實(AR)開發 (1) 建立一個openGL es 3.0開發環境

1.什麼是NDK,什麼是JNI? NDK:Native Development Kit(原生開發工具包), NDK允許使用者使用類似C / C++之類的原生程式碼語言執行部分程式。它包括下面的部分(1)從C / C++生成原生程式碼庫所需要的工具和buil

Android使用OpenGL ES 3.0實現隨手指旋轉3D立方體

OpenGL ES在做普通應用方面3D使用的不多,但有時候實現一些有趣的功能也是蠻不錯的。畫立方體的的demo網上已經很多了,這次我們就實現一個隨手指旋轉的立方體,這個demo基本可以瞭解各個座標系轉換矩陣的使用了。 先看一下最終效果: 話不多說,直接上

Android做RTP實時傳送音訊資料實現

       如果你也在Android端做RTP傳送資料的話,通過網上查詢資料,相信你不難發現,在使用RTP/RTCP協議傳送資料是有現成的庫進行呼叫的,Jlibrtp這個庫就是Java實現的,但是這個庫是沒有說明文件的,比較摳腳,而且百度谷歌找到例子又很少,基本上都沒什麼卵

孫其功陪你學之——OpenGL載入OBJ模型檔案並進行紋理修飾

本人在學習opengl做專案時,需要設計三維人臉模型。於是尋找多種方案。求得用opengl讀取OBJ模型檔案,並進行紋理修飾這一解決方案。在使用中用到了glm庫。以下是我對glm.c庫的兩篇分析。http://blog.csdn.net/a350203223/article

Android實現基於OPENGL ES 的深度學習前向傳播框架

這個專案斷斷續續寫了快4個月了,最近忙起來了,可能沒什麼時間完善了,先用blog記錄一下思路,要有機會再完善了。 目前整個專案只實現了卷積,池化,全連線,concat,flat(和tensorflow的flat邏輯不一樣),softmax,卷積部分實現了常規卷積,G

Android OpenGL ES 入門系列(一) --- 了解OpenGL ES的前世今生

target 初始化 vertex 單獨 http hang tex 變化 3d圖 轉載請註明出處 本文出自Hansion的博客 OpenGL ES (OpenGL for Embedded Systems) 是 OpenGL 三維圖形 API 的子集,

Android面試收集錄 OpenGL ES

face use 需要 pen 如何 clas bsp 使用 chan 1.如何用OpenGL ES繪制一個三角形? 編寫一個類實現Renderer接口,實現onDrawFrame方法,onSurfaceChanged方法,onSurfaceCreated方法

android讀取csv檔案資料

csv檔案是一種表格形式的檔案,如果把檔案字尾名改為.txt,會發現同一行資料之間是用英文“,”隔開的。 如何讀取csv檔案以便把資料存入資料庫呢,特別是csv檔案中有些資料是空? csv檔案如下: 把檔案字尾名改為.txt後如下: 電錶id,電錶編號,模組地址,描述,所屬站點名稱,

android平臺下OpenGL ES 3.0從零開始

OpenGL ES 3.0學習實踐 android平臺下OpenGL ES 3.0從零開始 android平臺下OpenGL ES 3.0繪製純色背景 android平臺下OpenGL ES 3.0繪製圓點、直線和三角形 android平臺下OpenGL E