摘要: 概述 在聊Android的View渲染流程中,通常会有一个比较核心的步骤:通过OpeGL ES接口调用GPU接口通知GPU绘制图形。其完整的流程:UI对象---->CPU处理为多维图形,纹理 -----通过OpeGL ES接口调用GPU----> GPU对图进行光栅化(Frame Rate ) ---->硬件时钟(Refresh Rate)----垂直同步---->投射到屏幕。

概述

在聊Android的View渲染流程中,通常会有一个比较核心的步骤:通过OpeGL ES接口调用GPU接口通知GPU绘制图形。其完整的流程:UI对象---->CPU处理为多维图形,纹理 -----通过OpeGL ES接口调用GPU----> GPU对图进行光栅化(Frame Rate ) ---->硬件时钟(Refresh Rate)----垂直同步---->投射到屏幕。

详解的绘制原理,后面会慢慢讲的。

渲染的基础知识

使用OpenGL ES,一般包括如下几个步骤:

  (1)EGL初始化
  (2)OpenGL ES初始化
  (3)OpenGL ES设置选项&绘制
  (4)OpenGL ES资源释放(可选)
  (5)EGL资源释放

Android提供的GLSurfaceView和Renderer自动完成了(1)(5)两个部分,这部分只需要开发者做一些简单配置即可。另外(4)这一步是可选的,因为随着EGL中上下文的销毁,openGL ES用到的资源也跟着释放了。因此只有(2)(3)是开发者必须做的。这大大简化了开发过程,但是灵活性也有所降低,利用这两个类是无法完成offscreen render的。要想完成offscreen render其实也很简单,相信大家也都猜到了,只要我们把(1)~(5)都自己完成就可以了。后续部分的代码大部分都是C/C++,少部分是Java。

初始化

EGL的功能是将OpenGL ES API和设备当前的窗口系统粘合在一起,起到了沟通桥梁的作用。不同设备的窗口系统千变万化,但是OpenGL ES提供的API却是统一的,所以EGL需要协调当前设备的窗口系统和OpenGL ES。下面EGL初始化的代码我是用C++写的,然后通过jni调用。Android在Java层面上也提供了对应的Java接口函数。

static EGLConfig eglConf;
static EGLSurface eglSurface;
static EGLContext eglCtx;
static EGLDisplay eglDisp;JNIEXPORT void JNICALL Java_com_handspeaker_offscreentest_MyGles_init
(JNIEnv*env,jobject obj)
{// EGL config attributesconst EGLint confAttr[] ={EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,// very important!EGL_SURFACE_TYPE,EGL_PBUFFER_BIT,//EGL_WINDOW_BIT EGL_PBUFFER_BIT we will create a pixelbuffer surfaceEGL_RED_SIZE,   8,EGL_GREEN_SIZE, 8,EGL_BLUE_SIZE,  8,EGL_ALPHA_SIZE, 8,// if you need the alpha channelEGL_DEPTH_SIZE, 8,// if you need the depth bufferEGL_STENCIL_SIZE,8,EGL_NONE};// EGL context attributesconst EGLint ctxAttr[] = {EGL_CONTEXT_CLIENT_VERSION, 2,// very important!EGL_NONE};// surface attributes// the surface size is set to the input frame sizeconst EGLint surfaceAttr[] = {EGL_WIDTH,512,EGL_HEIGHT,512,EGL_NONE};EGLint eglMajVers, eglMinVers;EGLint numConfigs;eglDisp = eglGetDisplay(EGL_DEFAULT_DISPLAY);if(eglDisp == EGL_NO_DISPLAY){//Unable to open connection to local windowing systemLOGI("Unable to open connection to local windowing system");}if(!eglInitialize(eglDisp, &eglMajVers, &eglMinVers)){// Unable to initialize EGL. Handle and recoverLOGI("Unable to initialize EGL");}LOGI("EGL init with version %d.%d", eglMajVers, eglMinVers);// choose the first config, i.e. best configif(!eglChooseConfig(eglDisp, confAttr, &eglConf, 1, &numConfigs)){LOGI("some config is wrong");}else{LOGI("all configs is OK");}// create a pixelbuffer surfaceeglSurface = eglCreatePbufferSurface(eglDisp, eglConf, surfaceAttr);if(eglSurface == EGL_NO_SURFACE){switch(eglGetError()){case EGL_BAD_ALLOC:// Not enough resources available. Handle and recoverLOGI("Not enough resources available");break;case EGL_BAD_CONFIG:// Verify that provided EGLConfig is validLOGI("provided EGLConfig is invalid");break;case EGL_BAD_PARAMETER:// Verify that the EGL_WIDTH and EGL_HEIGHT are// non-negative valuesLOGI("provided EGL_WIDTH and EGL_HEIGHT is invalid");break;case EGL_BAD_MATCH:// Check window and EGLConfig attributes to determine// compatibility and pbuffer-texture parametersLOGI("Check window and EGLConfig attributes");break;}}eglCtx = eglCreateContext(eglDisp, eglConf, EGL_NO_CONTEXT, ctxAttr);if(eglCtx == EGL_NO_CONTEXT){EGLint error = eglGetError();if(error == EGL_BAD_CONFIG){// Handle error and recoverLOGI("EGL_BAD_CONFIG");}}if(!eglMakeCurrent(eglDisp, eglSurface, eglSurface, eglCtx)){LOGI("MakeCurrent failed");}LOGI("initialize success!");
}

