Android OpenGL 解析obj文件繪製3D模型

繪製3D模型一般我們都會用到OpenGL裏面的一些api,看看如何使用這些api繪製3的模型。

使用OpenGl繪製3D模型我主要用兩種方式:

1:glDrawArrays(int mode, int first,int count)  

參數1:有三種取值

1.GL_TRIANGLES:每三個頂之間繪製三角形,之間不連接

2.GL_TRIANGLE_FAN:以V0V1V2,V0V2V3,V0V3V4,……的形式繪製三角形

3.GL_TRIANGLE_STRIP:順序在每三個頂點之間均繪製三角形。這個方法可以保證從相同的方向上所有三角形均被繪製。以V0V1V2,V1V2V3,V2V3V4……的形式繪製三角形

參數2:從數組緩存中的哪一位開始繪製,一般都定義爲0

參數3:頂點的數量

2:glDrawElements(int mode,int count,int type,java.nio.Buffer indices)

mode——指明被渲染的是哪種圖元,被允許的符號常量有GL_POINTS,GL_LINE_STRIP,GL_LINE_LOOP,GL_LINES,GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN和GL_TRIANGLES

count——指明被渲染的元素個數。

type——指明索引指的類型,不是GL_UNSIGNED_BYTE就是GL_UNSIGNED_SHORT。

indices——指明存儲索引的位置

 

第一種 glDrawArrays 繪製3D模型,主要分爲兩大部分。第一步,先將3D模型的3維數組座標寫好。第二步,將GLSurfaceView的渲染器配置好,將座標數組轉爲FloatBuffer,直接開始繪製就OK。使用這種方式繪製3D模型,一定要將做表數組寫好,座標數組的元素順序與後面你使用的繪製類型有關,我建議可以根據obj模型文件來確定數組。我的數組是直接從obj模型文件中提取的。

/**
 * 解析3D Obj 文件
 * 解析obj文件時,數據類型由開頭的首字母決定
 * switch(首字母){
 *     case "v":
 *      頂點座標數據
 *     break; 
 *     case "vn":
 *      法向量座標數據
 *     break; 
 *     case "vt":
 *      紋理座標數據
 *     break; 
 *     case "usemtl":
 *      材質
 *     break; 
 *     case "f":
 *      索引
 *     break;
 * }
 */
public class ObjLoaderUtil {
    // 存放解析出來的頂點的座標
    private ArrayList<ObjVertexs> mVertexArrayList;
    //存放解析出來面的索引
    private ArrayList<Integer> mIndexArrayList;
    //存放解析出來的法線座標
    private ArrayList<ObjVertexs> mNormalArrayList;
    //存放解析出來的法線索引
    private ArrayList<Integer> mIndexNormalArrayList;
    //存放3D模型的頂點座標數據
    public float[] mSurfaceFloat;
    //存放頂點座標的floatbuffer
    public FloatBuffer mVertexFloatBuffer;
    //頂點座標索引buffer
    public FloatBuffer mIndexVertexBuffer;
    //存放法線座標的floatbuffer
    public FloatBuffer mNormalFloatBuffer;


    public ObjLoaderUtil() {
        mVertexArrayList = new ArrayList();
        mIndexArrayList = new ArrayList<>();
        mNormalArrayList = new ArrayList<>();
        mIndexNormalArrayList = new ArrayList<>();
    }

