OpenGl-ES2.0 For Android 读书笔记(一)

一、开始

OpenGl主要需要实现两个类:

**GLSurfaceView
**
This class is a View where you can draw and manipulate objects using OpenGL API calls and is similar in function to a SurfaceView. You can use this class by creating an instance of GLSurfaceView and adding your Renderer to it. However, if you want to capture touch screen events, you should extend the GLSurfaceView class to implement the touch listeners, as shown in OpenGL training lesson, Responding to Touch Events.

这个是Android官方的解释,跟SurfaceView类似的可以用OpenGL去绘制的View。

**GLSurfaceView.Renderer
**
This interface defines the methods required for drawing graphics in a GLSurfaceView. You must provide an implementation of this interface as a separate class and attach it to your GLSurfaceView instance using GLSurfaceView.setRenderer().

GLSurfaceView需要设置一个GLSurfaceView.Renderer的实现类去做OpenGl绘制的处理。主要有三个方法:

从方法名字就可以看出来

  • onSurfaceCreated()主要做一些初始化的操作
  • onSurfaceChanged()主要做一些在view改变时候的处理
  • onDrawFrame()主要做绘制处理

二、绘制简单的撞球图面

1.总览

我们最终完成的效果如下图:

效果图.png

首先我们思考下我们怎么去画图,我们需要一只笔,需要知道在怎么地方画什么,所以我们的使用OpenGL去绘制的大概步骤如下:

  1. 定义绘图数据
  2. 告诉手机如何使用这些数据去绘制

2.定义绘图数据

首先我们需要知道一个概念,OpenGL的坐标系是从-1到1的,如下图:

OpenGL坐标系.png

然后我们还要了解到OpenGL只能绘制三种图形,点、线、三角形,所以我们是不能直接画出来一个矩形的,但是呢,我们可以用两个三角形去拼成一个矩形。
所以我们可以这样定义我们的数据:

private float[] mData = new float[]{
            //三角形
            -0.5f , 0.5f,
            -0.5f , -0.5f,
            0.5f , 0.5f,

            -0.5f , 0.5f,
            0.5f , 0.5f,
            0.5f , -0.5f,

            //线
            -0.5f , 0f,
            0.5f , 0f,

            //点
            0f , 0.25f,
            0f , -0.25f
    };

我们如果用Java的话可以这样去实现,但是现在我们又需要去知道一个知识点了,OpenGL是拿不到虚拟机里面的数据的,OpenGL只能使用Native的数据,所以现在我们需要找个办法去让OpenGL能够使用我们定义的数据,我们可以这样去做:
1.定义常量
private static final int BYTE_PRE_FLOAT = 4;
2.定义全局变量
private final FloatBuffer mVertexData;
3.在构造方法中添加如下代码

mVertexData = ByteBuffer
                .allocateDirect(mData.length * BYTE_PRE_FLOAT)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer();
mVertexData.put(mData);

3.使用OpenGL绘制

现在我们有了数据,而且能够使用数据了,现在我们就可以开始去做绘制的工作了。
首先我们需要知道OpenGL的绘制是通过Shader去实现的,而Shader分为两类:
1.vertex shader:主要告诉GPU哪个点要画在哪里,就是获得点绘制的位置
2.fragment shader:主要告诉GPU哪个点要用什么颜色去画,就是获得点绘制的颜色
现在我们就可以去写代码了,在res/下建个raw包来存放OpenGL的实现代码,然后创建simple_vertex_shader.glsl文件,实现如下代码:

attribute vec4 a_Position;

void main() {
    gl_Position = a_Position;
}

我们通过a_Position来获取位置传给gl_Position,gl_Position就是OpenGL最后用来绘制的位置。这里解释下vec4表示我们定义了一个向量,这个向量有4个参数,分别为(x,y,z,w),xyz为3D坐标,w后面的文章会讲到,默认值为1。
然后我们还需要创建一个simple_fragment_shader.glsl文件,实现如下代码:

precision mediump float;

uniform vec4 u_Color;

void main() {
    gl_FragColor = u_Color;
}