代码比较长,不过大部分都是检测当前函数调用是否出错的,核心的函数只有6个,只要它们的调用没有问题即可:

eglGetDisplay(EGL_DEFAULT_DISPLAY)

eglInitialize(eglDisp, &eglMajVers, &eglMinVers)

eglChooseConfig(eglDisp, confAttr, &eglConf, 1, &numConfigs)

eglCreatePbufferSurface(eglDisp, eglConf, surfaceAttr)

eglCreateContext(eglDisp, eglConf, EGL_NO_CONTEXT, ctxAttr)

eglMakeCurrent(eglDisp, eglSurface, eglSurface, eglCtx)

OpenGL ES初始化

JNIEXPORT void JNICALL Java_com_handspeaker_offscreentest_MyGles_draw
(JNIEnv*env,jobject obj)
{const char*vertex_shader=vertex_shader_fix;const char*fragment_shader=fragment_shader_simple;glPixelStorei(GL_UNPACK_ALIGNMENT,1);glClearColor(0.0,0.0,0.0,0.0);glEnable(GL_DEPTH_TEST);glDepthFunc(GL_LESS);glCullFace(GL_BACK);glViewport(0,0,512,512);GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);glShaderSource(vertexShader,1,&vertex_shader,NULL);glCompileShader(vertexShader);GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);glShaderSource(fragmentShader,1,&fragment_shader,NULL);glCompileShader(fragmentShader);GLuint program = glCreateProgram();glAttachShader(program, vertexShader);glAttachShader(program, fragmentShader);glLinkProgram(program);glUseProgram(program);GLuint aPositionLocation =glGetAttribLocation(program, "a_Position");glVertexAttribPointer(aPositionLocation,2,GL_FLOAT,GL_FALSE,0,tableVerticesWithTriangles);glEnableVertexAttribArray(aPositionLocation);//draw somethingglClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);glDrawArrays(GL_TRIANGLES,0,6);eglSwapBuffers(eglDisp,eglSurface);
}

EGL资源释放

JNIEXPORT void JNICALL Java_com_handspeaker_offscreentest_MyGles_release
(JNIEnv*env,jobject obj)
{eglMakeCurrent(eglDisp, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);eglDestroyContext(eglDisp, eglCtx);eglDestroySurface(eglDisp, eglSurface);eglTerminate(eglDisp);eglDisp = EGL_NO_DISPLAY;eglSurface = EGL_NO_SURFACE;eglCtx = EGL_NO_CONTEXT;
}

OpenGL ES在Android中的应用

为了让你的控件能够显示在界面上,你必须创建一个view作为容器。而要想创建View容器,最直接的方式莫过于从GLSurfaceView和GLSurfaceView.Renderer分别派生一个类,实际的绘图动作都是在GLSurfaceView.Renderer里面发生的。对于一个全屏或近全屏的graphicsview,它是最好的选择。如果只是在某个小部分显示OpenGLES图形则可以考虑TextureView。当然你也可以直接继承自OpenGLES view创建一个View,不过一般都不会这么做。

OpenGL ES 的Android实例

1,在Manifest中声明使用OpenGLES

为了能使用OpenGLES 2.0 API,你必须在你的manifest中添加以下声明:

<uses-feature android:glEsVersion="0x00020000" android:required="true" />

如果你的应用要使用纹理压缩功能,还必须声明设备需要支持什么样的压缩格式:

<supports-gl-texture android:name="GL_OES_compressed_ETC1_RGB8_texture" />
<supports-gl-texture android:name="GL_OES_compressed_paletted_texture" />