    /**
     * 將obj文件中的頂點座標與面索引解析出來,存放集合中
     *
     * @param mContext
     * @throws IOException
     */
    public void load(Context mContext) throws IOException {
        //獲取assets文件夾下的obj文件的數據輸入流
        InputStream mInputStream = mContext.getClass().getClassLoader().getResourceAsStream("assets/Lion_OBJ.obj");
        InputStreamReader mInputStreamReader = new InputStreamReader(mInputStream);
        BufferedReader mBufferedReader = new BufferedReader(mInputStreamReader);
        String temps = null;
        //利用buffer讀取流將obj文件的內容讀取出來存放在temps
        while ((temps = mBufferedReader.readLine()) != null) {
            String[] temp = temps.split(" ");
//            Log.d("tag", "文件信息" + temps);
            if (temp[0].trim().equals("v")) {
                mVertexArrayList.add(new ObjVertexs(Float.valueOf(temp[1]), Float.valueOf(temp[2]), Float.valueOf(temp[3])));
            }
            if (temp[0].trim().equals("vn")) {
                mNormalArrayList.add(new ObjVertexs(Float.valueOf(temp[1]), Float.valueOf(temp[2]), Float.valueOf(temp[3])));
            }
//            如果讀取到的首元素時"f",則表示讀取到的數據是面索引數據
            if (temp[0].trim().equals("f")) {
                if (temp[1].indexOf("/") == -1) {
                    for (int i = 1; i < temp.length; i++) {
                        mIndexArrayList.add(Integer.valueOf(temp[i]));
                    }
                } else {
                    for (int i = 1; i < temp.length; i++) {
                        mIndexArrayList.add(Integer.valueOf(temp[i].split("/")[0]));
                    }
                }
            }

        }

        mSurfaceFloat = getSurfaceFloat(mVertexArrayList, mIndexArrayList);
        mVertexFloatBuffer = makeFloatBuffer(mSurfaceFloat);
        mNormalFloatBuffer = makeFloatBuffer(getSurfaceFloat(mNormalArrayList, mIndexNormalArrayList));
    }

    /**
     * 將解析出來的頂點與索引組合起來,形成一個新的float數組,用於繪製3D模型,並將其返回
     *
     * @param mObjVertexs 存放座標點的集合
     * @param mIntegers   存放索引的集合
     * @return
     */
    public float[] getSurfaceFloat(ArrayList<ObjVertexs> mObjVertexs, ArrayList<Integer> mIntegers) {
        float[][] mFloats = new float[mIntegers.size()][3];
        float[] surfaceFloat = new float[mIntegers.size() * 3];
        for (int i = 0; i < mIntegers.size(); i++) {
            mFloats[i][0] = mObjVertexs.get(mIntegers.get(i) - 1).x;
            mFloats[i][1] = mObjVertexs.get(mIntegers.get(i) - 1).y;
            mFloats[i][2] = mObjVertexs.get(mIntegers.get(i) - 1).z;
        }
        int i = 0;
        for (float[] floats : mFloats) {
            for (float v : floats) {
                surfaceFloat[i++] = v;
            }
        }
        return surfaceFloat;
    }

    /**
     * 將存放3D模型的float數組,轉換爲floatbuffer
     *
     * @param mFloats
     * @return
     */
    public FloatBuffer makeFloatBuffer(float[] mFloats) {
        //爲float數組分配緩存空間,一個float大小爲4個字節
        ByteBuffer mByteBuffer = ByteBuffer.allocateDirect(mFloats.length * 4);
        //規定緩存區的字節順序爲本機字節順序
        mByteBuffer.order(ByteOrder.nativeOrder());
        //將bytebuffer轉換爲floatbuffer
        FloatBuffer loatBuffer = mByteBuffer.asFloatBuffer();
        //將float數組填充到floatbuffe中,完成轉換
        loatBuffer.put(mFloats);
        //規定緩存區的起始索引
        loatBuffer.position(0);
        return loatBuffer;
    }
}
/**
 * 繼承GLSurfaceView,實現Renderer接口,開始繪製
 */
public class GlModeView extends GLSurfaceView implements GLSurfaceView.Renderer, GestureDetector.OnGestureListener {
    //obj文件解析類
    private ObjLoaderUtil mObjLoaderUtil;
    //手勢工具類
    private GestureDetectorCompat mCompat;
    //模型沿x,y軸旋轉的角度
    private float xrot, yrot;

    public GlModeView(Context context, AttributeSet attrs) throws IOException {
        super(context, attrs);
        setRenderer(this);
        mObjLoaderUtil = new ObjLoaderUtil();
        mObjLoaderUtil.load(context);
        mCompat = new GestureDetectorCompat(context, this);
    }

