OpenGL ES3.0

OpenGL ES3.0

前言

一、OpenGL ES3.0 基础概念

二、OpenGL ES3.0与2.0的区别

三、OpenGL ES 3.0 PBO 的使用

四、动效



Android渲染架构

在这里插入图片描述

  • image stream produceers:渲染数据的生产者,如App的draw方法会把绘制指令通过canvas传递给framework层的RenderThread线程。
  • native Framework:RenderThread线程通过surface.dequeue得到缓冲区graphic bufer,然后在上面通过OpenGL来完成真正的渲染命令。在把缓冲区交还给BufferQueue队列中。
  • image stream consumers:surfaceFlinger从队列中获取数据,同时和HAL完成layer的合成工作,最终交给HAL展示。
  • HAL:硬件抽象层。把图形数据展示到设备屏幕。

可以看出,这其实是个很典型的生产者消费者模式。
在这里插入图片描述

  • 图像生产者:也就是我们的APP,再深入点就是canvas->surface

  • 图像消费者:SurfaceFlinger

  • 图像缓冲区:BufferQueue,一般是3缓冲区

    OpenGL、Vulkan、Skia的区别

    • OpenGL:是一种跨平台的3D图形绘制规范接口。OpenGL EL则是专门针对嵌入式设备,如手机做了优化。

    • Skia:skia是图像渲染库,2D图形绘制自己就能完成。3D效果(依赖硬件)由OpenGL、Vulkan、Metal支持。它不仅支持2D、3D,同时支持CPU软件绘制和GPU硬件加速。Android、flutter都是使用它来完成绘制。

    • Vulkan:最早由科纳斯组织(Khronos Group)Android引入了Vulkan支持。VulKan是用来替换OpenGL的。它不仅支持3D,也支持2D,同时更加轻量级

      UI 组件在绘制到屏幕之前,都需要经过 Rasterization(栅格化)操作,而栅格化又是一个非常耗时的操作。

      Rasterization 栅格化是绘制那些 Button、Shape、Path、String、Bitmap 等显示组件最基础的操作。栅格化将这些 UI 组件拆分到显示器的不同像素上进行显示。这是一个非常耗时的操作,GPU 的引入就是为了加快栅格化。

      在这里插入图片描述

      从图中可以看到,软件绘制使用 Skia 库,它是一款能在低端设备,如手机呈现高质量的 2D 跨平台图形框架,类似 Chrome、Flutter 内部使用的都是 Skia 库。

      硬件绘制的思想就是通过底层软件代码,将 CPU 不擅长的图形计算转换成 GPU 专用指令,由 GPU 完成绘制任务。

      所以说硬件加速的本质就是使用GPU代替CPU完成Graphic Buffer绘制工作,以实现更好的性能,Android从4.0开始默认开启了硬件加速,但还有一些API不支持硬件加速,因此需要手动关闭硬件加速。需要注意的是,软件绘制使用的Skia库,但这不代表Skia不支持硬件加速,从Android 8开始,我们可以选择使用Skia进行硬件加速,Android 9开始就默认使用Skia来进行硬件加速。Skia的硬件加速主要是通过 copybit 模块调用OpenGL或者SKia来实现。

      VSYNC信号
      在这里插入图片描述

      CPU/GPU的处理时间较长,超过了16.6ms时, 在第二个时间段内,但却因 GPU 还在处理 B 帧,缓存没能交换,导致 A 帧被重复显示。而B完成后,又因为缺乏VSync信号,它只能等待下一个signal的来临。于是在这一过程中,有一大段时间是被浪费的。

      当下一个VSync出现时,CPU/GPU马上执行操作(A帧),且缓存交换,相应的显示屏对应的就是B。这时看起来就是正常的。只不过由于执行时间仍然超过16ms,导致下一次应该执行的缓冲区交换又被推迟了——如此循环反复,便出现了越来越多的“Jank”。

      三缓冲就是在双缓冲机制基础上增加了一个Graphic Buffer缓冲区,这样可以最大限度的利用空闲时间,带来的坏处是多使用的一个Graphic Buffer所占用的内存。

      三缓冲机制有效利用了等待vysnc的时间,可以帮助减少jank。

      RenderThread

      经过 Android 4.1 的 Project Butter 黄油计划之后,Android 的渲染性能有了很大的改善。不过你有没有注意到这样一个问题,虽然利用了 GPU 的图形高性能运算,但是从计算 DisplayList,到通过 GPU 绘制到 Frame Buffer,整个计算和绘制都在 UI 主线程中完成。

      UI 线程任务过于繁重。如果整个渲染过程比较耗时,可能造成无法响应用户的操作,进而出现卡顿的情况。GPU 对图形的绘制渲染能力更胜一筹,如果使用 GPU 并在不同线程绘制渲染图形,那么整个流程会更加顺畅。

      正因如此,在 Android 5.0 引入两个比较大的改变。一个是引入了 RenderNode 的概念,它对 DisplayList 及一些 View 显示属性都做了进一步封装。另一个是引入了 RenderThread,所有的 GL 命令执行都放到这个线程上,渲染线程在 RenderNode 中存有渲染帧的所有信息,可以做一些属性动画,这样即便主线程有耗时操作的时候也可以保证动画流程。

      图像消费者

      SurfaceFlinger是Android系统中最重要的一个图像消费者,Activity绘制的界面图像,都会传递到SurfaceFlinger来,SurfaceFlinger的作用主要是接收GraphicBuffer,然后交给HWComposer或者OpenGL做合成,合成完成后,SurfaceFlinger会把最终的数据提交给FrameBuffer。

      SurfaceFlinger是图像数据的消费者。在应用程序请求创建surface的时候,SurfaceFlinger会创建一个Layer。Layer是SurfaceFlinger操作合成的基本单元。所以,一个surface对应一个Layer。当应用程序把绘制好的GraphicBuffer数据放入BufferQueue后,接下来的工作就是SurfaceFlinger来完成了。

      在这里插入图片描述

      系统会有多个应用程序,一个程序有多个BufferQueue队列。SurfaceFlinger就是用来决定何时以及怎么去管理和显示这些队列的。

      SurfaceFlinger请求HAL硬件层,来决定这些Buffer是硬件来合成还是自己通过OpenGL来合成。最终把合成后的buffer数据,展示在屏幕上。

      总得来说,Android图像渲染机制是一个生产者消费者的模型,如下图所示:

在这里插入图片描述

onMeasure、onLayout计算出view的大小和摆放的位置,这都是UI线程要做的事情,在draw方法中进行绘制,但此时是没有真正去绘制。而是把绘制的指令封装为displayList,进一步封装为RenderNode,在同步给RenderThread。



RenderThread通过dequeue拿到graphic buffer(surfaceFlinger的缓冲区),根据绘制指令直接操作OpenGL的绘制接口,最终通过GPU设备把绘制指令渲染到了离屏缓冲区graphic buffer。



完成渲染后,把缓冲区交还给SurfaceFlinger的BufferQueue。SurfaceFlinger会通过硬件设备进行layer的合成,最终展示到屏幕。



**GPU处理关键步骤:**

顶点处理:这阶段 GPU 读取描述 3D 图形外观的顶点数据并根据顶点数据确定 3D 图形的形状
及位置关系,建立起 3D 图形的骨架。在现有的 GPU 中,这些工作由硬件实现的 Vertex Shader
(顶点着色器)完成。



