我们前两篇介绍了OpenGL ES 基本概念和GLSL及Shader的渲染流程,这篇我们开始实战,通过GLSurfaceView加载着色器,来绘制三角形、正方形和直线这些平面图形。在实践过程中遇到的问题有时候让人没有头绪,检查了一遍又一遍代码,发现流程没有问题,但屏幕就是一片漆黑。。通过近一个小时的排查,发现问题出在了这里。下面开始我们今天的学习时间之旅,希望对你也有帮助。

GLSL着色器的编写

如果对OpenGL的基本概念以及渲染流程不清晰的,建议看下前两篇文章,这些基本概念和流程要了解或者理解,否则后面实践之旅就是跳坑之旅。

我们先通过下面重要的二张图,快速回顾下

工欲善其事,必先利其器,如何方便的编写GLSL代码呐?AndoridStudio提供了“Support for GLSL”插件。VS Code也有比较强大的插件比如“Shader Toy
”和“Shader languages support for VS Code”。但是都不足之处,就是没有自动提示和补全的功能。所以编写GLSL代码是要细心。

关注+后台私信我,领取2022最新最全学习提升资料包+面试题,内容包括(C/C++,Linux,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)等等

1.1 着色器代码的编写

首先我们来编写下顶点着色器和片元着色器

//vertex_shader.glsl 顶点着色器attribute vec4 a_Position;
attribute vec4 a_Color;varying vec4 v_Color;void main() {v_Color = a_Color;gl_Position = a_Position;
}

上述代码简单语法回顾

attribute是修饰符 只能用于顶点着色器,用于修饰可变的参数
vec4: 浮点型向量gl_Position:内置变量varying:也是一个修饰符,用于顶点着色器和片元着色器的值传递。
注意:必须要在顶点着色器和片元着色器都定义同名同类型同varying修饰符的变量,才能正常传递。
//fragment_shader.glsl 片元着色器precision mediump float;varying vec4 v_Color;void main() {gl_FragColor = v_Color;
}

这里对精度precision mediump 做下说明
用于修饰浮点型和整形,有三个等级 highp\mediump、lowp

1.2 着色器代码的读取

着色器代码通常放在assets目录下或者raw目录下,当然也见到过直接写在代码里。我们为了方便,采用比较通用的方式:把glsl代码文件放在了assets下,再加载前需要先把他们读到内存中,常规的文件读写

public static String loadAsset(Resources res, String path) {StringBuilder stringBuilder = new StringBuilder();try {InputStream is = res.getAssets().open(path);byte[] buffer = new byte[1024];int count;while (-1 != (count = is.read(buffer))) {stringBuilder.append(new String(buffer, 0, count));}String result = stringBuilder.toString().replaceAll("\\r\\n", "\n");return result;} catch (IOException e) {e.printStackTrace();}return "";}

1.3 着色器的创建、设置源码、编译