    @Override
    public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {
        //設置清屏色
        gl10.glClearColor(0.1f, 1f, 1f, 0);
        //用當前光線參數計算頂點顏色。否則僅僅簡單將當前顏色與每個頂點關聯。
        gl10.glEnable(GL10.GL_LIGHTING);
        //啓用服務器端GL功能,允許(apply)引入的顏色與顏色緩衝區中的值進行邏輯運算
        gl10.glEnable(GL10.GL_BLEND);
        //這一行啓用smooth shading(陰影平滑)。陰影平滑通過多邊形精細的混合色彩,並對外部光進行平滑。
        gl10.glShadeModel(GL10.GL_SMOOTH);
        //當前活動紋理單元爲二維紋理
        gl10.glEnable(GL10.GL_TEXTURE_2D);
        //做深度比較和更新深度緩存。值得注意的是即使深度緩衝區存在並且深度mask不是0,如果深度測試禁用的話,深度緩衝區也無法更新。
        gl10.glEnable(GL10.GL_DEPTH_TEST);
        // 法向量被計算爲單位向量
        gl10.glEnable(GL10.GL_NORMALIZE);
        //啓用面部剔除功能
        gl10.glEnable(GL10.GL_CULL_FACE);
        //指定哪些面不繪製
        gl10.glCullFace(GL10.GL_BACK);
        //開啓定義多邊形的正面和背面
        gl10.glEnable(GL10.GL_CULL_FACE);
        //改變每個頂點的散射和環境反射材質,可以使用顏色材質。
        gl10.glEnable(GL10.GL_COLOR_MATERIAL);
        // 開啓抗鋸齒
        gl10.glEnable(GL10.GL_POINT_SMOOTH);
        gl10.glHint(GL10.GL_POINT_SMOOTH_HINT, GL10.GL_NICEST);
        gl10.glEnable(GL10.GL_LINE_SMOOTH);
        gl10.glHint(GL10.GL_LINE_SMOOTH_HINT, GL10.GL_NICEST);
        gl10.glEnable(GL10.GL_POLYGON_OFFSET_FILL);
        gl10.glHint(GL10.GL_POLYGON_SMOOTH_HINT, GL10.GL_NICEST);
        //定義多邊形的正面和背面,在一個完全由不透明的密閉surface組成的場景中,多邊形的背面永遠不會被看到。剔除這些不能顯示出來的面可以加速渲染器渲染圖像的時間。
        gl10.glFrontFace(GL10.GL_CCW);
        //指明深度緩衝區的清理值
        gl10.glClearDepthf(1.0f);
        gl10.glDepthFunc(GL10.GL_LEQUAL);
    }

    @Override
    public void onSurfaceChanged(GL10 gl10, int i, int i1) {
        //選擇投影矩陣
        gl10.glMatrixMode(GL10.GL_PROJECTION);
        gl10.glViewport(0, 0, i, i1);
        gl10.glLoadIdentity();
        float x = (float) i / i1;
        gl10.glFrustumf(-x, x, -1, 1, 5, 200);
        GLU.gluLookAt(gl10, 0, 0, 50, 0, 0, 0, 0, 1, 0);
        //選擇模型觀察矩陣
        gl10.glMatrixMode(GL10.GL_MODELVIEW);
        gl10.glLoadIdentity();
    }

    @Override
    public void onDrawFrame(GL10 gl10) {
        //清除屏幕和深度緩存。
        gl10.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
        gl10.glLoadIdentity();
        //設置頂點座標buffer
        gl10.glVertexPointer(3, GL10.GL_FLOAT, 0,
                mObjLoaderUtil.mVertexFloatBuffer);
        //啓用頂點數組
        gl10.glEnableClientState(GL10.GL_VERTEX_ARRAY);
        //沿X軸旋轉
        gl10.glRotatef(xrot, 1, 0, 0);
        //沿Y軸旋轉
        gl10.glRotatef(yrot, 0, 1, 0);

        gl10.glColor4f(1f, 0f, 0f, 0);
        //模型如果四邊形化,i就是加4,模型如果三角化,i就是加3
        for (int i = 0; i <= (mObjLoaderUtil.mSurfaceFloat.length / 3 - 4); i += 4) {
            gl10.glDrawArrays(GL10.GL_TRIANGLE_FAN, i, 4);
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                getParent().requestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_UP:
                getParent().requestDisallowInterceptTouchEvent(false);
                break;
        }
        return mCompat.onTouchEvent(event);
    }

