音视频开发系列(34) OpenGL ES 绘制平面图形
我们前两篇介绍了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 状态查询
着色器Shader和Program创建后会拿到对应的句柄,通过检查是否大于0验证是否可用
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, status, 0);着色器编译后检查编译的状态是否大于0判断可用性。
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说明
获取着色器attribute一个属性:int aPosition = GLES20.glGetAttribLocation(programId, A_POSITION);_
数据的偏移:mVertexData.position(0); 因为有坐标和颜色两个变量的值,数据又是根据顶点一一设定的。
给attribute赋值:GLES20. glVertexAttribPointer(
int indx,//attribute的句柄
int size,//在数组中占用的位数
int type,//数据的类型
boolean normalized,
int stride,//步幅 单位字节数
java.nio.Buffer ptr // 元数据
)使能对应的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 环境搭建流程]
五、收获
通过代码实践加深了对GLSL语法,OpenGL基本概念和绘制流程的熟悉。
glsl程序编写androidstudio等IDE插件的了解
理解实现了如何给着色器输入数据,又如何在屏幕上绘制。
绘制三角形、正方形、直线等平面图形
遇到的问题分析解决,弥补认知不足。
感谢你的阅读。
音视频开发系列(34) OpenGL ES 绘制平面图形相关推荐
- 音视频开发系列(32)OpenGL ES 基本概念
目录 OpenGL ES的简介 OpenGL ES的基本流程和概念 篇外话:本来这篇要写SurfaceView和TextureView相关的,但是没有理解清楚,主要是对于纹理和SurfaceFling ...
- 音视频开发系列(41)OpenGL ES粒子效果-烟花爆炸
通过该篇的实践实现如下效果 一.烟花爆竹场景和属性 在上一篇OpenGL ES粒子系统 - 喷泉的基础上 实现烟花爆炸效果. 在具体实践之前,先来想一想,烟花爆炸的场景和属性 场景:从中心点开始爆炸, ...
- 【音视频开发系列】一学就会,快速掌握音视频开发的第一个开源项目FFmpeg
快速掌握音视频开发的第一个开源项目:FFmpeg 1.为什么要学FFmpeg 2.FFmpeg面向对象思想分析 3.FFmpeg各种组件剖析 视频讲解如下,点击观看: [音视频开发系列]一学就会,快速 ...
- 【音视频开发系列】盘点音视频直播RTSP/RTMP推流一定会遇到的各种坑,教你快速解决
聊聊RTSP/RTMP推流那些坑 1.推流架构分析 2.推流缓存队列的设计 3.FFmpeg函数阻塞问题分析 [音视频开发系列]盘点音视频直播一定会遇到的各种坑,教你快速解决 更多精彩内容包括:C/C ...
- 【音视频开发系列】srs-webrtc-janus开源流媒体服务器分析
全球最牛开源流媒体服务器源码分析 1.如何学习流媒体服务器 2.全球最牛流媒体服务器架构分析 3.我们能从全球最牛流媒体服务器得到什么 [音视频开发系列]srs-webrtc-janus流媒体服务器分 ...
- 音视频开发系列-H264编码原理
H264简介 来自百度百科的介绍: H.264是国际标准化组织(ISO)和国际电信联盟(ITU)共同提出的继MPEG4之后的新一代数字视频压缩格式. H.264是ITU-T以H.26x系列为名称命名的 ...
- 音视频开发系列(62)基于FFmpeg实现简单的视频解码器
一.FFmpeg解码过程流程图和关键的数据结构 FFmpeg解码涉及的知识点比较多,很容易被函数和结构体搞定不知所错,我们先从整体上对解码流程有个认知,画了张解码流程图,如下 1.1 解码流程如下 a ...
- 音视频开发系列--H264编解码总结
一.概述 H264,通常也被称之为H264/AVC(或者H.264/MPEG-4 AVC或MPEG-4/H.264 AVC) 对摄像头采集的每一帧视频需要进行编码,由于视频中存在空间和时间的冗余,需要 ...
- 音视频开发系列(19)玩转 WebRTC 安全通信:一文读懂 DTLS 协议
在 WebRTC 中,为了保证媒体传输的安全性,引入了 DTLS 来对通信过程进行加密.DTLS 的作用.原理与 SSL/TLS 类似,都是为了使得原本不安全的通信过程变得安全.它们的区别点是 DTL ...
最新文章
- 用户至上-阿里马马篇
- 每句话都可以品半辈子!!!
- python编程if语法-Python编程入门基础语法详解经典
- 从民宅到独栋大厦 我们搬家啦!
- IIS连接数、IIS并发连接数、IIS最大并发工作线程数、应用程序池的队列长度、应用程序池的......
- mysql超经典的8小时问题-wait_timeout
- SPOJ4487(Splay树)
- matlab 仿真钢琴,用Matlab模拟钢琴的声音
- 21天Jenkins打卡Day6安装插件
- 第八届“图灵杯”NEUQ-ACM程序设计竞赛个人赛(同步赛)BIJ,签到抽奖
- 使用sobel、prewitt、拉普拉斯算子、差分法提取图像的边缘
- 号称“绝对安全”的量子通信到底是什么?
- 什么是OGNL表达式
- windows版本修改,家庭版改专业版,专业版改教育版,或者是改家庭版
- sendmail php qq垃圾邮件,发送邮件,被QQ定义为疑似垃圾邮件,如何解决这个有关问题...
- DirectX12(D3D12)基础教程(十)——DXR(DirectX Raytracing)基础教程(上)
- pc端高德地图获取当前位置
- 一文学会CentOS 文件常用命令
- 客户期望,客户满意度,客户体验和客户忠诚度之间存在的联系
- 小芳同学的错题总结(十四)