安卓OpenGL ES 2.0 入门

通过学习《OpenGL ES应用开发实践指南》总结如何在安卓上创建一个OpenGL程序,并且在屏幕上绘制图案。

一、OpenGL管道

首先知道OpenGL在把本地内存中的数据绘制到屏幕所经历的步骤

这里写图片描述

  • Read Vertex Data:读取顶点数据
  • Execute Vertex Shader:执行顶点着色器

    顶点着色器:生成每个顶点的最终位置,每个顶点都会执行一次,一旦顶点的最终位置确定,OpenGL就把这些可见的顶点集合组装成点、线和三角形。

  • Assemble Primitives:组装图元

    图元:组成图像的基本单元

  • Rasterize Primitives:光栅化图元

    光栅化:即把每个点、直线及三角形分解成小片段

  • Execute Fragment Shader:执行片段着色器

    片段着色器:组成点、直线或者三角形的每个片段的最终颜色,针对每个片段,它都会执行一次,一个片段是一个小的、单一的长方形区域,类似于计算机屏幕上的一个像素

  • Write to Frame Buffer:写入帧缓冲区
  • See It on the Screen: 显示在屏幕上

上述过程描:
1. 先读取保存在内存的顶点数据;
2. 执行顶点着色器把顶点数据转化成为点、线或者三角形,并且确定在屏幕上的位置;
3. 之后为了确定这些点、线和三角形的颜色,而光栅化它们,即把它们分解成一个个的小片段
4. 执行片段着色器,确定每一个小片段的颜色
5. 确定完位置和颜色后,写入一个叫帧缓冲区的内存区域
6. 显示在屏幕上

二、创建程序

2.1 创建一个初始化OpenGL的实例

2.1.1 布局文件:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >

    <android.opengl.GLSurfaceView
        android:id="@+id/surface"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        />
</FrameLayout>

2.1.2 初始化类

public class MainActivity extends AppCompatActivity{
     private GLSurfaceView mGLSurfaceView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mGLSurfaceView = (GLSurfaceView)findViewById(R.id.surface);

        //checking if the system supports OpenGL ES 2.0
        final ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
        final ConfigurationInfo configurationInfo = activityManager.getDeviceConfigurationInfo();

        final boolean supportsEs2 = configurationInfo.reqGlEsVersion >= 0x20000
                || (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)
                &&(Build.FINGERPRINT.startsWith("generic"))
                || Build.FINGERPRINT.startsWith("unknow")
                || Build.MODEL.contains("google_sdk")
                || Build.MODEL.contains("Emulator")
                || Build.MODEL.contains("Android SDK built for x86");

        if(supportsEs2){
            mGLSurfaceView.setEGLContextClientVersion(2);
            mGLSurfaceView.setRenderer(new MyRenderer());
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        mGLSurfaceView.onPause();
    }

    @Override
    protected void onResume() {
        super.onResume();
        mGLSurfaceView.onResume();
    }
}
创建GLSurfaceView实例
setContentView(R.layout.activity_main);
mGLSurfaceView = (GLSurfaceView)findViewById(R.id.surface);
检查系统是否支持OpenGL ES 2.0
final ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
final ConfigurationInfo configurationInfo = activityManager.getDeviceConfigurationInfo();
final boolean supportsEs2 = configurationInfo.reqGlEsVersion >= 0x20000
|| (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)
&&(Build.FINGERPRINT.startsWith("generic"))
|| Build.FINGERPRINT.startsWith("unknow")
|| Build.MODEL.contains("google_sdk")
|| Build.MODEL.contains("Emulator")
|| Build.MODEL.contains("Android SDK built for x86");

首先,引用Android ActivityManager来获取设备的配置信息,然后取出reqGIEsVersion变量检查OpenGL ES版本号,然后检查版本号是否支持OpenGL ES 2.0

配置渲染表面
if(supportsEs2){
    mGLSurfaceView.setEGLContextClientVersion(2);
    mGLSurfaceView.setRenderer(new MyRenderer());
}

如果设备支持OpenGL ES 2.0,则调用setEGLContextClientVersion(2)配置这个surface视图,之后调用setRenderer传入自定义Renderer类的一个新实例

重写Android Activity生命周期事件
@Override
    protected void onPause() {
        super.onPause();
        mGLSurfaceView.onPause();
    }

    @Override
    protected void onResume() {
        super.onResume();
        mGLSurfaceView.onResume();
    }

加入GLSurfaceView类的onPause()方法和onResume()方法,使得surface视图能正确暂停,释放和续用OpenGL上下文

2.2 创建Renderer类

创建一个渲染器,渲染器接口定义的方法:

  • onSurfaceCreated(GL10 glUnused, EGLConfig config):当Surface被创建时,此方法被GLSurfaceView调用,此方法发生在应用程序第一次运行的时候,并且,当设备被唤醒或者用户从其它activity切换回来时也可能被调用。
  • onSurfaceChanged(GL10 glUnused, int width, int height):每次Surface尺寸变化时,这个方法被GLSurfaceView调用。在横屏、竖屏来回切换的时候,Surface尺寸会发生变化
  • onDrawFrame(GL10 glUnused):当绘制一帧时,这个方法会被GLSurfaceView调用。在这个方法中一定要绘制一些东西,即使是清空屏幕。

