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 mVertexArrayList;
    //存放解析出来面的索引
    private ArrayList mIndexArrayList;
    //存放解析出来的法线坐标
    private ArrayList mNormalArrayList;
    //存放解析出来的法线索引
    private ArrayList 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 mObjVertexs, ArrayList 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 mVertexArrayList;
    public float[] mVertexFloats;
    public FloatBuffer mVertexFloatBuffer;
    //存放顶点索引
    private ArrayList 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 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 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模型前,一定要把这两种方法研究下。

欢迎各方大佬,指点评论!!!!!!

 

你可能感兴趣的:(Android OpenGL 解析obj文件绘制3D模型)