绘制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 ArrayListmVertexArrayList; //存放解析出来面的索引 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 ArrayListmVertexArrayList; 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模型前,一定要把这两种方法研究下。
欢迎各方大佬,指点评论!!!!!!