    @Override
    public boolean onDown(MotionEvent motionEvent) {
        return true;
    }

    @Override
    public void onShowPress(MotionEvent motionEvent) {

    }

    @Override
    public boolean onSingleTapUp(MotionEvent motionEvent) {
        return false;
    }

    @Override
    public boolean onScroll(MotionEvent motionEvent, MotionEvent motionEvent1, float v, float v1) {
        xrot -= v;
        yrot -= v1;
        return true;
    }

    @Override
    public void onLongPress(MotionEvent motionEvent) {

    }

    @Override
    public boolean onFling(MotionEvent motionEvent, MotionEvent motionEvent1, float v, float v1) {
        return false;
    }
}

第二種 glDrawElements繪製3D模型,使用這種方式繪製3D模型,需要將頂點座標數據準備好,及頂點索引數組準備好。就ok。

這兩個數組也是直接從obj文件中解析。相對上面的方式而言,就簡單一點。

/**
 * 解析obj文件
 */
public class ObjUtils {
    //存放頂點座標
    private ArrayList<Point> mVertexArrayList;
    public float[] mVertexFloats;
    public FloatBuffer mVertexFloatBuffer;
    //存放頂點索引
    private ArrayList<Short> mIndexArrayList;
    public short[] mIndexShorts;
    public ShortBuffer mIndexShortBuffer;
    //存放讀取的obj文件的信息
    private String temps;


    public ObjUtils() {
        mVertexArrayList = new ArrayList<>();
        mIndexArrayList = new ArrayList<>();
     }

    /**
     * 加載obj文件數據
     */
    public void loadObj(Context context, String path) throws IOException {
        //讀取city.obj文件
        InputStream inputStream = context.getClass().getClassLoader().getResourceAsStream(path);
        InputStreamReader mInputStreamReader = new InputStreamReader(inputStream);
        BufferedReader mReader = new BufferedReader(mInputStreamReader);
        //如果讀取的文本內容不爲空,則一直讀取,否則停止
        while ((temps = mReader.readLine()) != null) {
            String[] temp = temps.split(" ");
            //以空格爲分割符,將讀取的一行temps分裂爲數組
            //如果讀取到的首元素時"v",則表示讀取到的數據是頂點座標數據
            if (temp[0].trim().equals("v")) {
                mVertexArrayList.add(new Point(Float.valueOf(temp[1]), Float.valueOf(temp[2]), Float.valueOf(temp[3])));
            }
//            如果讀取到的首元素時"f",則表示讀取到的數據是面索引數據
            if (temp[0].trim().equals("f")) {
                if (temp[1].indexOf("/") == -1) {
                    for (int i = 1; i < temp.length; i++) {
                        int t = Integer.valueOf(temp[i]);
                        mIndexArrayList.add((short) t);
                    }
                } else {
                    for (int i = 1; i < temp.length; i++) {
                        int t = Integer.valueOf(temp[i].split("/")[0]) - 1;
                        mIndexArrayList.add((short) t);
                    }
                }
            }

        }
        mReader.close();
        mVertexFloats = getVertex(mVertexArrayList);
        mIndexShorts = getIndex(mIndexArrayList);
        mVertexFloatBuffer = makeFloatBuffer(mVertexFloats);
        mIndexShortBuffer = getBufferFromArray(mIndexShorts);
         }