类似simple_vertex_shader.glsl,我们也是通过u_Color获取颜色传递给gl_FragColor,gl_FragColor就是最后要绘制的颜色。
第一行代码确定所有float型的清晰度,就像是Java中double和float。我们有三个值可以选择lowpmediumphighp,分别表示低清晰度,中等清晰度,高清晰度,vertex shader对清晰度要求比较高,默认设置为highp,所以我们不用设置。
这次我们用了uniform,在vertex shader我们使用的是attributeattribute对于每个点都是不同的,每个点都有自己的值,但是uniform的值是不变的,除非我们再次改变它。
这个我们依然用了vec4表示我们声明了一个4个参数的向量,只不过这次内容不一样了,是(r,g,b,a),应该都猜到了,就是红、绿、蓝、透明度。
这样我们就完成了OpenGl部分代码的编写,并对OpenGL有了一定的理解。
现在问题来了,我们要怎么用这两个东西去绘制到View上呢?现在就让我们去解决这个问题,大概需要五个步骤:
1.从.glsl文件读出代码内容
2.编译读出来的代码
3.链接到程序
4.获取参数的位置
5.使用获取到的参数的位置去绘制
现在就让我们一步一步去实现吧。

  1. .glsl文件读出代码内容
    可能很多地方都会用到读取文件内容的功能,所以我们把该功能抽出来做一个工具类,建一个util包,在该包下建一个TextResouceReader.java类,并实现如下方法:
public static String readTextFileFromResource(Context context , int resourceId){

        StringBuilder body = new StringBuilder();

        InputStream inputStream = null;
        InputStreamReader reader = null;
        BufferedReader bufferedReader = null;
        try {
            inputStream = context.getResources().openRawResource(resourceId);
            reader = new InputStreamReader(inputStream);
            bufferedReader = new BufferedReader(reader);
            String line = null;
            while ((line = bufferedReader.readLine()) != null){
                body.append(line + "\n");
            }
        }catch (Exception e){
            Logger.debug(TAG , "read file has error");
        }finally {
            try {
                if (bufferedReader != null){
                    bufferedReader.close();
                }
                if (reader != null){
                    reader.close();
                }
                if (inputStream != null){
                    inputStream.close();
                }
            }catch (Exception e){}
        }
        return body.toString();
    }

然后我们第一步就可以完成了,在onSurfaceCreated()方法中读取代码

String vertexShaderCode = TextResouceReader.readTextFileFromResource(mContext , R.raw.sample_vertex_shader);
String fragmentShaderCode = TextResouceReader.readTextFileFromResource(mContext , R.raw.simple_fragment_shader);
  1. 编译读出来的代码
    我们在util包下创建一个ShaderHelper.java类,然后实现如下方法
public static int compileShaderCode(int type ,  String sourceCode){
        final int shaderObjectId = glCreateShader(type);
        if (shaderObjectId == 0){
            Logger.debug(TAG , "can not create sahder");
            return 0;
        }
        glShaderSource(shaderObjectId , sourceCode);
        glCompileShader(shaderObjectId);
        final int[] compileStatus = new int[1];
        glGetShaderiv(shaderObjectId, GL_COMPILE_STATUS, compileStatus, 0);
        if (compileStatus[0] == 0){
            Logger.debug(TAG , "compile fail");           
        }
        return shaderObjectId;
    }

首先我们创建了一个shader对象,type有两个值GL_VERTEX_SHADER,GL_FRAGMENT_SHADER,返回了一个int型,这个值是我们OpeGL对象的引用,当我们以后需要应用的时候,就要用这个值,如果返回0则表示创建失败了。然后我们就需要用glShaderSource()上传代码,glCompileShader()编译代码,最后,我们检查了编译的状态。
为了方便使用我们在ShaderHelper.java方法中再添加两个方法:

public static int compileVertexShader(String sourceCode){
        return compileShaderCode(GL_VERTEX_SHADER , sourceCode);
    }
public static int compileFragmentShader(String sourceCode){
        return compileShaderCode(GL_FRAGMENT_SHADER , sourceCode);
    }
  1. 链接到程序
    接下来我们需要在ShaderHelper.java实现如下的方法:
public static int linkProgram(int vertexShaderId , int fragmentShaderId){
        int programId = glCreateProgram();

        if (programId == 0){
            Logger.debug(TAG , "create program fail");
            return 0;
        }
        glAttachShader(programId , vertexShaderId);
        glAttachShader(programId , fragmentShaderId);
        glLinkProgram(programId);

        final int[] linkStatus = new int[1];
        glGetProgramiv(programId , GL_LINK_STATUS , linkStatus , 0);
        if (linkStatus[0] == 0){
            Logger.debug(TAG , "link fail");
            return 0;
        }

        return programId;
    }

跟编译代码类似,我们先创建一个program对象,然后把vertexShader,fragmentShader传给program,最后链接程序,检查是否链接成功。

  1. 获取参数的位置
    在获取参数位置之前我们需要使我们链接的程序生效,还需要在ShaderHelper.java实现如下方法:
public static boolean vaildProgram(int programId){
        glValidateProgram(programId);

        final int[] validateStatus = new int[1];
        glGetProgramiv(programId, GL_VALIDATE_STATUS, validateStatus, 0);
        return validateStatus[0] != 0;
    }

我们已经在ShaderHelper.java中完成了这些方法,我们现在可以方便的用这些方法在Renderder的onSurfaceCreated()方法中去使用这些方法了。

@Override
    public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {
        String vertexShaderCode = TextResouceReader.readTextFileFromResource(mContext , R.raw.sample_vertex_shader);
        String fragmentShaderCode = TextResouceReader.readTextFileFromResource(mContext , R.raw.simple_fragment_shader);

        int vertextShaderId = ShaderHelper.compileVertexShader(vertexShaderCode);
        int fragmentShaderId = ShaderHelper.compileFragmentShader(fragmentShaderCode);

        mProgram = ShaderHelper.linkProgram(vertextShaderId , fragmentShaderId);
        ShaderHelper.vaildProgram(mProgram);

        glUseProgram(mProgram);
    }

在让我们的程序生效后,我们就要开始使用它了,所以我们调用了glUseProgram()方法。
接下来我们来获取我们定义的u_Color的位置信息,我们先做如下声明:

private static final String U_COLOR = "u_Color";
private int mUColorLocation;

然后在onSurfaceCreated()方法中获取u_Color的位置信息
mUColorLocation = glGetUniformLocation(mProgram , U_COLOR);
类似的我们可以获取a_Position的位置信息:

private static final String A_POSITION = "a_Position";
private int mAPositionLocation;

mAPositionLocation = glGetAttribLocation(mProgram , A_POSITION);

  1. 使用获取到的参数的位置去绘制
    最后我们就开始去绘制了,首先我们在Renderer的onSurfaceCreated()方法中添加如下代码:
mVertexData.position(0);
glVertexAttribPointer(mAPositionLocation , POSITION_COMPOENT_COUNT , GL_FLOAT , false , 0 , mVertexData);

POSITION_COMPOENT_COUNT为常量:
private static final int POSITION_COMPOENT_COUNT = 2;
mVertexData.position(0)让我们可以保证从数据的第一个值开始读取,然后我们用glVertexAttribPointer()方法去从vertexData中读取数据赋值给a_Position,该方法的各参数解释如下:
POSITION_COMPOENT_COUNT:表示每个向量读取两个值
GL_FLOAT:表示数据类型
false:该值只有在用int型值的时候为true
0:这个值只有在数据有多个参数的时候需要使用,后面会讨论,目前置为0
具体的大家可以自己去查下Api。
现在OpenGL就知道如何从vertexData中读取a_Position的值了,最后我们需要添加一行代码glEnableVertexAttribArray(aPositionLocation);,去让a_Position能够被使用。
接下来我们就可以去绘制了,在onDrawFrame()中添加如下代码:

//绘制两个三角形
glUniform4f(mUColorLocation, 1.0f, 1.0f, 1.0f, 1.0f);
glDrawArrays(GL_TRIANGLES, 0, 6);
//绘制线
glUniform4f(mUColorLocation, 1.0f, 0.0f, 0.0f, 1.0f);
glDrawArrays(GL_LINES, 6, 2);
//绘制点
glUniform4f(mUColorLocation, 0.0f, 0.0f, 1.0f, 1.0f);
glDrawArrays(GL_POINTS, 8, 1);
glUniform4f(mUColorLocation, 1.0f, 0.0f, 0.0f, 1.0f);
glDrawArrays(GL_POINTS, 9, 1);

glUniform4f()用来给u_Color赋值。
glDrawArrays()来绘制,第一个参数表示要绘制什么,第二个参数表示从之前载入的数据哪里开始读取,第三个参数表示读几个值。
这个时候就算完成了我们的基本功能了,但是运行之后,你会发现绘制的点不见了,其实只是太小了,看不到而已,在simple_vertex_shader.glsl文件中添加如下代码:gl_PointSize = 10;再运行一遍,就能看到我们想要的效果了。

项目代码在这里:https://github.com/KevinKmoo/SimpleAirHockey

能力有限,自己读书的学习所得,有错误请指导,轻虐!
转载请注明出处。----by kmoo

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,013评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,205评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,370评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,168评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,153评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,954评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,271评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,916评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,382评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,877评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,989评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,624评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,209评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,199评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,418评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,401评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,700评论 2 345

推荐阅读更多精彩内容