光栅化计算:显示器实际显示的图像是由像素组成的,我们需要将上面生成的图形上的点和
线通过一定的算法转换到相应的像素点。把一个矢量图形转换为一系列像素点的过程就称为
光栅化。例如,一条数学表示的斜线段,最终被转化成阶梯状的连续像素点。



纹理帖图:顶点单元生成的多边形只构成了 3D 物体的轮廓,而纹理映射(texture mapping)
工作完成对多变形表面的帖图,通俗的说,就是将多边形的表面贴上相应的图片,从而生成
“真实”的图形。TMU(Texture mapping unit)即是用来完成此项工作。



像素处理:这阶段(在对每个像素进行光栅化处理期间)GPU 完成对像素的计算和处理,从
而确定每个像素的最终属性。在支持 DX8 和 DX9 规格的 GPU 中,这些工作由硬件实现的 Pixel
Shader(像素着色器)完成。



最终输出:由 ROP(光栅化引擎)最终完成像素的输出,1 帧渲染完毕后,被送到显存帧缓
冲区。



GPU 的工作通俗的来说就是完成 3D 图形的生成,将图形映射到相应的像素点上,对每个像
素进行计算确定最终颜色并完成输出。

一、OpenGL ES3.0 基础概念

1、OpenGL 官网

OpenGL ES 官网: https://www.khronos.org/api/opengles

OpenGL ES 官方文档API: https://registry.khronos.org/OpenGL-Refpages/es3.0/

着色器语言说明:https://www.cnblogs.com/brainworld/p/7445290.html

OpenGLES 全称 OpenGL for Embedded Systems ,是三维图形应用程序接口 OpenGL 的子集,本质上是一个跨编程语言、跨平台的编程接口规范,主要应用于嵌入式设备,如手机、平板等。由 科纳斯(Khronos) 组织定义和推广,科纳斯是一个图形软硬件行业协会,该协会主要关注图形和多媒体方面的开放标准。

OpenGLES 3.0 实际上是 OpenGLES 2.0 的扩展版本,向下兼容 OpenGLES 2.0 ,但不兼容 OpenGLES 1.0

2、OpenGLES 3.0 主要新特性

纹理

  • sRGB 纹理和帧缓冲区——允许应用程序执行伽马校正渲染。
  • 2D 纹理数组——保存一组 2D 纹理的纹理目标。
  • 3D 纹理。一些 OpenGL ES 2.0 实现通过扩展支持3D纹理,而 OpenGL ES3.0 将此作为强制的功能。
  • 深度纹理和阴影比较——启用存储在纹理中的深度缓冲区。
  • 无缝立方图。在 OpenGL ES 3.0 中,立方图可以进行采样如过滤来使用相邻面的数据并删除接缝处的伪像。
  • 浮点纹理。

着色器

  • 二进制程序文件。在 OpenGL ES 3.0 中,完全链接过的二进制程序文件可以保存为离线二进制格式,运行时不需要链接步骤。这有助于减少应用程序的加载时间。
  • 非方矩阵。支持方阵之外的新矩阵类型,并在 API中 增加了相关的统一调用,以支持这些矩阵的加载。
  • 全整数支持。支持整数(以及无符号整数)标量和向量类型以及全整数操作。
  • 平面/平滑插值程序。 OpenGL ES 3.0 中插值程序可以显式声明为平面或者平滑着色。
  • 统一变量块。统一变量值可以组合为统一变量块。统一变量块可以更高效地加载,也可在多个着色器程序间共享。
  • 布局限定符。顶点着色器输入可以用布局限定符声明,以显式绑定着色器源代码中的位置,而不需要调用 API 。

几何形状

  • 变换反馈。可以在缓冲区对象中捕捉顶点着色器的输出。
  • 布尔遮挡查询。应用程序可以查询一个(或者一组)绘制调用的任何像素是否通过深度测试。
  • 实例渲染。有效地渲染包含类似几何形状但是属性(例如变化矩阵、颜色或者大小)不同的对象。

缓冲区对象

  • 统一变量缓冲区对象。为存储/绑定大的统一变量块提供高效的方法。统
  • VAO 顶点数组对象。提供绑定和在顶点数组状态之间切换的高效方法。
  • 采样器对象。将采样器状态(纹理循环模式和过滤)与纹理对象分离。
  • 同步对象。为应用程序提供检查一组操作是否在GPU上完成执行的机制。
  • 像素缓冲对象。使应用程序能够执行对像素操作和纹理传输操作的异步数据传输。
  • 缓冲区对象间拷贝。提供了高效地从一个缓冲区对象向另一个缓冲区对象传输数据的机制,不需要CPU干预。

帧缓冲区

  • 多重渲染目标(MRT)。允许应用程序同时渲染到多个颜色缓冲区。

  • 多重采样渲染缓冲区。使应用程序能够渲染到具备多重采样抗锯齿功能的屏幕外帧缓冲区。

  • 帧缓冲区失效提示。

3、调试工具
GAPID

GAPID (Graphics API Debugger)是 Google 的一款开源且跨平台的图形开发调试工具,用于记录和检查应用程序对图形驱动程序的调用,支持 OpenGL ES 和 Vulkan 调试。

OpenGL Google官方工具地址:https://github.com/google/gapid/releases

GAPID 的主要功能:

  • 查看 OpenGL ES 或 Vulkan 绘图接口的调用情况(调用顺序、流程);
  • 查看传入着色器程序的参数;
  • 查看纹理,导出模型、贴图等资源;
  • 查看、修改以及导出 shader 脚本。

编译完 shader 脚本生成的二进制代码,可以通过 GAPID 抓取到并反编译成原来的 shader 源码。总而言之就是,你的 shader 脚本实际上是在 GPU 上裸奔,尤其是对手机厂商来说。

shader 脚本在 GPU 层面上目前并没有有效的加密或混淆方法,比较通用的做法是将 shader 中的变量无意义化,比如用 var1、var2 等表示,或者将一个 shader 拆分成多个小 shader ,以达到降低可读性的目的。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

获取opengl 实列网址: https://www.shadertoy.com/

调试工具Android官网地址:Android GPU Inspector (AGI) :https://developer.android.com/agi#downloads

二、OpenGL ES3.0与2.0的区别

OpenGL ES 3.0与OpenGL ES 2.0在基础概念上存在一些显著的区别,这些区别主要体现在图形管线的可编程性、新增的功能和特性以及着色器脚本的编写方式等方面:

1、图形管线的可编程性
  • OpenGL ES 2.0:引入了可编程图形管线,允许开发者自己编写顶点着色器(Vertex Shader)和片段着色器(Fragment Shader)的代码。这两个阶段的着色器允许开发者对顶点和像素进行更细致的控制,从而实现更复杂的视觉效果。
  • OpenGL ES 3.0:继承了OpenGL ES 2.0的可编程图形管线,并进一步增强了这一特性。OpenGL ES 3.0是向后兼容OpenGL ES 2.0的,意味着使用2.0编写的应用程序可以在3.0中继续运行。
2、新增功能和特性
  • OpenGL ES 3.0

    相比于OpenGL ES 2.0,引入了许多新的功能和特性,这些功能使得开发更加灵活和便捷:

    • 纹理功能增强:支持sRGB纹理和帧缓冲区、2D纹理数组、3D纹理、深度纹理和阴影比较、无缝立方图、浮点纹理、ETC2/EAC纹理压缩、整数纹理等。
    • 渲染管线增强:实现了遮挡查询、变缓反馈、实例渲染、四个或更多渲染目标支持等。
    • 着色语言增强:新版GLSL ES 3.0着色语言全面支持整数和32位浮点操作。
    • 精确尺寸纹理和渲染缓冲格式:提供了一系列广泛的精确尺寸纹理和渲染缓冲格式,便于移动应用。
