精华内容
下载资源
问答
  • 离屏渲染
    2021-12-25 20:30:33

    界面渲染

    UIView继承自UIResponder,可以处理系统传递过来的事件,如:UIApplication、UIViewController、UIView,以及所有从UIView派生出来的UIKit类。每个UIView内部都有一个CALayer提供内容的绘制和显示,并且作为内部RootLayer的代理视图。

    下图为CALayer的结构图:
    在这里插入图片描述

    RunLoop有一个60fps的回调,即每16.7ms绘制一次屏幕,所以view的绘制必须在这个时间内完成,view内容的绘制是CPU的工作,然后把绘制的内容交给GPU渲染,包括多个View的拼接(Compositing)、纹理的渲染(Texture)等等,最后显示在屏幕上。但是,如果无法是16.7ms内完成绘制,就会出现丢帧的问题,一般情况下,如果帧率保证在30fps以上,界面卡顿效果不明显,那么就需要在33.4ms内完成View的绘制,而低于这个帧率,就会产生卡顿的效果,影响体验。

    渲染的过程如下:

    • UIView的layer层有一个content,指向一块缓存,即backing store
    • UIView绘制时,会调用drawRect方法,通过context将数据写入backing store
    • backing store写完后,通过render server交给GPU去渲染,将backing store中的bitmap数据显示在屏幕上

    在这里插入图片描述

    ios离屏渲染

    On-Screen Rendering:当前屏幕渲染,指的是 GPU 的渲染操作是在当前用于显示的屏幕缓冲区中进行
    Off-Screen Rendering:离屏渲染,分为 CPU 离屏渲染和 GPU 离屏渲染两种形式。GPU 离屏渲染指的是 GPU 在当前屏幕缓冲区外新开辟一个缓冲区进行渲染操作

    为什么会使用离屏渲染:
    当使用圆角,阴影,遮罩的时候,图层属性的混合体被指定为在未预合成之前不能直接在屏幕中绘制,所以就需要屏幕外渲染被唤起。

    GPU 离屏渲染的代价是很大的

    离屏渲染之所以会特别消耗性能,是因为要创建一个屏幕外的缓冲区,然后从当屏缓冲区切换到屏幕外的缓冲区,然后再完成渲染;其中,创建缓冲区和切换上下文最消耗性能,而绘制其实不是性能损耗的主要原因。

    上下文之间的切换这个过程的消耗会比较昂贵,涉及到 OpenGL的 pipeline 跟 barrier,而且 offscreen-render 在每一帧都会涉及到,因此处理不当肯定会对性能产生一定的影响。另外由于离屏渲染会增加 GPU 的工作量,可能会导致 CPU+GPU 的处理时间超出 16.7ms,导致掉帧卡顿。

    离屏渲染的场景和优化

    圆角优化

    方法一:
    一般情况下我们会用这个方法去设置圆角:

    iv.layer.cornerRadius = 30;
    iv.layer.masksToBounds = YES;
    

    使用cornerRadius进行切圆角,在iOS9之前会产生离屏渲染,比较消耗性能,而之后系统做了优化,则不会产生离屏渲染,但是操作最简单

    方法二:

    利用mask设置圆角,利用的是UIBezierPathCAShapeLayer来完成

    CAShapeLayer *mask = [[CAShapeLayer alloc] init];
    mask1.opacity = 0.3;
    mask1.path = [UIBezierPath bezierPathWithOvalInRect:iv.bounds].CGPath;
    iv.layer.mask = mask;
    

    方法三:
    利用CoreGraphics画一个圆形上下文,然后把图片绘制上去,得到一个圆形的图片

    - (UIImage *)drawCircleImage:(UIImage*)image
    {
        CGFloat side = MIN(image.size.width, image.size.height);
        
        UIGraphicsBeginImageContextWithOptions(CGSizeMake(side, side), false, [UIScreen mainScreen].scale);
        CGContextAddPath(UIGraphicsGetCurrentContext(), [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, side, side)].CGPath);
        CGContextClip(UIGraphicsGetCurrentContext());
        
        CGFloat marginX = -(image.size.width - side) * 0.5;
        CGFloat marginY = -(image.size.height - side) * 0.5;
        [image drawInRect:CGRectMake(marginX, marginY, image.size.width, image.size.height)];
        
        CGContextDrawPath(UIGraphicsGetCurrentContext(), kCGPathFillStroke);
        
        UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        
        return newImage;
    }
    

    三种方法里面,方法三是性能最好的。
    当然了,直接让美工画一个圆角的图效率是最高的。

    shadow优化

    我们可以通过设置shadowPath来优化性能,能大幅提高性能

    imageView.layer.shadowColor=[UIColorgrayColor].CGColor;
    
    imageView.layer.shadowOpacity=1.0;
    
    imageView.layer.shadowRadius=2.0;
    
    UIBezierPath *path=[UIBezierPathbezierPathWithRect:imageView.frame];
    
    imageView.layer.shadowPath=path.CGPath;
    

    组不透明

    开启CALayer的 allowsGroupOpacity 属性后,子 layer 在视觉上的透明度的上限是其父 layer 的 opacity (对应UIView的 alpha ),并且从 iOS 7 以后默认全局开启了这个功能,这样做是为了让子视图与其容器视图保持同样的透明度。

    所以,可以关闭 allowsGroupOpacity 属性,按产品需求自己控制layer透明度。

    抗锯齿

    不设置 allowsEdgeAntialiasing属性为YES(默认为NO)

    标题离屏渲染的检测

    Instruments的Core Animation工具中有几个和离屏渲染相关的检查选项:
    Color Offscreen-Rendered Yellow
    开启后会把那些需要离屏渲染的图层高亮成黄色,这就意味着黄色图层可能存在性能问题。

    Color Hits Green and Misses Red
    如果shouldRasterize被设置成YES,对应的渲染结果会被缓存,如果图层是绿色,就表示这些缓存被复用;如果是红色就表示缓存会被重复创建,这就表示该处存在性能问题了。

    iOS版本上的优化

    iOS 9.0 之前UIimageView跟UIButton设置圆角都会触发离屏渲染

    iOS 9.0 之后UIButton设置圆角会触发离屏渲染,而UIImageView里png图片设置圆角不会触发离屏渲染了,如果设置其他阴影效果之类的还是会触发离屏渲染的。

    善用离屏渲染

    尽管离屏渲染开销很大,但是当我们无法避免它的时候,可以想办法把性能影响降到最低。优化思路也很简单:既然已经花了不少精力把图片裁出了圆角,如果我能把结果缓存下来,那么下一帧渲染就可以复用这个成果,不需要再重新画一遍了。

    CALayer为这个方案提供了对应的解法:shouldRasterize。一旦被设置为true,Render Server就会强制把layer的渲染结果(包括其子layer,以及圆角、阴影、group opacity等等)保存在一块内存中,这样一来在下一帧仍然可以被复用,而不会再次触发离屏渲染。有几个需要注意的点:

    • shouldRasterize的主旨在于降低性能损失,但总是至少会触发一次离屏渲染。如果你的layer本来并不复杂,也没有圆角阴影等等,打开这个开关反而会增加一次不必要的离屏渲染
    • 离屏渲染缓存有空间上限,最多不超过屏幕总像素的2.5倍大小
    • 一旦缓存超过100ms没有被使用,会自动被丢弃
    • layer的内容(包括子layer)必须是静态的,因为一旦发生变化(如resize,动画),之前辛苦处理得到的缓存就失效了。如果这件事频繁发生,我们就又回到了“每一帧都需要离屏渲染”的情景,而这正是开发者需要极力避免的。针对这种情况,Xcode提供了“Color Hits Green and Misses Red”的选项,帮助我们查看缓存的使用是否符合预期
    • 其实除了解决多次离屏渲染的开销,shouldRasterize在另一个场景中也可以使用:如果layer的子结构非常复杂,渲染一次所需时间较长,同样可以打开这个开关,把layer绘制到一块缓存,然后在接下来复用这个结果,这样就不需要每次都重新绘制整个layer树了

    什么时候需要CPU渲染

    绝大多数情况下,得益于GPU针对图形处理的优化,我们都会倾向于让GPU来完成渲染任务,而给CPU留出足够时间处理各种各样复杂的App逻辑。为此Core Animation做了大量的工作,尽量把渲染工作转换成适合GPU处理的形式(也就是所谓的硬件加速,如layer composition,设置backgroundColor等等)。

    但是对于一些情况,如文字(CoreText使用CoreGraphics渲染)和图片(ImageIO)渲染,由于GPU并不擅长做这些工作,不得不先由CPU来处理好以后,再把结果作为texture传给GPU。除此以外,有时候也会遇到GPU实在忙不过来的情况,而CPU相对空闲(GPU瓶颈),这时可以让CPU分担一部分工作,提高整体效率。

    一个典型的例子是,我们经常会使用CoreGraphics给图片加上圆角(将图片中圆角以外的部分渲染成透明)。整个过程全部是由CPU完成的。这样一来既然我们已经得到了想要的效果,就不需要再另外给图片容器设置cornerRadius。另一个好处是,我们可以灵活地控制裁剪和缓存的时机,巧妙避开CPU和GPU最繁忙的时段,达到平滑性能波动的目的。

    但要注意的是:

    • 渲染不是CPU的强项,调用CoreGraphics会消耗其相当一部分计算时间,并且我们也不愿意因此阻塞用户操作,因此一般来说CPU渲染都在后台线程完成(这也是AsyncDisplayKit的主要思想),然后再回到主线程上,把渲染结果传回CoreAnimation。这样一来,多线程间数据同步会增加一定的复杂度
    • 同样因为CPU渲染速度不够快,因此只适合渲染静态的元素,如文字、图片(想象一下没有硬件加速的视频解码,性能惨不忍睹)
    • 作为渲染结果的bitmap数据量较大(形式上一般为解码后的UIImage),消耗内存较多,所以应该在使用完及时释放,并在需要的时候重新生成,否则很容易导致OOM
    • 如果你选择使用CPU来做渲染,那么就没有理由再触发GPU的离屏渲染了,否则会同时存在两块内容相同的内存,而且CPU和GPU都会比较辛苦
    更多相关内容
  • 本文主要介绍了IOS 性能优化中离屏渲染的资料,提供了几种方法讲解了优化,有需要的小伙伴可以参考下
  • 主要为大家详细介绍了iOS设置圆角阴影,避免离屏渲染,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
  • 编译 gcc osdemo.c -lGLU -lOSMesa -lm -o osdemo 运行 ./osdemo test.tga
  • cef-mixer:使用CEF的高性能离屏渲染(OSR)演示
  • 优化之离屏渲染

    2021-06-07 13:44:45
    这篇文章主要是总结一下我对离屏渲染的理解,之前一直不太理解离屏渲染到底是个什么,也不太理解为什么他会引起滚动界面卡顿,一直也没有去深入了解,自己也深感惭愧。以前看到了@ibireme写的iOS 保持界面流畅的技巧...

    66b52468c121889b900d4956032f1009.png

    8种机械键盘轴体对比

    本人程序员,要买一个写代码的键盘,请问红轴和茶轴怎么选?

    这篇文章主要是总结一下我对离屏渲染的理解,之前一直不太理解离屏渲染到底是个什么,也不太理解为什么他会引起滚动界面卡顿,一直也没有去深入了解,自己也深感惭愧。以前看到了@ibireme写的iOS 保持界面流畅的技巧这篇文章,里面也描述了下屏幕显示原理。现在再次深入阅读,才比较理解离屏渲染到底是怎么回事。

    屏幕显示原理首先从过去的 CRT 显示器原理说起。CRT 的电子枪按照上面方式,从上到下一行行扫描,扫描完成后显示器就呈现一帧画面,随后电子枪回到初始位置继续下一次扫描。为了把显示器的显示过程和系统的视频控制器进行同步,显示器(或者其他硬件)会用硬件时钟产生一系列的定时信号。当电子枪换到新的一行,准备进行扫描时,显示器会发出一个水平同步信号(horizonal synchronization),简称 HSync;而当一帧画面绘制完成后,电子枪回复到原位,准备画下一帧前,显示器会发出一个垂直同步信号(vertical synchronization),简称 VSync。显示器通常以固定频率进行刷新,这个刷新率就是 VSync 信号产生的频率。尽管现在的设备大都是液晶显示屏了,但原理仍然没有变。CPU 计算好显示内容提交到 GPU,GPU 渲染完成后将渲染结果放入帧缓冲区,随后视频控制器会按照 VSync 信号逐行读取帧缓冲区的数据,经过可能的数模转换传递给显示器显示。ios_screen_scan.png在最简单的情况下,帧缓冲区只有一个,这时帧缓冲区的读取和刷新都都会有比较大的效率问题。为了解决效率问题,显示系统通常会引入两个缓冲区,即双缓冲机制。在这种情况下,GPU 会预先渲染好一帧放入一个缓冲区内,让视频控制器读取,当下一帧渲染好后,GPU 会直接把视频控制器的指针指向第二个缓冲器。如此一来效率会有很大的提升。

    这里介绍了 GPU 为了效率问题,使用了两个帧缓冲区。对于帧缓冲区的个数,iOS 系统中使用的是双缓冲机制,而 Android 使用的三缓冲机制。题外话,不得不佩服 iOS 系统的设计,在硬件不如 Android 的提前下,界面的流畅度却超过 Android 机器。

    离屏渲染

    直接引用网络上的一段文字:OpenGL中,GPU屏幕渲染有以下两种方式:On-Screen Rendering 意为当前屏幕渲染,指的是GPU的渲染操作是在当前用于显示的屏幕缓冲区中进行。

    Off-Screen Rendering 意为离屏渲染,指的是GPU在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作。

    对于一个 UIView 的渲染过程:UIView的layer层有一个content,指向一块缓存,即backing store

    UIView绘制时,会调用drawRect方法,通过context将数据写入backing store

    在backing store写完后,通过render server交给GPU去渲染,将backing store中的bitmap数据显示在屏幕上uiview_render.png

    当我们在使用Group Opacity、corner(圆角)、shadow(阴影)、edge antialiasing(抗锯齿)和mask等视图功能的时候,由于图层不是那种简单的覆盖,而是需要额外计算图层的显示关系,由于当前已经是 GPU 渲染了,所以在 GPU 的当前屏幕缓冲区外,新建了一个缓冲区,在这个缓冲区中,去进行这些额外的渲染,这就是离屏渲染。创建离屏缓冲区是一个代价是很大的。

    (假设我们的在界面中画了圆角),如果我们创建的圆角 N 个,那么在这个离屏缓冲区中,去处理这 N 个圆角的时候,会大量的切换上下文,然而,切换上下文也是一个代价非常大的操作。

    所以,当在开发过程中,如果离屏渲染没有处理好,对 App 的性能影响是非常大的, 后文会有相关的实例对比。

    离屏渲染的优化尽量使用当前屏幕渲染。由于离屏渲染、CPU渲染可能带来的性能问题,一般情况下,尽量使用当前屏幕渲染。

    由于 GPU 的浮点运算能力比 CPU 强,虽然 CPU 渲染的效率不如 GPU,但有时使用 CPU 渲染的效率会比 GPU 离屏渲染好,毕竟离屏渲染要涉及到缓冲区创建和上下文切换等耗时操作。

    圆角

    一般情况,我们写圆角的时候,会这样写:1

    2view.layer.cornerRadius = 10;

    view.layer.masksToBounds = YES;

    运行时,查看 Off-screen Renderedcorner_offscreen_rendered.jpg

    corner_offscreen_rendered

    我们可以看到按钮上的四个角被黄色覆盖,表明这四个角被离屏渲染了,这是因为 GPU 渲染的时候,发现这个按钮不是一个规整的图形,不能简单的采用(x, y, width, height)的规则进行渲染,对于上面的四个角,必须进行离屏渲染,新建一块缓冲区进行计算渲染,和下一层的 layer 进行合成处理,这样才能再界面上形成一个有弧度的视觉效果。

    然而这里只有四个角所处的矩形范围进行了离屏渲染,是因为计算机里面的图形坐标都是以类似于(x, y, width, height)的规则来描述的,也就是说是一个矩形图形。然而这儿不是整个按钮而只是其他的四个角的区域的原因,是因为效率问题,GPU 确定除了四个角所在的矩形区域之外, 其他的区域都不需要合成处理,这样选择性的进行离屏渲染,能够获得更高的性能。

    这里我们如何处理圆角带来的离屏渲染呢?答案是采用 Core Graphics 提供的函数,在 CPU 里面绘制圆角,这样相当于在 CPU 处理的时候就已经 GPU 离屏渲染做的事情给完成了。

    使用下面提供的代码能够给 UIView 的子类添加圆角,能够避免离屏渲染。1

    2

    3

    4// 仅添加圆角

    [view addCorner:30];

    // 添加圆角和边框

    [view addCorner:30 borderWidth:0.5 borderColor:[UIColor redColor]];

    由于addCorner:和addCorner:borderWidth:borderColor:的代码较长,需要查看和下载的,请点击此处查看下载

    阴影

    对于 shadow 来说,1

    2

    3view.layer.shadowColor = [UIColor redColor].CGColor;

    view.layer.shadowOffset = CGSizeMake(5, 5);

    view.layer.shadowOpacity = 1.0;

    查看 Off-screen Renderedshadow_offscreen_rendered.jpg

    corner_offscreen_rendered

    可以看到,整个 view 的 frame,包括 frame 之外还有一点都进行了离屏渲染。

    这儿有一个问题,如果有谁知道,请在评论区讲解一下。为什么是整个 view 都被离屏渲染了呢? 为了效率中间的区域可以不需要被离屏渲染,但是对于 shadow 却被离屏渲染了?

    对于 shadow 的优化,直接在上面的代码后面添加一句就行了1

    2

    3

    4

    5view.layer.shadowColor = [UIColor redColor].CGColor;

    view.layer.shadowOffset = CGSizeMake(5, 5);

    view.layer.shadowOpacity = 1.0;

    // 添加 shadowPath,能够解决离屏渲染

    view.layer.shadowPath = [UIBezierPath bezierPathWithRect:view.bounds].CGPath;

    Mask

    一般的 Mask 设置代码如下:1

    2

    3UIImageView * mask = [[UIImageView alloc] initWithFrame:view.bounds];

    mask.image = [UIImage imageNamed:@"mask.png"];

    view.maskView = mask;

    对于 Mask 的优化,其实并没有一个很好的方式来解决,目前的解决方案是:rasterize(光栅化),然而 rasterize(光栅化)也会造成离屏渲染,不过由于离屏渲染大部分时候会在大量出现的时候造成性能问题,所以在滚动页面进行 rasterize(光栅化)的时候,会将光栅化后的内容缓存起来,如果对应的layer及其sublayers没有发生改变,在下一帧的时候可以直接复用, 把GPU的操作转到CPU上了,生成位图缓存,各种阴影遮罩等效果也会保存到位图中并缓存起来,直接读取复用,从而减少渲染的频度。rasterize(光栅化)有一个不好的效果,会造成文字 label 视觉效果模糊。

    展开全文
  • 改项目示例演示了将QWidget嵌入QML界面中的原理和流程。 环境WIN10,VS2010,QT5.5.1
  • 离屏渲染(Pbuffer)-附件资源
  • 图像绘制与离屏渲染

    2017-07-04 18:58:21
    图像绘制与离屏渲染
  • 【OpenGL ES】FBO离屏渲染

    千次阅读 2022-03-26 00:38:43
    OpenGL 默认把 framebuffer 当作渲染目的地,它由窗口系统创建并管理。应用程序也可以创建额外非可显示的 framebuffer object(FBO),以区别窗口系统提供的 framebuffer。OpenGL 应用程序可以重定向渲染目的地,让...

    1 前言

            OpenGL 默认把 framebuffer 当作渲染目的地,它由窗口系统创建并管理。应用程序也可以创建额外非可显示的 framebuffer objectFBO),以区别窗口系统提供的 framebuffer。OpenGL 应用程序可以重定向渲染目的地,让它输出到 FBO 而不是窗口系统提供的 framebuffer。

            与窗口系统提供的 framebuffer 类似,FBO 包含一系列渲染目的地:颜色缓冲区(color buffer)深度缓冲区(depth buffer)模板缓冲区(stencil buffer),FBO 中的这些逻辑缓冲区称为附着点,颜色附着点可以有多个,深度附着点和模板附着点只有一个,FBO 具有多个颜色附加点的原因是允许在同一时间将颜色缓冲区渲染到多个目的地。FBO本身不存放数据,它只有多个附着点。

            OpenGL 中有两种可附着的 framebuffer:纹理(texture)renderbuffer。如果纹理被附着到 FBO,OpenGL 将执行“渲染到纹理”。如果 renderbuffer 被附着到 FBO,则 OpenGL 将执行“离屏渲染”。渲染到纹理的一种传统方法是绘制到缓冲区,再使用 glCopyTexSubImage2D() 将 framebuffer 图像复制到纹理,FBO 将场景直接渲染到纹理,消除了额外的数据拷贝(从帧缓存到纹理)。

            离屏渲染是指:在后台渲染数据,处理完成后再送显到前台。原理是:在一块内存中渲染数据,处理完成后再取出来送显到前台。好处有:避免花屏,提升渲染效率。

            本文将使用 ImageView 显示离屏渲染后的图片(将原图片变灰)。由于 Renderer 需要一个 GLSurfaceView 调用 requestRender() 方法驱动渲染,但是本文又不将离屏渲染后的图片送显给 GLSurfaceView,因此将 GLSurfaceView 的宽高都设置为1,并且设置为透明的。如果不使用 GLSurfaceView,可以参考:EGL+FBO离屏渲染

            读者如果对 OpenGL ES 不太熟悉,请回顾以下内容:

            本文完整代码资源见→ FBO离屏渲染

            项目目录如下: 

     2 案例

            MainActivity.java

    package com.zhyan8.offscreen.activity;
    
    import android.graphics.Bitmap;
    import android.os.Bundle;
    import android.widget.FrameLayout;
    import android.widget.ImageView;
    import androidx.appcompat.app.AppCompatActivity;
    import com.zhyan8.offscreen.R;
    import com.zhyan8.offscreen.model.Model;
    import com.zhyan8.offscreen.opengl.MyGLSurfaceView;
    import com.zhyan8.offscreen.opengl.MyRender;
    
    public class MainActivity extends AppCompatActivity implements Model.Callback {
        private FrameLayout mRootView;
        private ImageView mImageView;
        private MyGLSurfaceView mGlSurfaceView;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            mRootView = findViewById(R.id.rootView);
            mImageView = findViewById(R.id.imageView);
            initGLView();
        }
    
        private void initGLView() {
            mGlSurfaceView = new MyGLSurfaceView(this);
            MyRender render = new MyRender(getResources());
            render.setCallback(this);
            mGlSurfaceView.init(render);
            mGlSurfaceView.layout(mRootView);
        }
    
        @Override
        public void onCall(final Bitmap bitmap) {
            runOnUiThread(() -> {
                mImageView.setImageBitmap(bitmap);
            });
        }
    }

            activity_main.xml

    <?xml version="1.0" encoding="utf-8"?>
    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/rootView">
    
        <ImageView
            android:id="@+id/imageView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scaleType="fitCenter"/>
    
    </FrameLayout>

            MyGLSurfaceView.java

    package com.zhyan8.offscreen.opengl;
    
    import android.content.Context;
    import android.graphics.PixelFormat;
    import android.opengl.GLSurfaceView;
    import android.util.AttributeSet;
    import android.view.ViewGroup;
    import android.view.WindowManager;
    
    public class MyGLSurfaceView extends GLSurfaceView {
        public MyGLSurfaceView(Context context) {
            super(context);
        }
    
        public MyGLSurfaceView(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        public void init(Renderer renderer) {
            setEGLContextClientVersion(3);
            setEGLConfigChooser(8, 8, 8, 8, 16, 0);
            getHolder().setFormat(PixelFormat.TRANSLUCENT);
            setZOrderOnTop(true);
            setRenderer(renderer);
        }
    
        public void layout(ViewGroup parent) {
            WindowManager.LayoutParams params = new WindowManager.LayoutParams();
            params.width = 1;
            params.height = 1;
            parent.addView(this, params);
        }
    }

            MyRender.java

    package com.zhyan8.offscreen.opengl;
    
    import android.content.res.Resources;
    import android.opengl.GLES30;
    import android.opengl.GLSurfaceView;
    import com.zhyan8.offscreen.model.Model;
    import javax.microedition.khronos.egl.EGLConfig;
    import javax.microedition.khronos.opengles.GL10;
    
    public class MyRender implements GLSurfaceView.Renderer {
        private Model mModel;
    
        public MyRender(Resources resources) {
            mModel = new Model(resources);
        }
    
        @Override
        public void onSurfaceCreated(GL10 gl, EGLConfig eglConfig) {
            //设置背景颜色
            GLES30.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
            //启动深度测试
            gl.glEnable(GLES30.GL_DEPTH_TEST);
            //创建程序id
            mModel.onModelCreate();
        }
    
        @Override
        public void onSurfaceChanged(GL10 gl, int width, int height) {
            mModel.onModelChange(width, height);
        }
    
        @Override
        public void onDrawFrame(GL10 gl) {
            // 将颜色缓存区设置为预设的颜色
            GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT | GLES30.GL_DEPTH_BUFFER_BIT);
            // 启用顶点的数组句柄
            GLES30.glEnableVertexAttribArray(0);
            GLES30.glEnableVertexAttribArray(1);
            // 绘制模型
            mModel.onModelDraw();
            // 禁止顶点数组句柄
            GLES30.glDisableVertexAttribArray(0);
            GLES30.glDisableVertexAttribArray(1);
        }
    
        public void setCallback(Model.Callback callback) {
            mModel.setCallback(callback);
        }
    }

            Model.java

    package com.zhyan8.offscreen.model;
    
    import android.content.res.Resources;
    import android.graphics.Bitmap;
    import android.graphics.Point;
    import android.opengl.GLES30;
    import com.zhyan8.offscreen.R;
    import com.zhyan8.offscreen.utils.ArraysUtils;
    import com.zhyan8.offscreen.utils.ShaderUtils;
    import com.zhyan8.offscreen.utils.TextureUtils;
    import java.nio.ByteBuffer;
    import java.nio.FloatBuffer;
    
    public class Model {
        private static final int TEXTURE_DIMENSION = 2; // 纹理坐标维度
        private static final int VERTEX_DIMENSION = 3; // 顶点坐标维度
        private Callback mCallback;
        private Resources mResources;
        private float mVertex[] = {-1.0f, 1.0f, 0.0f, -1.0f, -1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, -1.0f, 0.0f};
        private float[] mFboTexture = {0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f};
        protected FloatBuffer mVertexBuffer;
        protected FloatBuffer mFboTextureBuffer;
        // 帧缓冲对象 - 颜色、深度、模板附着点,纹理对象可以连接到帧缓冲区对象的颜色附着点
        private int[] mFrameBufferId = new int[1];
        private int[] mTextureId = new int[2];
        private int mProgramId;
        private Point mBitmapSize = new Point();
    
        public Model(Resources resources) {
            mResources = resources;
            mVertexBuffer = ArraysUtils.getFloatBuffer(mVertex);
            mFboTextureBuffer = ArraysUtils.getFloatBuffer(mFboTexture);
        }
    
        // 模型创建
        public void onModelCreate() {
            mProgramId = ShaderUtils.createProgram(mResources, R.raw.vertex_shader, R.raw.fragment_shader);
            TextureUtils.loadTexture(mResources, R.raw.xxx, mBitmapSize, mTextureId, mFrameBufferId);
        }
    
        // 模型参数变化
        public void onModelChange(int width, int height) {
            GLES30.glViewport(0, 0, mBitmapSize.x, mBitmapSize.y);
        }
    
        // 模型绘制
        public void onModelDraw() {
            GLES30.glUseProgram(mProgramId);
            //准备顶点坐标和纹理坐标
            GLES30.glVertexAttribPointer(0, VERTEX_DIMENSION, GLES30.GL_FLOAT, false, 0, mVertexBuffer);
            GLES30.glVertexAttribPointer(1, TEXTURE_DIMENSION, GLES30.GL_FLOAT, false, 0, mFboTextureBuffer);
            //激活纹理
            GLES30.glActiveTexture(GLES30.GL_TEXTURE);
            //绑定纹理
            GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mTextureId[0]);
            // 绑定缓存
            GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, mFrameBufferId[0]);
            // 绘制贴图
            GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, 4);
            showBitmap();
        }
    
        private void showBitmap() {
            // 分配字节缓区大小, 一个像素4个字节
            ByteBuffer byteBuffer = ByteBuffer.allocate(mBitmapSize.x * mBitmapSize.y * 4);
            GLES30.glReadPixels(0, 0, mBitmapSize.x, mBitmapSize.y, GLES30.GL_RGBA, GLES30.GL_UNSIGNED_BYTE, byteBuffer);
            Bitmap bitmap = Bitmap.createBitmap(mBitmapSize.x, mBitmapSize.y, Bitmap.Config.ARGB_8888);
            // 从缓存区读二进制缓冲数据
            bitmap.copyPixelsFromBuffer(byteBuffer);
            // 回调
            mCallback.onCall(bitmap);
        }
    
        public void setCallback(Callback callback) {
            mCallback = callback;
        }
    
        public interface Callback{
            void onCall(Bitmap bitmap);
        }
    }

            注意: 纹理贴图 中为防止显示的图片变形,对顶点坐标进行了正交投影变换;本文案例中却没有这样做,但显示的图片也未变形,主要因为缓存中的数据还未送显到屏幕,不知道屏幕尺寸,不必进行缩放调整。

            ShaderUtils.java

    package com.zhyan8.offscreen.utils;
    
    import android.content.res.Resources;
    import android.opengl.GLES30;
    import java.io.BufferedReader;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    
    public class ShaderUtils {
        //创建程序id
        public static int createProgram(Resources resources, int vertexShaderResId, int fragmentShaderResId) {
            final int vertexShaderId = compileShader(resources, GLES30.GL_VERTEX_SHADER, vertexShaderResId);
            final int fragmentShaderId = compileShader(resources, GLES30.GL_FRAGMENT_SHADER, fragmentShaderResId);
            return linkProgram(vertexShaderId, fragmentShaderId);
        }
    
        //通过外部资源编译着色器
        private static int compileShader(Resources resources, int type, int shaderId){
            String shaderCode = readShaderFromResource(resources, shaderId);
            return compileShader(type, shaderCode);
        }
    
        //通过代码片段编译着色器
        private static int compileShader(int type, String shaderCode){
            int shader = GLES30.glCreateShader(type);
            GLES30.glShaderSource(shader, shaderCode);
            GLES30.glCompileShader(shader);
            return shader;
        }
    
        //链接到着色器
        private static int linkProgram(int vertexShaderId, int fragmentShaderId) {
            final int programId = GLES30.glCreateProgram();
            //将顶点着色器加入到程序
            GLES30.glAttachShader(programId, vertexShaderId);
            //将片元着色器加入到程序
            GLES30.glAttachShader(programId, fragmentShaderId);
            //链接着色器程序
            GLES30.glLinkProgram(programId);
            return programId;
        }
    
        //从shader文件读出字符串
        private static String readShaderFromResource(Resources resources, int shaderId) {
            InputStream is = resources.openRawResource(shaderId);
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            String line;
            StringBuilder sb = new StringBuilder();
            try {
                while ((line = br.readLine()) != null) {
                    sb.append(line);
                    sb.append("\n");
                }
                br.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return sb.toString();
        }
    }

            TextureUtils.java

    package com.zhyan8.offscreen.utils;
    
    import android.content.res.Resources;
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    import android.graphics.Point;
    import android.opengl.GLES30;
    import android.opengl.GLUtils;
    
    public class TextureUtils {
        // 加载纹理贴图
        public static void loadTexture(Resources resources, int resourceId, Point bitmapSize, int[] textureId, int[] frameBufferId) {
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inScaled = false;
            Bitmap bitmap = BitmapFactory.decodeResource(resources, resourceId, options);
            bitmapSize.set(bitmap.getWidth(), bitmap.getHeight());
            // 生成纹理id
            GLES30.glGenTextures(2, textureId, 0);
            for (int i = 0; i < 2; i++) {
                // 绑定纹理到OpenGL
                GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textureId[i]);
                GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_NEAREST);
                GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR);
                GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_S, GLES30.GL_CLAMP_TO_EDGE);
                GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_T, GLES30.GL_CLAMP_TO_EDGE);
                if (i == 0) {
                    // 第一个纹理对象给渲染管线(加载bitmap到纹理中)
                    GLUtils.texImage2D(GLES30.GL_TEXTURE_2D, 0, GLES30.GL_RGBA, bitmap, 0);
                } else {
                    // 第二个纹理对象给帧缓冲区
                    GLES30.glTexImage2D(GLES30.GL_TEXTURE_2D, 0, GLES30.GL_RGBA, bitmap.getWidth(), bitmap.getHeight(),
                            0, GLES30.GL_RGBA, GLES30.GL_UNSIGNED_BYTE, null);
                }
                // 取消绑定纹理
                GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, GLES30.GL_NONE);
            }
            // 创建帧缓存id
            GLES30.glGenFramebuffers(1, frameBufferId, 0);
            // 绑定帧缓存
            GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, frameBufferId[0]);
            // 将第二个纹理附着在帧缓存的颜色附着点上
            GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_COLOR_ATTACHMENT0, GLES30.GL_TEXTURE_2D, textureId[1], 0);
            // 取消绑定帧缓存
            GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, GLES30.GL_NONE);
        }
    }

            ArraysUtils.java

    package com.zhyan8.offscreen.utils;
    
    import java.nio.ByteBuffer;
    import java.nio.ByteOrder;
    import java.nio.FloatBuffer;
    
    public class ArraysUtils {
        public static FloatBuffer getFloatBuffer(float[] floatArr) {
            FloatBuffer fb = ByteBuffer.allocateDirect(floatArr.length * Float.BYTES)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer();
            fb.put(floatArr);
            fb.position(0);
            return fb;
        }
    }

            vertex_shader.glsl

    #version 300 es
    layout (location = 0) in vec4 vPosition;
    layout (location = 1) in vec2 aTextureCoord;
    out vec2 vTexCoord;
    void main() {
         gl_Position  = vPosition;
         vTexCoord = aTextureCoord;
    }

            fragment_shader.glsl

    #version 300 es
    precision mediump float;
    uniform sampler2D uTextureUnit;
    in vec2 vTexCoord;
    out vec4 fragColor;
    void main() {
         vec4 color = texture(uTextureUnit, vTexCoord);
         float rgb = color.g;
         vec4 c = vec4(rgb, rgb, rgb, color.a);
         fragColor = c;
    }

    3 运行效果

            原图:

            处理后: 

    展开全文
  • 【OpenGL ES】EGL+FBO离屏渲染

    千次阅读 2022-04-01 00:45:06
    FBO离屏渲染中使用 GLSurfaceView 来驱动 Renderer 渲染图片,为了隐藏 GLSurfaceView,将其设置为透明的,并且宽高都设置为1。本文将使用 EGL 代替 GLSurfaceView 生成 OpenGL ES 的渲染环境,实现离屏渲染,将渲染...

    1 前言

            FBO离屏渲染 中使用 GLSurfaceView 来驱动 Renderer 渲染图片,为了隐藏 GLSurfaceView,将其设置为透明的,并且宽高都设置为1。本文将使用 EGL 代替 GLSurfaceView 生成 OpenGL ES 的渲染环境,实现离屏渲染,将渲染后的图片显示在 ImageView 上。

            EGL 为 OpenGL ES 提供了绘制表面(或渲染画布),是 OpenGL ES 与显示设备的桥梁让 OpenGL ES 绘制的内容能够在呈现当前设备上。

            EGL 环境创建分为以下5步:

            1)创建EGLDisplay

    EGLDisplay mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
    int[] versions = new int[2];
    EGL14.eglInitialize(mEGLDisplay, versions,0, versions, 1);

            2)创建EGLConfig

    int[] mEGLConfigAttrs = {
        EGL14.EGL_RED_SIZE, 8,
        EGL14.EGL_GREEN_SIZE, 8,
        EGL14.EGL_BLUE_SIZE, 8,
        EGL14.EGL_ALPHA_SIZE, 8,
        EGL14.EGL_DEPTH_SIZE, 8,
        EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
        EGL14.EGL_NONE
    };
    EGLConfig[] configs = new EGLConfig[1];
    int[] configNum = new int[1];
    EGL14.eglChooseConfig(mEGLDisplay, mEGLConfigAttrs, 0, configs, 0,1,  configNum, 0);
    EGLConfig mEGLConfig = configs[0];

            3)创建EGLContext

    int[] mEGLContextAttrs = {EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE};
    EGLContext mEGLContext = EGL14.eglCreateContext(mEGLDisplay, mEGLConfig, EGL14.EGL_NO_CONTEXT, mEGLContextAttrs, 0);

            4)创建EGLSurface

    int[] eglSurfaceAttrs = {EGL14.EGL_WIDTH, mWidth, EGL14.EGL_HEIGHT, mHeight, EGL14.EGL_NONE};
    EGLSurface mEGLSurface = EGL14.eglCreatePbufferSurface(mEGLDisplay, mEGLConfig, eglSurfaceAttrs, 0);

            5)绑定EGLSurface和EGLContext到显示设备(EGLDisplay)

    EGL14.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext);

            读者如果对 OpenGL ES 不太熟悉,请回顾以下内容:

            本文完整代码资源见→EGL+FBO离屏渲染

            项目目录如下: 

    2 案例

            本案例实现了将彩色图片转换为灰色,并且使用 ImageView 显示转换后的图片。

            MainActivity.java

    package com.zhyan8.egl.activity;
    
    import android.graphics.Bitmap;
    import android.os.Bundle;
    import android.widget.ImageView;
    import androidx.appcompat.app.AppCompatActivity;
    import com.zhyan8.egl.R;
    import com.zhyan8.egl.model.Model;
    import com.zhyan8.egl.opengl.MyEGLSurface;
    import com.zhyan8.egl.opengl.MyRender;
    
    public class MainActivity extends AppCompatActivity implements Model.Callback {
        private ImageView mImageView;
        private MyEGLSurface mEGlSurface;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            mImageView = findViewById(R.id.imageView);
            initEGLSurface();
            mEGlSurface.requestRender();
        }
    
        private void initEGLSurface() {
            mEGlSurface = new MyEGLSurface(this);
            MyRender render = new MyRender(getResources());
            render.setCallback(this);
            mEGlSurface.init(render);
        }
    
        @Override
        public void onCall(final Bitmap bitmap) {
            runOnUiThread(() -> {
                mImageView.setImageBitmap(bitmap);
            });
        }
    }

            activity_main.xml

    <?xml version="1.0" encoding="utf-8"?>
    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#556688">
    
        <ImageView
            android:id="@+id/imageView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scaleType="fitCenter"/>
    
    </FrameLayout>

            BaseEGLSurface.java

    package com.zhyan8.egl.opengl;
    
    import android.content.Context;
    import android.opengl.EGL14;
    import android.opengl.EGLConfig;
    import android.opengl.EGLContext;
    import android.opengl.EGLDisplay;
    import android.opengl.EGLSurface;
    import android.util.DisplayMetrics;
    import android.view.WindowManager;
    
    public class BaseEGLSurface {
        protected EGLDisplay mEGLDisplay;
        protected EGLConfig mEGLConfig;
        protected EGLContext mEGLContext;
        protected EGLSurface mEGLSurface;
        protected Context mContext;
        protected Renderer mRenderer;
        protected EglStatus mEglStatus = EglStatus.INVALID;
        protected int mWidth;
        protected int mHeight;
    
        public BaseEGLSurface(Context context) {
            mContext = context;
            WindowManager mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
            DisplayMetrics displayMetrics = new DisplayMetrics();
            mWindowManager.getDefaultDisplay().getRealMetrics(displayMetrics);
            mWidth = displayMetrics.widthPixels;
            mHeight = displayMetrics.heightPixels;
        }
    
        public BaseEGLSurface(Context context, int width, int height) {
            mContext = context;
            mWidth = width;
            mHeight = height;
        }
    
        // 设置渲染器
        public void setRenderer(Renderer renderer) {
            mRenderer = renderer;
        }
    
        // EGLDisplay宽高发生变化
        public void onSurfaceChanged(int width, int height) {
            mWidth = width;
            mHeight = height;
            EGL14.eglDestroySurface(mEGLDisplay, mEGLSurface);
            createSurface();
            mEglStatus = EglStatus.CREATED;
        }
    
        // 请求渲染
        public void requestRender() {
            if (mEglStatus == mEglStatus.INVALID) {
                return;
            }
            if (mEglStatus == EglStatus.INITIALIZED) {
                mRenderer.onSurfaceCreated();
                mRenderer.onSurfaceChanged(mWidth, mHeight);
                mEglStatus = EglStatus.CREATED;
            }
            if (mEglStatus == EglStatus.CREATED || mEglStatus == EglStatus.DRAW) {
                mRenderer.onDrawFrame();
                mEglStatus = EglStatus.DRAW;
            }
        }
    
        // 创建EGL环境
        public void createEGLEnv() {
            createDisplay();
            createConfig();
            createContext();
            createSurface();
            makeCurrent();
        }
    
        // 销毁EGL环境
        public void destroyEGLEnv() {
            // 与显示设备解绑
            EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT);
            // 销毁 EGLSurface
            EGL14.eglDestroySurface(mEGLDisplay, mEGLSurface);
            // 销毁EGLContext
            EGL14.eglDestroyContext(mEGLDisplay, mEGLContext);
            // 销毁EGLDisplay(显示设备)
            EGL14.eglTerminate(mEGLDisplay);
            mEGLContext = null;
            mEGLSurface = null;
            mEGLDisplay = null;
        }
    
        // 1.创建EGLDisplay
        private void createDisplay() {
            mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
            int[] versions = new int[2];
            EGL14.eglInitialize(mEGLDisplay, versions,0, versions, 1);
        }
    
        // 2.创建EGLConfig
        private void createConfig() {
            EGLConfig[] configs = new EGLConfig[1];
            int[] configNum = new int[1];
            EGL14.eglChooseConfig(mEGLDisplay, mEGLConfigAttrs, 0, configs, 0,1,  configNum, 0);
            if (configNum[0] > 0) {
                mEGLConfig = configs[0];
            }
        }
    
        // 3.创建EGLContext
        private void createContext() {
            if (mEGLConfig != null) {
                mEGLContext = EGL14.eglCreateContext(mEGLDisplay, mEGLConfig, EGL14.EGL_NO_CONTEXT, mEGLContextAttrs, 0);
            }
        }
    
        // 4.创建EGLSurface
        private void createSurface() {
            if (mEGLContext != null && mEGLContext != EGL14.EGL_NO_CONTEXT) {
                int[] eglSurfaceAttrs = {EGL14.EGL_WIDTH, mWidth, EGL14.EGL_HEIGHT, mHeight, EGL14.EGL_NONE};
                mEGLSurface = EGL14.eglCreatePbufferSurface(mEGLDisplay, mEGLConfig, eglSurfaceAttrs, 0);
            }
        }
    
        // 5.绑定EGLSurface和EGLContext到显示设备(EGLDisplay)
        private void makeCurrent() {
            if (mEGLSurface != null && mEGLSurface != EGL14.EGL_NO_SURFACE) {
                EGL14.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext);
                mEglStatus = EglStatus.INITIALIZED;
            }
        }
    
        // EGLConfig参数
        private int[] mEGLConfigAttrs = {
                EGL14.EGL_RED_SIZE, 8,
                EGL14.EGL_GREEN_SIZE, 8,
                EGL14.EGL_BLUE_SIZE, 8,
                EGL14.EGL_ALPHA_SIZE, 8,
                EGL14.EGL_DEPTH_SIZE, 8,
                EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
                EGL14.EGL_NONE
        };
    
        // EGLContext参数
        private int[] mEGLContextAttrs = {EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE};
    
        // EGL状态
        enum EglStatus {
            INVALID, INITIALIZED, CREATED, DRAW
        }
    
        // 渲染器接口
        interface Renderer {
            void onSurfaceCreated();
            void onSurfaceChanged(int width, int height);
            void onDrawFrame();
        }
    }

            MyEGLSurface.java

    package com.zhyan8.egl.opengl;
    
    import android.content.Context;
    
    public class MyEGLSurface extends BaseEGLSurface {
        public MyEGLSurface(Context context) {
            super(context);
        }
    
        public MyEGLSurface(Context context, int width, int height) {
            super(context, width, height);
        }
    
        public void init(Renderer renderer) {
            setRenderer(renderer);
            createEGLEnv();
        }
    }

            MyRender.java

    package com.zhyan8.egl.opengl;
    
    import android.content.res.Resources;
    import android.opengl.GLES30;
    import com.zhyan8.egl.model.Model;
    
    public class MyRender implements BaseEGLSurface.Renderer {
        private Model mModel;
    
        public MyRender(Resources resources) {
            mModel = new Model(resources);
        }
    
        @Override
        public void onSurfaceCreated() {
            //设置背景颜色
            GLES30.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
            //启动深度测试
            GLES30.glEnable(GLES30.GL_DEPTH_TEST);
            //创建程序id
            mModel.onModelCreate();
        }
    
        @Override
        public void onSurfaceChanged(int width, int height) {
            mModel.onModelChange(width, height);
        }
    
        @Override
        public void onDrawFrame() {
            GLES30.glClearColor(0.5f, 0.7f, 0.3f, 1.0f);
            // 将颜色缓存区设置为预设的颜色
            GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT | GLES30.GL_DEPTH_BUFFER_BIT);
            // 启用顶点的数组句柄
            GLES30.glEnableVertexAttribArray(0);
            GLES30.glEnableVertexAttribArray(1);
            // 绘制模型
            mModel.onModelDraw();
            // 禁止顶点数组句柄
            GLES30.glDisableVertexAttribArray(0);
            GLES30.glDisableVertexAttribArray(1);
        }
    
        public void setCallback(Model.Callback callback) {
            mModel.setCallback(callback);
        }
    }

            Model.java

    package com.zhyan8.egl.model;
    
    import android.content.res.Resources;
    import android.graphics.Bitmap;
    import android.graphics.Point;
    import android.opengl.GLES30;
    import com.zhyan8.egl.R;
    import com.zhyan8.egl.utils.ArraysUtils;
    import com.zhyan8.egl.utils.ShaderUtils;
    import com.zhyan8.egl.utils.TextureUtils;
    import java.nio.ByteBuffer;
    import java.nio.FloatBuffer;
    
    public class Model {
        private static final int TEXTURE_DIMENSION = 2; // 纹理坐标维度
        private static final int VERTEX_DIMENSION = 3; // 顶点坐标维度
        private Callback mCallback;
        private Resources mResources;
        private float mVertex[] = {-1.0f, 1.0f, 0.0f, -1.0f, -1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, -1.0f, 0.0f};
        private float[] mFboTexture = {0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f};
        protected FloatBuffer mVertexBuffer;
        protected FloatBuffer mFboTextureBuffer;
        // 帧缓冲对象 - 颜色、深度、模板附着点,纹理对象可以连接到帧缓冲区对象的颜色附着点
        private int[] mFrameBufferId = new int[1];
        private int[] mTextureId = new int[2];
        private int mProgramId;
        private Point mBitmapSize = new Point();
    
        public Model(Resources resources) {
            mResources = resources;
            mVertexBuffer = ArraysUtils.getFloatBuffer(mVertex);
            mFboTextureBuffer = ArraysUtils.getFloatBuffer(mFboTexture);
        }
    
        // 模型创建
        public void onModelCreate() {
            mProgramId = ShaderUtils.createProgram(mResources, R.raw.vertex_shader, R.raw.fragment_shader);
            TextureUtils.loadTexture(mResources, R.raw.xxx, mBitmapSize, mTextureId, mFrameBufferId);
        }
    
        // 模型参数变化
        public void onModelChange(int width, int height) {
            GLES30.glViewport(0, 0, mBitmapSize.x, mBitmapSize.y);
        }
    
        // 模型绘制
        public void onModelDraw() {
            GLES30.glUseProgram(mProgramId);
            // 准备顶点坐标和纹理坐标
            GLES30.glVertexAttribPointer(0, VERTEX_DIMENSION, GLES30.GL_FLOAT, false, 0, mVertexBuffer);
            GLES30.glVertexAttribPointer(1, TEXTURE_DIMENSION, GLES30.GL_FLOAT, false, 0, mFboTextureBuffer);
            // 激活纹理
            GLES30.glActiveTexture(GLES30.GL_TEXTURE);
            // 绑定纹理
            GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, mTextureId[0]);
            // 绑定缓存
            GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, mFrameBufferId[0]);
            // 绘制贴图
            GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, 4);
            showBitmap();
        }
    
        private void showBitmap() {
            // 分配字节缓区大小, 一个像素4个字节
            ByteBuffer byteBuffer = ByteBuffer.allocate(mBitmapSize.x * mBitmapSize.y * Integer.BYTES);
            GLES30.glReadPixels(0, 0, mBitmapSize.x, mBitmapSize.y, GLES30.GL_RGBA, GLES30.GL_UNSIGNED_BYTE, byteBuffer);
            Bitmap bitmap = Bitmap.createBitmap(mBitmapSize.x, mBitmapSize.y, Bitmap.Config.ARGB_8888);
            // 从缓存区读二进制缓冲数据
            bitmap.copyPixelsFromBuffer(byteBuffer);
            // 回调
            mCallback.onCall(bitmap);
        }
    
        public void setCallback(Callback callback) {
            mCallback = callback;
        }
    
        public interface Callback{
            void onCall(Bitmap bitmap);
        }
    }

            ShaderUtils.java

    package com.zhyan8.egl.utils;
    
    import android.content.res.Resources;
    import android.opengl.GLES30;
    import java.io.BufferedReader;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    
    public class ShaderUtils {
        //创建程序id
        public static int createProgram(Resources resources, int vertexShaderResId, int fragmentShaderResId) {
            final int vertexShaderId = compileShader(resources, GLES30.GL_VERTEX_SHADER, vertexShaderResId);
            final int fragmentShaderId = compileShader(resources, GLES30.GL_FRAGMENT_SHADER, fragmentShaderResId);
            return linkProgram(vertexShaderId, fragmentShaderId);
        }
    
        //通过外部资源编译着色器
        private static int compileShader(Resources resources, int type, int shaderId){
            String shaderCode = readShaderFromResource(resources, shaderId);
            return compileShader(type, shaderCode);
        }
    
        //通过代码片段编译着色器
        private static int compileShader(int type, String shaderCode){
            int shader = GLES30.glCreateShader(type);
            GLES30.glShaderSource(shader, shaderCode);
            GLES30.glCompileShader(shader);
            return shader;
        }
    
        //链接到着色器
        private static int linkProgram(int vertexShaderId, int fragmentShaderId) {
            final int programId = GLES30.glCreateProgram();
            //将顶点着色器加入到程序
            GLES30.glAttachShader(programId, vertexShaderId);
            //将片元着色器加入到程序
            GLES30.glAttachShader(programId, fragmentShaderId);
            //链接着色器程序
            GLES30.glLinkProgram(programId);
            return programId;
        }
    
        //从shader文件读出字符串
        private static String readShaderFromResource(Resources resources, int shaderId) {
            InputStream is = resources.openRawResource(shaderId);
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            String line;
            StringBuilder sb = new StringBuilder();
            try {
                while ((line = br.readLine()) != null) {
                    sb.append(line);
                    sb.append("\n");
                }
                br.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return sb.toString();
        }
    }

            TextureUtils.java

    package com.zhyan8.egl.utils;
    
    import android.content.res.Resources;
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    import android.graphics.Point;
    import android.opengl.GLES30;
    import android.opengl.GLUtils;
    
    public class TextureUtils {
        // 加载纹理贴图
        public static void loadTexture(Resources resources, int resourceId, Point bitmapSize, int[] textureId, int[] frameBufferId) {
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inScaled = false;
            Bitmap bitmap = BitmapFactory.decodeResource(resources, resourceId, options);
            bitmapSize.set(bitmap.getWidth(), bitmap.getHeight());
            // 生成纹理id
            GLES30.glGenTextures(2, textureId, 0);
            for (int i = 0; i < 2; i++) {
                // 绑定纹理到OpenGL
                GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textureId[i]);
                GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_NEAREST);
                GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR);
                GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_S, GLES30.GL_CLAMP_TO_EDGE);
                GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_T, GLES30.GL_CLAMP_TO_EDGE);
                if (i == 0) {
                    // 第一个纹理对象给渲染管线(加载bitmap到纹理中)
                    GLUtils.texImage2D(GLES30.GL_TEXTURE_2D, 0, GLES30.GL_RGBA, bitmap, 0);
                } else {
                    // 第二个纹理对象给帧缓冲区
                    GLES30.glTexImage2D(GLES30.GL_TEXTURE_2D, 0, GLES30.GL_RGBA, bitmap.getWidth(), bitmap.getHeight(),
                            0, GLES30.GL_RGBA, GLES30.GL_UNSIGNED_BYTE, null);
                }
                // 取消绑定纹理
                GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, GLES30.GL_NONE);
            }
            // 创建帧缓存id
            GLES30.glGenFramebuffers(1, frameBufferId, 0);
            // 绑定帧缓存
            GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, frameBufferId[0]);
            // 将第二个纹理附着在帧缓存的颜色附着点上
            GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_COLOR_ATTACHMENT0, GLES30.GL_TEXTURE_2D, textureId[1], 0);
            // 取消绑定帧缓存
            GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, GLES30.GL_NONE);
        }
    }

            ArraysUtils.java

    package com.zhyan8.egl.utils;
    
    import java.nio.ByteBuffer;
    import java.nio.ByteOrder;
    import java.nio.FloatBuffer;
    
    public class ArraysUtils {
        public static FloatBuffer getFloatBuffer(float[] floatArr) {
            FloatBuffer fb = ByteBuffer.allocateDirect(floatArr.length * Float.BYTES)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer();
            fb.put(floatArr);
            fb.position(0);
            return fb;
        }
    }

            vertex_shader.glsl

    #version 300 es
    layout (location = 0) in vec4 vPosition;
    layout (location = 1) in vec2 aTextureCoord;
    out vec2 vTexCoord;
    void main() {
         gl_Position  = vPosition;
         vTexCoord = aTextureCoord;
    }

            fragment_shader.glsl

    #version 300 es
    precision mediump float;
    uniform sampler2D uTextureUnit;
    in vec2 vTexCoord;
    out vec4 fragColor;
    void main() {
         vec4 color = texture(uTextureUnit, vTexCoord);
         float rgb = color.g;
         vec4 c = vec4(rgb, rgb, rgb, color.a);
         fragColor = c;
    }

    3 运行效果

            原图:

            处理后:

    展开全文
  • OpenGL-离屏渲染

    2022-02-22 15:47:07
    什么叫做离屏渲染 正常情况下,渲染数据存放在帧缓冲区(Frame Buffer)中,但是在特定条件下,需要先将渲染数据先放在屏缓冲区(Offscreen Buffer)中,在将多个图层的渲染数据叠加计算后,再传入帧缓冲区进行...
  • 关于离屏渲染

    2021-11-26 17:03:03
    一般情况下界面的呈现是在当前屏幕渲染,首先CPU计算好frame等属性,将计算好的内容提交给GPU去渲染,GPU渲染完成之后就会放入屏幕帧缓冲区,然后控制器每隔一段时间会去屏幕缓存区读取渲染好的内容,从而显示出来。...
  • cef离屏渲染的例子

    热门讨论 2015-03-11 15:53:21
    cef离屏渲染的c++代码示例,改自cefclient(在群里朋友的基础上修改的,更直接明了,删掉了多余的文件),有需要的朋友可以参考一下
  • OpenGL之离屏渲染

    千次阅读 2020-11-28 14:08:33
    什么是离屏渲染 Off-Screen Rendering意为离屏渲染,指的是GPU在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作 为什么需要离屏渲染 因为在帧缓冲区渲染一个视图到屏幕中就抛弃的机制.所以当我们有多组视图需要...
  • 深入剖析【离屏渲染】原理 离屏渲染与正常渲染 屏幕上最终显示的数据有两种加载流程 正常渲染加载流程 离屏渲染加载流程 从图上看,他们之间的区别就是离屏渲染比正常渲染多了一个屏缓冲区,这个缓冲区的作用是...
  • OpenGL离屏渲染流程代码

    千次阅读 2020-08-22 17:09:22
    目录OpenGL离屏渲染初始化用于离屏渲染的`Renderbuffer`和`Framebuffer`离屏渲染绘制到窗口从离屏渲染Framebuffer读取图像到内存 OpenGL离屏渲染 初始化用于离屏渲染的Renderbuffer和Framebuffer // 初始化 // -----...
  • 离屏渲染

    2018-02-07 11:24:04
    离屏渲染 一、概述 OpenGL ES是一套多功能开放标准的用于嵌入系统的C-based的图形库,用于2D和3D数据的可视化。OpenGL被设计用来转换一组图形调用功能到底层图形硬件(GPU),由GPU执行图形命令,用来...
  • Linux平台CEF离屏渲染

    千次阅读 2022-01-25 17:00:43
    一、简介 Chromium 嵌入式框架 (CEF)。是一个简单的框架,用于将基于 Chromium 的... 四、参考资料 1、linux CEF 离屏渲染详解 2、Qt+Cef3离屏渲染 3、Chromium内核 CEF3 4、通过VNC搭建Ubuntu 18.04和20.04图形界面
  • ZHCornerRadius一句话设置图片圆角,高性能,避免离屏渲染,畅享丝滑.imageView.cornerRadius = XXX
  • canvas性能优化——离屏渲染

    万次阅读 2018-08-14 22:06:28
    但是,发现在2000个粒子的时候fps已经降低到25左右,上面没有使用离屏渲染的时候4000个粒子fps还有50,由此感觉离屏渲染反而降低了性能。 回过头来一想,都说离屏渲染提升性能,难道是操作有问题,接着又进行了一...
  • 首先,关于UIButton的离屏渲染,并不是设置了 layer 的 CornerRadius 和 MasksToBounds 就会导致离屏渲染,在设置了以上两种的情况下,只有设置 UIButton 的背景图片,才会导致离屏渲染,光设置背景颜色和 ...
  • 最近学习了一下OpenGL的相关知识,在此记录一下FBO的使用和Qt下离屏渲染的注意事项。 有关FBO的介绍在很多博客中都有介绍,本文就不再赘述了,FBO初始化代码: glGenFramebuffers(1, &m_frameBuffer); ...
  • 2、Off-Screen Rendering(离屏渲染) 指的是GPU在当前屏幕缓冲区以外开辟一个缓冲区进行渲染操作。 离屏渲染的代价很高体现在两个方面,1、需要创建一个新的缓冲区。2、上下文切换操作。 会引发离屏渲染的操作:...
  • iOS避免离屏渲染性能优化

    千次阅读 2018-05-31 14:46:45
    还记得之前将离屏渲染和渲染路径时的示意图么,离屏渲染的最后一步是把此前的多个路径组合起来。如果这个组合过程能由CPU完成,就会大量减少GPU的工作。这种技术在绘制地图中可能用到。 第七个选项“Color ...
  • WebGLRenderTarget(离屏渲染) WebGL渲染目标对象WebGLRenderTarget实现了WebGL的离屏渲染功能,如果你有一定的WebGL或OpenGL基础,对帧缓冲区、离线渲染、后处理等概念应该是不陌生的。 .render()方法 WebGL渲染器...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 193,823
精华内容 77,529
关键字:

离屏渲染