分类: android开发2013-01-23 11:01 10295人阅读 评论(9) 收藏 举报
androidAndroidANDROIDOpenGLopenglopenGLOPENGLOpenglOpenGl
目录(?)[+]
(欢迎大家加入android技术交流QQ群:209796692)
为了能在你的Android应用中使用OpenGLES绘画,你必须创建一个view作为容器。而最直接的方式就是从GLSurfaceView和GLSurfaceView.Renderer分别派生一个类。GLSurfaceView作为OpenGL绘制所在的容器,而实际的绘图动作都是在GLSurfaceView.Renderer里面发生的。
使用GLSurfaceView几乎是整合OpenGLES到你的应用的唯一方式。对于一个全屏或近全屏的graphicsview,它是最好的选择。如果只是在某个小部分显示OpenGLES图形则可以考虑TextureView。当然如果你比较变态,你完全可以使用SurfaceView创建一个OpenGLES view。
本教程演示如何完成一个最小实现的OpenGLES2.0应用。
为了能使用OpenGLES 2.0 API,你必须在你的manifest中添加以下声明:
如果你的应用要使用纹理压缩功能,你必须还要声明设备需要支持什么样的压缩格式:
这个Activity与其它类型应用的Activity并无不同,要说不同,也仅仅是放到Activity的layout的View不一样,你需要放入的是一个GLSurfaceView。
下面的代码演示了使用GLSurfaceView作为主视图的Acitivity的最少代码实现:
public class OpenGLES20 extends Activity { private GLSurfaceView mGLView; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //创建一个GLSurfaceView实例然后设置为activity的ContentView. mGLView = new MyGLSurfaceView(this); setContentView(mGLView); }}
注:OpenGL ES 2.0需要Android2.2 (API Level 8) 及以上版本。
GLSurfaceView中其实不需要做太多工作,实际的绘制任务都在GLSurfaceView.Renderer中了。所以GLSurfaceView中的代码也非常少,你甚至可以直接使用GLSurfaceView,但是,然而,别这样做。因为你需要扩展这个类来响应触摸事件啊孩子。
扩展GLSurfaceView的类像这样写就可以了:
class MyGLSurfaceView extends GLSurfaceView { public MyGLSurfaceView(Context context){ super(context); //设置Renderer到GLSurfaceView setRenderer(new MyRenderer()); }}
当使用OpenGLES 2.0时,你必须在GLSurfaceView构造器中调用另外一个函数,它说明了你将要使用2.0版的API:
// 创建一个OpenGL ES 2.0 contextsetEGLContextClientVersion(2);
另一个可以添加的你的GLSurfaceView实现的可选的操作是设置render模式为只在绘制数据发生改变时才绘制view。使用GLSurfaceView.RENDERMODE_WHEN_DIRTY:
// 只有在绘制数据改变时才绘制viewsetRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
此设置会阻止绘制GLSurfaceView的帧,直到你调用了requestRender(),这样会非常高效。
此类控制向GLSurfaceView的绘制工作。它有三个方法被Android系统调用来计算在GLSurfaceView上画什么以及如何画。
·
onSurfaceCreated()- 仅调用一次,用于设置view的OpenGLES环境。
·
·
onDrawFrame()- 每次View被重绘时被调用。
·
·
onSurfaceChanged()- 如果view的几和形状发生变化了就调用,例如当竖屏变为横屏时。
·
下面是一个OpenGLES renderer的最基本的实现,它仅在GLSurfaceView上画了一个灰色的背景:
public class MyGL20Renderer implements GLSurfaceView.Renderer { public void onSurfaceCreated(GL10 unused, EGLConfig config) { //设置背景的颜色 GLES20.glClearColor(0.5f, 0.5f, 0.5f, 1.0f); } public void onDrawFrame(GL10 unused) { // 重绘背景色 GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); } public void onSurfaceChanged(GL10 unused, int width, int height) { GLES20.glViewport(0, 0, width, height); }}
以上就是所有需要做的东西!上面的代码们创建了一个简单的Android应用,它使用OpenGL显示了一个灰色的屏幕。但这段代码并没有做什么有趣的事情,只是为使用OpenGL绘图做好了准备。
注:你可以不明白为什么这些方法们都具有一个GL10参数,但你使用的却是OpengGLES 2.0 API们。这其实是为了使Android框架能简单的兼容各OpenGLES版本而做的。
如果你对OpenGLES API很熟悉,你其实现在已经可以开始进行绘图了。然而,如果你熟悉,就继续学习下一章吧。
分类: android开发2013-01-31 14:42 11972人阅读 评论(6) 收藏 举报
androidAndroidANDROIDOpenGLopenglopenGLOPENGLOpenglOpenGl
目录(?)[+]
(欢迎大家加入android技术交流QQ群:209796692)
会定义在OpenGLES view上所绘制的形状,是你创建高端图形应用杰作的第一步。如果你不懂OpenGLES定义图形对象的一些基本知识,使用OpenGLES可能有一点棘手。
本文解释OpenGLES相对于Android设备屏幕的坐标系统、定义一个形状的基础知识、形状的外观、以及如何定义三角形和正方形。
OpenGLEs允许你使用坐本在三个维度上定义绘制对象。所以,在你可以绘制一个三角形之前,你必须定义它的坐标。在OpenGL中,典型的方式是为坐标定义一个浮点类型的顶点数组。为了最高效,你应把这些坐标都写进一个ByteBuffer,它会被传到OpenGLES图形管线以进行处理。
class Triangle { private FloatBuffer vertexBuffer; // 数组中每个顶点的坐标数 static final int COORDS_PER_VERTEX = 3; static float triangleCoords[] = { // 按逆时针方向顺序: 0.0f, 0.622008459f, 0.0f, // top -0.5f, -0.311004243f, 0.0f, // bottom left 0.5f, -0.311004243f, 0.0f // bottom right }; // 设置颜色,分别为red, green, blue 和alpha (opacity) float color[] = { 0.63671875f, 0.76953125f, 0.22265625f, 1.0f }; public Triangle() { // 为存放形状的坐标,初始化顶点字节缓冲 ByteBuffer bb = ByteBuffer.allocateDirect( // (坐标数 * 4)float占四字节 triangleCoords.length * 4); // 设用设备的本点字节序 bb.order(ByteOrder.nativeOrder()); // 从ByteBuffer创建一个浮点缓冲 vertexBuffer = bb.asFloatBuffer(); // 把坐标们加入FloatBuffer中 vertexBuffer.put(triangleCoords); // 设置buffer,从第一个坐标开始读 vertexBuffer.position(0); }}
缺省情况下,OpenGLES 假定[0,0,0](X,Y,Z) 是GLSurfaceView 帧的中心,[1,1,0]是右上角,[-1,-1,0]是左下角。
注意此形状的坐标是按逆时针方向定义的。绘制顺序很重要,因为它定义了哪面是形状的正面,哪面是反面,使用OpenGLES 的cullface特性,你可以只画正面而不画反面。
在OpenGL中定义正方形是十分容易的,有很多方法能做的,但是典型的做法是使用两个三角形:
图1.使用两个三角形画一个正方形
你要为两个三角形都按逆时针方向定义顶点们,并且将这些坐标值们放入一个ByteBuffer中。为了避免分别为两个三角形定义两个坐标数组,我们使用一个绘制列表来告诉OpenGLES图形管线如果画这些顶点们。下面就是这个形状的代码:
class Square { private FloatBuffer vertexBuffer; private ShortBuffer drawListBuffer; // 每个顶点的坐标数 static final int COORDS_PER_VERTEX = 3; static float squareCoords[] = { -0.5f, 0.5f, 0.0f, // top left -0.5f, -0.5f, 0.0f, // bottom left 0.5f, -0.5f, 0.0f, // bottom right 0.5f, 0.5f, 0.0f }; // top right private short drawOrder[] = { 0, 1, 2, 0, 2, 3 }; // 顶点的绘制顺序 public Square() { // initialize vertex byte buffer for shape coordinates ByteBuffer bb = ByteBuffer.allocateDirect( // (坐标数 * 4) squareCoords.length * 4); bb.order(ByteOrder.nativeOrder()); vertexBuffer = bb.asFloatBuffer(); vertexBuffer.put(squareCoords); vertexBuffer.position(0); // 为绘制列表初始化字节缓冲 ByteBuffer dlb = ByteBuffer.allocateDirect( // (对应顺序的坐标数 * 2)short是2字节 drawOrder.length * 2); dlb.order(ByteOrder.nativeOrder()); drawListBuffer = dlb.asShortBuffer(); drawListBuffer.put(drawOrder); drawListBuffer.position(0); }}
本例让你见识了用OpenGL如何创建更复杂的形状。通常,你都是使用一群小三(三角形)来绘制对象。下一章,你将学会如何将这些形状画到屏幕上。
分类: android开发2013-02-20 10:02 13677人阅读 评论(13) 收藏 举报
OpenGLES
目录(?)[+]
你定义了要绘制的形状后,你就要画它们了。使用OpenGLES 2.0会形状会有一点点复杂,因为API提供了大量的对渲染管线的控制能力。
本文讲解如何绘制你在前文中定义的那些形状们。
在你做任何绘制之前,你必须初始化形状然后加载它。除非形状的结构(指原始的坐标们)在执行过程中发生改变,你都应该在你的Renderer的方法onSurfaceCreated()中进行内存和效率方面的初始化工作。
public void onSurfaceCreated(GL10 unused, EGLConfig config) { ... // 初始化一个三角形 mTriangle = new Triangle(); // 初始化一个正方形 mSquare = new Square();}
使用OpenGLES 2.0画一个定义好的形状需要一大坨代码,因为你必须为图形渲染管线提供一大堆信息。典型的,你必须定义以下几个东西:
·
VertexShader-用于渲染形状的顶点的OpenGLES 图形代码。
·
·
FragmentShader-用于渲染形状的外观(颜色或纹理)的OpenGLES 代码。
·
·
Program-一个OpenGLES对象,包含了你想要用来绘制一个或多个形状的shader。
·
你至少需要一个vertexshader来绘制一个形状和一个fragmentshader来为形状上色。这些形状必须被编译然后被添加到一个OpenGLES program中,program之后被用来绘制形状。下面是一个展示如何定义一个可以用来绘制形状的基本shader的例子:
private final String vertexShaderCode = "attribute vec4 vPosition;" + "void main() {" + " gl_Position = vPosition;" + "}";private final String fragmentShaderCode = "precision mediump float;" + "uniform vec4 vColor;" + "void main() {" + " gl_FragColor = vColor;" + "}";
Shader们包含了OpenGLShading Language (GLSL)代码,必须在使用前编译。要编译这些代码,在你的Renderer类中创建一个工具类方法:
public static int loadShader(int type, String shaderCode){ // 创建一个vertex shader类型(GLES20.GL_VERTEX_SHADER) // 或fragment shader类型(GLES20.GL_FRAGMENT_SHADER) int shader = GLES20.glCreateShader(type); // 将源码添加到shader并编译之 GLES20.glShaderSource(shader, shaderCode); GLES20.glCompileShader(shader); return shader;}
为了绘制你的形状,你必须编译shader代码,添加它们到一个OpenGLES program 对象然后链接这个program。在renderer对象的构造器中做这些事情,从而只需做一次即可。
注:编译OpenGLES shader们和链接linkingprogram们是很耗CPU的,所以你应该避免多次做这些事。如果在运行时你不知道shader的内容,你应该只创建一次code然后缓存它们以避免多次创建。
public Triangle() { ... int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode); int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode); mProgram = GLES20.glCreateProgram(); // 创建一个空的OpenGL ES Program GLES20.glAttachShader(mProgram, vertexShader); // 将vertex shader添加到program GLES20.glAttachShader(mProgram, fragmentShader); // 将fragment shader添加到program GLES20.glLinkProgram(mProgram); // 创建可执行的 OpenGL ES program}
此时,你已经准备好增加真正的绘制调用了。需要为渲染管线指定很多参数来告诉它你想画什么以及如何画。因为绘制操作因形状而异,让你的形状类包含自己的绘制逻辑是个很好主意。
创建一个draw()方法负责绘制形状。下面的代码设置位置和颜色值到形状的vertexshader和fragmentshader,然后执行绘制功能。
public void draw() { // 将program加入OpenGL ES环境中 GLES20.glUseProgram(mProgram); // 获取指向vertex shader的成员vPosition的 handle mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition"); // 启用一个指向三角形的顶点数组的handle GLES20.glEnableVertexAttribArray(mPositionHandle); // 准备三角形的坐标数据 GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, vertexStride, vertexBuffer); // 获取指向fragment shader的成员vColor的handle mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor"); // 设置三角形的颜色 GLES20.glUniform4fv(mColorHandle, 1, color, 0); // 画三角形 GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount); // 禁用指向三角形的顶点数组 GLES20.glDisableVertexAttribArray(mPositionHandle);}
一旦你完成这些代码,画这个对象只需要在Renderer的onDrawFrame()调用draw()方法。当你运行应用时,它应该看起来这样:
图1.没有投射和视口时画三角形
此例子中的代码还有很多问题。首先,它不会给人留下印像。其次,三角形会在你从竖屏变为横屏时被压扁。三角形变形的原因是其顶点们没有跟据屏幕的宽高比进行修正。你可以使用投射和视口更正这个问题。那在下一讲了。
分类: android开发2013-03-01 06:21 10657人阅读 评论(5) 收藏 举报
androidOpenGLES
目录(?)[+]
在OpenGLES环境中,投影和相机视口使你绘制的对象以更接近物理对象的样子显示。这是通过对坐标精确的数学变换实现的。
·
投影-这种变换跟据所在GLSurfaceView的宽和高调整对象的坐标。如果没有此变换,对象会被不规则的视口扭曲。投射变换一般只需要在OpenGLview创建或发生变化时调用,代码写在renderer的onSurfaceChanged()方法中。
·
·
相机视口-此变换基于一个虚拟相机的位置调整对象的坐标。注意OpenGLES并没有定义一个真的相机对象,而是提供了一些工具方法变换绘制对象的显示来模拟一个相机。一个相机视口的变换可能只在创建GLSurfaceView时调用,或跟据用户动作动态调用。
·
本文讲解了如何创建一个投影和一个相机视口然后应用到GLSurfaceView的形状绘制过程。
投影变换的数据是在GLSurfaceView.Renderer 类的 onSurfaceChanged() 方法中计算。下面的例子跟据GLSurfaceView 的宽和高,使用Matrix.frustumM()方法计算出了一个投影变换Matrix:
@Overridepublic void onSurfaceChanged(GL10 unused, int width, int height) { GLES20.glViewport(0, 0, width, height); float ratio = (float) width / height; // 此投影矩阵在onDrawFrame()中将应用到对象的坐标 Matrix.frustumM(mProjMatrix, 0, -ratio, ratio, -1, 1, 3, 7);}
以下代码产生了一个投影矩阵mProjMatrix ,你可以把它在 onDrawFrame() 方法中与一个相机视口变换结合。
注: 只对你的对象应用一个投影变换一般会导制什么也看不到。通常,你必须也对其应用一个视口变换才能看到东西。
再定义一个相机视口变换以使对绘制对象的变换处理变得完整。在下面的例子中,使用方法Matrix.setLookAtM()计算相机视口变换,然后结合前面所计算的投影矩阵。结合后的变换矩阵之后传给要绘制的对象。
@Overridepublic void onDrawFrame(GL10 unused) { ... // 设置相机的位置(视口矩阵) Matrix.setLookAtM(mVMatrix, 0, 0, 0, -3, 0f, 0f, 0f, 0f, 1.0f, 0.0f); // 计算投影和视口变换 Matrix.multiplyMM(mMVPMatrix, 0, mProjMatrix, 0, mVMatrix, 0); // 绘制形状 mTriangle.draw(mMVPMatrix);}
为了使用前面的合并后的投影和相机视口变换矩阵,修改你的图形对象的方法draw(),接受结果矩阵并应用到你的形状上:
public void draw(float[] mvpMatrix) { // 传递计算出的变换矩阵 ... // 获得形状的变换矩阵的handle mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix"); // 应用投影和视口变换 GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0); // 绘制三角形 GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount); ...}
一旦你正确的计算和应用了投影和视口变换,你的图像将出现在正确的位置,看起来像下面这样:
图1.应用了投影和视口变换后绘制的三角形
现在你拥有了一个正确显示你的形状的应用了,是让你的图形动起来的时候了...嘿嘿...
分类: android开发2013-03-08 14:58 7865人阅读 评论(1) 收藏 举报
OpenGLES
目录(?)[+]
在屏幕上绘制是OpenGL的基础能力,但是你也可以用其它的Android图形框架类来做,包括Canvas和Drawable。 但是OpenGL ES提供了另外的能力,可以在三维上移动和变换对象。总之它能创造很牛B的用户体验。在本文中,你将学会如何使用OpenGL ES为形状添加旋转功能。
使用OpenGL ES 2.0旋转一个对象也是十分简单地。你创建另外一个变换矩阵(一个旋转矩阵)然后把它合并到你的投影和相机视口变换矩阵就行了:
[java] view plaincopy
1. private float[] mRotationMatrix = new float[16];
2. public void onDrawFrame(GL10 gl) {
3. ...
4. // 为三角形创建一个旋转变换
5. long time = SystemClock.uptimeMillis() % 4000L;
6. float angle = 0.090f * ((int) time);
7. Matrix.setRotateM(mRotationMatrix, 0, mAngle, 0, 0, -1.0f);
8.
9. // 把旋转矩阵合并到投影和相机矩阵
10. Matrix.multiplyMM(mMVPMatrix, 0, mRotationMatrix, 0, mMVPMatrix, 0);
11.
12. // 画三角形
13. mTriangle.draw(mMVPMatrix);
14. }
如果你的三角形在此新后转不起来,则要查看是否把GLSurfaceView.RENDERMODE_WHEN_DIRTY 设置注释了,下面马上就讲到。
到现在,你应在代码中注释掉设置只在数据改变时才渲染的代码,否则,OpenGL 只有转一次然后等待直到GLSurfaceView 的包含者调用requestRender():
[java] view plaincopy
1. public MyGLSurfaceView(Context context) {
2. ...
3. // Render the view only when there is a change in the drawing data
4. //setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); // 注释掉以自动旋转
5. }
除非你不让对象与用户有交互,否则启用这个设置是一个好做法。要准备解除这句的注释了,因为下一讲会用到它。
分类: android开发 jni OpenGLES2013-03-14 16:04 10060人阅读 评论(3) 收藏 举报
目录(?)[+]
使你的OpenGL ES应用能响应触摸的关键是扩展你实现的GLSurfaceView 代码,覆写onTouchEvent() 方法来监听触摸事件。
本文向你展示如何监听用户的触摸事件以使用户可以旋转某个OpenGL ES对象。
为了使你的OpenGL Es应用响应触摸事件,你必须在你的GLSurfaceView 类中实现onTouchEvent()事件。下面的例子演示了如何监听MotionEvent.ACTION_MOVE 事件然后把它们转换成一个形状的旋转角度。
[java] view plaincopy
1. @Override
2. public boolean onTouchEvent(MotionEvent e) {
3. // MotionEvent带有从触摸屏幕来的输入的详细信息以及其它输入控制
4. // 此处,你只需对触摸位置的改变感兴趣即可。
5.
6. float x = e.getX();
7. float y = e.getY();
8.
9. switch (e.getAction()) {
10. case MotionEvent.ACTION_MOVE:
11.
12. float dx = x - mPreviousX;
13. float dy = y - mPreviousY;
14.
15. // reverse direction of rotation above the mid-line
16. if (y > getHeight() / 2) {
17. dx = dx * -1 ;
18. }
19.
20. // reverse direction of rotation to left of the mid-line
21. if (x < getWidth() / 2) {
22. dy = dy * -1 ;
23. }
24.
25. mRenderer.mAngle += (dx + dy) * TOUCH_SCALE_FACTOR; // = 180.0f / 320
26. requestRender();
27. }
28.
29. mPreviousX = x;
30. mPreviousY = y;
31. return true;
32. }
注意在计算完旋转角度之后,本方法调用requestRender() 来告诉renderer要渲染帧了。这样做是很高效的,因为在没有发生旋转时不需要重画帧。然而,在你没有要求只在数据发生改变才重画之前,还不能达到最高效,即别忘了解除这一句的注释:
[java] view plaincopy
1. public MyGLSurfaceView(Context context) {
2. ...
3. // Render the view only when there is a change in the drawing data
4. setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
5. }
上面的例子要求你向其它类曝露出你的旋转角度,所以你要为你的renderer添加一个public成员。既然renderer的代码运行于主界面之外的单独线程中,你必须声明这个公开变量为volatile. 。下面的代码就是这样做的:
[java] view plaincopy
1. public class MyGLRenderer implements GLSurfaceView.Renderer {
2. ...
3. public volatile float mAngle;
4. }
要应用触摸所产生的旋转, 注释掉产生角度的代码并且添加mAngle,它包活了触摸所产生的角度:
[java] view plaincopy
1. public void onDrawFrame(GL10 gl) {
2. ...
3. // Create a rotation for the triangle
4. // long time = SystemClock.uptimeMillis() % 4000L;
5. // float angle = 0.090f * ((int) time);
6. Matrix.setRotateM(mRotationMatrix, 0, mAngle, 0, 0, -1.0f);
7.
8. // 合并旋转矩阵到投影和相机视口矩阵
9. Matrix.multiplyMM(mMVPMatrix, 0, mRotationMatrix, 0, mMVPMatrix, 0);
10.
11. // 画一个角度
12. mTriangle.draw(mMVPMatrix);
13. }
当你完成了上述的几步,运行程序然后在陪同幕上拖动你的手指头,你会看到下面这样:
Figure 1. 跟据触摸输入的转动的三角形(圈圈显示了触摸的位置)。