渲染器:

public class MyRenderer implements GLSurfaceView.Renderer{

    private static final int POSITION_COMPONENT_COUNT = 2;

    private final FloatBuffer vertexData;

    private int program;

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

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

    private static final String VERTEX_SHADER =
            "attribute vec4 a_Position;\n"
                    +"void main() {\n"
                    +"gl_Position = a_Position;\n"
                    +"gl_PointSize = 10.0;\n"
                    +"}";
    private static final String FRAGMENT_SHADER =
            "precision mediump float;\n"
                    +"uniform vec4 u_Color;\n"
                    + "void main() {\n"
                    + " gl_FragColor = u_Color;\n"
                    + "}";
    private static final float[] VERTEX = {
            //Triangle 1
            -0.5f, -0.5f,
            0.5f, 0.5f,
            -0.5f, 0.5f,

            //Triangle 2
            -0.5f, -0.5f,
            0.5f, -0.5f,
            0.5f, 0.5f,

            //Line 1
            -0.5f, 0f,
            0.5f, 0f,

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

    public MyRenderer() {
        vertexData = ByteBuffer.allocateDirect(VERTEX.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
        vertexData.put(VERTEX);
    }

    static int loadShader(int type, String shaderCode) {
        int shader = GLES20.glCreateShader(type);
        GLES20.glShaderSource(shader, shaderCode);
        GLES20.glCompileShader(shader);
        return shader;
    }

    @Override
    public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {
        GLES20.glClearColor(0.0f,0.0f,0.0f,0.0f);

        program = GLES20.glCreateProgram();
        int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, VERTEX_SHADER);
        int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, FRAGMENT_SHADER);
        GLES20.glAttachShader(program, vertexShader);
        GLES20.glAttachShader(program, fragmentShader);
        GLES20.glLinkProgram(program);

        glUseProgram(program);

        uColorLocation = glGetUniformLocation(program,U_COLOR);
        aPositionLocation = glGetAttribLocation(program,A_POSITION);

        vertexData.position(0);
        glVertexAttribPointer(aPositionLocation,POSITION_COMPONENT_COUNT,GL_FLOAT,false,0,vertexData);
        glEnableVertexAttribArray(aPositionLocation);
        }

    @Override
    public void onSurfaceChanged(GL10 gl10, int width, int height) {
        GLES20.glViewport(0,0,width,height);

    }

    @Override
    public void onDrawFrame(GL10 gl10) {
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
        glUniform4f(uColorLocation,1.0f,1.0f,1.0f,1.0f);
        glDrawArrays(GL_TRIANGLES,0,6);

        glUniform4f(uColorLocation,1.0f,0.0f,0.0f,1.0f);
        glDrawArrays(GL_LINES,6,2);

        glUniform4f(uColorLocation,0.0f,0.0f,1.0f,1.0f);
        glDrawArrays(GL_POINTS,8,1);

        glUniform4f(uColorLocation,1.0f,0.0f,0.0f,1.0f);
        glDrawArrays(GL_POINTS,9,1);
    }
}
代码中定义顶点
private static final float[] VERTEX = {
            //Triangle 1
            -0.5f, -0.5f,
            0.5f, 0.5f,
            -0.5f, 0.5f,

            //Triangle 2
            -0.5f, -0.5f,
            0.5f, -0.5f,
            0.5f, 0.5f,

            //Line 1
            -0.5f, 0f,
            0.5f, 0f,

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

采用浮点数的顺序列表定义顶点数据,分别定义了两个三角形(组成一个矩形);一条直线,两个点
这里写图片描述

使数据可以被OpenGL存取

顶点定义完成之后,由于代码运行的环境与OpenGL运行的环境使用了不同的语言,需要将代码与OpenGL之间通信。

当调用android.opengl.GLES20包里的方法时,使用了Java本地接口,将运行在Davik虚拟机的代码与OpenGL通信。

我们把内存从Java堆复制到本地堆,使得顶点数据从Davik可以传输到OpenGL

private final FloatBuffer vertexData;
public MyRenderer() {
    vertexData = ByteBuffer.allocateDirect(VERTEX.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
    vertexData.put(VERTEX);
    }
  • 定义一个FloatBuffer类型变量;
  • 使用ByteBuff.allocateDirect()分配一块本地内存,这块内存不会被Davik虚拟机的垃圾回收器管理;每个顶点存储在一个浮点数组里,每个浮点数4个字节,所以内存大小是VERTEX.length * 4
  • 调用order(ByteOrder.nativeOrder())保证字节缓冲区按照本地字节序组织它的内容
  • 调用asFloatBuffer()得到一个FloatBuffer类的实例,即不使用单独的字节,而是使用浮点数
  • 最后调用vertexData.put(VERTEX)把数据从Dalvik的内存复制到本地内存

重写三个渲染器接口定义方法

1. onSurfaceCreated
    private static final String U_COLOR = "u_Color";
    private int uColorLocation;

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

    @Override
    public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {
        GLES20.glClearColor(0.0f,0.0f,0.0f,0.0f);

        program = GLES20.glCreateProgram();
        int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, VERTEX_SHADER);
        int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, FRAGMENT_SHADER);
        GLES20.glAttachShader(program, vertexShader);
        GLES20.glAttachShader(program, fragmentShader);
        GLES20.glLinkProgram(program);

        glUseProgram(program);

        uColorLocation = glGetUniformLocation(program,U_COLOR);
        aPositionLocation = glGetAttribLocation(program,A_POSITION);

        vertexData.position(0);
        glVertexAttribPointer(aPositionLocation,POSITION_COMPONENT_COUNT,GL_FLOAT,false,0,vertexData);
        glEnableVertexAttribArray(aPositionLocation);
    }
  • 首先执行一下清屏函数glClearColor(),四个参数分别对应红色、绿色、蓝色、透明度,此处都为0.0f,则清屏为黑色
  • loadShader()是自定义的方法,用来创建着色器,并把代码上传到着色器中,并且编译