3、着色器脚本编写方式
  • OpenGL ES 2.0:着色器脚本使用attribute和varying关键字来处理顶点和片段之间的数据传递。

  • OpenGL ES 3.0

    :着色器脚本的编写方式发生了较大变化,主要体现在以下几点:

    • 版本声明:必须在着色器脚本中使用#version 300 es声明为指定使用OpenGL ES 3.0版本。
    • 关键字变化:新增了in、out、inout关键字,用来取代attribute和varying关键字。
    • 输出方式变化:片段着色器不再使用gl_FragColor,而是使用out声明字段进行输出。
    • 变量赋值:可以直接使用layout对指定位置的变量赋值。
4、其他区别
  • 兼容性:OpenGL ES 3.0是向后兼容OpenGL ES 2.0的,但OpenGL ES 2.0不一定兼容OpenGL ES 3.0的所有新特性和功能。
  • 性能优化:OpenGL ES 3.0在降低电源消耗、提升着色器处理性能等方面进行了优化,使其更适合在手持和嵌入式设备上使用。

OpenGL ES 3.0在图形管线的可编程性、新增功能和特性以及着色器脚本编写方式等方面相较于OpenGL ES 2.0有了显著的提升和增强。这些改进使得OpenGL ES 3.0成为开发高级3D图形应用时更为强大和灵活的工具。

OpenGL ES主要版本及对应Android API:
OpenGL ES版本Anadroid API
OpenGL ES 2.0Android 2.2+(API 8+)
OpenGL ES 3.0Android 4.3+(API 18+)
OpenGL ES 3.1Android 5.0+(API 21+)
OpenGL ES 3.2Android 7.0+API 24+)

三、OpenGL ES 3.0 PBO 的使用

1、PBO 是什么?

OpenGL PBO(Pixel Buffer Object),被称为像素缓冲区对象,主要被用于异步像素传输操作。PBO 仅用于执行像素传输,不连接到纹理,且与 FBO (帧缓冲区对象)无关。

OpenGL PBO(像素缓冲区对象) 类似于 VBO(顶点缓冲区对象),PBO 开辟的也是 GPU 缓存,而存储的是图像数据。

在这里插入图片描述

与 PBO 绑定相关的 Target 标签有 2 个: GL_PIXEL_UNPACK_BUFFER GL_PIXEL_PACK_BUFFER

其中将 PBO 绑定为 GL_PIXEL_UNPACK_BUFFER 时, glTexImage2D() glTexSubImage2D() 表示从 PBO 中解包(unpack)像素数据并复制到帧缓冲区 。

将 PBO 绑定为 GL_PIXEL_PACK_BUFFER 时, glReadPixels() 表示从帧缓冲区中读取像素数据并打包进(pack) PBO 。

2、为什么要用 PBO

OpenGL 开发中,特别是在低端平台上处理高分辨率的图像时,图像数据在内存和显存之前拷贝往往会造成性能瓶颈,而利用 PBO 可以在一定程度上解决这个问题。

使用 PBO 可以在 GPU 的缓存间快速传递像素数据,不影响 CPU 时钟周期,除此之外,PBO 还支持异步传输。

在这里插入图片描述

上图从文件中加载纹理,图像数据首先被加载到 CPU 内存中,然后通过 glTexImage2D 函数将图像数据从 CPU 内存复制到 OpenGL 纹理对象中 (GPU 内存),两次数据传输(加载和复制)完全由 CPU 执行和控制。

在这里插入图片描述

如上图所示,文件中的图像数据可以直接加载到 PBO 中,这个操作是由 CPU 控制。 我们可以通过 glMapBufferRange 获取 PBO 对应 GPU 缓冲区的内存地址。

将图像数据加载到 PBO 后,再将图像数据从 PBO 传输到纹理对象中完全是由 GPU 控制,不会占用 CPU 时钟周期。所以,绑定 PBO 后,执行 glTexImage2D (将图像数据从 PBO 传输到纹理对象) 操作,CPU 无需等待,可以立即返回。

通过对比这两种(将图像数据传送到纹理对象中)方式,可以看出,利用 PBO 传输图像数据,省掉了一步 CPU 耗时操作(将图像数据从 CPU 内存复制到 纹理对象中)。

3、怎么用 PBO

    GLuint m_UploadPboIds[2] = {GL_NONE};;
    GLuint m_DownloadPboIds[2] = {GL_NONE};
	//初始化 PBO
	//生成2个缓冲对象名称
    glGenBuffers(2, m_UploadPboIds);
    int imgByteSize = m_RenderImage.width * m_RenderImage.height * 4;//RGBA
    //绑定为 GL_PIXEL_UNPACK_BUFFER 表示该 PBO 用于将像素数据从程序传送到 OpenGL 中(GL_ARRAY_BUFFER, GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, GL_ELEMENT_ARRAY_BUFFER, GL_PIXEL_PACK_BUFFER, GL_PIXEL_UNPACK_BUFFER, GL_TRANSFORM_FEEDBACK_BUFFER, or GL_UNIFORM_BUFFER)
    glBindBuffer(GL_PIXEL_UNPACK_BUFFER, m_UploadPboIds[0]);
    //创建并初始化一个缓冲对象的数据存储
    glBufferData(GL_PIXEL_UNPACK_BUFFER, imgByteSize, 0, GL_STREAM_DRAW);
    glBindBuffer(GL_PIXEL_UNPACK_BUFFER, m_UploadPboIds[1]);
    glBufferData(GL_PIXEL_UNPACK_BUFFER, imgByteSize, 0, GL_STREAM_DRAW);
    glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);

    glGenBuffers(2, m_DownloadPboIds);
    //绑定为 GL_PIXEL_PACK_BUFFER 表示该 PBO 用于从 OpenGL 中读回像素数据
    glBindBuffer(GL_PIXEL_PACK_BUFFER, m_DownloadPboIds[0]);
    glBufferData(GL_PIXEL_PACK_BUFFER, imgByteSize, 0, GL_STREAM_READ);
    glBindBuffer(GL_PIXEL_PACK_BUFFER, m_DownloadPboIds[1]);
    glBufferData(GL_PIXEL_PACK_BUFFER, imgByteSize, 0, GL_STREAM_READ);
    glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);

PBO 的创建和初始化类似于 VBO (Vertex Buffer Object-顶点缓冲区对象,在显存中提前开辟好一块内存,用于缓存顶点数据或者图元索引数据[EBO],从而避免每次绘制时的 CPU 与 GPU 之间的内存拷贝,可以提升渲染性能,降低内存带宽和功耗。),以上示例表示创建 PBO ,并申请大小为 imgByteSize 的缓冲区。绑定为 GL_PIXEL_UNPACK_BUFFER 表示该 PBO 用于将像素数据从程序传送到 OpenGL 中;绑定为 GL_PIXEL_PACK_BUFFER 表示该 PBO 用于从 OpenGL 中读回像素数据。

从上面内容我们知道,加载图像数据到纹理对象时,CPU 负责将图像数据拷贝到 PBO ,而 GPU 负责将图像数据从 PBO 传送到纹理对象。所以,当我们使用多个 PBO 时,通过交换 PBO 的方式进行拷贝和传送,可以实现这两步操作同时进行。

