在Android、iOS等移动平台上,开发者可以使用跨平台应用编程接口创建二维或者三维图形,或进行图像处理和计算机视觉应用,结合两者将能构建丰富有趣的交互体验。前者称为OpenGL,后者称为OpenCV,不过本文主要介绍前者,OpenCV在后续文章中涉及。OpenGL应用于桌面系统的历史已经很长了,但考虑到移动平台的特点(计算能力、性能等),将OpenGL应用与移动端使用的是一个特殊的嵌入式版本:OpenGL ES(OpenGL for Embedded System)。OpenGL ES有三个版本:版本1.0提供了一个不太灵活的、固定功能的管道;2.0版本推出了可编程的管道,提供我们所需的所有功能;3.0版本在2.0的基础上增加了一些新的特性,目前还没有广泛使用。

《OpenGL ES应用开发实践指南(Android卷)》基于2.0版本进行说明,本文主要内容如下:

  1. OpenGL ES的基本用法,即HelloWorld实现
  2. 二维或者三维图形绘制流程
  3. 着色器(顶点和片段)编译

一、OpenGL ES的基本用法

OpenGL ES的HelloWorld实现非常简单,只需两步:(1)在xml布局文件或者代码中引入GLSurfaceView;(2)为GLSurfaceView设置Renderer。代码如下:

glSurfaceView = new GLSurfaceView(this);// Check if the system supports OpenGL ES 2.0.ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);ConfigurationInfo configurationInfo = activityManager.getDeviceConfigurationInfo();// Even though the latest emulator supports OpenGL ES 2.0,// it has a bug where it doesn't set the reqGlEsVersion so// the above check doesn't work. The below will detect if the// app is running on an emulator, and assume that it supports// OpenGL ES 2.0.final boolean supportsEs2 =configurationInfo.reqGlEsVersion >= 0x20000|| (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1 && (Build.FINGERPRINT.startsWith("generic")|| Build.FINGERPRINT.startsWith("unknown")|| Build.MODEL.contains("google_sdk") || Build.MODEL.contains("Emulator")|| Build.MODEL.contains("Android SDK built for x86")));if (supportsEs2) {// Request an OpenGL ES 2.0 compatible context.glSurfaceView.setEGLContextClientVersion(2);            // Assign our renderer.glSurfaceView.setRenderer(new AirHockeyRenderer(this));rendererSet = true;} else {/** This is where you could create an OpenGL ES 1.x compatible* renderer if you wanted to support both ES 1 and ES 2. Since * we're not doing anything, the app will crash if the device * doesn't support OpenGL ES 2.0. If we publish on the market, we * should also add the following to AndroidManifest.xml:* * <uses-feature android:glEsVersion="0x00020000"* android:required="true" />* * This hides our app from those devices which don't support OpenGL* ES 2.0.*/Toast.makeText(this, "This device does not support OpenGL ES 2.0.",Toast.LENGTH_LONG).show();return;}setContentView(glSurfaceView);

首先通过GLSurfaceView的构造函数创建GLSurfaceView对象glSurfaceView,然后检查系统是否支持OpenGL ES 2.0版,支持就为glSurfaceView设置Renderer,否则弹出提示并返回,最后设置界面展示。

需要说明的是,使用GLSurfaceView还需要处理Activity的生命周期,否则,用户切换到另一个应用,应用就会奔溃。

@Overrideprotected void onPause() {super.onPause();if (rendererSet) {glSurfaceView.onPause();}
}@Override
protected void onResume() {super.onResume();if (rendererSet) {glSurfaceView.onResume();}
}

除了GLSurfaceView,另一个重点就是Renderer。Renderer是一个接口,定义了上如下方法:

public interface Renderer {void onSurfaceCreated(GL10 gl, EGLConfig config);void onSurfaceChanged(GL10 gl, int width, int height);void onDrawFrame(GL10 gl);}

onSurfaceCreated(GL10 gl, EGLConfig config)方法在Surface被创建的时候调用。但在实践中,设备被唤醒或者用户从其他activity切换回来时,这个方法也可能被调用。

onSurfaceChanged(GL10 gl, int width, int height)方法在Surface尺寸变化时调用,在横竖屏切换时,Surface尺寸都会变化。

onDrawFrame(GL10 gl)方法在绘制一帧时调用。在这个方法中,一定要绘制一些东西,即使只是清空屏幕,因为在这个方法返回后,渲染缓冲区会被交换并显示到屏幕上,如果什么都没画,可能看到糟糕的闪烁效果。

