opengl es学习笔记

标签: opengl  opengl es

OpenGL ES

OpenGL 是一种应用程序编程接口,它是一种可以对图形硬件设备特性进行访问的软件库。

重点:OpenGL 是一种接口,既然是接口,那么就必然要有实现。

事实上,它的实现是由显示设备厂商提供的,而且依赖于厂商提供的硬件设备。

OpenGL 常用于 CAD、虚拟实境、科学可视化程序和电子游戏开发。

在 Android 上使用的是 OpenGL ES,它是 OpenGL 的子集,在 OpenGL 的基础之上裁剪掉了一些非必要的部分,主要是针对手机、PAD 和游戏主机等嵌入式设备设计的。

在 Android 上开发 OpenGL 既可以使用 Java 也可以使用 C ,话不多说,撸起袖子就是干!

渲染管线

703548-20190402212658795-1353723850.png

渲染管线也称为渲染流水线像素流水线像素管线,是GPU处理图形信号的相互独立的并行处理单元。

这张图展示了我们调用OpenGL的drawXXX()方法后执行的流程,我们传递的顶点首先会经过顶点着色器vertex shader的处理,一般会在里面做顶点变换相关的逻辑,然后进行图元装配,再经过几何着色器geometry shader,这个着色器相对来说使用得少一些,可暂时先忽略,然后接下来就是光栅化,所谓光栅化就是把我们要渲染的图像打碎成屏幕上的像素,因为最终要显示到屏幕上,就必须将图形对应到像素上,光栅化完成后,我们就有了要渲染的图形对应的像素,此时像素还没有颜色,需要我们填上颜色,这时就到达到了片段着色器fragment shader,在fragment shader中我们通常进行颜色的计算,确定对应的像素显示什么颜色,fragment shader将在下篇文章中介绍。

在整个渲染管线中,vertex shader、geometry shader和fragment shader这三部分是可编程分部,可编写shader代码实现相应的功能,我们目前重点关注vertex shader和fragment shader。

这里特别注意一点,我们的shader代码并不是像普通程序那样,一次性输入所有顶点,然后再输出,例如对于vertex shader,我们传递了3个顶点,并不是3个顶点一起执行一次vertex shader,而是分别对这3个顶点执行一次,也就是执行了3次。对于fragment shader也是类似的,并不是执行一次为所有的像素填充颜色,而是对每个像素都执行一次。这个特点有时让初学者感到困惑。

坐标

image.png

下面是纹理坐标系统和位置坐标系统之间的对应关系:

14387082-7add173376ac70ba.png

内存拷贝

当我们定义好顶点坐标,那么就可以将顶点坐标传入渲染管线,进行一些列操作。但是我们如何传给Opengl呢?

OpenGL 的实现是由显示设备厂商提供的,它作为本地系统库直接运行在硬件上。而我们定义的顶点 Java 代码是运行在虚拟机上的,这就涉及到了如何把 Java 层的内存复制到 Native 层了。

一种方法是直接使用JNI开发,直接调用本地系统库,也就是用 C++ 来开发 OpenGL,这种实现肯定要学会的。

另一种方法就是在 Java 层把内存块复制到 Native 层。

使用ByteBuffer.allocateDirect()方法就可以分配一块 Native 内存,这块内存不会被 Java 的垃圾回收器管理。

ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * (Float.SIZE / 8));
vbb.order(ByteOrder.nativeOrder());               //设置字节顺序
FloatBuffer vertexBuf = vbb.asFloatBuffer();      //转换为Float型缓冲
vertexBuf.put(vertices);                          //向缓冲区中放入顶点坐标数据
vertexBuf.position(0);                            //设置缓冲区起始位置

allocateDirect方法分配了内存并指定了大小之后,下一步就是告诉 ByteBuffer 按照本地字节序组织它的内容。本地字节序是指,当一个值占用多个字节时,比如 32 位整型数,字节按照从最重要位到最不重要位或者相反顺序排列。

接下来asFloatBuffer方法可以得到一个反映底层字节的 FloatBuffer 类实例,避免直接操作单独的字节,而是使用浮点数。

最后,通过put方法就可以把数据从 Java 层内存复制到 Native 层了,当进程结束时,这块内存就会被释放掉。

顶点着色器(Vertex Shader)

主要负责描绘图形,也就是根据顶点坐标,建立图形模型。