使用两个 PBO 加载图像数据到纹理对象
在这里插入图片描述

如图示,利用 2 个 PBO 加载图像数据到纹理对象,使用 glTexSubImage2D 通知 GPU 将图像数据从 PBO2 传送到纹理对象,同时 CPU 将新的图像数据复制到 PBO1 中。

	//使用 `glTexSubImage2D` 将图像数据从 PBO1 传送到纹理对象
	int index = m_FrameIndex % 2;
	int nextIndex = (index + 1) % 2;
	BEGIN_TIME("PBOSample::UploadPixels Copy Pixels from PBO to Textrure Obj")
	//绑定m_ImageTextureId 到纹理目标中(GL_TEXTURE_2D, GL_TEXTURE_3D, GL_TEXTURE_2D_ARRAY, or GL_TEXTURE_CUBE_MAP)
		glBindTexture(GL_TEXTURE_2D, m_ImageTextureId);
	//以 GL_PIXEL_UNPACK_BUFFER 绑定m_UploadPboIds 到PBO 中(GL_PIXEL_UNPACK_BUFFER影响以下函数: glCompressedTexImage2D, glCompressedTexImage3D, glCompressedTexSubImage2D, glCompressedTexSubImage3D, glTexImage2D, glTexImage3D, glTexSubImage2D, and glTexSubImage3D)
		glBindBuffer(GL_PIXEL_UNPACK_BUFFER, m_UploadPboIds[index]);
	    //调用 glTexSubImage2D 后立即返回,不影响 CPU 时钟周期(glBindBuffer绑定的是GL_PIXEL_UNPACK_BUFFER,故关联到 m_UploadPboIds )
		glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_RenderImage.width, m_RenderImage.height, GL_RGBA, GL_UNSIGNED_BYTE, 0);
	END_TIME("PBOSample::UploadPixels Copy Pixels from PBO to Textrure Obj")

        
        
	//更新图像数据,复制到 PBO 中
	BEGIN_TIME("PBOSample::UploadPixels Update Image data")
	glBindBuffer(GL_PIXEL_UNPACK_BUFFER, m_UploadPboIds[nextIndex]);
	//创建并初始化一个缓冲对象的数据存储(null 表示没有数据复制)
	glBufferData(GL_PIXEL_UNPACK_BUFFER, dataSize, nullptr, GL_STREAM_DRAW);
	//映射一个缓冲区数据的一段(3.0)
	GLubyte *bufPtr = (GLubyte *) glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0,
												   dataSize,
												   GL_MAP_WRITE_BIT |
												   GL_MAP_INVALIDATE_BUFFER_BIT);
	GO_CHECK_GL_ERROR();
	LOGCATE("PBOSample::UploadPixels bufPtr=%p",bufPtr);
	if(bufPtr)
	{
		GO_CHECK_GL_ERROR();
		memcpy(bufPtr, m_RenderImage.ppPlane[0], static_cast<size_t>(dataSize));
		//update image data
		int randomRow = rand() % (m_RenderImage.height - 5);
		memset(bufPtr + randomRow * m_RenderImage.width * 4, 188,
        static_cast<size_t>(m_RenderImage.width * 4 * 5));
		//解除映射
		glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
	}
	//解除绑定
	glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
	END_TIME("PBOSample::UploadPixels Update Image data")

在这里插入图片描述

	BEGIN_TIME("PBOSample::UploadPixels Copy Pixels from System Mem to Textrure Obj")
    glBindTexture(GL_TEXTURE_2D, m_ImageTextureId);
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_RenderImage.width, m_RenderImage.height, GL_RGBA, GL_UNSIGNED_BYTE, m_RenderImage.ppPlane[0]);
    END_TIME("PBOSample::UploadPixels Copy Pixels from System Mem to Textrure Obj")
        
      NativeImage nativeImage = m_RenderImage;
	NativeImageUtil::AllocNativeImage(&nativeImage);
	BEGIN_TIME("PBOSample::UploadPixels Update Image data")
		//update image data
		int randomRow = rand() % (m_RenderImage.height - 5);
		memset(m_RenderImage.ppPlane[0] + randomRow * m_RenderImage.width * 4, 188,
		static_cast<size_t>(m_RenderImage.width * 4 * 5));
        NativeImageUtil::CopyNativeImage(&m_RenderImage, &nativeImage);
	END_TIME("PBOSample::UploadPixels Update Image data")
	NativeImageUtil::FreeNativeImage(&nativeImage);

通过直接写buffer

在这里插入图片描述

使用两个 PBO 从帧缓冲区读回图像数据
在这里插入图片描述

如上图所示,利用 2 个 PBO 从帧缓冲区读回图像数据,使用 glReadPixels 通知 GPU 将图像数据从帧缓冲区读回到 PBO2 中,同时 CPU 可以直接处理 PBO1 中的图像数据。

    int dataSize = m_RenderImage.width * m_RenderImage.height * 4;
	NativeImage nativeImage = m_RenderImage;
	nativeImage.format = IMAGE_FORMAT_RGBA;


	uint8_t *pBuffer = new uint8_t[dataSize];
	nativeImage.ppPlane[0] = pBuffer;
	BEGIN_TIME("DownloadPixels glReadPixels without PBO")
		glReadPixels(0, 0, nativeImage.width, nativeImage.height, GL_RGBA, GL_UNSIGNED_BYTE, pBuffer);
	//NativeImageUtil::DumpNativeImage(&nativeImage, "/sdcard/DCIM", "Normal");
	END_TIME("DownloadPixels glReadPixels without PBO")
    delete []pBuffer;

	//交换 PBO
	//每次draw时m_FrameIndex++
    int index = m_FrameIndex % 2;
    int nextIndex = (index + 1) % 2;

	//将图像数据从帧缓冲区读回到 PBO 中
    BEGIN_TIME("DownloadPixels glReadPixels with PBO")
    glBindBuffer(GL_PIXEL_PACK_BUFFER, m_DownloadPboIds[index]);
    //从帧缓冲区读取数据块
    glReadPixels(0, 0, m_RenderImage.width, m_RenderImage.height, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
    END_TIME("DownloadPixels glReadPixels with PBO")


        
		m_DownloadImages[nextIndex] = m_RenderImage;
		m_DownloadImages[nextIndex].format = IMAGE_FORMAT_RGBA;

		// glMapBufferRange 获取 PBO 缓冲区指针
		BEGIN_TIME("DownloadPixels PBO glMapBufferRange")
			glBindBuffer(GL_PIXEL_PACK_BUFFER, m_DownloadPboIds[nextIndex]);
			//映射一个缓冲区数据的一段(3.0)
			GLubyte *bufPtr = static_cast<GLubyte *>(glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0,
																	  dataSize,
																	  GL_MAP_READ_BIT));

			if (bufPtr) {
				m_DownloadImages[nextIndex].ppPlane[0] = bufPtr;
				//NativeImageUtil::DumpNativeImage(&nativeImage, "/sdcard/DCIM", "PBO");
			}
			glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
		END_TIME("DownloadPixels PBO glMapBufferRange")
		glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);


	if(m_DownloadImages[nextIndex].ppPlane[0] != nullptr) {
		char key[] = "PBO";
		char fileName[64] = {0};
		sprintf(fileName, "%s_%d", key, nextIndex);
		std::string path(DEFAULT_OGL_ASSETS_DIR);
		NativeImageUtil::DumpNativeImage(&m_DownloadImages[nextIndex], path.c_str(), fileName);
	}

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

