在介绍OpenGl开发之前,首先我们得明白OpenGL—坐标变换的一些基本概念:
坐标:
物体坐标:
设置的顶点坐标即为物体本身的坐标,即每个物体内部都有一个自己的坐标系,管理自己的位置。
- glBegin(GL_TRIANGLE_FAN);
- glVertex3f(1, 0, 0);
- glVertex3f(0, 1, 0);
- glVertex3f(0, 0, 1);
- glEnd();
全局坐标:
全局坐标 = 模型矩阵 * 三角形每个顶点坐标;
全局坐标即物体相对整个空间的坐标。比如人这个实体,心脏是坐标中心,头就在心脏的上方,脚在心脏下方,这是相对物体的坐标来说的,也就是刚才的描述是在物体坐标下描述。而全局坐标即你这个人相对地球(假如整个地球就是我们要描述的空间)来说在哪个经纬度,这个就是全局坐标。
视觉坐标:
视觉坐标 = 模型视图矩阵 * 顶点坐标 = 视图矩阵 * 模型矩阵 * 顶点坐标;
视觉坐标就是将当前全局的坐标转换到了相对眼睛看到的坐标。因为整个世界太大了,如果只想看某一部分,所以就引入了视觉坐标。视觉坐标就是定义了一个摄像机,看到场景中的哪些实体就将这些实体渲染出来,不在摄像机范围内的就不显示。视觉坐标就是将以摄像机的坐标为坐标原点,其他的物体都是相对摄像机的坐标来确定的。比如摄像机中的一个三角形,现在这个三角形在摄像机中的上面,还是下面,距离摄像机多远等,此时都是在视觉坐标下来计算的。
裁剪坐标:
裁剪坐标 = 投影矩阵 * 模型视图矩阵 * 顶点坐标;
裁剪坐标就是经过投影矩阵裁剪后的坐标,比如上述定义的摄像机,这个摄像机看到的范围是有限的,有一个夹角和一个近距离和远距离。夹角外的看不到,设置一个近距离裁剪,就是太近的也当做看不见,太远的也看不见。也就是将上述的视觉坐标定义了一个裁剪体,在这个范围内的才渲染。同时将这个视觉坐标这些3d的顶点投影都这个视景体中,也就是差不多是最后2D的画面了。
标准设备坐标:
设备坐标 = 裁剪坐标 / w;
此时设备坐标的x,y,z 范围是[-1, 1]之间
显示坐标:
即最后的显示在显示器上的坐标,也就是将[-1, 1] 与窗口的实际宽高对应
正常的设置投影矩阵的opengl函数为glFrustum 或者 gluPerspective,通过设置视口的宽高,近裁剪与远裁剪等方式来形成一个投影矩阵,通过将投影矩阵与视图坐标相乘形成裁剪坐标。具体投影矩阵算法推倒可参照网上资料。
在android中使用OpenGL ES需要三个步骤:
1. 创建GLSurfaceView组件,使用Activity来显示GLSurfaceView组建。
2. 为GLSurfaceView组建创建GLSurfaceView.Renderer实例,实现GLSurfaceView.Renderer类时需要实现该接口里的三个方法:
-
abstract void onDrawFrame(GL10 gl):Called to draw the current frame.
-
abstract void onSurfaceChanged(GL10 gl, int width, int height):Called when the surface changed size.
-
abstract void onSurfaceCreated(GL10 gl, EGLConfig config):Called when the surface is created or recreated.
3. 调用GLSurfaceView组建的setRenderer (GLSurfaceView.Renderer renderer) 方法指定Renderer对象,该对象将会完成GLSurfaceView里3D图形的绘制。
最近学习 opengl,看了一些简单的教程。发现一般都是到实现金字塔或者立方体就结束了。
纹理方面,对三维物体的多个面未做处理。参看了这些例子,做一个android上的多纹理的立方体。
效果图:
一、Activity
先上简单的代码,实现onKeyUp(int keyCode, KeyEvent event)、onTrackballEvent(MotionEvent e)、onTouchEvent(MotionEvent e)三个方法,实现3D 图片随touch事件旋转功能。
package com.ant.opengl_cube; import android.app.Activity; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.opengl.GLSurfaceView; import android.os.Bundle; import android.view.KeyEvent; import android.view.MotionEvent; public class MainActivity extends Activity { GLRender render = new GLRender(); private float mPreviousX; private float mPreviousY; private final float TOUCH_SCALE_FACTOR = 180.0f / 320; private final float TRACKBALL_SCALE_FACTOR = 36.0f; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); GLImage.load(this.getResources()); GLSurfaceView glView = new GLSurfaceView(this); glView.setRenderer(render); setContentView(glView); } public boolean onKeyUp(int keyCode, KeyEvent event) { render.onKeyUp(keyCode, event); return false; } public boolean onTrackballEvent(MotionEvent e) { render.xrot += e.getX() * TRACKBALL_SCALE_FACTOR; render.yrot += e.getY() * TRACKBALL_SCALE_FACTOR; return true; } public boolean onTouchEvent(MotionEvent e) { float x = e.getX(); float y = e.getY(); switch (e.getAction()) { case MotionEvent.ACTION_MOVE: float dx = x - mPreviousX; float dy = y - mPreviousY; render.xrot += dx * TOUCH_SCALE_FACTOR; render.yrot += dy * TOUCH_SCALE_FACTOR; } mPreviousX = x; mPreviousY = y; return true; } } class GLImage { public static Bitmap mBitmap1; public static Bitmap mBitmap2; public static Bitmap mBitmap3; public static Bitmap mBitmap4; public static Bitmap mBitmap5; public static Bitmap mBitmap6; public static void load(Resources resources) { mBitmap1 = BitmapFactory.decodeResource(resources, R.drawable.icon1); mBitmap2 = BitmapFactory.decodeResource(resources, R.drawable.icon2); mBitmap3 = BitmapFactory.decodeResource(resources, R.drawable.icon3); mBitmap4 = BitmapFactory.decodeResource(resources, R.drawable.icon4); mBitmap5 = BitmapFactory.decodeResource(resources, R.drawable.icon5); mBitmap6 = BitmapFactory.decodeResource(resources, R.drawable.icon6); } }
二、Renderer核心代码
1、glDrawElements()
OpenGL基本的绘图函数例如glVertex、glNormal等在调试模式下运行时,如果模型的顶点数或者三角面数过大(比如超过一万时),则程序运行速度会非常慢,根本就无法进行正常的调试。为此查阅了相关资料,找到glArrayElement、glDrawElements这两个个函数。这两个函数都能通过少数几条语句的调用实现大量数据的绘制,从而节省了函数调用的资源占用。
GLenum mode ,
GLsizei count ,
GLenum type ,
const GLvoid *indices
);
Parameters
-
mode
- The kind of primitives to render. It can assume one of the following symbolic values: GL_POINTS, GL_LINE_STRIP, GL_LINE_LOOP, GL_LINES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN, GL_TRIANGLES, GL_QUAD_STRIP, GL_QUADS, and GL_POLYGON. count
- The number of elements to be rendered. type
- The type of the values in indices. Must be one of GL_UNSIGNED_BYTE, GL_UNSIGNED_SHORT, or GL_UNSIGNED_INT. indices
- A pointer to the location where the indices are stored.
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3, GL_FLOAT, 0, (float*)m_vDataCoord[0]);
glEnableClientState(GL_NORMAL_ARRAY);
glNormalPointer(GL_FLOAT, 0, (float*)m_vDataNormal[0]);
glEnableClientState(GL_COLOR_ARRAY);
glColorPointer(3, GL_FLOAT, 0, (float*)m_vDataColor[0]);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glTexCoordPointer(2, GL_FLOAT, 0, (float*)m_vDataUv[0]);
glTexCoordPointer(int size, int type, int stride, Buffer pointer); 设置顶点数组为纹理坐标缓存
其中: size:纹理顶点坐标的分量个数; //size: number of coordinates per vertex;
type:纹理坐标的数据类型;short, int, float, double都可以;
stride:位图的宽度,可以理解为相邻的两个纹理之间跨多少个字节,一般为0,因为一般不会在纹理中再添加其他的信 息。//stride: offset between 2 consecutive vertices;
pointer:存放纹理坐标的数组,指明将绘制的第i个点(i<count)分别对应着贴图的哪一个角,四个角分别用(0,1)(左上角)、(1,1)(右上角)、(1,0)(右下角)、(0,0)(左下角)表示。如
IntBuffer texCoords = IntBuffer.wrap(new int[]{ 0,1,1,1,1,0,0,0, }); gl.glTexCoordPointer(2, GL10.GL_FIXED, 0, texCoords);
注意:pointer是以顶点数组为参照的,而不是以绘制索引为参照!如
gl.glVertexPointer(3, GL10.GL_FIXED, 0, vertices); gl.glTexCoordPointer(2, GL10.GL_FIXED, 0, texCoords); gl.glDrawElements(GL10.GL_TRIANGLE_STRIP, 4, GL10.GL_UNSIGNED_BYTE, indices);
poInter以vertices中顶点的顺序为参照,而不是indices所指定的顺序
下面是Renderer类,其中,代码中都有注释了,应该简单易懂
package com.ant.cube3d; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; import java.nio.IntBuffer; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; import android.opengl.GLSurfaceView.Renderer; import android.opengl.GLUtils; import android.view.KeyEvent; public class GLRender implements Renderer { public GLRender() { // 注意构造函数中那些Buffer的创建方式。在这个地方, // 不能直接使用FloatBuffer/IntBuffer 的wrap() method。 // 直接用这个method创建出来的buffer会导致JE: vertices = bufferUtil(verticesdata); texCoords = bufferUtil(texCoordsdata); normals = bufferUtil(normalsdata); lightAmbient = bufferUtil(light1); lightDiffuse = bufferUtil(light2); lightPosition = bufferUtil(light3); } // 通过事件分发,提供方法给View层,动态onDrawFram(),就是3D物体跟着转 public boolean onKeyUp(int keyCode, KeyEvent event) { key = !key; return false; } @Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { gl.glDisable(GL10.GL_DITHER); // 告诉系统对透视进行修正 gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST); // 黑色背景 gl.glClearColor(0, 0, 0, 0); gl.glEnable(GL10.GL_CULL_FACE); // 启用阴影平滑 gl.glShadeModel(GL10.GL_SMOOTH); // 启用深度测试 gl.glEnable(GL10.GL_DEPTH_TEST); // 设置光线,,1.0f为全光线,a=50% gl.glColor4f(1.0f, 1.0f, 1.0f, 0.5f); // 基于源象素alpha通道值的半透明混合函数 gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE); // 纹理相关 GLTextureUtil(gl); // 深度测试相关 gl.glClearDepthf(1.0f); gl.glDepthFunc(GL10.GL_LEQUAL); gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_NICEST); gl.glEnable(GL10.GL_TEXTURE_2D); // 设置环境光 gl.glLightfv(GL10.GL_LIGHT1, GL10.GL_AMBIENT, lightAmbient); // 设置漫射光 gl.glLightfv(GL10.GL_LIGHT1, GL10.GL_DIFFUSE, lightDiffuse); // 设置光源位置 gl.glLightfv(GL10.GL_LIGHT1, GL10.GL_POSITION, lightPosition); // 开启一号光源 gl.glEnable(GL10.GL_LIGHT1); // 开启混合 gl.glEnable(GL10.GL_BLEND); } @Override public void onSurfaceChanged(GL10 gl, int width, int height) { float ratio = (float) width / height; // 设置OpenGL场景的大小 gl.glViewport(0, 0, width, height); // 设置投影矩阵 gl.glMatrixMode(GL10.GL_PROJECTION); // 重置投影矩阵 gl.glLoadIdentity(); // 设置视口的大小 gl.glFrustumf(-ratio, ratio, -1, 1, 1, 10); // 选择模型观察矩阵 gl.glMatrixMode(GL10.GL_MODELVIEW); // 重置模型观察矩阵 gl.glLoadIdentity(); } @Override public void onDrawFrame(GL10 gl) { // 清除屏幕和深度缓存 gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT); gl.glMatrixMode(GL10.GL_MODELVIEW); // 重置当前的模型观察矩阵 gl.glLoadIdentity(); gl.glEnable(GL10.GL_LIGHTING); gl.glTranslatef(0.0f, 0.0f, z); // 设置旋转 gl.glRotatef(xrot, 0.0f, 1.0f, 0.0f); gl.glRotatef(yrot, 1.0f, 0.0f, 0.0f); // 设置法线 gl.glNormalPointer(GL10.GL_FIXED, 0, normals); // size:指定了每个顶点对应的坐标个数,只能是2,3,4中的一个,默认值是4 // type:指定了数组中每个顶点坐标的数据类型,可取常量:GL_BYTE, GL_SHORT,GL_FIXED,GL_FLOAT; // stride:指定了连续顶点间的字节排列方式,如果为0,数组中的顶点就会被认为是按照紧凑方式排列的,默认值为0 // pointer:制订了数组中第一个顶点的首地址,默认值为0,对于我们的android,大家可以不用去管什么地址的,一般给一个int就可以了 gl.glVertexPointer(3, GL10.GL_FIXED, 0, vertices); // 设置顶点数组为纹理坐标缓存 gl.glTexCoordPointer(2, GL10.GL_FIXED, 0, texCoords); // glEnableClientState - glDisableClientState这对。 // 它们的区别是通知的具体对象在概念上不一样——分别是服务端和客户端。 // 事实上我也无法很清楚地解释清楚,反正电脑上的具体程序,包括它用到 // 的内存等等看作客户端,把你电脑里面的——显卡里的OpenGL“模块”,乃 // 至整张拥有OpenGL流水线、硬件实现OpenGL功能的显卡,作为服务端。 // 它们各自维护一些“状态”,glEnable 等是直接维护流水线处理相关的状态的, // glEnableClientState 维护的则是进入流水线前的状态 gl.glEnableClientState(GL10.GL_NORMAL_ARRAY); gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY); // 绘制四边形 BindElementsWithTexture(gl); // 修改旋转角度 // xrot+=0.3f; // yrot+=0.2f; // 混合开关 if (key) { gl.glEnable(GL10.GL_BLEND); // 打开混合 gl.glDisable(GL10.GL_DEPTH_TEST); // 关闭深度测试 } else { gl.glDisable(GL10.GL_BLEND); // 关闭混合 gl.glEnable(GL10.GL_DEPTH_TEST); // 打开深度测试 } } // 绘制四边形和Texture元素绑定 public void BindElementsWithTexture(GL10 gl) { gl.glBindTexture(GL10.GL_TEXTURE_2D, texture[0]); gl.glDrawElements(GL10.GL_TRIANGLE_STRIP, 4, GL10.GL_UNSIGNED_BYTE, indices1); gl.glBindTexture(GL10.GL_TEXTURE_2D, texture[1]); gl.glDrawElements(GL10.GL_TRIANGLE_STRIP, 8, GL10.GL_UNSIGNED_BYTE, indices2); gl.glBindTexture(GL10.GL_TEXTURE_2D, texture[2]); gl.glDrawElements(GL10.GL_TRIANGLE_STRIP, 12, GL10.GL_UNSIGNED_BYTE, indices3); gl.glBindTexture(GL10.GL_TEXTURE_2D, texture[3]); gl.glDrawElements(GL10.GL_TRIANGLE_STRIP, 16, GL10.GL_UNSIGNED_BYTE, indices4); gl.glBindTexture(GL10.GL_TEXTURE_2D, texture[4]); gl.glDrawElements(GL10.GL_TRIANGLE_STRIP, 20, GL10.GL_UNSIGNED_BYTE, indices5); gl.glBindTexture(GL10.GL_TEXTURE_2D, texture[5]); gl.glDrawElements(GL10.GL_TRIANGLE_STRIP, 24, GL10.GL_UNSIGNED_BYTE, indices6); gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY); gl.glDisableClientState(GL10.GL_VERTEX_ARRAY); gl.glDisableClientState(GL10.GL_NORMAL_ARRAY); } public FloatBuffer bufferUtil(float[] arr) { FloatBuffer buffer; ByteBuffer qbb = ByteBuffer.allocateDirect(arr.length * 6); qbb.order(ByteOrder.nativeOrder()); buffer = qbb.asFloatBuffer(); buffer.put(arr); buffer.position(0); return buffer; } public IntBuffer bufferUtil(int[] arr) { IntBuffer buffer; ByteBuffer qbb = ByteBuffer.allocateDirect(arr.length * 6); qbb.order(ByteOrder.nativeOrder()); buffer = qbb.asIntBuffer(); buffer.put(arr); buffer.position(0); return buffer; } // Texture纹理初始化 public void GLTextureUtil(GL10 gl) { IntBuffer textureBuffer = IntBuffer.allocate(6); gl.glGenTextures(6, textureBuffer); texture = textureBuffer.array(); gl.glBindTexture(GL10.GL_TEXTURE_2D, texture[0]); GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, GLImage.mBitmap1, 0); gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_NEAREST); gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST); gl.glBindTexture(GL10.GL_TEXTURE_2D, texture[1]); GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, GLImage.mBitmap2, 0); gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_NEAREST); gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST); gl.glBindTexture(GL10.GL_TEXTURE_2D, texture[2]); GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, GLImage.mBitmap3, 0); gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_NEAREST); gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST); gl.glBindTexture(GL10.GL_TEXTURE_2D, texture[3]); GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, GLImage.mBitmap4, 0); gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_NEAREST); gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST); gl.glBindTexture(GL10.GL_TEXTURE_2D, texture[4]); GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, GLImage.mBitmap5, 0); gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_NEAREST); gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST); gl.glBindTexture(GL10.GL_TEXTURE_2D, texture[5]); GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, GLImage.mBitmap6, 0); gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_NEAREST); gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST); } boolean key = true; float xrot = 0.0f; float yrot = 0.0f; float xspeed, yspeed; float z = -5.0f; int one = 0x10000;//void glVertexPointer(GLint size,
//GLenum type,
//GLsizei stride,
//const GLvoid * pointer)这里不同的数据类型含义不同,如果选择GL_FIXED,那么0x10000表示单位长度,如果选择GL_FLOAT那么1.0f表示单位度。) // 光线参数 FloatBuffer lightAmbient; FloatBuffer lightDiffuse; FloatBuffer lightPosition; float[] light1 = new float[] { 0.5f, 0.5f, 0.5f, 1.0f }; float[] light2 = new float[] { 1.0f, 1.0f, 1.0f, 1.0f }; float[] light3 = new float[] { 0.0f, 0.0f, 2.0f, 1.0f }; int[] texture; IntBuffer vertices; IntBuffer normals; IntBuffer texCoords; int[] verticesdata = new int[] { -one,-one,one, one,-one,one, one,one,one, -one,one,one, -one,-one,-one, -one,one,-one, one,one,-one, one,-one,-one, -one,one,-one, -one,one,one, one,one,one, one,one,-one, -one,-one,-one, one,-one,-one, one,-one,one, -one,-one,one, one,-one,-one, one,one,-one, one,one,one, one,-one,one, -one,-one,-one, -one,-one,one, -one,one,one, -one,one,-one, }; int[] normalsdata = new int[] { 0,0,one, 0,0,one, 0,0,one, 0,0,one, 0,0,one, 0,0,one, 0,0,one, 0,0,one, 0,one,0, 0,one,0, 0,one,0, 0,one,0, 0,-one,0, 0,-one,0, 0,-one,0, 0,-one,0, one,0,0, one,0,0, one,0,0, one,0,0, -one,0,0, -one,0,0, -one,0,0, -one,0,0, }; int[] texCoordsdata = new int[] { one,0,0,0,0,one,one,one, 0,0,0,one,one,one,one,0, one,one,one,0,0,0,0,one, 0,one,one,one,one,0,0,0, 0,0,0,one,one,one,one,0, one,0,0,0,0,one,one,one, }; ByteBuffer indices1 = ByteBuffer.wrap(new byte[] { 0,1,3,2, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, }); ByteBuffer indices2 = ByteBuffer.wrap(new byte[] { 0,0,0,0, 4,5,7,6, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, }); ByteBuffer indices3 = ByteBuffer.wrap(new byte[] { 0,0,0,0, 0,0,0,0, 8,9,11,10, 0,0,0,0, 0,0,0,0, 0,0,0,0, }); ByteBuffer indices4 = ByteBuffer.wrap(new byte[] { 0,0,0,0, 0,0,0,0, 0,0,0,0, 12,13,15,14, 0,0,0,0, 0,0,0,0, }); ByteBuffer indices5 = ByteBuffer.wrap(new byte[] { 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 16,17,19,18, 0,0,0,0, }); ByteBuffer indices6 = ByteBuffer.wrap(new byte[] { 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 20,21,23,22, }); }