根据上图的渲染管线,顶点着色器到片段着色器之间,还要经过组装图元光栅化图元

片段着色器 (Fragment Shader)

片段着色器的主要目的就是告诉 GPU 每个片段的最终颜色应该是什么。

光栅化技术

移动设备的显示屏由成百上千个小的、独立的部件组成,他们称为像素。每个像素通常由三个单独的子组件构成,它们发出红色、绿色和蓝色的光,因为每个像素都非常小,人的眼睛会把红色、绿色和蓝色的光混合在一起,从而创造出巨量的颜色范围。

OpenGL 就是通过 光栅化 技术的过程把每个点、直线及三角形分解成大量的小片段,它们可以映射到移动设备显示屏的像素上,从而生成一幅图像。这些片段类似于显示屏上的像素,每一个都包含单一的纯色。

如下图所示:

640.png

OpenGL 通过光栅化技术把一条直线映射为一个片段集合,显示系统通常会把这些片段直接映射到屏幕上的像素,结果一个片段就对应一个像素。

明白了这样的显示原理,就可以在其中做一些操作了,这就是片段着色器的功能了。

帧缓冲(FrameBuffer Object )

frame buffer,即帧缓存,顾名思义,它就是能缓存一帧的这么个东西,它有什么用呢?大家回想我们之前的教程,我们都是通过一次渲染把内容渲染到屏幕(严格来说是渲染到GLSurfaceview上),如果我们的渲染由多个步骤组成,而每个步骤的渲染结果会给到下一个步骤作为输入,那么就要用到 frame buffer.

640.jpg

frame buffer有一些个attachment,例如color attachmentdepth attachmentstencil attachmentframe buffer具有什么样的功能,就与frame buffer绑定的attachment有关。

其中color attachment就是用来绑定texture的,当将一个color attachment绑定到一个texture上后,就可以用这个frame buffer来承载渲染的结果,渲染的结果实际上是到了这个绑定的texture上。

depth attachment是用来存储深度信息的,在3D渲染时才会用到,stencil attachment则是在模板测试时会用到,这里先不介绍。

可以看到,frame buffer本身其实并不会存储数据,都是通过attachment去绑定别的东西来存储相应的数据,我们今天要讲的就是color attachment,我们会将frame buffer中的一个attachment绑定到一个texture上,然后先将第一步的效果渲染到这个frame buffer上作为中间结果,然后将这个texture作为第二步的输入。

EGL

一句总结就是:EGL是连接OpenGL ES与本地窗口系统的桥梁。

我们知道OpenGL是跨平台的,但是不同平台上的窗口系统是不一样的,它就需要一个东西帮助OpenGL与本地窗口系统进行对接、管理及执行GL命令等。

这听起来挺底层的,我们为什么需要去了解这个呢?我举几个例子,比如你想把你的GL逻辑多线程化,以提升效率,如果不了解EGL,直接把GL操作简单地拆分到多个线程中执行,会发现有问题,后文也会提到,再比如,你想用MediaCodec做视频编解码,你会发现,也常常需要了解EGL,特别是当你想在编码前、解码后做OpenGL特效处理时,比如将原视频进行OpenGL ES特效渲染然后编码保存,或者是解码原视频然后进行OpenGL ES特效渲染再显示出来。编码时需要将要编码的帧渲染到MediaCodec给你的一块surface上,而这些操作需要有EGL才能做,而解码时是解码到一块你自己指定的surface上,此时你也没有一个现成的EGL环境,如果你想解码出来先用OpenGL ES做些特效处理再显示出来,那么这时也需要EGL环境。

//EGL创建步骤

EGLDisplay eglDisplay = EGL14.EGL_NO_DISPLAY;
EGLContext eglContext = EGL14.EGL_NO_CONTEXT;
EGLConfig eglConfig = null;
int glVersion = -1;

//1.获取显示设备  
//这里获取的是default的显示设备,大多数情况下我们都是获取default,因为大多数情况下设备只有一个屏幕
eglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);

//2.初始化设备
//这里初始化完成后,会返回给我们支持的EGL的主版本和子版本
int[] version = new int[2];
EGL14.eglInitialize(eglDisplay, version, 0, version, 1);