四、动效

1、线段

java代码:

public class MyGLRender implements GLSurfaceView.Renderer {
    private static final String TAG = "MyGLRender";
    private MyNativeRender mNativeRender;
    private int mSampleType;

    MyGLRender() {
        mNativeRender = new MyNativeRender();
    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        mNativeRender.native_OnSurfaceCreated();
        Log.e(TAG, "onSurfaceCreated() called with: GL_VERSION = [" + gl.glGetString(GL10.GL_VERSION) + "]");
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        mNativeRender.native_OnSurfaceChanged(width, height);

    }

    @Override
    public void onDrawFrame(GL10 gl) {
        mNativeRender.native_OnDrawFrame();

    }
    
    public void init() {
        mNativeRender.native_Init();
    }

    public void unInit() {
        mNativeRender.native_UnInit();
    }
    ...
  }


//初始化renderer
  private MyGLRender mGLRender = new MyGLRender();
//MainActivity初始化GLSurfaceView
  mGLSurfaceView = new MyGLSurfaceView(MainActivity.this, mGLRender);
//设置模式为调用requestRender()方法或者surface创建的时候渲染;
  mGLSurfaceView.setRenderMode(RENDERMODE_WHEN_DIRTY);
//设置画幅
  mGLSurfaceView.setAspectRatio(mRootView.getWidth(), mRootView.getHeight());

//自定义MyGLSurfaceView类实现
public class MyGLSurfaceView extends GLSurfaceView implements ScaleGestureDetector.OnScaleGestureListener {
    ...
   @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);

        if (0 == mRatioWidth || 0 == mRatioHeight) {
            setMeasuredDimension(width, height);
        } else {
            if (width < height * mRatioWidth / mRatioHeight) {
                setMeasuredDimension(width, width * mRatioHeight / mRatioWidth);
            } else {
                setMeasuredDimension(height * mRatioWidth / mRatioHeight, height);
            }
        }
    }
    
       @Override
    public boolean onTouchEvent(MotionEvent e) {
        ...
        mNativeRender.native_UpdateTransformMatrix(rotateX, rotateY, scaleX, scaleY);
        //开始渲染
        requestRender();
    }
}

初始化方法:

void TriangleSample::Init()
{
	if(m_ProgramObj != 0)
		return;
	//顶点着色器
	char vShaderStr[] =
			"#version 300 es                          \n"
			"layout(location = 0) in vec4 vPosition;  \n"
			"out vec4 vColor;                         \n"
			"void main()                              \n"
			"{                                        \n"
			"   gl_Position = vPosition;              \n"
			"   vColor = vec4(1.0,0.8,0.0,1.0);       \n"
			"}                                        \n";

	//片段着色器
	char fShaderStr[] =
			"#version 300 es                              \n"
			"precision mediump float;                     \n"
			"in vec4 vColor;                              \n"
			"out vec4 fragColor;                          \n"
			"void main()                                  \n"
			"{                                            \n"
			"   fragColor = vColor;                       \n"
			"}                                            \n";

	m_ProgramObj = GLUtils::CreateProgram(vShaderStr, fShaderStr, m_VertexShader, m_FragmentShader);

}

CreateProgram工具方法:**

#define FUN_BEGIN_TIME(FUN) {\
    LOGCATE("%s:%s func start", __FILE__, FUN); \
    long long t0 = GetSysCurrentTime();

#define FUN_END_TIME(FUN) \
    long long t1 = GetSysCurrentTime(); \
    LOGCATE("%s:%s func cost time %ldms", __FILE__, FUN, (long)(t1-t0));}

//检查是否有GL错误
void GLUtils::CheckGLError(const char *pGLOperation)
{
    //返回对应的错误标志,有几个就会返回几次,直到GL_NO_ERROR
    for (GLint error = glGetError(); error; error = glGetError())
    {
        LOGCATE("GLUtils::CheckGLError GL Operation %s() glError (0x%x)\n", pGLOperation, error);
    }

}

//加载shader
GLuint GLUtils::LoadShader(GLenum shaderType, const char *pSource)
{
    GLuint shader = 0;
	FUN_BEGIN_TIME("GLUtils::LoadShader")
	//shadertype 指定要创建的着色器类型。必须是GL_VERTEX_SHADER或GL_FRAGMENT_SHADER中的一个。
        shader = glCreateShader(shaderType);
        if (shader)
        {
            //替换shader对象规范中的源代码
            glShaderSource(shader, 1, &pSource, NULL);
            //编译着色器对象
            glCompileShader(shader);
            GLint compiled = 0;
            //返回一个预定的着色器参数值:  GL_SHADER_TYPE、GL_DELETE_STATUS、GL_COMPILE_STATUS、GL_INFO_LOG_LENGTH、 GL_SHADER_SOURCE_LENGTH
            glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
            if (!compiled)
            {
                GLint infoLen = 0;
                glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
                if (infoLen)
                {
                    char* buf = (char*) malloc((size_t)infoLen);
                    if (buf)
                    {
                        //返回着色器对象的信息log
                        glGetShaderInfoLog(shader, infoLen, NULL, buf);
                        LOGCATE("GLUtils::LoadShader Could not compile shader %d:\n%s\n", shaderType, buf);
                        free(buf);
                    }
                    //删除shader
                    glDeleteShader(shader);
                    shader = 0;
                }
            }
        }
	FUN_END_TIME("GLUtils::LoadShader")
	return shader;
}



GLuint GLUtils::CreateProgram(const char *pVertexShaderSource, const char *pFragShaderSource, GLuint &vertexShaderHandle, GLuint &fragShaderHandle)
{
    GLuint program = 0;
    FUN_BEGIN_TIME("GLUtils::CreateProgram")
       //编译顶点着色器
        vertexShaderHandle = LoadShader(GL_VERTEX_SHADER, pVertexShaderSource);
        if (!vertexShaderHandle) return program;
        //编译片段着色器
        fragShaderHandle = LoadShader(GL_FRAGMENT_SHADER, pFragShaderSource);
        if (!fragShaderHandle) return program;
        //创建一个空的程序对象
        program = glCreateProgram();
        if (program)
        {
            //将顶点着色器对象附加到程序对象上
            glAttachShader(program, vertexShaderHandle);
            //检查错误
            CheckGLError("glAttachShader");
            //将片段着色器对象附加到程序对象上
            glAttachShader(program, fragShaderHandle);
            //检查错误
            CheckGLError("glAttachShader");
            //连接程序对象
            glLinkProgram(program);
            GLint linkStatus = GL_FALSE;
            //返回预定程序对象-连接状态的值(GL_ACTIVE_ATTRIBUTES, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, GL_ACTIVE_UNIFORMS, GL_ACTIVE_UNIFORM_BLOCKS, GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH, GL_ACTIVE_UNIFORM_MAX_LENGTH, GL_ATTACHED_SHADERS, GL_DELETE_STATUS, GL_INFO_LOG_LENGTH, GL_LINK_STATUS, GL_PROGRAM_BINARY_RETRIEVABLE_HINT, GL_TRANSFORM_FEEDBACK_BUFFER_MODE, GL_TRANSFORM_FEEDBACK_VARYINGS, GL_TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH and GL_VALIDATE_STATUS.)
            glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);

            //从程序对象中分离顶点着色器
            glDetachShader(program, vertexShaderHandle);
            //删除顶点着色器
            glDeleteShader(vertexShaderHandle);
            vertexShaderHandle = 0;
            //从程序对象中分离片段着色器
            glDetachShader(program, fragShaderHandle);
            //删除片段着色器
            glDeleteShader(fragShaderHandle);
            fragShaderHandle = 0;
            //连接程序对象流程失败
            if (linkStatus != GL_TRUE)
            {
                GLint bufLength = 0;
                //返回预定程序对象-打印log的值
                glGetProgramiv(program, GL_INFO_LOG_LENGTH, &bufLength);
                if (bufLength)
                {
                    char* buf = (char*) malloc((size_t)bufLength);
                    if (buf)
                    {
                        //返回程序对象的log信息
                        glGetProgramInfoLog(program, bufLength, NULL, buf);
                        LOGCATE("GLUtils::CreateProgram Could not link program:\n%s\n", buf);
                        free(buf);
                    }
                }
                //删除程序对象
                glDeleteProgram(program);
                program = 0;
            }
        }
    FUN_END_TIME("GLUtils::CreateProgram")
    LOGCATE("GLUtils::CreateProgram program = %d", program);
	return program;
}