HelloWorld应用中使用了AirHockeyRenderer,该类实现了Renderer接口。

@Override
public void onSurfaceCreated(GL10 glUnused, EGLConfig config) {glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
}
@Override
public void onSurfaceChanged(GL10 glUnused, int width, int height) {// Set the OpenGL viewport to fill the entire surface.glViewport(0, 0, width, height);
}
@Override
public void onDrawFrame(GL10 glUnused) {// Clear the rendering surface.glClear(GL_COLOR_BUFFER_BIT);
}

二、绘制流程

首先,在OpenGL ES中,只支持三种类型的绘制:点、直线以及三角形。所以需要在绘制图像之前,需要把一个图像分解为这三种图像的组合。

其次,OpenGL作为本地库直接运行在硬件上,没有虚拟机,也没有垃圾回收或者内存压缩。在Java层定义图像的数据需要能被OpenGL存取,因此,需要把内存从Java堆复制到本地堆。使用的方法是通过ByteBuffer:

private final FloatBuffer vertexData;vertexData = ByteBuffer.allocateDirect(tableVerticesWithTriangles.length * BYTES_PER_FLOAT).order(ByteOrder.nativeOrder()).asFloatBuffer();vertexData.put(tableVerticesWithTriangles);

在上述代码中,首先使用ByteBuffer.allocateDirect()分配一块本地内存,这块本地内存不会被垃圾回收器管理。这个方法需要确定分配的内存的大小(单位为字节);因为顶点存储在浮点数组中,并且每个浮点数有4个字节,所以这块内存的大小为tableVerticesWithTriangles.length * BYTES_PER_FLOAT。order(ByteOrder.nativeOrder())的意思是使缓冲区按照本地字节序组织内容,字节序请移步这里。asFloatBuffer()方法得到一个可以反映底层字节的FloatBuffer类实例,因为我们直接操作的是浮点数,而不是单独的字节。

最后,通过在OpenGL管道传递数据,着色器告诉GPU如何绘制数据。着色器分为顶点着色器和片段着色器。

综上,整体流程为:读取顶点数据——执行顶点着色器——组装图元——光栅化图元——执行片段着色器——写入帧缓冲区——显示到屏幕上

    再介绍下着色器,顶点着色器生成每个顶点的最终位置,针对每个顶点,执行一次;片段着色器为组成点、直线和三角形的每个片段生成最终的颜色,针对每个片段,执行一次。

顶点着色器:

attribute vec4 a_Position;             void main()
{                              gl_Position = a_Position;gl_PointSize = 10.0;
}

片段着色器:

precision mediump float; uniform vec4 u_Color;                                                 void main()
{                                  gl_FragColor = u_Color;
}

着色器使用GLSL定义,GLSL是OpenGL的着色语言,语法结构与C语言相似。顶点着色器中,gl_Position接收当前顶点的位置,gl_PointSize设置顶点的大小;片段着色器中,gl_FragColor接收片段的颜色。precision mediump float为精度限定符,可以选择lowp、mediump和highp,分别对应低精度、中等精度和高精度。

三、着色器编译

首先,从资源中加载着色器文本。即将本地资源文件读流,然后将流转换为String:

 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();}

这段代码很简单,不做具体介绍了。

其次,编译着色器。