2,创建一个Activity

这个Activity和普通的activity一样,不过其使用的布局layout需要使用GLSurfaceView包裹。

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

注:OpenGL ES 2.0需要Android2.2 (API Level 8) 及以上版本。

3,构键GLSurfaceView对象

GLSurfaceView中其实不需要做太多工作,实际的绘制任务都在GLSurfaceView.Renderer中了。这里我们可以直接使用GLSurfaceView。

class MyGLSurfaceView extends GLSurfaceView {public MyGLSurfaceView(Context context){super(context);//设置Renderer到GLSurfaceViewsetRenderer(new MyRenderer());}
}

当使用OpenGLES 2.0时,你必须在GLSurfaceView构造器中调用另外一个函数,它说明了你将要使用2.0版的API:

setEGLContextClientVersion(2);

另一个可以添加的你的GLSurfaceView实现的可选的操作是设置render模式为只在绘制数据发生改变时才绘制view。使用GLSurfaceView.RENDERMODE_WHEN_DIRTY:

setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);

4, 构建一个Renderer类

Renderer类主要负责GLSurfaceView的绘制工作,它主要有三个方法:

  • onSurfaceCreated()- 仅调用一次,用于设置view的OpenGLES环境。
  • onDrawFrame()- 每次View被重绘时被调用。
  • onSurfaceChanged()- 如果view的几和形状发生变化了就调用,例如当竖屏变为横屏时。

如我们要在GLSurfaceView上画了一个灰色的背景。

public class MyGL20Renderer implements GLSurfaceView.Renderer {public void onSurfaceCreated(GL10 unused, EGLConfig config) {//设置背景的颜色GLES20.glClearColor(0.5f, 0.5f, 0.5f, 1.0f);}public void onDrawFrame(GL10 unused) {// 重绘背景色GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);}public void onSurfaceChanged(GL10 unused, int width, int height) {GLES20.glViewport(0, 0, width, height);}
}

OpenGL ES渲染原理

首先来看一个OpenGL ES2.0的渲染原理图。

1 VBO/VAO

VBO/VAO是cpu提供给GPU的顶点信息,包括了顶点的位置、颜色、纹理坐标(用于纹理贴图)等顶点信息。 
VBO,全名Vertex Buffer Object。它是GPU里面的一块缓冲区,当我们需要传递数据的时候,可以先向GPU申请一块内存,然后往里面填充数据。最后,再通过调用glVertexAttribPointer把数据传递给Vertex Shader。 
VAO,全名为Vertex Array Object,它的作用主要是记录当前有哪些VBO,每个VBO里面绑定的是什么数据,还有每一个vertex attribute绑定的是哪一个VBO。

2 VertexShader(顶点着色器)

顶点着色器的输入数据由下面组成:

  • Attributes:使用顶点数组封装每个顶点的数据,一般用于每个顶点都各不相同的变量,如顶点位置、颜色等
  • Uniforms:顶点着色器使用的常量数据,不能被着色器修改,一般用于对同一组顶点组成的单个3D物体中所有顶点都相同的变量,如当前光源的位置。
  • Samplers:这个是可选的,一种特殊的uniforms,表示顶点着色器使用的纹理。
  • Shader program:顶点着色器的源码或可执行文件,描述了将对顶点执行的操作。

顶点着色器的输出:

  • varying:在图元光栅化阶段,这些varying值为每个生成的片元进行计算,并将结果作为片元着色器的输入数据。从分配给每个顶点的原始varying值来为每个片元生成一个varying值的机制叫做插值。
  • 另外,还有gl_postion、gl_FrontFacing和gl_PointSize。

顶点着色器可用于传统的基于顶点的操作,例如:基于矩阵变换位置,进行光照计算来生成每个顶点的颜色,生成或者变换纹理坐标。 
另外因为顶点着色器是由应用程序指定的,所以你可以用来进行任意自定义的顶点变换。

3 PrimitiveAssembly(图元装配):

顶点着色器下一个阶段是图元装配,这个阶段,把顶点着色器输出的顶点组合成图元。图元(primitive)是一个能用opengl es绘图命令绘制的几何体,包括三角形、直线或者点精灵等几何对象,绘图命令指定了一组顶点属性,描述了图元的几何形状和图元类型。在图元装配阶段,这些着色器处理过的顶点被组装到一个个独立的几何图元中,例如三角形、线、点精灵。对于每个图元,必须确定它是否位于视椎体内(3维空间显示在屏幕上的可见区域),如果图元部分在视椎体中,需要进行裁剪,如果图元全部在视椎体外,则直接丢弃图元。裁剪之后,顶点位置转换成了屏幕坐标。背面剔除操作也会执行,它根据图元是正面还是背面,如果是背面则丢弃该图元。经过裁剪和背面剔除操作后,就进入渲染流水线的下一个阶段:光栅化。

4 rasterization(光栅化)

光栅化是将图元转化为一组二维片段的过程,然后,这些片段由片段着色器处理(片段着色器的输入)。这些二维片段代表着可在屏幕上绘制的像素。用于从分配给每个图元顶点的顶点着色器输出生成每个片段值的机制称作插值(Interpolation)。这句不是人话的话解释了一个问题,就是从cpu提供的分散的顶点信息是如何变成屏幕上密集的像素的,图元装配后顶点可以理解成变为图形,光栅化时可以根据图形的形状,插值出那个图形区域的像素(纹理坐标v_texCoord、颜色等信息)。注意,此时的像素并不是屏幕上的像素,是不带有颜色的。接下来的片段着色器完成上色的工作。总之,光栅化阶段把图元转换成片元集合,之后会提交给片元着色器处理,这些片元集合表示可以被绘制到屏幕的像素。

5 FragmentShader(片段着色器)

片段着色器为片段(像素)上的操作实现了通用的可编程方法,光栅化输出的每个片段都执行一遍片段着色器,对光栅化阶段生成每个片段执行这个着色器,生成一个或多个(多重渲染)颜色值作为输出。 
片元着色器对片元实现了一种通用的可编程方法,它对光栅化阶段产生的每个片元进行操作,需要的输入数据如下:

  • Varying variables:顶点着色器输出的varying变量经过光栅化插值计算后产生的作用于每个片元的值。
  • Uniforms:片元着色器使用的常量数据
  • Samplers:一种特殊的uniforms,表示片元着色器使用的纹理。
  • Shader program:片元着色器的源码或可执行文件,描述了将对片元执行的操作。

片元着色器也可以丢弃片元或者为片元生成一个颜色值,保存到内置变量gl_FragColor。光栅化阶段产生的颜色、深度、模板和屏幕坐标(Xw, Yw)成为流水线中pre-fragment阶段(FragmentShader之后)的输入。

6Per-Fragment Operations(逐个片元操作阶段)

片元着色器之后就是逐个片元操作阶段,包括一系列的测试阶段。一个光栅化阶段产生的具有屏幕坐标(Xw, Yw)的片元,只能修改framebuffer(帧缓冲)中位置在(Xw, Yw)的像素。

上图显示了Opengl es 2.0逐片元操作过程:

  • Pixel ownership test:像素所有权测试决定framebuffer中某一个(Xw,Yw)位置的像素是否属于当前Opengl ES的context,比如:如果一个Opengl ES帧缓冲窗口被其他窗口遮住了,窗口系统将决定被遮住的像素不属于当前Opengl ES的context,因此也就不会被显示。
  • Scissor test:裁剪测试决定位置为(Xw, Yw)的片元是否位于裁剪矩形内,如果不在,则被丢弃。
  • Stencil and depth tests:模板和深度测试传入片元的模板和深度值,决定是否丢弃片元。
  • Blending:将新产生的片元颜色值和framebuffer中某个(Xw, Yw)位置存储的颜色值进行混合。
  • Dithering:抖动可以用来最大限度的减少使用有限精度存储颜色值到framebuffer的工件。
  • 逐片元操作之后,片元要么被丢弃,要么一个片元的颜色,深度或者模板值被写入到framebuffer的(Xw,Yw)位置,不过是否真的会写入还得依赖于write masks启用与否。write masks能更好的控制颜色、深度和模板值写入到合适的缓冲区。例如:颜色缓冲区中的write mask可以被设置成没有红色值写入到颜色缓冲区。另外,Opengl ES 2.0提framebuffer中获取像素的接口,不过需要记住的是像素只能从颜色缓冲区读回,深度和模板值不能读回。

参考:
OpenGL渲染流程 http://www.cnblogs.com/BigFeng/p/5068715.html 
OpenGL ES 2.0渲染管线 http://codingnow.cn/opengles/1504.html 
OpenGL ES 2.0可编程管道 http://www.cnblogs.com/listenheart/p/3292672.html 
OpenGL ES 2.0编程基础 http://blog.csdn.net/iispring/article/details/7649628 
OpenGL-渲染管线的流程 http://www.cnblogs.com/zhanglitong/p/3238989.html

OpenGL ES简介(一)相关推荐