开始draw:

void TriangleSample::Draw(int screenW, int screenH)
{
   LOGCATE("TriangleSample::Draw");
   //点坐标
   GLfloat vVertices[] = {
         0.25f, 0.25f, 0.0f,  //V0
         -0.75f, 0.25f, 0.0f, //V1
         -0.75f, -0.75f, 0.0f, //V2
         0.25f, -0.75f, 0.0f, //V3

         0.75f, -0.25f, 0.0f, //V4
         0.75f, 0.75f, 0.0f, //V5
         -0.25f, 0.75f, 0.0f, //V6
         -0.25f, -0.25f, 0.0f, //V7

         -0.25f, 0.75f, 0.0f, //V6
         -0.75f, 0.25f, 0.0f, //V1

         0.75f, 0.75f, 0.0f, //V5
         0.25f, 0.25f, 0.0f, //V0

         -0.25f, -0.25f, 0.0f, //V7
         -0.75f, -0.75f, 0.0f, //V2

         0.75f, -0.25f, 0.0f, //V4
         0.25f, -0.75f, 0.0f //V3
   };

   if(m_ProgramObj == 0)
      return;

   glClear(GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   //设置背景色
   glClearColor(1.0, 1.0, 1.0, 1.0);

   //使用程序片段
   glUseProgram (m_ProgramObj);

   // Load the vertex data
   glVertexAttribPointer (0, 3, GL_FLOAT, GL_FALSE, 0, vVertices );
   glEnableVertexAttribArray (0);

   //指定线宽
   glLineWidth(5);

   glDrawArrays(GL_LINE_LOOP, 0, 4);

   glDrawArrays(GL_LINE_LOOP, 4, 4);

   glDrawArrays(GL_LINES, 8, 8);

// glDrawArrays (GL_TRIANGLES, 0, 3);
   glUseProgram (GL_NONE);

}

在这里插入图片描述

打印错误:
在这里插入图片描述

修改代码画立方体:

void TriangleSample::Draw(int screenW, int screenH)
{
   LOGCATE("TriangleSample::Draw");
/*
   //点坐标
   GLfloat vVertices[] = {
         0.25f, 0.25f, 0.0f,  //V0
         -0.75f, 0.25f, 0.0f, //V1
         -0.75f, -0.75f, 0.0f, //V2
         0.25f, -0.75f, 0.0f, //V3

         0.75f, -0.25f, 0.0f, //V4
         0.75f, 0.75f, 0.0f, //V5
         -0.25f, 0.75f, 0.0f, //V6
         -0.25f, -0.25f, 0.0f, //V7

         -0.25f, 0.75f, 0.0f, //V6
         -0.75f, 0.25f, 0.0f, //V1

         0.75f, 0.75f, 0.0f, //V5
         0.25f, 0.25f, 0.0f, //V0

         -0.25f, -0.25f, 0.0f, //V7
         -0.75f, -0.75f, 0.0f, //V2

         0.75f, -0.25f, 0.0f, //V4
         0.25f, -0.75f, 0.0f //V3
   };
*/
/**
     * 点的坐标
     */
   GLfloat vVertices[] = {
         //背面矩形
         0.75f, 0.75f, 0.0f, //V5
         -0.25f, 0.75f, 0.0f, //V6
         -0.25f, -0.25f, 0.0f, //V7
         0.75f, 0.75f, 0.0f, //V5
         -0.25f, -0.25f, 0.0f, //V7
         0.75f, -0.25f, 0.0f, //V4

         //左侧矩形
         -0.25f, 0.75f, 0.0f, //V6
         -0.75f, 0.25f, 0.0f, //V1
         -0.75f, -0.75f, 0.0f, //V2
         -0.25f, 0.75f, 0.0f, //V6
         -0.75f, -0.75f, 0.0f, //V2
         -0.25f, -0.25f, 0.0f, //V7

         //底部矩形
         0.75f, -0.25f, 0.0f, //V4
         -0.25f, -0.25f, 0.0f, //V7
         -0.75f, -0.75f, 0.0f, //V2
         0.75f, -0.25f, 0.0f, //V4
         -0.75f, -0.75f, 0.0f, //V2
         0.25f, -0.75f, 0.0f, //V3

         //正面矩形
         0.25f, 0.25f, 0.0f,  //V0
         -0.75f, 0.25f, 0.0f, //V1
         -0.75f, -0.75f, 0.0f, //V2
         0.25f, 0.25f, 0.0f,  //V0
         -0.75f, -0.75f, 0.0f, //V2
         0.25f, -0.75f, 0.0f, //V3

         //右侧矩形
         0.75f, 0.75f, 0.0f, //V5
         0.25f, 0.25f, 0.0f, //V0
         0.25f, -0.75f, 0.0f, //V3
         0.75f, 0.75f, 0.0f, //V5
         0.25f, -0.75f, 0.0f, //V3
         0.75f, -0.25f, 0.0f, //V4

         //顶部矩形
         0.75f, 0.75f, 0.0f, //V5
         -0.25f, 0.75f, 0.0f, //V6
         -0.75f, 0.25f, 0.0f, //V1
         0.75f, 0.75f, 0.0f, //V5
         -0.75f, 0.25f, 0.0f, //V1
         0.25f, 0.25f, 0.0f  //V0
   };


   //立方体的顶点颜色
   GLfloat vColors[] = {
         //背面矩形颜色
         1.0f, 0.0f, 1.0f, 1.0f,
         1.0f, 0.0f, 1.0f, 1.0f,
         1.0f, 0.0f, 1.0f, 1.0f,
         1.0f, 0.0f, 1.0f, 1.0f,
         1.0f, 0.0f, 1.0f, 1.0f,
         1.0f, 0.0f, 1.0f, 1.0f,

         //左侧矩形颜色
         0.0f, 1.0f, 0.0f, 1.0f,
         0.0f, 1.0f, 0.0f, 1.0f,
         0.0f, 1.0f, 0.0f, 1.0f,
         0.0f, 1.0f, 0.0f, 1.0f,
         0.0f, 1.0f, 0.0f, 1.0f,
         0.0f, 1.0f, 0.0f, 1.0f,

         //底部矩形颜色
         1.0f, 0.0f, 0.5f, 1.0f,
         1.0f, 0.0f, 0.5f, 1.0f,
         1.0f, 0.0f, 0.5f, 1.0f,
         1.0f, 0.0f, 0.5f, 1.0f,
         1.0f, 0.0f, 0.5f, 1.0f,
         1.0f, 0.0f, 0.5f, 1.0f,

         //正面矩形颜色
         0.2f, 0.3f, 0.2f, 1.0f,
         0.2f, 0.3f, 0.2f, 1.0f,
         0.2f, 0.3f, 0.2f, 1.0f,
         0.2f, 0.3f, 0.2f, 1.0f,
         0.2f, 0.3f, 0.2f, 1.0f,
         0.2f, 0.3f, 0.2f, 1.0f,

         //右侧矩形颜色
         0.1f, 0.2f, 0.3f, 1.0f,
         0.1f, 0.2f, 0.3f, 1.0f,
         0.1f, 0.2f, 0.3f, 1.0f,
         0.1f, 0.2f, 0.3f, 1.0f,
         0.1f, 0.2f, 0.3f, 1.0f,
         0.1f, 0.2f, 0.3f, 1.0f,

         //顶部矩形颜色
         0.3f, 0.4f, 0.5f, 1.0f,
         0.3f, 0.4f, 0.5f, 1.0f,
         0.3f, 0.4f, 0.5f, 1.0f,
         0.3f, 0.4f, 0.5f, 1.0f,
         0.3f, 0.4f, 0.5f, 1.0f,
         0.3f, 0.4f, 0.5f, 1.0f,
   };

   if(m_ProgramObj == 0)
      return;

   glClear(GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   //设置背景色
   glClearColor(1.0, 1.0, 1.0, 1.0);

   //使用程序片段
   glUseProgram (m_ProgramObj);

   // Load the vertex data
   glVertexAttribPointer (0, 3, GL_FLOAT, GL_FALSE, 0, vVertices );
   glEnableVertexAttribArray (0);

   // Load the color data
   glVertexAttribPointer (1, 4, GL_FLOAT, GL_FALSE, 0, vColors );
   glEnableVertexAttribArray (1);

   //指定线宽
// glLineWidth(5);
//
// glDrawArrays(GL_LINE_LOOP, 0, 4);
//
// glDrawArrays(GL_LINE_LOOP, 4, 4);
//
// glDrawArrays(GL_LINES, 8, 8);

// glDrawArrays (GL_TRIANGLES, 0, 3);

   glClear(GL_COLOR_BUFFER_BIT);
    
	//指定模型和数量(GL_POINTS, GL_LINE_STRIP, GL_LINE_LOOP, GL_LINES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN and GL_TRIANGLES)
   glDrawArrays(GL_TRIANGLES, 0, 36);

   glUseProgram (GL_NONE);

}
2、波动

波动效果实际上就是一组组相互交替、幅度向外部逐渐减小的缩小放大效果组合。可以将波动模型简化成一组放大和缩小效果随时间逐步向外部偏移。

如上图所示,我们以点击位置为中心,发生形变的区域是内圆和外圆之间的区域,以归一化时间变量 u_Time 大小为半径构建的圆(蓝色虚线)为边界,设定内侧是实现缩小效果的区域,外侧为实现放大效果的区域,也可以反之设定。

发生形变区域的宽度为固定值 2*u_Boundary ,然后这个形变区域随着 u_Time 的变大逐步向外侧移动,最后就形成了动态的波动效果。

我们设采样点到中心点的距离为 Distance ,然后计算 Distance-u_Time=diff 的值来判定,采样点是位于缩小区域(diff < 0)还是放大区域(diff > 0),最后我们只需要构建一个平滑的函数,以 diff 作为输入,diff < 0 的时候函数输出正值,diff > 0 的时候函数输出负值。

为什么要这样做?因为我们的根本目标就是为了实现一定区域内的缩小和放大效果,我们以平滑函数的输出值作为纹理采样坐标的偏移程度,当平滑函数输出正值时,采样坐标向圆外侧偏移,呈现缩小效果,而平滑函数输出负值时,采样坐标向圆内侧偏移,呈现放大效果。

另外,为了防止形变效果的跳变,我们还需要平滑函数满足在边界处输出值为 0 (或者接近于 0 ),表示此边界为是否发生形变的临界线。

在这里插入图片描述

绘制函数曲线网址:绘制函数曲线连接

char fShaderStr[] =
			"#version 300 es\n"
			"precision highp float;\n"
			"in vec2 v_texCoord;\n"
			"layout(location = 0) out vec4 outColor;\n"
			"uniform sampler2D s_TextureMap;//采样器\n"
			"uniform vec2 u_TouchXY;//点击的位置(归一化)\n"
			"uniform vec2 u_TexSize;//纹理尺寸\n"
			"uniform float u_Time;//归一化的时间\n"
			"uniform float u_Boundary;//边界 0.1\n"
			"void main()\n"
			"{\n"
			"    float ratio = u_TexSize.y / u_TexSize.x;\n"
			"    vec2 texCoord = v_texCoord * vec2(1.0, ratio);//根据纹理尺寸,对采用坐标进行转换\n"
			"    vec2 touchXY = u_TouchXY * vec2(1.0, ratio);//根据纹理尺寸,对中心点坐标进行转换\n"
			"    float distance = distance(texCoord, touchXY);//采样点坐标与中心点的距离\n"
			"    if ((u_Time - u_Boundary) > 0.0\n"
			"    && (distance <= (u_Time + u_Boundary))\n"
			"    && (distance >= (u_Time - u_Boundary))) {\n"
			"        float x = (distance - u_Time);//输入 diff\n"
			"        float moveDis = 20.0 * x * (x - 0.1)*(x + 0.1);//平滑函数 y=20.0 * x * (x - 0.1)*(x + 0.1)  采样坐标移动距离\n"
			"        vec2 unitDirectionVec = normalize(texCoord - touchXY);//单位方向向量\n"
			"        texCoord = texCoord + (unitDirectionVec * moveDis);//采样坐标偏移(实现放大和缩小效果)\n"
			"    }\n"
			"\n"
			"    texCoord = texCoord / vec2(1.0, ratio);//转换回来\n"
			"    outColor = texture(s_TextureMap, texCoord);\n"
			"}";
m_ProgramObj = GLUtils::CreateProgram(vShaderStr, fShaderStr, m_VertexShader, m_FragmentShader);
	if (m_ProgramObj)
	{
		LOGCATE("ShockWaveSample::Init create program fail");
	}
//顶点坐标
	GLfloat verticesCoords[] = {
			-1.0f,  1.0f, 0.0f,  // Position 0
			-1.0f, -1.0f, 0.0f,  // Position 1
			1.0f,  -1.0f, 0.0f,  // Position 2
			1.0f,   1.0f, 0.0f,  // Position 3
	};
//纹理坐标
	GLfloat textureCoords[] = {
			0.0f,  0.0f,        // TexCoord 0
			0.0f,  1.0f,        // TexCoord 1
			1.0f,  1.0f,        // TexCoord 2
			1.0f,  0.0f         // TexCoord 3
	};
//索引
	GLushort indices[] = { 0, 1, 2, 0, 2, 3 };

	//生成3个缓冲对象名称 
	glGenBuffers(3, m_VboIds);
//GL_ARRAY_BUFFER - 顶点数组指针参数被解释为缓冲区对象内以基本机器单位测量的偏移量
	glBindBuffer(GL_ARRAY_BUFFER, m_VboIds[0]);
//创建并初始化一个顶点缓冲对象的数据存储
	glBufferData(GL_ARRAY_BUFFER, sizeof(verticesCoords), verticesCoords, GL_STATIC_DRAW);

	glBindBuffer(GL_ARRAY_BUFFER, m_VboIds[1]);
	glBufferData(GL_ARRAY_BUFFER, sizeof(textureCoords), textureCoords, GL_STATIC_DRAW);

//glDrawElements、glDrawElementsInstanced、glDrawRangeElements的索引参数在缓冲区对象内的偏移量以基本机器单位测量
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_VboIds[2]);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

draw方法:

void ShockWaveSample::Draw(int screenW, int screenH)
{
	LOGCATE("ShockWaveSample::Draw()");
    m_SurfaceWidth = screenW;
    m_SurfaceHeight = screenH;
	if(m_ProgramObj == GL_NONE || m_TextureId == GL_NONE) return;

	m_FrameIndex ++;

    //根据X/Y旋转角度和宽高比计算矩阵变换值
	UpdateMVPMatrix(m_MVPMatrix, m_AngleX, m_AngleY, (float)screenW / screenH);
....

    //设置
	GLUtils::setMat4(m_ProgramObj, "u_MVPMatrix", m_MVPMatrix);

    //选择一个活动纹理单元
	glActiveTexture(GL_TEXTURE0);
    //绑定m_TextureId到二维纹理中
	glBindTexture(GL_TEXTURE_2D, m_TextureId);
...

    float time = static_cast<float>(fmod(m_FrameIndex, 150) / 120);
	GLUtils::setFloat(m_ProgramObj, "u_Time", time);

	//设置点击位置
	GLUtils::setVec2(m_ProgramObj, "u_TouchXY", m_touchXY);
	//设置纹理尺寸
	GLUtils::setVec2(m_ProgramObj, "u_TexSize", vec2(m_RenderImage.width, m_RenderImage.height));
	//设置边界值
	GLUtils::setFloat(m_ProgramObj, "u_Boundary", 0.1f);

    //对应
	glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, (const void *)0);

}

