本文的文字大部分都是从《OpenGL ES 2.0 编程向导》中摘抄而来,特此说明。
该文是基于OpengGL ES 2.0的,算是本人学习OpenGL的“Hello world”吧。
一个OpengGL ES 2.0程序的实现大致如下所示:
作为一个“Hello world”类的程序,功能就是在手机上绘制一个三角形。因而代码就会比较简陋,预先准备的食材也就比较简单,就是下面这五个文件(连布局文件都省了):
AndroidManifest.xml
<!-- Tell the system this app requires OpenGL ES 2.0. -->
<uses-feature android:glEsVersion="0x00020000" android:required="true" />
MainActivity.java
public class MainActivity extends Activity {
private TriangleGLSurfaceView mTriangleGLSurfaceView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mTriangleGLSurfaceView = new TriangleGLSurfaceView(getApplication());
setContentView(mTriangleGLSurfaceView);
}
}
TriangleGLSurfaceView.java
public class TriangleGLSurfaceView extends GLSurfaceView{
public TriangleGLSurfaceView(Context context) {
super(context);
setEGLContextClientVersion(2);
setRenderer(new TriangleRender());
}
}
TriangleRender.java
public class TriangleRender implements Renderer{
private Triangle mTriangle;
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
GLES20.glClearColor(0, 0, 0, 1);
mTriangle = new Triangle();
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
GLES20.glViewport(0, 0, width, height);
}
@Override
public void onDrawFrame(GL10 gl) {
// Clear the color buffer
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
mTriangle.draw();
}
}
Triangle.java
public class Triangle {
...省略大段文字,会在下面补充啦...
}
下面这些操作大都在Triangle.java
这个类里面实现的。
OpenGL ES 2.0 中,在有效的顶点和片段着色器被装载前,什么渲染都做不了。定义定点着色器:
private static final String VERTEX_SHADER =
"attribute vec4 vPosition; \n" +
"void main() \n" +
"{ \n" +
" gl_Position = vPosition; \n" +
"}";
顶点着色器定义一个输入attribute,它是4 个成员的矢量vPosition。后面的draw
函数将为每个顶点设置位置变量值。main
函数声明着色器宣布着色器开始执行。着色器主体非常简单,它复制输入vPosition 属性到gl_Position 输出变量中,每个顶点着色器必须输出位置值到gl_Position变量中,这个变量传入到管线的下一个阶段中。
顶点着色器如下:
private static final String FRAGMENT_SHADER =
"precision mediump float; \n" +
"void main() \n" +
"{ \n" +
" gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); \n" +
"}";
第一行宣布着色器默认的浮点变量精度。main 函数上,它的输出值(1.0, 0.0, 0.0, 1.0)赋给变量gl_FragColor,gl_FragColor是片段着色器最终的输出值,本例中输出值是红色。
典型的一个游戏或应用将不会内联一个着色器源码串,大多数的实际应用着色器应该填充文字或数据,然后被API 装载,我们为简化起见,在程序源码上直接赋值。
定义了着色器源码后,我们将着色器装入OpenGL ES,这由loadShader
函数完成,检查没有错误后,这个函数返回着色器对象,这个对象在后面被用于连接项目对象
private int loadShader(int type, String shaderCode){
int shader;
int[] compiled = new int[1];
// Create the shader object
shader = GLES20.glCreateShader(type);
if (shader == 0) {
return 0;
}
// Load the shader source
GLES20.glShaderSource(shader, shaderCode);
// Compile the shader
GLES20.glCompileShader(shader);
// Check the compile status
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
if (compiled[0] == 0) {
Log.e(TAG, "Could not compile shader " + type + ":");
Log.e(TAG, GLES20.glGetShaderInfoLog(shader));
GLES20.glDeleteShader(shader);
shader = 0;
}
return shader;
}
如果着色器对象装载成功,一个新的着色器对象被返回,在后面将连接到项目对象上。
一旦应用程序已经创建了顶点、片段着色器对象,它需要去创建项目对象,项目是最终的链接对象,每个着色器在被绘制前都应该联系到项目或者项目对象。
// Create the program object.
mProgram = GLES20.glCreateProgram();
if (mProgram == 0) {
return;
}
GLES20.glAttachShader(mProgram, vertexShader);
GLES20.glAttachShader(mProgram, fragmentShader);
// Bind vPosition to attribute 0
GLES20.glBindAttribLocation(mProgram, 0, "vPosition");
顶点属性、顶点矩阵、缓冲区对象, 讲述更多的细节现在我们看glBindAttribLocation函数绑定vPosition 属性到顶点着色器位置0,当我们指定顶点数据后,位置指针指向下一个位置。
// Link the program
GLES20.glLinkProgram(mProgram);
// Check the link status
int[] linkStatus = new int[1];
GLES20.glGetProgramiv(mProgram, GLES20.GL_LINK_STATUS, linkStatus, 0);
if (linkStatus[0] != GLES20.GL_TRUE) {
Log.e(TAG, "Could not link program: ");
Log.e(TAG, GLES20.glGetProgramInfoLog(mProgram));
GLES20.glDeleteProgram(mProgram);
mProgram = 0;
}
所有这些步骤后,编译着色器,检查错误,创建项目对象,附加上着色器,链接项目,检查链接错误。成功后可以使用项目对象去渲染。
创建了渲染平面初始化并装载了着色器,准备去绘制实际的物体,我们就要用到:TriangleRender.java
这个类了。而GLSurfaceView.Renderer
这个接口,在Android官方给出的描述是这样的:
public interface Renderer {
/**
* Called when the surface is created or recreated.
* <p>
* Called when the rendering thread
* starts and whenever the EGL context is lost. The EGL context will typically
* be lost when the Android device awakes after going to sleep.
* <p>
* Since this method is called at the beginning of rendering, as well as
* every time the EGL context is lost, this method is a convenient place to put
* code to create resources that need to be created when the rendering
* starts, and that need to be recreated when the EGL context is lost.
* Textures are an example of a resource that you might want to create
* here.
* <p>
* Note that when the EGL context is lost, all OpenGL resources associated
* with that context will be automatically deleted. You do not need to call
* the corresponding "glDelete" methods such as glDeleteTextures to
* manually delete these lost resources.
* <p>
* @param gl the GL interface. Use <code>instanceof</code> to
* test if the interface supports GL11 or higher interfaces.
* @param config the EGLConfig of the created surface. Can be used
* to create matching pbuffers.
*/
void onSurfaceCreated(GL10 gl, EGLConfig config);
/**
* Called when the surface changed size.
* <p>
* Called after the surface is created and whenever
* the OpenGL ES surface size changes.
* <p>
* Typically you will set your viewport here. If your camera
* is fixed then you could also set your projection matrix here:
* <pre class="prettyprint">
* void onSurfaceChanged(GL10 gl, int width, int height) {
* gl.glViewport(0, 0, width, height);
* // for a fixed camera, set the projection too
* float ratio = (float) width / height;
* gl.glMatrixMode(GL10.GL_PROJECTION);
* gl.glLoadIdentity();
* gl.glFrustumf(-ratio, ratio, -1, 1, 1, 10);
* }
* </pre>
* @param gl the GL interface. Use <code>instanceof</code> to
* test if the interface supports GL11 or higher interfaces.
* @param width
* @param height
*/
void onSurfaceChanged(GL10 gl, int width, int height);
/**
* Called to draw the current frame.
* <p>
* This method is responsible for drawing the current frame.
* <p>
* The implementation of this method typically looks like this:
* <pre class="prettyprint">
* void onDrawFrame(GL10 gl) {
* gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
* //... other gl calls to render the scene ...
* }
* </pre>
* @param gl the GL interface. Use <code>instanceof</code> to
* test if the interface supports GL11 or higher interfaces.
*/
void onDrawFrame(GL10 gl);
}
我们解释的第1个函数是glViewport,它定义OpenGL ES 的将要绘制物体的2D 窗口的坐标原点和窗口宽度和高度。在OpenGL ES 中viewport 定义渲染操作将要显示的2D 长方形显示区域。
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
GLES20.glViewport(0, 0, width, height);
}
viewport 设定窗口的原点origin (x, y)、宽度和高度
设定窗口后,下一步是清除屏幕,在OpenGL ES 中,有多种需要绘制的缓冲区类型,颜色、深度和模板。在本例中仅仅颜色缓冲区被使用。开始每帧绘制前,我们使用glClear 清除颜色缓冲区。
@Override
public void onDrawFrame(GL10 gl) {
// Clear the color buffer
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
...Something else...
}
缓冲区将被用glClearColor 函数的颜色参数值清除,本例初始化结束后,清除颜色被设定为(0.0, 0.0, 0.0, 1.0),于是屏幕变成黑色。清除颜色通过glClear 设定。
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
GLES20.glClearColor(0, 0, 0, 1);
...Something else...
}
现在我们已清除了颜色缓冲区,设定了视口,装载了项目对象,我们指定要绘制的几何图形是三角形,三角形顶点的坐标如下:
static float triangleCoords[] = {
0.0f, 0.5f, 0.0f,
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f
};
...
public Triangle() {
ByteBuffer bb = ByteBuffer.allocateDirect(triangleCoords.length*4);
bb.order(ByteOrder.nativeOrder());
mVertexBuffer = bb.asFloatBuffer();
mVertexBuffer.put(triangleCoords);
mVertexBuffer.position(0);
}
...
public void draw(){
// Use the program object
GLES20.glUseProgram(mProgram);
// Load the vertex data
GLES20.glVertexAttribPointer(0, 3, GLES20.GL_FLOAT, false, 0, mVertexBuffer);
GLES20.glEnableVertexAttribArray(0);
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 3);
}
顶点位置需要被装载到GL 联系到vPosition,你是否想到先前我们绑定vPosition 变量到属性位置0,每个顶点着色器中的属性都有一个唯一的用无符号整形数标示的位置,调用glVertexAttribPointer 函数,我们把数据装载到位置0
绘制三角形最后一步是调用OpenGL ES 去绘制基元,本例中使用glDrawArrays 函数。这个函数绘制三角形、直线或带状物等基元,将在第7 章详细介绍这些几何图形的细节。
最后在谈谈绘制在缓冲区中的三角形。最后说明缓冲区如何显示到屏幕上,讨论之前,先介绍一点双缓冲区概念。
在显示屏上看到的帧缓冲区是2 维的空间的像素数据,可能想到的方法是简单更新可见缓冲区中的数据,直接更新显示缓冲区中数据有困难,对显示屏幕缓冲区内存更新要有固定的频率。如果两次时间不同,将看到闪烁的痕迹。
为解决这个问题,系统一般使用双缓冲区:前缓冲区和后缓冲区,所有的渲染发生在后缓冲区,它是屏幕上不可见的缓冲区内存,渲染完成后,交换前后缓冲区,即在下一帧时前
缓冲区变成后缓冲区。
使用这个技术,在一帧渲染完成前,我们不显示任何图像,这种OpenGL ES 方法使用
通过EGL 实现,使用函数是eglSwapBuffers;这个EGL 函数交换前后缓冲区,eglSwapBuffers 的输入参数是EGL 显示区和窗口,这两个参数代表了实际的物理显示区和渲染区。现在我们知道我们交换缓冲区后,显示了要显示的三角形。
结果就像下面图里显示的那样,不过由于没有处理横竖屏时屏幕的变化,所以样子会根据屏幕方向的切换而变化。
横屏
竖屏
版权声明:本文为博主原创文章,未经博主允许不得转载。
Hello Triangle:OpenGL ES 2.0 版的“Hello world”
原文地址:http://blog.csdn.net/x13945/article/details/47713331