//3.选择config
//attribList是我们期望的配置,我们这里的配置是将RGBA颜色深度设置为8位,并将OpenGL ES版本设置为2和3,表示同时支持OpenGL 2和OpenGL 3,最后以一个EGL14.EGL_NONE作为结束符。
int[] attribList = {
    EGL14.EGL_RED_SIZE, 8,
    EGL14.EGL_GREEN_SIZE, 8,
    EGL14.EGL_BLUE_SIZE, 8,
    EGL14.EGL_ALPHA_SIZE, 8,
    EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT | EGLExt.EGL_OPENGL_ES3_BIT_KHR,
    EGL14.EGL_NONE
};
EGLConfig[] configs = new EGLConfig[1];
int[] numConfigs = new int[1];
EGL14.eglChooseConfig(eglDisplay, attribList, 
                      0, configs, 0, configs.length,numConfigs, 0);
//eglConfig是返回的尽可能接近我们期望的配置的列表,通常我们取第0个来使用,即最符合我们期望配置。
eglConfig = configs[0];

//4.创建EGL Context
int[] attrib3_list = {
	EGL14.EGL_CONTEXT_CLIENT_VERSION, 3,
	EGL14.EGL_NONE
};
//注意第三个参数,它是指定一个共享的EGL Context,共享后,2个EGL Context可以相互使用对方创建的texture等资源,默认情况下是不共享的,但不是所有资源都能共享,例如program就是不共享的。
eglContext = EGL14.eglCreateContext(
    eglDisplay,
    eglConfig,
    EGL14.EGL_NO_CONTEXT,
    attrib3_list,
    0
);

//5.创建EGL Surface
//可以理解成是一个用于承载显示内容的东西,这里有2种EGL Surface可以选择,一种是window surface,一种是pbuffer surface,
int[] surfaceAttribs = {
	EGL14.EGL_NONE
};
EGLSurface eglSurface1 = EGL14.eglCreateWindowSurface(
    eglDisplay, eglConfig, surface,surfaceAttribs, 0);

/*
int[] surfaceAttribs2 = {
    EGL14.EGL_WIDTH, width,
    EGL14.EGL_HEIGHT, height,
	EGL14.EGL_NONE
};
EGLSurface eglSurface2 = EGL14.eglCreatePbufferSurface(
    eglDisplay, 
    eglConfig,
    surfaceAttribs2, 
    0
);
*/

//6.绑定EGL
//一个线程只能绑定一个EGL环境,如果之前绑过其它的,后面又绑了一个,那就会是最后绑的那个。至此,就能让一个线程拥有EGL环境了,此后就可以顺利地做GL操作了。
EGL14.eglMakeCurrent(
    eglDisplay, 
    eglSurface, 
    eglSurface, 
    eglContext
);

常用代码

  • 创建纹理

    //获取bitmap
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inScaled = false;
    Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), resourceId, options);
    //创建纹理
    int[] textureIds = new int[1];
    glGenTextures(1, textureIds, 0);
    
    //绑定纹理
    glBindTexture(GL_TEXTURE_2D, textureIds[0]);
    
    // 设置缩小的情况下过滤方式
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    // 设置放大的情况下过滤方式
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    
    // 加载纹理到 OpenGL,读入 Bitmap 定义的位图数据,并把它复制到当前绑定的纹理对象
    // 当前绑定的纹理对象就会被附加上纹理图像。
    texImage2D(GL_TEXTURE_2D, 0, bitmap, 0);
    bitmap.recycle();
    
    //解除与纹理的绑定,避免用其他的纹理方法意外地改变这个纹理
    glBindTexture(GL_TEXTURE_2D, 0);
    
    
  • 帧缓冲 FBO(FrameBuffer Object)

    int frameBuffer = 0;
    int frameBufferTexture = 0;
    
    //创建FBO
    int[] framebuffers = new int[1];
    GLES20.glGenFramebuffers(framebuffers.length, framebuffers, 0);
    frameBuffer = framebuffers[0];
    
    //创建FBO需要绑定的texture
    int[] textures = new int[1];
    GLES20.glGenTextures(textures.length, textures, 0);
    frameBufferTexture = textures[0];
    glBindTexture(GLES20.GL_TEXTURE_2D, framebufferTexture);
    //设置texture过滤方式
    GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, 
                           GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
    GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,
                           GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
    //设置texture环绕方式
    GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, 
                           GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
    GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, 
                           GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
    //给texture分配内存,对于这个texture,我们只分配内存,而不去填充它,因此最后的参数为null
    GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, width, height, 
                        0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null);
    
    //把texture绑定到颜色附件
    GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, frameBuffer);
    GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, frameBufferTexture, 0);
    
    