/*** Loads and compiles a vertex shader, returning the OpenGL object ID.*/public static int compileVertexShader(String shaderCode) {return compileShader(GL_VERTEX_SHADER, shaderCode);}/*** Loads and compiles a fragment shader, returning the OpenGL object ID.*/public static int compileFragmentShader(String shaderCode) {return compileShader(GL_FRAGMENT_SHADER, shaderCode);}/*** Compiles a shader, returning the OpenGL object ID.*/private static int compileShader(int type, String shaderCode) {// Create a new shader object.final int shaderObjectId = glCreateShader(type);if (shaderObjectId == 0) {if (LoggerConfig.ON) {Log.w(TAG, "Could not create new shader.");}return 0;}// Pass in the shader source.glShaderSource(shaderObjectId, shaderCode);// Compile the shader.glCompileShader(shaderObjectId);// Get the compilation status.final int[] compileStatus = new int[1];glGetShaderiv(shaderObjectId, GL_COMPILE_STATUS, compileStatus, 0);if (LoggerConfig.ON) {// Print the shader info log to the Android log output.Log.v(TAG, "Results of compiling source:" + "\n" + shaderCode + "\n:" + glGetShaderInfoLog(shaderObjectId));}// Verify the compile status.if (compileStatus[0] == 0) {// If it failed, delete the shader object.glDeleteShader(shaderObjectId);if (LoggerConfig.ON) {Log.w(TAG, "Compilation of shader failed.");}return 0;}// Return the shader object ID.return shaderObjectId;}

compileVertexShader和compileFragmentShader都是通过compileShader来实现的,编译顶点着色器和片段着色器通过type进行区分:GL_VERTEX_SHADER和GL_FRAGMENT_SHADER。

compileShader方法包含的步骤是:新建着色器对象(glCreateShader)——上传着色器源代码(glShaderSource)——取出编译状态(glCompileShader)——取出着色器信息日志(glGetShaderiv)——验证编译状态并返回着色器对象ID(compileStatus[0] == 0。

再次,把着色器一起链接进OpenGL的程序。

/*** Links a vertex shader and a fragment shader together into an OpenGL* program. Returns the OpenGL program object ID, or 0 if linking failed.*/public static int linkProgram(int vertexShaderId, int fragmentShaderId) {// Create a new program object.final int programObjectId = glCreateProgram();if (programObjectId == 0) {if (LoggerConfig.ON) {Log.w(TAG, "Could not create new program");}return 0;}// Attach the vertex shader to the program.glAttachShader(programObjectId, vertexShaderId);// Attach the fragment shader to the program.glAttachShader(programObjectId, fragmentShaderId);// Link the two shaders together into a program.glLinkProgram(programObjectId);// Get the link status.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));            }// Verify the link status.if (linkStatus[0] == 0) {// If it failed, delete the program object.glDeleteProgram(programObjectId);if (LoggerConfig.ON) {Log.w(TAG, "Linking of program failed.");}return 0;}// Return the program object ID.return programObjectId;}

一个OpenGL的程序就是把一个顶点着色器和一个片段着色器链接在一起变成单个对象。链接与编译在流程上大致相同,主要流程为:新建程序并附上着色器对象——链接程序——验证链接状态并返回程序对象ID——给渲染类加入代码。

再次是验证OpenGL程序的对象:

/*** 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;}

调用glValidateProgram来验证这个程序,然后用GL_VALIDATE_STATUS参数调用glGetProgramiv()方法检测结果。需要说明的是,只有在开发和调试应用的时候才去验证程序。

在onSurfaceCreate()结尾处使用程序。glUseProgram告诉OpenGL在绘制任何东西到屏幕上的时候使用这里定义的程序。验证程序对象之后需要获取uniform的位置和属性的位置,最后关联属性与顶点数据的数组,使能顶点数组。上述流程走完之后,就可以正常绘制任何图像了,绘制图像的代码在onDrawFrame()处:

// Clear the rendering surface.glClear(GL_COLOR_BUFFER_BIT);// Draw the table.glUniform4f(uColorLocation, 1.0f, 1.0f, 1.0f, 1.0f);        glDrawArrays(GL_TRIANGLES, 0, 6);// Draw the center dividing line.glUniform4f(uColorLocation, 1.0f, 0.0f, 0.0f, 1.0f);        glDrawArrays(GL_LINES, 6, 2); // Draw the first mallet blue.        glUniform4f(uColorLocation, 0.0f, 0.0f, 1.0f, 1.0f);        glDrawArrays(GL_POINTS, 8, 1);// Draw the second mallet red.glUniform4f(uColorLocation, 1.0f, 0.0f, 0.0f, 1.0f);        glDrawArrays(GL_POINTS, 9, 1);

      glUniform4f更新着色器代码中的u_Color值,glDrawArrays代表绘制,GL_TRIANGLES表示要画三角形,0,6表示读入从开头的6个顶点。


总结:

(1)定义顶点属性数组,并将数组复制到本地内存;

(2)创建顶点着色器和片段着色器,着色器只是可以运行在GPU上的一个特殊类型的程序。

(3)如何创建和编译着色器;

(4)链接顶点着色器和片段着色器形成OpenGL程序对象;

(5)关联顶点着色器内部的属性变量与顶点属性数组

OpenGL ES基本用法相关推荐

  1. OpenGL ES着色器语言之语句和结构体(官方文档第六章)内建变量(官方文档第七、八章)...

    OpenGL ES着色器语言之语句和结构体(官方文档第六章) OpenGL ES着色器语言的程序块基本构成如下: 语句和声明 函数定义 选择(if-else) 迭代(for, while, do-wh ...

  2. Android OpenGL ES 离屏渲染(offscreen render)

    通常在Android上使用OpenGL ES,都是希望把渲染后的结果显示在屏幕上,例如图片处理.模型显示等.这种情况下,只需要使用Android API中提供的GLSurfaceView类和Rende ...

  3. OpenGL ES (三)着色器和程序

    OpenGL ES学习系列文章: 上一篇:OpenGL ES (二)EGL介绍和使用 下一篇:OpenGL ES (二)EGL介绍和使用 着色器和程序 前言 1.创建Shader 2.加载Shader ...

  4. 使用Android OpenGL ES 2.0绘图之三:绘制形状

    传送门 ☞ 轮子的专栏 ☞ 转载请注明 ☞ http://blog.csdn.net/leverage_1229 在定义好待绘制的形状之后,就要开始绘制它们了.使用OpenGL ES 2.0绘制形状可 ...

  5. viewpager初始化fragment没有绘制_NDK OpenGL ES渲染系列 之 绘制三角形

    前言 新的知识学习都是循序渐进的,从基础到复杂.前面OpenGL ES概念 已经介绍了OpenGL ES的相关概念,这篇文章开始我们就正式开始OpenGL ES渲染系列第一站---绘制三角形.绘制三角 ...

  6. ANDROID 高性能图形处理 之 OPENGL ES

    原文:http://tangzm.com/blog/?p=20 在之前的介绍中我们说到在Android 4.2上使用RenderScript有诸多限制,我们于是尝试改用OpenGL ES 2.0来实现 ...

  7. 【OpenGL ES】着色器Shader与程序Program

    在OpenGL ES 3程序中,Shader和Program是两个重要的概念,至少需要创建一个顶点Shader对象.一个片段Shader对象和一个Program对象,才能用着色器进行渲染,理解Shad ...

  8. OPENGL ES 2.0 知识串讲 (4)——GLSL 语法(II)

    上节回顾 上一节,我们讲解了 Shader 的功能,并从预处理和注释开始,讲解 GLSL 的语法知识.想要学习和使用一门语言,必须先学习这门语言的语法,语法中除了上一节说到的预处理.注释,还有更加重要 ...

  9. Android OpenGl ES使用原理总结与代码示例

    一.相关概念简介: OpenGl : OpenGl是一个定义好的跨平台图形处理接口库,通过它可操作GPU来完成图像处理.它跨平台是因为各个硬件厂家都按照这套接口规范具体实现了对应功能,供上层调用. O ...

最新文章

  1. linux下编译php扩展
  2. 深入理解指针以及二级指针(指针的指针)
  3. jvm系列(四):jvm调优-命令大全(jps jstat jmap jhat jstack jinfo)
  4. python处理表格数据-python通过xrld库读取表格数据
  5. 自定义控件(一) Activity的构成(PhoneWindow、DecorView)
  6. python期末考试编程题_智慧树知到_Python程序设计基础_期末考试答案
  7. Android --- This project contains Java compilation errors,which can cause rendering failures for
  8. JavaEE目标及企业应用和互联网应用区别
  9. 用咨询的角度去实施软件项目
  10. vue js 和原生app调用回调方法问题
  11. c判断数组是否为空_剑指offer编程题 1.二维数组中的查找
  12. 【在线分享】考研数学思维导图+高数思维导图+汤家凤重点笔记+武忠祥重点笔记以及高数Xmind思维导图
  13. java数组排序方法
  14. 破解魔术的秘密(一)——直面秘密的角落
  15. 学术-物理-维空间:二维空间
  16. 阿姆斯特朗数python
  17. 无失真传输matlab原理,信号与系统实验(MATLAB版)实验23综合实验4——无失真传输系统.ppt...
  18. html引入本地css样式无效,vue在index.html里面引入css文件样式加载失败
  19. 计算机毕业论文选题java毕业设计软件源代码SSH健身房管理系统[包运行成功]
  20. 亲测四款好用的Mac电脑手账软件

热门文章

  1. 【Computer Organization笔记05】运算器基本功能,定点运算器,Am2901的组成与功能,VHDL硬件描述语言
  2. 《强化学习》中的第10章:基于函数逼近的同轨策略控制
  3. 比较SynchronizedMap、Hashtable和ConcurrentHashMap的效率
  4. strstr查找子字符串函数
  5. Windows用户密码基础知识
  6. signature=0727ee8cc38ba70036807ebbc0b018d6,NMSSM+
  7. k8s glusterfs mysql_k8s使用glusterfs实现动态持久化存储
  8. 开始使用windows live writer写博客。
  9. MySQL数据库与Oracle数据库在存储中文字符以字节或字符存储的区别
  10. matlab图像拼接_FPGA大赛【四】具体模块设计图像缓存