     /**
     * 獲取頂點數組
     */
    private float[] getVertex(ArrayList<Point> pointArrayList) {
        float[][] mFloats = new float[pointArrayList.size()][3];
        float[] surfaceFloat = new float[pointArrayList.size() * 3];
        for (int i = 0; i < pointArrayList.size(); i++) {
            mFloats[i][0] = pointArrayList.get(i).x;
            mFloats[i][1] = pointArrayList.get(i).y;
            mFloats[i][2] = pointArrayList.get(i).z;
        }
        int i = 0;
        for (float[] floats : mFloats) {
            for (float v : floats) {
                surfaceFloat[i++] = v;
            }
        }
        return surfaceFloat;
    }

    /**
     * 獲取索引數組
     */
    private short[] getIndex(ArrayList<Short> integerArrayList) {
        short[] mFloats = new short[integerArrayList.size()];
        for (int i = 0; i < integerArrayList.size(); i++) {
            mFloats[i] = integerArrayList.get(i);
        }
        return mFloats;
    }

    /**
     * 將float數組轉換爲buffer
     */
    private FloatBuffer makeFloatBuffer(float[] floats) {
        ByteBuffer mByteBuffer = ByteBuffer.allocateDirect(floats.length * 4);
        mByteBuffer.order(ByteOrder.nativeOrder());
        FloatBuffer floatBuffer = mByteBuffer.asFloatBuffer();
        floatBuffer.put(floats);
        floatBuffer.position(0);
        return floatBuffer;
    }

    private static ShortBuffer getBufferFromArray(short[] array) {
        ByteBuffer buffer = ByteBuffer.allocateDirect(array.length * 2);
        buffer.order(ByteOrder.nativeOrder());
        ShortBuffer shortBuffer = buffer.asShortBuffer();
        shortBuffer.put(array);
        shortBuffer.position(0);
        return shortBuffer;
    }
}
public class ModeView extends GLSurfaceView implements GLSurfaceView.Renderer, GestureDetector.OnGestureListener {
    private ObjUtils mObjUtils;
    private GestureDetectorCompat mCompat;
    private float xrot, yrot;
    private FloatBuffer colorBuffer;

    public ModeView(Context context, AttributeSet attrs) throws IOException {
        super(context, attrs);
        mObjUtils = new ObjUtils();
        setRenderer(this);
        mObjUtils.loadObj(context, "assets/mode.obj");
        mCompat = new GestureDetectorCompat(context, this);
    }

    @Override
    public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {
        //設置清屏色
        gl10.glClearColor(1f, 1f, 1f, 0);
        //用當前光線參數計算頂點顏色。否則僅僅簡單將當前顏色與每個頂點關聯。
        gl10.glEnable(GL10.GL_LIGHTING);
        //啓用服務器端GL功能,允許(apply)引入的顏色與顏色緩衝區中的值進行邏輯運算
        gl10.glEnable(GL10.GL_BLEND);
        //這一行啓用smooth shading(陰影平滑)。陰影平滑通過多邊形精細的混合色彩,並對外部光進行平滑。
        gl10.glShadeModel(GL10.GL_SMOOTH);
        //當前活動紋理單元爲二維紋理
        gl10.glEnable(GL10.GL_TEXTURE_2D);
        //做深度比較和更新深度緩存。值得注意的是即使深度緩衝區存在並且深度mask不是0,如果深度測試禁用的話,深度緩衝區也無法更新。
        gl10.glEnable(GL10.GL_DEPTH_TEST);
        // 法向量被計算爲單位向量
        gl10.glEnable(GL10.GL_NORMALIZE);
        //啓用面部剔除功能
        gl10.glEnable(GL10.GL_CULL_FACE);
        //指定哪些面不繪製
        gl10.glCullFace(GL10.GL_BACK);
        //開啓定義多邊形的正面和背面
        gl10.glEnable(GL10.GL_CULL_FACE);
        //改變每個頂點的散射和環境反射材質,可以使用顏色材質。
        gl10.glEnable(GL10.GL_COLOR_MATERIAL);
        // 開啓抗鋸齒
        gl10.glEnable(GL10.GL_POINT_SMOOTH);
        gl10.glHint(GL10.GL_POINT_SMOOTH_HINT, GL10.GL_NICEST);
        gl10.glEnable(GL10.GL_LINE_SMOOTH);
        gl10.glHint(GL10.GL_LINE_SMOOTH_HINT, GL10.GL_NICEST);
        gl10.glEnable(GL10.GL_POLYGON_OFFSET_FILL);
        gl10.glHint(GL10.GL_POLYGON_SMOOTH_HINT, GL10.GL_NICEST);
        //定義多邊形的正面和背面,在一個完全由不透明的密閉surface組成的場景中,多邊形的背面永遠不會被看到。剔除這些不能顯示出來的面可以加速渲染器渲染圖像的時間。
        gl10.glFrontFace(GL10.GL_CCW);
        //指明深度緩衝區的清理值
        gl10.glClearDepthf(1.0f);
        gl10.glDepthFunc(GL10.GL_LEQUAL);
    }