  1. OpenGL ES 简介

    目录 一.前言 1.WebGL 2.OpenCV 3.Direct3D 4.OpenGL 5.OpenGL ES 和 OpenGL 二.OpenGL ES 跨平台 1.OpenGL ES 2.Meta ...

  2. 1、OPenGL ES - 简介、iOS中GLKit简单应用

    OPenGL ES -  简介.iOS中GLKit简单应用 一.OPenGL ES 1.简介: OpenGL ES 是以手持和嵌入式为目标的高级的3D图形应用程序编程接口(API),OpenGL ES ...

  3. 【OpenGL ES】OpenGL ES简介

    [参考-khronos]https://www.khronos.org/opengles/ 1.简介 OpenGL ES(OpenGL for Embeded System)是OpenGL(Open ...

  4. OpenGL ES简介及几个相关重要概念

    本文主要简述什么是OpenGLES,以及记录一下与OpenGLES相关的几个最重要的概念. 一.OpenGL ES是什么 OpenGL ES是使用在手机端和嵌入式里的3D图形应用程序编程接口,是跨平台 ...

  5. OpenGL ES EGL 简介

    目录 一.EGL 简介 二.EGL 跨平台之 ANGLE 1.ANGLE 支持跨平台 2.ANGLE 支持渲染器 3.ANGLE 下载地址 三.EGL 坐标系 四.EGL 绘图步骤 五.猜你喜欢 零基 ...

  6. OpenGL ES入门(使用指南)

    转载地址:https://www.ict528.com/wpozv3sz3srrtywpoq1qvuyqooqxz1usvwr2uqoo.html. OpenGL ES 入门 一.前言 OpenGL ...

  7. OpenGL ES EGL eglDestroyContext

    目录 一. EGL 前言 二. EGL 绘制流程简介 三.eglDestroyContext 函数简介 四.eglDestroyContext 使用 四.猜你喜欢 零基础 OpenGL ES 学习路线 ...

  8. OpenGL ES EGL eglCreatePbufferSurface

    目录 一. EGL 前言 二. EGL 绘制流程简介 三.eglCreatePbufferSurface 函数简介 1.eglCreatePbufferSurface 简介 2.eglCreatePb ...

  9. OpenGL ES EGL eglQueryContext

    目录 一. EGL 前言 二. EGL 绘制流程简介 三.eglQueryContext 函数简介 四.eglQueryContext 使用 四.猜你喜欢 零基础 OpenGL ES 学习路线推荐 : ...

最新文章

  1. golang strings.Fields 使用
  2. Go Mvc的一个示例
  3. Java爬虫——B站弹幕爬取
  4. orcale存储过程学习之路--创建空存储过程(二)
  5. 海量数据 - join处理
  6. 4 Oracle 操作表中数据
  7. Maven Web项目解决跨域问题
  8. openstack服务编排
  9. python 连续三个数满足条件_计算满足条件的连续值数(Pandas Dataframe)
  10. 服务器系统怎么找便签,Win10电脑怎么找回便签记录?如何恢复误删的内容?
  11. using在sql中是什么意思_扇贝英语地道表达法——“call for”是什么意思呢?
  12. 字符串插入mysql_mysql实现随机字符串插入
  13. QT5.14.2 官方例子 - 学习系列
  14. 430单片机实现三人投票表决器_基于单片机的五人表决器的设计
  15. 微博音视频下载与合并
  16. 【Pygame小游戏】这款“打地鼠”小游戏要火了(来来来)
  17. Sicily 1090. Highways
  18. w7怎么写html代码,笔记本win7系统使用记事本编辑和运行html代码的方法
  19. 【c++primer】P86--练习:读入一个包含标段符号的字符串,标点符号去除后输出
  20. 找不到ps选择主体_怎么找不到ps“选择主体”功能?

热门文章

  1. 论一种迫不得已用全中文数据库的情景
  2. html进阶css(5)
  3. asp.net mvc 简单文件下载
  4. 【Scheme归纳】4 高阶函数
  5. asp 后台批量管理程序
  6. 两台Windows7的笔记本,充分利用自带无线网卡共享上网。
  7. HTML5 Canvas 和 SVG
  8. web开发兼容性测试工具
  9. 怎么多快好省地学习中文自然语言处理
  10. Javascript第七章cookie的读取和写入源码第一课