static int loadShader(int type, String shaderCode) {
        int shader = GLES20.glCreateShader(type);
        GLES20.glShaderSource(shader, shaderCode);
        GLES20.glCompileShader(shader);
        return shader;
}
glCreateShader(type):创建一个新的着色器对象,type为着色器对象的类型(顶点着色器和片段着色器)
GLES20.glShaderSource(shader, shaderCode):把着色器源代码上传到着色器对象里
GLES20.glCompileShader(shader):编译着色器

- 新建一个程序对象,并且把顶点着色器和片段着色器都附加到程序对象上,并链接

  • program = GLES20.glCreateProgram():调用glCreateProgram()新建程序对象
  • 加载着色器:创建并且上传着色器代码和编译顶点着色器和片段着色器
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, VERTEX_SHADER);
int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, FRAGMENT_SHADER);
  • 将顶点着色器和片段着色器都附加到程序对象上
GLES20.glAttachShader(program, vertexShader);
GLES20.glAttachShader(program, fragmentShader);
  • 链接程序:将两个着色器联合起来
GLES20.glLinkProgram(program);
  • 获取片段着色器中定义的uniform(u_Color)的位置和位置着色器中属性(a_Position)的位置。
uColorLocation = glGetUniformLocation(program,U_COLOR);
aPositionLocation = glGetAttribLocation(program,A_POSITION);

当OpenGL 把着色器链接成一个程序的时候,实际上用位置编号编号把片段着色器中定义的每个uniform都关联起来,并且给位置着色器中的属性也分配位置编号

而OpenGL寻找所需要的数据时,使用的是uniform(u_Color)和属性(a_Position)的位置,而不是名字

  • 使用vertexData.position(0);把创建到本地内存缓冲区的指针设置到数据的开头
  • 调用glVertexAttribPointer(),使得OpenGL在vertexData中找到a_Position对应的数据
  • 调用glEnableVertexAttribArray()使能数据属性
2. onSurfaceChanged
@Override
public void onSurfaceChanged(GL10 gl10, int width, int height) {
        GLES20.glViewport(0,0,width,height);

    }

如果Surface尺寸发生变化,则根据相应长宽调整显示大小

3. onDrawFrame
    @Override
    public void onDrawFrame(GL10 gl10) {
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
        glUniform4f(uColorLocation,1.0f,1.0f,1.0f,1.0f);
        glDrawArrays(GL_TRIANGLES,0,6);

        glUniform4f(uColorLocation,1.0f,0.0f,0.0f,1.0f);
        glDrawArrays(GL_LINES,6,2);

        glUniform4f(uColorLocation,0.0f,0.0f,1.0f,1.0f);
        glDrawArrays(GL_POINTS,8,1);

        glUniform4f(uColorLocation,1.0f,0.0f,0.0f,1.0f);
        glDrawArrays(GL_POINTS,9,1);
    }
  • 调用glClear(GLES20.GL_COLOR_BUFFER_BIT)清空屏幕,擦除屏幕上所有颜色,并用之前glClearColor()调用定义的颜色填充屏幕
  • 绘制两个白色的三角形,即组成一个矩形
glUniform4f(uColorLocation,1.0f,1.0f,1.0f,1.0f);
glDrawArrays(GL_TRIANGLES,0,6);
  • 绘制一条红色的线
glUniform4f(uColorLocation,1.0f,0.0f,0.0f,1.0f);
glDrawArrays(GL_LINES,6,2);
  • 绘制一个蓝色的点和一个红色的点,点的大小由位置着色器里gl_PointSize设定
glUniform4f(uColorLocation,0.0f,0.0f,1.0f,1.0f);
glDrawArrays(GL_POINTS,8,1);
glUniform4f(uColorLocation,1.0f,0.0f,0.0f,1.0f);
glDrawArrays(GL_POINTS,9,1);

三、 绘制结果

这里写图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值