    @Override
    public void onSurfaceChanged(GL10 gl10, int i, int i1) {
        //選擇投影矩陣
        gl10.glMatrixMode(GL10.GL_PROJECTION);
        gl10.glViewport(0, 0, i, i1);
        gl10.glLoadIdentity();
        float x = (float) i / i1;
        gl10.glFrustumf(-x, x, -1, 1, 4, 200);
        GLU.gluLookAt(gl10, 0, 0, 60, 0, 0, 0, 0, 1, 0);
        //選擇模型觀察矩陣
        gl10.glMatrixMode(GL10.GL_MODELVIEW);
        gl10.glLoadIdentity();
    }

    @Override
    public void onDrawFrame(GL10 gl10) {
        //啓用頂點數組
        gl10.glEnableClientState(GL10.GL_VERTEX_ARRAY);
        gl10.glEnableClientState(GL10.GL_COLOR_ARRAY);
        //清除屏幕和深度緩存。
        gl10.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
        gl10.glLoadIdentity();
        //設置頂點座標buffer
        gl10.glVertexPointer(3, GL10.GL_FLOAT, 0,
                mObjUtils.mVertexFloatBuffer);
        gl10.glTranslatex(0, -20, 0);
        gl10.glRotatef(xrot, 1, 0, 0);
        gl10.glRotatef(yrot, 0, 1, 0);
        gl10.glPushMatrix();
        gl10.glDrawElements(GL10.GL_TRIANGLES, mObjUtils.mIndexShorts.length,
                GL10.GL_UNSIGNED_SHORT, mObjUtils.mIndexShortBuffer);
        gl10.glDisableClientState(GL10.GL_COLOR_ARRAY);
        //關閉點座標管道
        gl10.glDisableClientState(GL10.GL_VERTEX_ARRAY);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                getParent().requestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_UP:
                getParent().requestDisallowInterceptTouchEvent(false);
                break;
        }
        return mCompat.onTouchEvent(event);
    }

    @Override
    public boolean onDown(MotionEvent motionEvent) {
        return true;
    }

    @Override
    public void onShowPress(MotionEvent motionEvent) {

    }

    @Override
    public boolean onSingleTapUp(MotionEvent motionEvent) {
        return false;
    }

    @Override
    public boolean onScroll(MotionEvent motionEvent, MotionEvent motionEvent1, float v, float v1) {
        xrot -= v;
        yrot -= v1;
        return true;
    }

    @Override
    public void onLongPress(MotionEvent motionEvent) {

    }

    @Override
    public boolean onFling(MotionEvent motionEvent, MotionEvent motionEvent1, float v, float v1) {
        return false;
    }
}

這兩種方式繪製3D模型的主要區別就是在 onDrawFrame這個方法中。

一:

for (int i = 0; i <= (mObjLoaderUtil.mSurfaceFloat.length / 3 - 4); i += 4) { gl10.glDrawArrays(GL10.GL_TRIANGLE_FAN, i, 4); }

二:

gl10.glDrawElements(GL10.GL_TRIANGLES, mObjUtils.mIndexShorts.length, GL10.GL_UNSIGNED_SHORT, mObjUtils.mIndexShortBuffer);

在使用OpenGl繪製3D模型前,一定要把這兩種方法研究下。

歡迎各方大佬,指點評論!!!!!!

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章