static void setFloat(GLuint programId, const std::string &name, float value) {
    glUniform1f(glGetUniformLocation(programId, name.c_str()), value);
}

void ShockWaveSample::UpdateTransformMatrix(float rotateX, float rotateY, float scaleX, float scaleY)
{
	GLSampleBase::UpdateTransformMatrix(rotateX, rotateY, scaleX, scaleY);
	m_AngleX = static_cast<int>(rotateX);
	m_AngleY = static_cast<int>(rotateY);
	m_ScaleX = scaleX;
	m_ScaleY = scaleY;
}
OpenGL ES 3.0 英文版 第1章——OpenGL ES 3.0简介   第1章简单介绍OpenGL ES,概述了OpenGL ES 3.0图形管线,讨论了OpenGL ES 3.0的设计理念和限制,最后介绍了OpenGL ES 3.0中使用的一些约定和类型。   第2章——你好,三角形:一个OpenGL ES 3.0示例   第2章介绍绘制三角形的一个简单OpenGL ES 3.0示例。我们的目的是说明OpenGL ES 3.0程序的样子,向读者介绍一些API概念,并说明如何构建和运行OpenGL ES 3.0示例程序。   第3章——EGL简介   第3章介绍EGL——为OpenGL ES 3.0创建表面和渲染上下文的API。我们说明与原生窗口系统通信、选择配置和创建EGL渲染上下文及表面的方法,传授足够多的EGL知识,你可以了解到启动OpenGL ES 3.0进行渲染所需的所有知识。   第4章——着色器和程序   着色器对象和程序对象是OpenGL ES 3.0中最基本的对象。第4章介绍创建着色器对象、编译着色器和检查编译错误的方法。这一章还说明如何创建程序对象、将着色器对象连接到程序对象以及链接最终程序对象的方法。我们讨论如何查询程序对象的信息以及加载统一变量(uniform)的方法。此外,你将学习有关源着色器和程序二进制代码之间的差别以及它们的使用方法。   第5章——OpenGL ES着色语言   第5章介绍编写着色器所需的着色语言的基础知识。这些着色语言基础知识包括变量和类型、构造器、结构、数组、统一变量、统一变量块(uniform block)和输入/输出变量。该章还描述着色语言的某些更细微的部分,例如精度限定符和不变性。   第6章——顶点属性、顶点数组和缓冲区对象   从第6章开始(到第11章为止),我们将详细介绍管线,教授设置和编程图形管线各个部分的方法。这一旅程从介绍几何形状输入图形管线的方法开始,包含了对顶点属性、顶点数组和缓冲区对象的讨论。   第7章——图元装配和光栅化   在前一章讨论几何形状输入图形管线的方法之后,第7章将讨论几何形状如何装配成图元,介绍OpenGL ES 3.0中所有可用的图元类型,包括点精灵、直线、三角形、三角形条带和三角扇形。此外,我们还说明了在顶点上进行坐标变换的方法,并简单介绍了OpenGL ES 3.0管线的光栅化阶段。   第8章——顶点着色器   我们所介绍的管线的下一部分是顶点着色器。第8章概述了顶点着色器如何融入管线以及OpenGL ES 着色语言中可用于顶点着色器的特殊变量,介绍了多个顶点着色器的示例,包括逐像素照明和蒙皮(skinning)。我们还给出了用顶点着色器实现OpenGL ES 1.0(和1.1)固定功能管线的示例。   第9章——纹理   第9章开始介绍片段着色器,描述OpenGL ES 3.0中所有可用的纹理功能。该章提供了创建纹理、加载纹理数据以及纹理渲染的细节,描述了纹理包装模式、纹理过滤、纹理格式、压缩纹理、采样器对象、不可变纹理、像素解包缓冲区对象和Mip贴图。该章介绍了OpenGL ES 3.0支持的所有纹理类型:2D纹理、立方图、2D纹理数组和3D纹理。   第10章——片段着色器   第9章的重点是如何在片段着色器中使用纹理,第10章介绍编写片段着色器所需知道的其他知识。该章概述了片段着色器和所有可用的特殊内建变量,还演示了用片段着色器实现OpenGL ES 1.1中所有固定功能技术的方法。多重纹理、雾化、Alpha测试和用户裁剪平面的例子都使用片段着色器实现。   第11章——片段操作   第11章讨论可以适用于整个帧缓冲区或者在OpenGL ES 3.0片段管线中执行片段着色器后适用于单个片段的操作。这些操作包括剪裁测试、模板测试、深度测试、多重采样、混合和抖动。本章介绍OpenGL ES 3.0图形管线的最后阶段。   第12章——帧缓冲区对象   第12章讨论使用帧缓冲区对象渲染屏幕外表面。帧缓冲区对象有多种用法,最常见的是渲染到一个纹理。本章提供API帧缓冲区对象部分的完整概述。理解帧缓冲区对象对于实现许多高级特效(如反射、阴影贴图和后处理)至关重要。   第13章——同步对象和栅栏   第13章概述同步对象和栅栏,它们是在OpenGL ES 3.0主机应用和GPU执行中同步的有效图元。我们讨论同步对象和栅栏的使用方法,并以一个示例作为结束。   第14章——OpenGL ES 3.0高级编程   第14章是核心章节,将本书介绍的许多主题串联在一起。我们已经选择了高级渲染技术的一个样本,并展示了实现这些功能的示例。该章包含使用法线贴图的逐像素照明、环境贴图、粒子系统、图像后处理、程序纹理、阴影贴图、地形渲染
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值