我常给一些人的建议:如果条件不错,就不要来做程序员了,因为这不是人干的事!程序员睡觉的时候也是清洁工人开始扫马路的时候!
废话不多说,自己也是作为学习笔记而已,也是督促自己,因为如果仅仅运行一个例子很简单.自己研究这个当然也是需要应用到一定背景下的.
android APP如果需要使用opengl制图,如果在java层实现,一般是GLSurfaceView来显示出opengl制图,GLSurfaceView的特点:
1.管理一个平面,这个平面是一个特殊的内存块,它可以和android视图系统混合. 2.管理一个EGL显示,它能够让OpenGL渲染到一个平面. 3.接受一个用户提供的实际显示的Renderer对象. 4.使用一个专用线程去渲染从而和UI线程解耦. 5.支持on-demand 和连续的渲染. 6.可选的包,追踪 和/或者错误检查这个渲染器的OpenGL调用.
一个GLSurfaceView 一定要注意,这个activity的paused 和resumed 两种状态.当这个activity处于pauses 状态时,GLSurfaceView 客户端需要去调用 onPause() 方法,当activity处于resumed 状态时,GLSurfaceView 客户端也需要去调用 onResume() 方法.
下面新建一个使用opengl的工程,在android studio中.
<1> : 新建一个DurianOpenGL1工程,下面直接贴代码:
package org.durian.durianopengl1; import android.app.Activity; import android.opengl.GLSurfaceView; import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; import org.durian.durianopengl1.gl.Durian3DRender; import org.durian.durianopengl1.gl.DurianGLRender; import org.durian.durianopengl1.gl.DurianPhotoRender; import org.durian.durianopengl1.gl.DurianRotateRender; import org.durian.durianopengl1.gl.DurianTextureRender; import org.durian.durianopengl1.input.DurianInputSurfaceView; import org.durian.durianopengl1.texturefilter.DurianTextureFilterSurfaceView; public class DurianMainActivity extends Activity { private GLSurfaceView glview; private DurianInputSurfaceView glinputview; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); glview=new GLSurfaceView(this); //glinputview=new DurianInputSurfaceView(this); //glview.setRenderer(new DurianPhotoRender(this)/*new DurianTextureRender(this)*//*new Durian3DRender(this)*//*new DurianRotateRender(this)*//*new DurianGLRender(this)*/); glview.setRenderer(new DurianGLRender(this)); setContentView(/*new DurianTextureFilterSurfaceView(this)*//*glinputview*/glview/*R.layout.activity_durian_main*/); } @Override protected void onResume() { super.onResume(); glview.onResume(); } @Override protected void onPause() { super.onPause(); glview.onPause(); } }
注意上面Activity的onResume和onPause两个周期,GLSurfaceView也需要根据Activity做相应的调整.
下面是渲染器Renderer:
package org.durian.durianopengl1.gl; import android.content.Context; import android.opengl.GLSurfaceView; import android.opengl.GLU; import org.durian.durianopengl1.draw2d.Square; import org.durian.durianopengl1.draw2d.Triangle; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; /** * Created by Administrator on 2016/4/11. */ public class DurianGLRender implements GLSurfaceView.Renderer { private Context mContext; private Triangle triangle; private Square square; private org.durian.durianopengl1.draw2d.color.Triangle ctriangle; private org.durian.durianopengl1.draw2d.color.Square csquare; public DurianGLRender(Context context){ mContext=context; triangle=new Triangle(); square=new Square(); //ctriangle=new org.durian.durianopengl1.draw2d.color.Triangle(); //csquare=new org.durian.durianopengl1.draw2d.color.Square(); } @Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { gl.glClearColor(0.0f,0.0f,0.0f,1.0f); gl.glClearDepthf(1.0f); gl.glEnable(GL10.GL_DEPTH_TEST); gl.glDepthFunc(GL10.GL_LEQUAL); gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT,GL10.GL_NICEST); gl.glShadeModel(GL10.GL_SMOOTH); gl.glDisable(GL10.GL_DITHER); } @Override public void onSurfaceChanged(GL10 gl, int width, int height) { if(height==0){ height=1; } float aspect=(float)width/height; gl.glViewport(0,0,width,height); gl.glMatrixMode(GL10.GL_PROJECTION); gl.glLoadIdentity(); GLU.gluPerspective(gl,45,aspect,0.1f,100.0f); 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.glLoadIdentity(); gl.glTranslatef(-3.0f,0.0f,-6.0f); triangle.draw(gl); gl.glTranslatef(3.0f,0.0f,1.0f); square.draw(gl); gl.glTranslatef(-5.0f,0.0f,-6.0f); //ctriangle.draw(gl); gl.glTranslatef(5.0f,0.0f,1.0f); //csquare.draw(gl); } }
这个类中其中有一个Triangle和Square的类,一个是绘制边型,一个是方形的.
实现Renderer类就要实现它的三个基本方法.
@Override public void onSurfaceCreated(GL10 gl, EGLConfig config)
这个方法在Renderer创建的时候就会运行,一般在生命周期中只会运行一次.
这个方法一般是做一些初始化的工作.
gl.glClearColor(0f, 0f, 0f, 0f);
设置清除屏幕时所用的颜色。如果您对色彩的工作原理不清楚的话,我快速解释一下。色彩值的范围从0.0f到1.0f。0.0f代表最黑的情况,1.0f就是最亮的情况。glClearColor 后的第一个参数是Red Intensity(红色分量),第二个是绿色,第三个是蓝色。最大值也是1.0f,代表特定颜色分量的最亮情况。最后一个参数是Alpha值。当它用来清除屏幕的时候,我们不用关心第四个数字。现在让它为0.0f。我会用另一个教程来解释这个参数。
通过混合三种原色(红、绿、蓝),您可以得到不同的色彩。希望您在学校里学过这些。因此,当您使用glClearColor(0.0f,0.0f,1.0f,0.0f),您将用亮蓝色来清除屏幕。如果您用 glClearColor(0.5f,0.0f,0.0f,0.0f)的话,您将使用中红色来清除屏幕。不是最亮(1.0f),也不是最暗 (0.0f)。要得到白色背景,您应该将所有的颜色设成最亮(1.0f)。要黑色背景的话,您该将所有的颜色设为最暗(0.0f)。我们在这里设置屏幕为黑色.
gl.glClearDepthf(1.0f); gl.glEnable(GL10.GL_DEPTH_TEST); gl.glDepthFunc(GL10.GL_LEQUAL);这三行是关于depth buffer(深度缓存)的。将深度缓存设想为屏幕后面的层。深度缓存不断的对物体进入屏幕内部有多深进行跟踪。我们本节的程序其实没有真正使用深度缓存,但几乎所有在屏幕上显示3D场景OpenGL程序都使用深度缓存。它的排序决定那个物体先画。这样您就不会将一个圆形后面的正方形画到圆形上来。深度缓存是OpenGL十分重要的部分.
gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT,GL10.GL_NICEST);这里告诉OpenGL我们希望进行最好的透视修正。这会十分轻微的影响性能。但使得透视图看起来好一点.
gl.glShadeModel(GL10.GL_SMOOTH);
启用smooth shading(阴影平滑).阴影平滑通过多边形精细的混合色彩,并对外部光进行平滑,在以后的课程中会看到他的效果.
gl.glDisable(GL10.GL_DITHER);
关闭服务器端GL功能,在GL中很多都是一对一对的,比如这个的另一个gl.glEnable(...).
@Override public void onSurfaceChanged(GL10 gl, int width, int height)
当发生绘制变化的时候运行.比如横屏切换到竖屏.
gl.glViewport(0,0,width,height);此方法用来设定可见区域,即OpenGL应把渲染之后的图形绘制在窗体的哪个部位,当视见区域是整个窗体时,OpenGL将把渲染结果绘制到整个窗口.
gl.glMatrixMode(GL10.GL_PROJECTION);这个函数其实就是对接下来要做什么进行一下声明,也就是在要做下一步之前告诉计算机我要对“什么”进行操作了,这个“什么”在glMatrixMode的“()”里的选项(参数)有3种模式: GL_PROJECTION 投影, GL_MODELVIEW 模型视图, GL_TEXTURE 纹理.
gl.glLoadIdentity();
重设视图模型变换 , 用于观测创建的物体.
GLU.gluPerspective(gl,45,aspect,0.1f,100.0f);参考:http://blog.chinaunix.net/uid-24448954-id-3059473.html
@Override public void onDrawFrame(GL10 gl)这个才是真正绘制图形的地方,类似于自定义的View的onDraw方法.
gl.glClear(GL10.GL_COLOR_BUFFER_BIT|GL10.GL_DEPTH_BUFFER_BIT);
将缓存清除为预先的设置值.
gl.glTranslatef(-3.0f,0.0f,-6.0f);
这个方法是位移操作,三个参数分别是x,y,z左边,(0,0,0)通过前面的glLoadIdentity将被设置在平面中间位置,z的正方向是从屏幕指向上(或者屏幕外).
下面的实现边形的类:
package org.durian.durianopengl1.draw2d; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; import javax.microedition.khronos.opengles.GL10; /** * Created by Administrator on 2016/4/11. */ public class Triangle { private FloatBuffer vertexBuffer; private ByteBuffer indexBuffer; private float[] vertices={ 0.0f,1.0f,0.0f, -1.0f,-1.0f,0.0f, 1.0f,-1.0f,0.0f }; private byte[] indices={0,1,2}; public Triangle(){ ByteBuffer vbb=ByteBuffer.allocateDirect(vertices.length*4); vbb.order(ByteOrder.nativeOrder()); vertexBuffer=vbb.asFloatBuffer(); vertexBuffer.put(vertices); vertexBuffer.position(0); indexBuffer=ByteBuffer.allocateDirect(indices.length); indexBuffer.put(indices); indexBuffer.position(0); } public void draw(GL10 gl){ gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); gl.glVertexPointer(3,GL10.GL_FLOAT,0,vertexBuffer); gl.glDrawElements(GL10.GL_TRIANGLES,indices.length,GL10.GL_UNSIGNED_BYTE,indexBuffer); gl.glDisableClientState(GL10.GL_VERTEX_ARRAY); } }
边是由两点一线组成,上面是三边形,那么就有三个坐标,由于opengl是空间坐标,所以坐标是由x,y,z三项组成:
private float[] vertices={ 0.0f,1.0f,0.0f, -1.0f,-1.0f,0.0f, 1.0f,-1.0f,0.0f };
上面每一行分别对应是x,y,z
构造方法体中:
ByteBuffer vbb=ByteBuffer.allocateDirect(vertices.length*4); vbb.order(ByteOrder.nativeOrder()); vertexBuffer=vbb.asFloatBuffer(); vertexBuffer.put(vertices); vertexBuffer.position(0);opengl对于顶点操作实际是在native层完成的,内存分配也是在native完成的,所以上面是完成顶点的数据的内存分配
gl.glEnableClientState
gl.glDisableClientState
这两个一般是成对出现,可以控制管道(pipeline)开关
下图显示了OpenGL ES 1.x 固定管道的结构图:
管道“工序”大致可以分为 Transformation Stage 和 Rasterization Stage两大步。
OpenGL ES 支持的基本图形为 点Point, 线Line, 和三角形Triangle ,其它所有复制图形都是通过这几种基本几何图形组合而成。
在发出绘图指令后,会对顶点(Vertices)数组进行指定的坐标变换或光照处理。
顶点处理完成后,通过Rasterizer 来生成像素信息,称为”Fragments“ 。
对于Fragment 在经过Texture Processing, Color Sum ,Fog 等处理并将最终处理结果存放在内存中(称为FrameBuffer)。
OpenGL 2.0可以通过编程来修改蓝色的步骤,称为Programmable Shader.
以上管道中工序可以通过设置来打开或关闭某些功能(比如无需雾化Fog处理),并可以为某个工序设置参数,比如设置Vertext Array.
gl.glVertexPointer(3,GL10.GL_FLOAT,0,vertexBuffer);
第一个参数代表每个点或者顶点用几个坐标表示,本例中使用三个xyz。你也可以指定两个只用xy,这样话,z就为0.需要注意的是,第一个参数并不代表有几个点,如果你想要使用20个点来代表一个三角形,这里你需要传入的不是20而是2或3.
第二个参数表示坐标值应当被解析为float类型(这样OpenGL就可以知道每个值占用几位).
第三个参数可以称为“步长”,代表每个点之间有几位分割。本例中,0代表一个点挨着一个点,有时候你可能会在点的后面定义颜色,这时,你应该指出每个颜色占用的位长,以便OpenGL在解析时跳过这段长度.
gl.glDrawElements(GL10.GL_TRIANGLES,indices.length,GL10.GL_UNSIGNED_BYTE,indexBuffer);
第一个参数代表你想要画的几何图形(GL_TRIANGLE_STRIP代表一个三角strip)。其他可能的选项包括GL_POINTS,GL_LINE_STRIP,GL_LINES,GL_LINE_LOOP,GL_TRIANGLES和G:_TRIANGLE_FAN.
第二个参数:glDrawElements其它的参数可以让你重用预定义的带你。比如说,一个四边形包括四个点,每一个四边形可以用两个三角形组合而来。如果你想使用两个三角形组合成一个四边形,你有必要去定义6个点吗?当然不是,你只需要定义四个点,然后引用6次画出两个三角形就可以了,这种处理被称为“indexing into the point buffer”如下所示:
Points:(p1,p2,p3,p4)
Draw indices(p1,p2,p3, p2,p3,p4)
知道了这些,glDrawElements的第二个参数就好理解了,它代表了index buffer中有几个坐标标示(可以理解为点).
第三个参数代表index 数组中值的类型,是GL_UNSIGNED_SHORT还是GL_UNSIGNED_BYTE。
最后一个参数引用index buffer.
OK,基本操作和解释已经相当清楚了.
另外一个方形类就不过多解释了;
package org.durian.durianopengl1.draw2d; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; import javax.microedition.khronos.opengles.GL10; /** * Created by Administrator on 2016/4/11. */ public class Square { private FloatBuffer vertexBuffer; private float[] vertices={ -1.0f,-1.0f,0.0f, 1.0f,-1.0f,0.0f, -1.0f,1.0f,0.0f, 1.0f,1.0f,0.0f }; public Square(){ ByteBuffer vbb=ByteBuffer.allocateDirect(vertices.length*4); vbb.order(ByteOrder.nativeOrder()); vertexBuffer=vbb.asFloatBuffer(); vertexBuffer.put(vertices); vertexBuffer.position(0); } public void draw(GL10 gl){ gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); gl.glVertexPointer(3,GL10.GL_FLOAT,0,vertexBuffer); gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP,0,vertices.length/3); gl.glDisableClientState(GL10.GL_VERTEX_ARRAY); } }