Android下Opengl ES导引.docx
《Android下Opengl ES导引.docx》由会员分享,可在线阅读,更多相关《Android下Opengl ES导引.docx(46页珍藏版)》请在冰点文库上搜索。
![Android下Opengl ES导引.docx](https://file1.bingdoc.com/fileroot1/2023-4/30/dfa09e86-941a-4233-9de5-70d0c18259a1/dfa09e86-941a-4233-9de5-70d0c18259a11.gif)
Android下OpenglES导引
Android下OpenglES导引
ch2zh1@编译(2011-8-7)
-第一部分
这里打算写几篇关于Android手机上的openGLES导引文章。
对于不同的设备,OpenGLES的理论是相同的,所以,也可以很容易就把这个导引转换到其它平台上。
我不能总记得我曾在什么地方找到过特殊信息,所以这里给出的参考文献就引文来讲可能并不总是正确的。
如果你认为我这里引用了你的资料,而又忘记把你的名字添加到引用者中,请给我发邮件。
在例子代码中,每一个函数都有两个不同的链接。
一个链接是链接到Android资料,而后一个将链接到OpenGL资料。
例如:
gl.glClearColor(0.0f,0.0f,0.0f,0.5f);//OpenGL资料。
注:
在翻译文档中,不考虑资料的链接,如果想查看原文,请收索《OpenGLESTutorialforAndroid》。
现在就让我们开始吧。
在这个导引中我将向你展示怎样设置你的OpenGL观察控件,这总是一个最好的开始。
设置OpenGLES观察
设置OpenGL观察并不困难,而且在Android上它还相当容易。
实际上你只需最两件事情就可以了。
GLSurfaceView
GLSurfaceView是Android1.5中一个API类,用于辅助编写OpenGLES应用的。
●提供胶粘代码来连接OpenGLES到Android的View观察系统。
●提供胶粘代码来使OpenGLES与活动的生命期一同工作。
●使它易于选择一个适当的帧缓冲像素格式。
●建立个管理单独的演播线程,使动画效果平滑。
●提供易于使用的排错工具,跟踪OpenGLESAPI的调用和检查错误。
如果你想要快速的开发OpenGLES应用,现在就应该可以开始了。
唯一的一个你需要调用的函数是:
PublicvoidsetRender(GLSurfaceView.Renderrenderer)
请阅读GLSurfaceView来获得更多的帮助。
GLSurfaceView.Renderer
GLSurfaceView.Renderer是一个普通的演播器接口。
在你自己的演播器实现中,应该包含所有演播一个帧的调用。
有三个函数需要实现:
//在表面建立时或再建立时调用的函数
publicvoidonSurfacCreated(GL10gl,EGLConfigconfig)
//画当前帧时调用
publicvoidonDrawFrame(GL10gl)
//表面尺寸改变时调用
PublicvoidonSuraceChanged(GL10gl,intwidth,intheight)
onSurfaceCreated
在这里通常设置演播期间不改变的所有东西。
比如屏幕要清除到的颜色,允许Z-缓冲等。
onDrawFrame
这里是实际绘画发生的地方。
onSurfaceChanged
如果你的设备支持水平和垂直翻转,当翻转发生时,这个函数则被调用。
在这里你要做的是设置比率的变化。
请阅读GLSurfaceView.Renderer来获得更多的帮助。
把这些都穿连到一起
首先建立一个活动(activity),我们要保持它的清晰和简单。
packagese.jayway.opengl.tutorial;
importandroid.app.Activity;
importandroid.opengl.GLSurfaceView;
importandroid.os.Bundle;
publicclassTutorialPartIextendsActivity{
/**Calledwhentheactivityisfirstcreated.*/
@Override
publicvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
GLSurfaceViewview=newGLSurfaceView(this);
view.setRenderer(newOpenGLRenderer());
setContentView(view);
}
}
我们的演播器有一小点设置工作要做,代码如下,我们也更详细一点地解释这些代码。
packagese.jayway.opengl.tutorial;
importjavax.microedition.khronos.egl.EGLConfig;
importjavax.microedition.khronos.opengles.GL10;
importandroid.opengl.GLU;
importandroid.opengl.GLSurfaceView.Renderer;
publicclassOpenGLRendererimplementsRenderer{
/*
*(non-Javadoc)
*
*@see
*android.opengl.GLSurfaceView.Renderer#onSurfaceCreated(javax.
*microedition.khronos.opengles.GL10,javax.microedition.khronos.
*egl.EGLConfig)
*/
publicvoidonSurfaceCreated(GL10gl,EGLConfigconfig){
//设置背景那个颜色到黑色(rgba).
gl.glClearColor(0.0f,0.0f,0.0f,0.5f);//OpenGLdocs.
//允许阴影平滑,默认也可以,不是必须的
gl.glShadeModel(GL10.GL_SMOOTH);//OpenGLdocs.
//深度缓冲设置
gl.glClearDepthf(1.0f);//OpenGLdocs.
//允许深度检测
gl.glEnable(GL10.GL_DEPTH_TEST);//OpenGLdocs.
//要做的深度检测类型
gl.glDepthFunc(GL10.GL_LEQUAL);//OpenGLdocs.
//真是细微的透视计算
gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT,//OpenGLdocs.
GL10.GL_NICEST);
}
/*
*(non-Javadoc)
*
*@see
*android.opengl.GLSurfaceView.Renderer#onDrawFrame(javax.
*microedition.khronos.opengles.GL10)
*/
publicvoidonDrawFrame(GL10gl){
//清除屏幕和深度缓冲
gl.glClear(GL10.GL_COLOR_BUFFER_BIT|//OpenGLdocs.
GL10.GL_DEPTH_BUFFER_BIT);
}
/*
*(non-Javadoc)
*
*@see
*android.opengl.GLSurfaceView.Renderer#onSurfaceChanged(javax.
*microedition.khronos.opengles.GL10,int,int)
*/
publicvoidonSurfaceChanged(GL10gl,intwidth,intheight){
//设置当前视口到新尺寸
gl.glViewport(0,0,width,height);//OpenGLdocs.
//选择投影矩阵
gl.glMatrixMode(GL10.GL_PROJECTION);//OpenGLdocs.
//重置投影矩阵
gl.glLoadIdentity();//OpenGLdocs.
//计算窗口的外观比率
GLU.gluPerspective(gl,45.0f,
(float)width/(float)height,
0.1f,100.0f);
//选择模式观察矩阵
gl.glMatrixMode(GL10.GL_MODELVIEW);//OpenGLdocs.
//重置模式观察矩阵
gl.glLoadIdentity();//OpenGLdocs.
}
}
全屏显示
把下面这些行代码加到OpenGLDemo类中,这将设置应用到全屏显示。
publicvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
this.requestWindowFeature(Window.FEATURE_NO_TITLE);//(NEW)
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);//(NEW)
...//前面的代码
}
这无论在外观和运行上都漂亮得多。
如果你编译并运行这个Demo,将会看到一个完美的黑色屏幕。
-第二部分
前面的第一部分说明了怎样设置GLSurfaceView的所有技术。
一定要仔细阅读,这实际上是能够继续下去的重要条件。
建立多边形
在这一节中我们将首先建立和演播一个多边形。
建立3D模式使用较少的元素(顶点,边,表面,和多边形),这些都可以单独处理。
顶点(Vertex)
顶点(vertex/vertices)是3D模型的最小构建块。
一个顶点就是一个点,在这一点上两个或多个边交接。
在3D模型中,一个顶点可以在所有相关联的边、面和多边形中共享。
顶点也可以是照相机或光源位置的表示。
在下图中你可以看到用黄色标注的顶点。
要在Android中定义顶点,我们使用浮点数组,
把它们放进字节缓冲来获得较好的性能,请看右
侧的图像,匹配顶点的代码如下:
privatefloatvertices[]={
-1.0f,1.0f,0.0f,//0,左上
-1.0f,-1.0f,0.0f,//1,坐下
1.0f,-1.0f,0.0f,//2,右下
1.0f,1.0f,0.0f,//3,右上
};
//一个浮点是4个字节,因此要用顶点数乘以4。
ByteBuffervbb=ByteBuffer.allocateDirect(vertices.length*4);
vbb.order(ByteOrder.nativeOrder());
FloatBuffervertexBuffer=vbb.asFloatBuffer();
vertexBuffer.put(vertices);
vertexBuffer.position(0);
不要忘了浮点是4字节,用顶点数乘以它来获得正确的缓冲尺寸。
OpenGLES在演播时有一种途径使用这些函数。
默认情况下,绝大多数这些函数都是不被允许的,所以你必须记住要打开你可能要是用的函数。
你也可能需要告诉这些函数所要做的工作。
因此,在我们这个顶点的例子中,需要告诉OpenGLES准备好了用我们建立的顶点缓冲来进行工作,我们还需要告诉它缓冲在哪儿。
//在演播时允许顶点缓冲写入和使用。
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);//OpenGLdocs.
//指定顶点数组的位置和格式
//演播时使用的坐标
gl.glVertexPointer(3,GL10.GL_FLOAT,0,vertexBuffer);//OpenGLdocs.
在使用完后一定不要忘了禁止它。
//禁止顶点缓冲
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);//OpenGLdocs.
边(Edge)
边是在两个顶点之间的线段。
它们是表面和多边形的边缘线。
在3D模型中,便可以在两个邻近表面或多边形之间共享。
转换一条边,影响所有相连接的顶点,面和多边形。
在OpenGLES中,我们不能定义边,你只能通过给定的顶点定义面,这将至少构建三条边。
如果你想要修改一条边,你应该改变产生这条边的两个顶点。
在下图中,你可以看到用黄色标注的边。
面(Face)
面是一个三角形,面就是这三个角顶点和三个边所围成的表面,变换一个面影响所有关联的顶点,边和多边形。
顺序的关系
在环绕面的时候,重要都是右方向环绕,因为环绕方向定义了面的那一边是正面那一边是背面。
为什么这很重要呢,因为要获得性能上的提高,我们不想画两个边,所以关闭了背面。
一个好的方法是在整个工程中都使用相同的环绕方向。
可以使用函数glFrontFace来改变哪个环绕方向定义正面。
gl.glFrontFace(GL10.GL_CCW);//OpenGLdocs
要使OpenGLES跳过已经画在屏幕上的面,可以使用某些称之为背面消隐的方法。
这所要做的就是通过检查右序的环绕方向确定是否一个图形多边形对象是可视的。
gl.glEnable(GL10.GL_CULL_FACE);//OpenGLdocs
当然可以改变面的那一边应该被画出,或不被画出。
gl.glCullFace(GL10.GL_BACK);//OpenGLdocs
多边形(Polygon)
在环绕面时,一定要记住我们已经确定使用默认的环绕方向,也就是说逆时针方向。
如右图所示,下面的代码说明了怎样环绕这个方块的。
privateshort[]indices={0,1,2,0,2,3};
为了获得性能的提高,我们把这个也放到字节缓冲区中。
//短整数(short)占两个字节,因而我们用顶点数乘以2。
ByteBufferibb=ByteBuffer.allocateDirect(indices.length*2);
ibb.order(ByteOrder.nativeOrder());
ShortBufferindexBuffer=ibb.asShortBuffer();
indexBuffer.put(indices);
indexBuffer.position(0);
不要忘了短整数占两个字节,所以用顶点数乘以2是字节缓冲的正确尺寸。
演播器(Render)
是要在屏幕上观察效果的时候了。
有两个函数用来画图,我们必须确定使用哪一个。
这两个函数是:
publicabstractvoidglDrawArrays(intmode,intfirst,intcount)//OpenGLdocs
glDrawArrays函数使用我们在构造缓冲区verticesBuffer时的顶点顺序画顶点构图。
publicabstractvoidglDrawElements(intmode,intcount,inttype,//OpenGLdocs
Bufferindices)
glDrawElements函数需要稍微多一点参数才能画顶点构图。
它需要知道所要画顶点的顺序,也就是一个索引缓冲(indicesBuffer)。
假设我们已经给出了一个索引缓冲(这是我们以后打算使用的方式)。
这个函数所要做的是它不仅要知道它应该画的是什么,还需要知道演播的原始风格。
由于有一些不同的方法来演播这种索引,并且有些方法对于排错而言是不错的方法,所以我们将这些方法统统讨论一遍。
什么是演播的原始风格(primitives)
GL_POINTS
在屏幕上画不同的点。
GL_LINE_STRIP
一系列相连的线段。
GL_LINE_LOOP
有点类似于上面的只是增加了最后顶点到第一个顶点的线段,画一个不和曲线段。
GL_LINES
一对顶点被解释成不同的线段。
GL_TRIANGLES
顶点被解释成三角形。
GL_TRIANGLE_STRIP
画一系列三角形(三个边的多边形)使用顶点v0,v1,v2,然后v2,v1,v3(注意顺序),然后v2,v3,v4,等等。
顺序是保证所画三角形的正面方向相同。
这个带子能正确地形成表面的一部分。
GL_TRIANGLE_FAN
类似于GL_TRIANGLE_STRIP,除了顶点的顺序是v0,v1,v2,然后v0,v2,v3,然后v0,v3,v4,等等以外。
我认为GL_TRIANGLES是是最容易使用的,所以我们现在就开始使用它。
把这些都穿连到一起
现在,我们把正方形放到一个类中。
packagese.jayway.opengl.tutorial;
importjava.nio.ByteBuffer;
importjava.nio.ByteOrder;
importjava.nio.FloatBuffer;
importjava.nio.ShortBuffer;
importjavax.microedition.khronos.opengles.GL10;
publicclassSquare{
//顶点.
privatefloatvertices[]={
-1.0f,1.0f,0.0f,//0,左上
-1.0f,-1.0f,0.0f,//1,左下
1.0f,-1.0f,0.0f,//2,右下
1.0f,1.0f,0.0f,//3,右上
};
//我们想要连接这些顶点的顺序。
privateshort[]indices={0,1,2,0,2,3};
//顶点缓冲
privateFloatBuffervertexBuffer;
//索引缓冲
privateShortBufferindexBuffer;
publicSquare(){
//一个浮点数有四个字节,因此需要用顶点数乘以4。
ByteBuffervbb=ByteBuffer.allocateDirect(vertices.length*4);
vbb.order(ByteOrder.nativeOrder());
vertexBuffer=vbb.asFloatBuffer();
vertexBuffer.put(vertices);
vertexBuffer.position(0);
//短整数占两个字节,因此需要用顶点数乘以2
//来得索引缓冲的正确尺寸
ByteBufferibb=ByteBuffer.allocateDirect(indices.length*2);
ibb.order(ByteOrder.nativeOrder());
indexBuffer=ibb.asShortBuffer();
indexBuffer.put(indices);
indexBuffer.position(0);
}
/**
*这个函数画正方形到屏幕上。
*@paramgl
*/
publicvoiddraw(GL10gl){
//逆时针环绕。
gl.glFrontFace(GL10.GL_CCW);//OpenGLdocs
//允许面遴选。
gl.glEnable(GL10.GL_CULL_FACE);//OpenGLdocs
//什么样的遮蔽面被删除(指定背面)。
gl.glCullFace(GL10.GL_BACK);//OpenGLdocs
//允许演播时顶点缓冲的写入操作。
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);//OpenGLdocs.
//指定演播时顶点数组中数据格式和位置坐标。
gl.glVertexPointer(3,GL10.GL_FLOAT,0,//OpenGLdocs
vertexBuffer);
gl.glDrawElements(GL10.GL_TRIANGLES,indices.length,//OpenGLdocs
GL10.GL_UNSIGNED_SHORT,indexBuffer);
//禁止顶点缓冲。
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);//OpenGLdocs
//禁止面遴选。
gl.glDisable(GL10.GL_CULL_FACE);//OpenGLdocs
}
}
在OpenGLRenderer类中,我们必须要初始化这个方块。
//初始化方块类。
Squaresquare=newSquare();
在画图函数中调用这个花方块的对象。
publicvoidonDrawFrame(GL10gl){
//清除屏幕和深度缓冲。
gl.glClear(GL10.GL_COLOR_BUFFER_BIT|//OpenGLdocs.
GL10.GL_DEPTH_BUFFER_BIT);
//画方块。
square.draw(gl);//(NEW)
}
现在,如果你运行这个应用,屏幕仍然是黑的。
为什么呢?
因为OpenGLES是从当前位置开始演播的。
默认的位置是点:
0,0,0这与观察口所定的位置相同。
而且OpenGLES不能演播任何太靠近观察口的东西。
解决方法是在演播这个方块之前移动画图位置稍许进入到屏幕内部几步:
//变换到屏幕内部4个单位。
gl.glTranslatef(0,0,-4);//OpenGLdocs
在随后的各节中我们将讲解各种不同的变换。
再次运行这个应用,你将看到一个方块,但是很快就越来越远地移动到屏幕深处。
OpenGLES在各帧之间并不重置画图点,这需要你自己来做:
//用同一性矩阵替换当前矩阵(画图点返回坐标原点)
gl.glLoadIdentity();//OpenGLdocs
现在如果运行这个应用,你就能在固定位置上看到一个方块。
-第三部分
上一节我们构建了多边形的概念,这一节我们就讲解有关怎样移动多边形的变换。
我们将继续使用前面构建的多边形概念,这样就可以继续使用上面给出的源代码或它的拷贝。
我并不打算用数学知识来烦扰你,但是我确信了解OpenGL演播一个网栅物体时使用矩阵乘以这个网栅的所有定点是非常重要的