OpenglES2.0 for Android:来画个三角形吧


先看看我们的整个流程:




理解坐标系:




左侧是Opengl默认的坐标系,右边是典型的android设备屏幕的坐标系。左侧的瘦瘦的三角形映射到android屏幕上就变成了胖胖的三角形(屏幕横向的时候),我们可以使用 camera和投影解决这个问题,具体怎么解决这里就先不累述了。这里我们只需要知道屏幕的左上角是(-1.0,1.0)横向向右为X轴正向,纵向向下为Y轴 负向,其范围都是从 -1到 +1。
定义三角形顶点:
我们在第一个android的小demo的工程里新建一个包shape,然后新建一个类 Triangle 。然后我们在该类中定义三角形的三个顶点的数据: 此时该类如下(Triangle.java):
package com.cumt.shape;import android.content.Context;public class Triangle {private Context context;// 数组中每个顶点的坐标数    static final int COORDS_PER_VERTEX = 2;// 每个顶点的坐标数  X ,  Y     static float triangleCoords[] = { 0.0f,  0.5f ,   // top                                     -0.5f, -0.5f ,   // bottom left                                      0.5f, -0.5f };   // bottom right        public Triangle(Context context){    this.context = context;    }}

我们在该类中定义的float类型的数据并不能直接被opengl使用,float[ ]是属于虚拟机环境的,而Opengl作为本地系统库直接运行在硬件上,所以我们需要将float[ ] 转化为
FloatBuffer以使数据可以被opengl使用,此时该类如下(Triangle.java ):
package com.cumt.shape;import java.nio.ByteBuffer;import java.nio.ByteOrder;import java.nio.FloatBuffer;import android.content.Context;public class Triangle {private Context context;private static final int BYTES_PER_FLOAT = 4;private FloatBuffer vertexBuffer;// 数组中每个顶点的坐标数    static final int COORDS_PER_VERTEX = 2;// 每个顶点的坐标数  X ,  Y     static float triangleCoords[] = { 0.0f,  0.5f ,   // top                                     -0.5f, -0.5f ,   // bottom left                                      0.5f, -0.5f };   // bottom right        public Triangle(Context context){    this.context = context;        vertexBuffer = ByteBuffer    .allocateDirect(triangleCoords.length * BYTES_PER_FLOAT)    .order(ByteOrder.nativeOrder())    .asFloatBuffer();    // 把坐标们加入FloatBuffer中        vertexBuffer.put(triangleCoords);        // 设置buffer,从第一个坐标开始读        vertexBuffer.position(0);    }}

编写着色器:

着色器语言(shading language)是opengl es2.0 比opengl es 1.X 新增的内容,使我们更加自由的实现自己想要的效果,该语言基于C/C++。该语言的具体类型等等这里不详细说明。
我们需要两个着色器——顶点着色器和片段着色器,顾名思义顶点着色器是用于处理顶点坐标的(不要与屏幕的像素点混淆),而片段着色器用于处理片段,所谓片段简单理解就是顶点着色器处理后的一堆顶点形成的片段。
现在我们来编写这两个顶点着色器:在 res 目录下新建一个文件夹 raw 在该文件夹下新建一个文件simple_vertex_shader.glsl ,其内容如下:
attribute vec4 a_Position;     void main()                    {                                  gl_Position = a_Position;}   

gl_Position即opengl定义的顶点的坐标,我们目的就是通过这个来告诉opengl我们的顶点数据。vec4是着色器语言中的向量类型的一种,包含了四个浮点数的向量。
接下来在raw文件夹内新建文件 simple_fragment_shader.glsl ,其内容如下:
precision mediump float;        uniform vec4 u_Color;       void main()                    {                                  gl_FragColor = u_Color;                                  }

这里我们只传入一个颜色信息。这里注意一下 上面顶点着色器的 限定符 attribute 和 uniform 。attribute 一般用于每个顶点各不相同的量,如顶点位置等,后者一般用于
同一组顶点组成的相同的量,如光源位置,一组颜色等。

编译着色器及绘制:

接下来我们进行最后一步,编译着色器以及绘制三角形,这里我们来写一个工具类用于加载着色器程序以及编译着色器。 首先新建一个包utils ,此时目录结构如下图所示:

在utils包下新建类 LoggerConfig 用于管理我们的日志(是否输出Logcat的信息),代码如下(LoggerConfig.java):
package com.cumt.utils;public class LoggerConfig {    public static final boolean ON = true;}

再在该包下新建类ShaderHelper 用于加载着色器程序以及编译着色器:
package com.cumt.utils;import static android.opengl.GLES20.GL_COMPILE_STATUS;import static android.opengl.GLES20.GL_FRAGMENT_SHADER;import static android.opengl.GLES20.GL_LINK_STATUS;import static android.opengl.GLES20.GL_VALIDATE_STATUS;import static android.opengl.GLES20.GL_VERTEX_SHADER;import static android.opengl.GLES20.glAttachShader;import static android.opengl.GLES20.glCompileShader;import static android.opengl.GLES20.glCreateProgram;import static android.opengl.GLES20.glCreateShader;import static android.opengl.GLES20.glDeleteProgram;import static android.opengl.GLES20.glDeleteShader;import static android.opengl.GLES20.glGetProgramInfoLog;import static android.opengl.GLES20.glGetProgramiv;import static android.opengl.GLES20.glGetShaderInfoLog;import static android.opengl.GLES20.glGetShaderiv;import static android.opengl.GLES20.glLinkProgram;import static android.opengl.GLES20.glShaderSource;import static android.opengl.GLES20.glValidateProgram;import android.util.Log;public class ShaderHelper {    private static final String TAG = "ShaderHelper";    /**     * 加载并编译顶点着色器,返回得到的opengl id     * @param shaderCode     * @return     */    public static int compileVertexShader(String shaderCode) {        return compileShader(GL_VERTEX_SHADER, shaderCode);    }    /**     * 加载并编译片段着色器,返回opengl id     * @param shaderCode     * @return     */    public static int compileFragmentShader(String shaderCode) {        return compileShader(GL_FRAGMENT_SHADER, shaderCode);    }    /**     * 加载并编译着色器,返回opengl id     * @param type     * @param shaderCode     * @return     */    private static int compileShader(int type, String shaderCode) {        // 建立新的着色器对象        final int shaderObjectId = glCreateShader(type);        if (shaderObjectId == 0) {            if (LoggerConfig.ON) {                Log.w(TAG, "不能创建新的着色器.");            }            return 0;        }        // 传递着色器资源代码.        glShaderSource(shaderObjectId, shaderCode);        //编译着色器        glCompileShader(shaderObjectId);        // 获取编译的状态        final int[] compileStatus = new int[1];        glGetShaderiv(shaderObjectId, GL_COMPILE_STATUS,            compileStatus, 0);        if (LoggerConfig.ON) {        //打印log            Log.v(TAG, "代码编译结果:" + "\n" + shaderCode                + "\n:" + glGetShaderInfoLog(shaderObjectId));        }        // 确认编译的状态        if (compileStatus[0] == 0) {            // 如果编译失败,则删除该对象            glDeleteShader(shaderObjectId);            if (LoggerConfig.ON) {                Log.w(TAG, "编译失败!.");            }            return 0;        }        // 返回着色器的opengl id        return shaderObjectId;    }    /**     * 链接顶点着色器和片段着色器成一个program     * 并返回这个pragram的opengl id     * @param vertexShaderId     * @param fragmentShaderId     * @return     */    public static int linkProgram(int vertexShaderId, int fragmentShaderId) {        // 新建一个program对象        final int programObjectId = glCreateProgram();        if (programObjectId == 0) {            if (LoggerConfig.ON) {                Log.w(TAG, "不能新建一个 program");            }            return 0;        }        // Attach the vertex shader to the program.        glAttachShader(programObjectId, vertexShaderId);        // Attach the fragment shader to the program.        glAttachShader(programObjectId, fragmentShaderId);        // 将两个着色器连接成一个program        glLinkProgram(programObjectId);        // 获取连接状态        final int[] linkStatus = new int[1];        glGetProgramiv(programObjectId, GL_LINK_STATUS,            linkStatus, 0);        if (LoggerConfig.ON) {            // Print the program info log to the Android log output.            Log.v(                TAG,                "Results of linking program:\n"                    + glGetProgramInfoLog(programObjectId));        }        // 验证连接状态        if (linkStatus[0] == 0) {            // If it failed, delete the program object.            glDeleteProgram(programObjectId);            if (LoggerConfig.ON) {                Log.w(TAG, "连接 program 失败!.");            }            return 0;        }        // Return the program object ID.        return programObjectId;    }    /**     * Validates an OpenGL program. Should only be called when developing the     * application.     */    public static boolean validateProgram(int programObjectId) {        glValidateProgram(programObjectId);        final int[] validateStatus = new int[1];        glGetProgramiv(programObjectId, GL_VALIDATE_STATUS,            validateStatus, 0);        Log.v(TAG, "Results of validating program: " + validateStatus[0]            + "\nLog:" + glGetProgramInfoLog(programObjectId));        return validateStatus[0] != 0;    }    /**     * /**     * 编译,连接 ,返回 program 的 ID     * @param vertexShaderSource     * @param fragmentShaderSource     * @return     */    public static int buildProgram(String vertexShaderSource,        String fragmentShaderSource) {        int program;        // Compile the shaders.        int vertexShader = compileVertexShader(vertexShaderSource);        int fragmentShader = compileFragmentShader(fragmentShaderSource);        // Link them into a shader program.        program = linkProgram(vertexShader, fragmentShader);        if (LoggerConfig.ON) {            validateProgram(program);        }        return program;    }}

上面整个过程如图所示:




以后我们就可以很方便的一直使用该工具类来加载编译着色器了。只需要调用buildProgram,传入两个着色器的String文本就可以获得program的id是不是很方便。 我们似乎遗忘了什么,对的我们的两个着色器的文本内容该怎么活的呢,我们再在该目录下定义一个工具类TextResourceReader类来获得文本的内容,返回String类型 代码如下(TextResourceReader.java):
package com.cumt.utils;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import android.content.Context;import android.content.res.Resources;public class TextResourceReader {    /**     * Reads in text from a resource file and returns a String containing the     * text.     */    public static String readTextFileFromResource(Context context,        int resourceId) {        StringBuilder body = new StringBuilder();        try {            InputStream inputStream =                 context.getResources().openRawResource(resourceId);            InputStreamReader inputStreamReader =                 new InputStreamReader(inputStream);            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);            String nextLine;            while ((nextLine = bufferedReader.readLine()) != null) {                body.append(nextLine);                body.append('\n');            }        } catch (IOException e) {            throw new RuntimeException(                "Could not open resource: " + resourceId, e);        } catch (Resources.NotFoundException nfe) {            throw new RuntimeException("Resource not found: " + resourceId, nfe);        }        return body.toString();    }}

OK ,接下来就让我们来使用者两个工具类获得program的id吧,先回到 前面定义的 Triangle 类:
我们来定义一个私有方法getProgram 并在构造函数中调用 ,此时代码如下 (Triangle.java):
package com.cumt.shape;import java.nio.ByteBuffer;import java.nio.ByteOrder;import java.nio.FloatBuffer;import com.cumt.openglestwo_test_one.R;import com.cumt.utils.ShaderHelper;import com.cumt.utils.TextResourceReader;import android.content.Context;import android.opengl.GLES20;public class Triangle {private Context context;private static final int BYTES_PER_FLOAT = 4;private FloatBuffer vertexBuffer;// 数组中每个顶点的坐标数    static final int COORDS_PER_VERTEX = 2;// 每个顶点的坐标数  X ,  Y     static float triangleCoords[] = { 0.0f,  0.5f ,   // top                                     -0.5f, -0.5f ,   // bottom left                                      0.5f, -0.5f };   // bottom right        private int program;        public Triangle(Context context){    this.context = context;        vertexBuffer = ByteBuffer    .allocateDirect(triangleCoords.length * BYTES_PER_FLOAT)    .order(ByteOrder.nativeOrder())    .asFloatBuffer();    // 把坐标们加入FloatBuffer中        vertexBuffer.put(triangleCoords);        // 设置buffer,从第一个坐标开始读        vertexBuffer.position(0);                getProgram();            }        //获取program的id    private void getProgram(){    //获取顶点着色器文本    String vertexShaderSource = TextResourceReader.readTextFileFromResource(context, R.raw.simple_vertex_shader);    //获取片段着色器文本String fragmentShaderSource = TextResourceReader.readTextFileFromResource(context, R.raw.simple_fragment_shader);//获取program的idprogram = ShaderHelper.buildProgram(vertexShaderSource, fragmentShaderSource);GLES20.glUseProgram(program);    }}

至此我们已经胜利在望了,接下来思考一下,我们应该做哪些工作?显然我们需要将定义的数据传入着色器中来使用。详细的步骤见下面的代码(Triangle.java):
package com.cumt.shape;import java.nio.ByteBuffer;import java.nio.ByteOrder;import java.nio.FloatBuffer;import com.cumt.openglestwo_test_one.R;import com.cumt.utils.ShaderHelper;import com.cumt.utils.TextResourceReader;import android.content.Context;import android.opengl.GLES20;public class Triangle {private Context context;private static final int BYTES_PER_FLOAT = 4;private FloatBuffer vertexBuffer;//---------第四步:定义坐标元素的个数,这里有三个顶点private static final int POSITION_COMPONENT_COUNT = 3;// 数组中每个顶点的坐标数    static final int COORDS_PER_VERTEX = 2;// 每个顶点的坐标数  X ,  Y     static float triangleCoords[] = { 0.0f,  0.5f ,   // top                                     -0.5f, -0.5f ,   // bottom left                                      0.5f, -0.5f };   // bottom right        private int program;            //------------第一步 : 定义两个标签,分别于着色器代码中的变量名相同,     //------------第一个是顶点着色器的变量名,第二个是片段着色器的变量名private static final String A_POSITION = "a_Position";private static final String U_COLOR = "u_Color";//------------第二步: 定义两个ID,我们就是通ID来实现数据的传递的,这个与前面//------------获得program的ID的含义类似的private int uColorLocation;private int aPositionLocation;    public Triangle(Context context){    this.context = context;        vertexBuffer = ByteBuffer    .allocateDirect(triangleCoords.length * BYTES_PER_FLOAT)    .order(ByteOrder.nativeOrder())    .asFloatBuffer();    // 把坐标们加入FloatBuffer中        vertexBuffer.put(triangleCoords);        // 设置buffer,从第一个坐标开始读        vertexBuffer.position(0);                getProgram();                //----------第三步: 获取这两个ID ,是通过前面定义的标签获得的        uColorLocation = GLES20.glGetUniformLocation(program, U_COLOR);aPositionLocation = GLES20.glGetAttribLocation(program, A_POSITION);//---------第五步: 传入数据GLES20.glVertexAttribPointer(aPositionLocation, COORDS_PER_VERTEX,GLES20.GL_FLOAT, false, 0, vertexBuffer);GLES20.glEnableVertexAttribArray(aPositionLocation);            }        //获取program    private void getProgram(){    //获取顶点着色器文本    String vertexShaderSource = TextResourceReader.readTextFileFromResource(context, R.raw.simple_vertex_shader);    //获取片段着色器文本String fragmentShaderSource = TextResourceReader.readTextFileFromResource(context, R.raw.simple_fragment_shader);//获取program的idprogram = ShaderHelper.buildProgram(vertexShaderSource, fragmentShaderSource);GLES20.glUseProgram(program);    }        //----------第七步:绘制    public void draw(){    GLES20.glUniform4f(uColorLocation, 0.0f, 0.0f, 1.0f, 1.0f);GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, POSITION_COMPONENT_COUNT);    }}


最后我们只需要在我们前面定义的MyRender中使用即可,此时MyRender代码(这里在构造函数中引入了Context上下文环境,因为需要我们的Triangle对象的构造函数需要Context), 如下(MyRender.java ):
package com.cumt.render;import javax.microedition.khronos.egl.EGLConfig;import javax.microedition.khronos.opengles.GL10;import com.cumt.shape.Triangle;import android.content.Context;import android.opengl.GLSurfaceView.Renderer;import android.util.Log;import static android.opengl.GLES20.glClear;import static android.opengl.GLES20.glClearColor;import static android.opengl.GLES20.glViewport;import static android.opengl.GLES20.GL_COLOR_BUFFER_BIT;public class MyRender implements Renderer {private Context context;public MyRender(Context context){this.context = context;}//定义三角形对象Triangle triangle;public void onSurfaceCreated(GL10 gl, EGLConfig config) {Log.w("MyRender","onSurfaceCreated");// TODO Auto-generated method stub//First:设置清空屏幕用的颜色,前三个参数对应红绿蓝,最后一个对应alphaglClearColor(0.0f, 0.0f, 0.0f, 0.0f);triangle = new Triangle(context);}public void onSurfaceChanged(GL10 gl, int width, int height) {Log.w("MyRender","onSurfaceChanged");// TODO Auto-generated method stub//Second:设置视口尺寸,即告诉opengl可以用来渲染的surface大小glViewport(0,0,width,height);}public void onDrawFrame(GL10 gl) {Log.w("MyRender","onDrawFrame");// TODO Auto-generated method stub//Third:清空屏幕,擦除屏幕上所有的颜色,并用之前glClearColor定义的颜色填充整个屏幕glClear(GL_COLOR_BUFFER_BIT);//绘制三角形triangle.draw();}}


运行结果如下:








更多相关文章

  1. Android(安卓)NDK Hello + JNI 与 NDK区别
  2. Android的View体系(三):View坐标以及方法说明
  3. Android(安卓)Building System 总结
  4. Android开发实践:在任意目录执行NDK编译
  5. Android应用构建过程解析
  6. 定制android主界面。让你自己写的Android的Launcher成为系统中第
  7. Android.mk——makefile分析
  8. android 在init。rc中配置socket及其使用
  9. 怎样用 C/C++ 开发一个原生程序运行在 Android(安卓)下而不用 AD

随机推荐

  1. 数据结构简明备忘录 线性表
  2. SQL对冗余数据的删除重复记录只保留单条
  3. 一次SQL调优数据库性能问题后的过程(300W
  4. 根据日期知道当天是星期几的手动计算方法
  5. SQLServer 附加数据库后出现只读或失败的
  6. SQL 合并多行记录的相同字段值
  7. MSSQL Server 查询优化方法 整理
  8. mysql 数据库中索引原理分析说明
  9. SQL批量插入数据几种方案的性能详细对比
  10. SQL数据库的高级sql注入的一些知识