private static int loadShader(int type, String codeStr) {//1. 根据类型(顶点着色器、片元着色器)创建着色器,拿到着色器句柄int shader = GLES20.glCreateShader(type);Log.i(TAG, "compileShaderCode: type=" + type + " shaderId=" + shader);if (shader > 0) {//2. 设置着色器代码 ,shader句柄和code进行绑定GLES20.glShaderSource(shader, codeStr);//3. 编译着色器,GLES20.glCompileShader(shader);//4. 查询编译状态int[] status = new int[1];GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, status, 0);Log.i(TAG, "loadShader: status[0]=" + status[0]);//如果失败,释放资源if (status[0] == 0) {GLES20.glDeleteShader(shader);return 0;}}return shader;}

1.4 程序的创建、attach着色器、链接、使用

public static int loadProgram(String verCode, String fragmentCode) {//1. 创建Shader程序,获取到program句柄int programId = GLES20.glCreateProgram();if(programId == 0){Log.e(TAG, "loadProgram: glCreateProgram error" );return 0;}//2. 根据着色器语言类型和代码,attach着色器GLES20.glAttachShader(programId, loadShader(GLES20.GL_VERTEX_SHADER, verCode));GLES20.glAttachShader(programId, loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentCode));//3. 链接GLES20.glLinkProgram(programId);//4. 使用GLES20.glUseProgram(programId);return programId;}

1.5 状态查询

  1. 着色器Shader和Program创建后会拿到对应的句柄,通过检查是否大于0验证是否可用

  2. GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, status, 0);着色器编译后检查编译的状态是否大于0判断可用性。

  3. glGetProgramiv(programObjectId, GL_VALIDATE_STATUS,
    validateStatus, 0);//程序链接后,检查程序的可用性

1.6 输入与输出

输入:给着色器赋值
输出:在屏幕上显示

前面5个步骤把准备工作都做好了,那边现在面临两个问题.

1. 我们看到顶点着色器中有两个attribute修饰的变量,如何给它们赋值?
2. 如何把着色器在屏幕上绘制出来?

这个环节我们就来解决这两个问题

首先定义好顶点坐标和颜色的位数和着色器的数据

    //每个顶点坐标的个数private final static int COORDS_PER_VERTEX = 2;//每个顶点颜色的个数private final static int COLOR_PER_VERTEX = 3;// 浮点类型占用的字节数private final static int BYTES_PER_FLOAT = 4;//下面两个字符串常量就是GLSL顶点着色器的输入private static final String A_POSITION = "a_Position";private static final String A_COLOR = "a_Color";//STRIDE是一个顶点的字节偏移(顶点坐标xy+颜色rgb)private final int STRIDE = (COORDS_PER_VERTEX+ COLOR_PER_VERTEX )* BYTES_PER_FLOAT;private FloatBuffer mVertexData;public MyRender2() {//顶点数组float[] TRIANGLE_COORDS = {0.5f, 0.5f,1f, 0.5f,0.5f,-0.5f, -0.5f,0.5f, 1f,0.5f,0.5f, -0.5f,0.5f, 0.5f,1f};//通过nio ByteBuffer把设置的顶点数据加载到内存mVertexData = ByteBuffer.allocateDirect(TRIANGLE_COORDS.length * BYTES_PER_FLOAT) //需要多少字节内存.order(ByteOrder.nativeOrder())//大小端排序.asFloatBuffer().put(TRIANGLE_COORDS);//设置数据}

然后给顶点着色器的变量赋值

 String vertexCode = ShaderHelper.loadAsset(MyApplication.getContext().getResources(), "vertex_shader.glsl");String fragmentCode = ShaderHelper.loadAsset(MyApplication.getContext().getResources(), "fragment_shader.glsl");//创建着色器程序programId = ShaderHelper.loadProgram(vertexCode, fragmentCode);int aPosition = GLES20.glGetAttribLocation(programId, A_POSITION);Log.i(TAG, "drawFrame: aposition="+aPosition);mVertexData.position(0);GLES20.glVertexAttribPointer(aPosition,COORDS_PER_VERTEX,//用几个偏移描述一个顶点GLES20.GL_FLOAT,//顶点数据类型false,STRIDE,//一个顶点需要多少个字节偏移mVertexData//分配的buffer);//开启顶点着色器的attributeGLES20.glEnableVertexAttribArray(aPosition);int aColor = GLES20.glGetAttribLocation(programId, A_COLOR);mVertexData.position(COORDS_PER_VERTEX);GLES20.glVertexAttribPointer(aColor,COLOR_PER_VERTEX,GL_FLOAT,false,STRIDE,mVertexData);GLES20.glEnableVertexAttribArray(aColor);

关键API说明

  1. 获取着色器attribute一个属性:int aPosition = GLES20.glGetAttribLocation(programId, A_POSITION);_

  2. 数据的偏移:mVertexData.position(0); 因为有坐标和颜色两个变量的值,数据又是根据顶点一一设定的。

  3. 给attribute赋值:GLES20. glVertexAttribPointer(
    int indx,//attribute的句柄
    int size,//在数组中占用的位数
    int type,//数据的类型
    boolean normalized,
    int stride,//步幅 单位字节数
    java.nio.Buffer ptr // 元数据
    )

  4. 使能对应的attribute属性:GLES20.glEnableVertexAttribArray(aPosition);

通过上面几个环节我们可以看到,我们可以看到,是如何给着色器语言中的变量赋值的。下面我们看来下如何渲染。

我们采用GlSurfaceView,通过Render来实现,具体如下:

    //1. 设置OpenGL ES的版本glSView.setEGLContextClientVersion(3);//2. 给glSurfaceView设置renderglSView.setRenderer(new MyRender2());public class MyRender2 implements GLSurfaceView.Renderer {@Overridepublic void onSurfaceCreated(GL10 gl, EGLConfig config) {//着色器的加载、赋值}@Overridepublic void onSurfaceChanged(GL10 gl, int width, int height) {GLES20.glViewport(0, 0, width, height);}@Overridepublic void onDrawFrame(GL10 gl) {//清屏GLES20.glClear(GL_COLOR_BUFFER_BIT);//绘制GLES20.glDrawArrays(GLES20.GL_TRIANGLE_FAN,0,3);}
}是的,在onDrawFrame中进行不断的 glDrawArrays来绘制刷新   public static native void glDrawArrays(int mode, //点、线、三角形int first,//顶点的第一个数据的indexint count//顶点的总数);

二、实践:用GLSurfaceView加载GLSL绘制屏幕图形

2.1 三角形

上面的代码中定义的就是三角形,对应顶点数据如下

float[] TRIANGLE_COORDS = {0.5f, 0.5f,1f, 0.5f,0.5f,-0.5f, -0.5f,0.5f, 1f,0.5f,0.5f, -0.5f,0.5f, 0.5f,1f};两个坐标位x&y,和三个颜色位rgb

效果如下

2.2 正方形

只需要修改 顶点数组和glDrawArrays的count参数即可

float[] TRIANGLE_COORDS = {0.5f, 0.5f,1f, 0.5f,0.5f,-0.5f, -0.5f,0.5f, 1f,0.5f,0.5f, -0.5f,0.5f, 0.5f,1f,0.5f, 0.5f,1f, 0.5f,0.5f,-0.5f, 0.5f,0.5f, 0.5f,1f,-0.5f, -0.5f,0.5f, 1f,0.5f,};public void onDrawFrame(GL10 gl) {GLES20.glClear(GL_COLOR_BUFFER_BIT);GLES20.glDrawArrays(GLES20.GL_TRIANGLES,0,6);}

2.3 直线

     float[] TRIANGLE_COORDS = {-0.5f, 0f, 1f, 0f, 0f,0.5f, 0f, 0f, 0f, 1f,};public void onDrawFrame(GL10 gl) {GLES20.glClear(GL_COLOR_BUFFER_BIT);GLES20.glDrawArrays(GLES20.GL_LINES,0,2);}

通过log分析,我们发现GLSurfaceView.Renderer是运行中一个叫GLThread的线程中,它的作用和意义是什么,下一篇我们通过对GLSurfaceView的源码分析来理解EGL和GLThread,了解完整的流程,然后不使用Render,采用自建管理EGL和创建GLThread,通过TextureView实现图形的绘制。做到知其然,也知其所以然。

三、遇到的问题

1. A/libc: Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0 in tid 9940 (GLThread 6703)_发现是GLES20.glCreateProgram()时出现的上述崩溃
原因是因为没有glSView.setEGLContextClientVersion(3);
2. GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, status, 0)
Shader编译后获取状态为0(失败了)原因是因为glsl语言注释不对用成了 # 而不是 //
3.  无法正常的看到期望的图片
这个就是开头说的说的那个折腾了一个小时的问题,一个“逗号”引发的问题具体如下:float[] TRIANGLE_COORDS = {0.5f, 0.5f,1f, 0.5f,0.5f ///注意这里没有加逗号,就是这个导致的,glsl认为到这里就结束了。。但是代码上写的是3个顶点,找不到就无法正常绘制。多么痛的领悟。-0.5f, -0.5f,0.5f, 1f,0.5f,0.5f, -0.5f,0.5f, 0.5f,1f};

四、参考

《OpenGL ES应用开发实践指南》
《音视频开发进阶指南》
[搭建OpenGL ES环境的两种方式]
[Android OpenGL ES(一)-开始描绘一个平面三角形]
[Android OpenGL ES(三)-平面图形]
[EGL 环境搭建流程]

五、收获

  1. 通过代码实践加深了对GLSL语法,OpenGL基本概念和绘制流程的熟悉。

  2. glsl程序编写androidstudio等IDE插件的了解

  3. 理解实现了如何给着色器输入数据,又如何在屏幕上绘制。

  4. 绘制三角形、正方形、直线等平面图形

  5. 遇到的问题分析解决,弥补认知不足。

感谢你的阅读。

音视频开发系列(34) OpenGL ES 绘制平面图形相关推荐

  1. 音视频开发系列(32)OpenGL ES 基本概念

    目录 OpenGL ES的简介 OpenGL ES的基本流程和概念 篇外话:本来这篇要写SurfaceView和TextureView相关的,但是没有理解清楚,主要是对于纹理和SurfaceFling ...

  2. 音视频开发系列(41)OpenGL ES粒子效果-烟花爆炸

    通过该篇的实践实现如下效果 一.烟花爆竹场景和属性 在上一篇OpenGL ES粒子系统 - 喷泉的基础上 实现烟花爆炸效果. 在具体实践之前,先来想一想,烟花爆炸的场景和属性 场景:从中心点开始爆炸, ...

  3. 【音视频开发系列】一学就会,快速掌握音视频开发的第一个开源项目FFmpeg

    快速掌握音视频开发的第一个开源项目:FFmpeg 1.为什么要学FFmpeg 2.FFmpeg面向对象思想分析 3.FFmpeg各种组件剖析 视频讲解如下,点击观看: [音视频开发系列]一学就会,快速 ...

  4. 【音视频开发系列】盘点音视频直播RTSP/RTMP推流一定会遇到的各种坑,教你快速解决

    聊聊RTSP/RTMP推流那些坑 1.推流架构分析 2.推流缓存队列的设计 3.FFmpeg函数阻塞问题分析 [音视频开发系列]盘点音视频直播一定会遇到的各种坑,教你快速解决 更多精彩内容包括:C/C ...

  5. 【音视频开发系列】srs-webrtc-janus开源流媒体服务器分析

    全球最牛开源流媒体服务器源码分析 1.如何学习流媒体服务器 2.全球最牛流媒体服务器架构分析 3.我们能从全球最牛流媒体服务器得到什么 [音视频开发系列]srs-webrtc-janus流媒体服务器分 ...

  6. 音视频开发系列-H264编码原理

    H264简介 来自百度百科的介绍: H.264是国际标准化组织(ISO)和国际电信联盟(ITU)共同提出的继MPEG4之后的新一代数字视频压缩格式. H.264是ITU-T以H.26x系列为名称命名的 ...

  7. 音视频开发系列(62)基于FFmpeg实现简单的视频解码器

    一.FFmpeg解码过程流程图和关键的数据结构 FFmpeg解码涉及的知识点比较多,很容易被函数和结构体搞定不知所错,我们先从整体上对解码流程有个认知,画了张解码流程图,如下 1.1 解码流程如下 a ...

  8. 音视频开发系列--H264编解码总结

    一.概述 H264,通常也被称之为H264/AVC(或者H.264/MPEG-4 AVC或MPEG-4/H.264 AVC) 对摄像头采集的每一帧视频需要进行编码,由于视频中存在空间和时间的冗余,需要 ...

  9. 音视频开发系列(19)玩转 WebRTC 安全通信:一文读懂 DTLS 协议

    在 WebRTC 中,为了保证媒体传输的安全性,引入了 DTLS 来对通信过程进行加密.DTLS 的作用.原理与 SSL/TLS 类似,都是为了使得原本不安全的通信过程变得安全.它们的区别点是 DTL ...

最新文章

  1. 用户至上-阿里马马篇
  2. 每句话都可以品半辈子!!!
  3. python编程if语法-Python编程入门基础语法详解经典
  4. 从民宅到独栋大厦 我们搬家啦!
  5. IIS连接数、IIS并发连接数、IIS最大并发工作线程数、应用程序池的队列长度、应用程序池的......
  6. mysql超经典的8小时问题-wait_timeout
  7. SPOJ4487(Splay树)
  8. matlab 仿真钢琴,用Matlab模拟钢琴的声音
  9. 21天Jenkins打卡Day6安装插件
  10. 第八届“图灵杯”NEUQ-ACM程序设计竞赛个人赛(同步赛)BIJ,签到抽奖
  11. 使用sobel、prewitt、拉普拉斯算子、差分法提取图像的边缘
  12. 号称“绝对安全”的量子通信到底是什么?
  13. 什么是OGNL表达式
  14. windows版本修改,家庭版改专业版,专业版改教育版,或者是改家庭版
  15. sendmail php qq垃圾邮件,发送邮件,被QQ定义为疑似垃圾邮件,如何解决这个有关问题...
  16. DirectX12(D3D12)基础教程(十)——DXR(DirectX Raytracing)基础教程(上)
  17. pc端高德地图获取当前位置
  18. 一文学会CentOS 文件常用命令
  19. 客户期望,客户满意度,客户体验和客户忠诚度之间存在的联系
  20. 小芳同学的错题总结(十四)

热门文章

  1. Bazel 国内镜像源加速下载
  2. 如何利用数据挖掘平台,通过数据建模,解决垃圾短信带来的困扰
  3. 微信公众号获取用户当前经纬度
  4. 若可框架,关于dicts字典的用法
  5. SAP-MM采购订单相关的主要后台表间关系
  6. springboot报错discard long time none received connection. 的解决方法
  7. outlook添加新账户服务器信息怎么填,outlook如何再添加一个新账户?
  8. 2021年中国粗钢市场供需情况分析:海外需求,拉动增长[图]
  9. Linux下的线程池源代码请到下面的链接下载
  10. 助你健康的25条习惯(转)