版权声明:本文为lzverygood原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/lzverygood/article/details/102784848

智能推荐

PoolThreadCache

缓存构成   PoolThreadCache的缓存由三部分构成:tiny、small 和 normal。 tiny   缓存数据大小区间为[16B, 496B]数据,数组长度为32,根据数据大小计算索引的办法:数据大小除以16,如下代码所示: small   缓存数据大小区间为[512B, 4KB]数据,数组长度为4,根据数据大小计算索引的办法:数据大小除以512,然后log2得到指数,如下代码所...

Intellij IDEA 搭建Spring Boot项目(一)

Intellij IDEA 搭建Spring Boot项目 标签(空格分隔): SpringBoot JAVA后台 第一步 选择File –> New –> Project –>Spring Initialer –> 点击Next  第二步 自己修改 Group 和 Artif...

CentOS学习之路1-wget下载安装配置

参考1: https://blog.csdn.net/zhaoyanjun6/article/details/79108129 参考2: http://www.souvc.com/?p=1569 CentOS学习之路1-wget下载安装配置 1.wget的安装与基本使用 安装wget yum 安装软件 默认安装保存在/var/cache/yum ,用于所有用户使用。 帮助命令 基本用法 例子:下载...

深入浅出Spring的IOC容器,对Spring的IOC容器源码进行深入理解

文章目录 DispatcherServlet整体继承图 入口:DispatcherServlet.init() HttpServletBean.init() FrameworkServlet.initServletBean() 首先大家,去看Spring的源码入口,第一个就是DispatcherServlet DispatcherServlet整体继承图 入口:DispatcherServlet....

laravel框架的课堂知识点概总

1. MVC 1.1 概念理解 MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑 MVC 是一种使用 MVC(Model View Controller ...

猜你喜欢

Unity人物角色动画系统学习总结

使用动画系统控制人物行走、转向、翻墙、滑行、拾取木头 混合树用来混合多个动画 MatchTarget用来匹配翻墙贴合墙上的某一点,人物以此为支点翻墙跳跃 IK动画类似于MatchTarget,控制两只手上的两个点来指定手的旋转和位置,使得拾取木头时更逼真 创建AnimatorController: 首先创建一个混合树,然后双击 可以看到该混合树有五种状态机,分别是Idle、WalkForward、...

Composer 安装 ThinkPHP6 问题

Composer 安装 ThinkPHP6 问题 先说说问题 一.运行环境要求 二.配置 参考: ThinkPHP6.0完全开发手册 先说说问题 执行ThinkPHP6的安装命令 遇到问题汇总如下: 看提示是要更新版本,执行命令更新。 更新之后,再次安装ThinkPHP,之后遇到如下问题。 尝试了很多方法,依然不能解决。其中包括使用https://packagist.phpcomposer.com...

Spring Boot 整合JDBC

今天主要讲解一下SpringBoot如何整合JDBC,没啥理论好说的,直接上代码,看项目整体结构 看一下对应的pom.xml 定义User.java 定义数据源配置,这里使用druid,所以需要写一个配置类 上面指定druid的属性配置,和用户登录的账号信息以及对应的过滤规则: 下面定义数据访问接口和对应的实现: 数据访问层很简单,直接注入JdbcTemplate模板即可,下面再看对应的servi...

html鼠标悬停显示样式

1.显示小手:     在style中添加cursor:pointer 实现鼠标悬停变成小手样式     实例:         其他参数: cursor语法: cursor : auto | crosshair | default | hand | move | help | wait | tex...

Yupoo(又拍网)的系统架构

Yupoo!(又拍网) 是目前国内最大的图片服务提供商,整个网站构建于大量的开源软件之上。以下为其使用到的开源软件信息: 操作系统:CentOS、MacOSX、Ubuntu 服务器:Apache、Nginx、Squid 数据库:MySQLmochiweb、MySQLdb 服务器监控:Cacti、Nagios、 开发语言:PHP、Python、Erlang、Java、Lua 分布式计算:Hadoop...