[cc.RenderTexture cc.Camera FBO] 截图, 放大镜, Shader 伴侣

最近在继续研究 LoS - 视野, 光照, 阴影 实现分享 相关技术内容的时候,涉及到了大量需要使用 FBO 的地方,比如 gpu 实现 阴影的计算和渲染。
论坛中搜了下,只有大量的问题贴和少量的例子贴。参考了一些帖子和官方例子,虽然能简单实现功能,但是这东西用起来真的问题挺多。

于是就花了一些时间,把这个功能好好的整理,设计,扩展了一下,做了比较方便易用的插件。

注意插件是收费的 (给大哥挣点华子钱),看过下面的文章,如果你感兴趣的话可以考虑购买支持一下 ^_^/

PS: 插件已经通过审核,下载地址戳这里


0x01 About && 关于

RenderTexture 简称 RTT,指的是使用 FBO -- Frame Buffer Object 技术来实现实时的纹理渲染。

在 multi-pass 的时候经常要使用 RTT 技术,包括一些 post-effect 后期。

0x02 Scenarios && 使用场景

上面简单提到了 FBO 的应用场景,下面就来细分一下这些例子,其中大部分的使用场景,都是和 Shader 相关的。

Multi-Pass Shader

大部分 Shader 效果只需要干一次活,就可以实现效果,比如说一个 GrayScale Shader ,或者 Outline Shader。

但是有些效果,干一次活不够,而且这些活又不能合并到一次做完。

一个例子,就是模糊效果,模糊效果有很多实现方式可以一次搞定,比如高斯模糊,径向模糊,均值模糊。

但是也有需要多次渲染才能实现的效果,比如说先进行水平模糊,再对第一次渲染输出的纹理,进行垂直模糊,达到最终效果。

对应 Creator 中的实际操作,可以将第一次 Shader 处理 cc.Node 后的纹理结果,渲染到 RenderTexture 对象,然后再将第二次 Shader 作用于 RenderTexture对象,这样最后的输出纹理,就是想要的结果。

Screen Post-Effect Shader

屏幕后期特效,很多游戏中,都很常见,比如射击游戏中,血量低下时屏幕四周的血液效果,人物跑动后体力不足,屏幕边缘的模糊,或是呼吸效果。

要实现这些效果都可以使用 Shader 但是这些 Shader 的作用对象,应该是整个屏幕纹理渲染到的 RenderTexture 中的纹理。

UV-Related Shader

Creator 中的很多 Shader 在实际使用时会生奇怪的错误,论坛上经常提到,最常见的一个原因,就是勾选了纹理打包选项, Shader 中使用的纹理坐标错误,从而导致最终 Shader 效果的错误。

很多 Shader 完全不关心纹理坐标,比如灰度图,黑白效果,等等。

有些 Shader 只关心纹理的相对坐标,比如说一些模糊算法,对于每一个处理的像素,它只关心和它临近的像素的信息。

最后还有一些 Shader 是关注纹理坐标在整个纹理中所处的绝对坐标的,比如一个类似小草摇曳的效果,需要对纹理从上到下赋予幅度递增的弯曲效果。

对于最后一种 Shader ,只要合图就会出现错误,一种解决方案,是对纹理坐标进行简单的换算。

还有一种方法,也可以用 FBO 来解决。就是把目标对象的纹理,渲染到 RenderTexture,然后将 Shader 作用于 RenderTexture。

对于帧动画来说,也是属于最后一种情况,每帧切换合图中的不同纹理,如果是纹理绝对坐标依赖的 Shader,直接使用必然是得不到想要的效果的。

Bones Animation Shader

除了上面提到的三种纹理坐标的情况,还有这一种更特殊的例子,那就是骨骼动画。

比如 Spine , DragonBones 这些骨骼动画,它们并不只是单纯的在合图中寻找,切换不同的纹理。而是将合图中各种骨骼纹理取出,根据预制好的动画规则,将骨骼纹理进行形变后组合,并且每帧变化,来呈现出动画效果。

对于这样的骨骼动画,和纹理坐标相关的 Shader 在使用时,直接使用,必然会有很多问题,而且也很难像帧动画一样,通过换算纹理坐标来解决问题。

但是同样的,通过使用 FBO 的套路,一样可以解决问题。

Screenshot

一个很常见的使用场景,截图,最后将纹理导出。

Magnifier

另一个很常见的使用场景,就是将 RenderTexture 中的纹理,进行放大或是缩小操作,实现放大镜的效果。

0x03 Component && 组件

既然是这么有用的东西,那么 Creator 中必然有提供。通过使用 cc.Camera 和 cc.RenderTexture 就可以实现 FBO 的效果。但是通过搜索论坛就可以发现,这个东西是个满是坑 的功能。

总有各种各样的问题来阻止使用者实现想要的效果,即使官方已经在测试用例中给出了简单的演示功能。

也正因为如此,有了这次的组件 SSRFBONodeComponent ,就像名字一样,很直接,就是为了能够在 Creator 中方便的使用 FBO 功能而开发的。把各种复杂的易错的细节都在组件中进行了统一处理,使开发者只需要及其简单的设置就可以使用。

目前组件所测试过的,确认支持的 Creator 中的渲染组件:

cc.Sprite 单图精灵

cc.Animation 帧动画

cc.ParticleSystem 粒子系统

cc.Label 文本

cc.RichText 富文本

DragonBones 龙骨骨骼动画

sp.Skeleton Spine 骨骼动画

以上所有的渲染组件在和 Mask 组件混用是,同样都能够正常使用。

0x04 Mode && 模式

SSRFBONodeComponent 包含了多种模式来满足实际的 FBO 使用需求,主要有如下四种 (截图中红色部分为 FBO 部分,为了方便演示):

SSRFBONodeScreenComponent

全屏 FBO 专用组件,用于获取全屏纹理。可以用于实现如全屏模糊的效果。

SSRFBONodeRegionComponent

指定大小区域 FBO 专用组件,用于获取节点问中心的,指定大小区域的纹理。可以用于实现如局部区域模糊的效果。

SSRFBONodeInPlaceComponent

指定区域 FBO 专用组件,用于获取指定区域范围内的纹理。可以用于实现如局部区域倒影的效果。

SSRFBONodeTargetComponent

指定对象 FBO 专用组件,用于获取指定对象的纹理。可以用于实现一些骨骼动画相关的 Shader 效果。

0x05 Features && 功能

Common

UpdateMode

更新模式,可以根据需要求,选择是进更新一次纹理,还是实时更新。

根据实际的使用场景,选择合适的更新模式,可以提升 FBO 的性能。比如用户可以在选择 UpdateOnce 模式,然后再需要更新纹理的时候,主动调用 UpdateFBO 接口即可。

flipY

可以指定纹理是否需要翻转。

最常使用 cc.SpriteFrame 的 flipY 属性来实现,但是该属性配合 cc.RenderTexture 使用的时候,在动态运行时更新后无法马上刷新纹理。因此暂时改用 -scaleY 实现。

group

指定 FBO 相关节点专用的分组。

backgroundColor

可以指定 FBO 对象的背景颜色。

RegionComponent

zoom

可以指定纹理的缩放比例,实现类似放大镜的效果。

InPlaceComponent

rect

指定抓取纹理的矩形范围。

编辑器中,有 gizmo 会标识出实际抓取的纹理矩形范围。

TargetComponent

target

指定抓取纹理的对象节点。

syncAngle syncScale syncSize syncAnchor

可指定是否需要和对象的旋转 / 缩放 / 尺寸/锚点 属性保持同步。

在需要精准的纹理计算时,可以开启,这样就会保证 FBONode 和目标节点的 BoundingBox 保持同步。

**目前发现,Creator 中的 RenderTexture 在原生环境下调用 UpdateSize 存在 bug ,暂时采用重新创建纹理的方式实现,因此 syncSize 的性能降低。 **

cc.RenderTexture 原生环境调用 updateSize 报错?

offsetX offsetY

可指定抓取后的纹理的横向 / 纵向偏移量。

对于某些骨骼动画,通过设置该属性,可以将渲染目标节点调整到合适的位置。

inflateW inflateH

可指定输出纹理在宽度 / 高度上的膨胀大小。主要用于一些需要外扩的 Shader 情况。

在测试中发现 Creator 中目前对有些 Spine 动画在大小读取上有问题,比如官方例子中的 SpineBoy,添加到编辑器中后,size 的大小显示为 0, 0。但是 SpineRator 则是正确的。

对于前一种情况,可以通过设置 inflate 属性,来实现正确的纹理抓取。

CaptureComponent

接受一个 FBOComponent 对象,并将其中的纹理导出为图片。目前实现了 Web android iOS 平台中的导出图片功能。

Editor

所有的控件几属性均可以在编辑器中实时预览,所见即所得。

0x06 Platform Support && 平台支持

目前测试过的平台,设备,相关信息。

从测试结果来看,所有功能在所有测试的平台和设备上都可以正常运行 (除了微信小游戏没有做保存图片的功能)。

引擎版本

Creator v2.3.4

Creator v2.4.0

Creator v2.4.3

测试设备

Model.1

2018 产

MacBook Pro (13-inch)

macOS Catalina 10.15.6

Google Chrome 版本 85.0.4183.83(正式版本)(64 位)

Firefox 80.0.1 (64 位)

Safari 版本13.1.2 (15609.3.5.1.3)

Model.2

2014 产

iPhone 6 11.3.1 MG4H27P/A

Model.3

2017 产

iPhone X 13.3.1 MQA92CH/A

Model.4

2017 产

Huawei Honor 7X BND-AL10 Android 9.0 EMUI 9.1.0

Model.5

2018 产

Huawei Mate20 Pro LYA-AL00 Android 10.1 EMUI v10.1.0

发布平台 / 测试结果

Web Desktop

所有测试用例在 Model.1 所有浏览器中都可以满帧运行。

Web Mobile

所有测试用例在

Model.3/4/5/6 微信浏览器和系统默认浏览器中都可以满帧运行。

Model.2 系统默认浏览器中,部分测试用例为 30 ~ 40FPS,其余都可以满帧运行。

Mac Xode Version 11.6 (11E708)

所有测试用例在 Model.1 中都可以满帧运行。

iOS Xode Version 11.6 (11E708)

所有测试用例在

Model.2/3 中,部分测试用例为 30 ~ 40FPS,其余都可以满帧运行。

Android Android-29 armeabi-v7a

所有测试用例在 Model.5 中都可以满帧运行。

Model.4 中,部分测试用例为 30 ~ 40FPS,其余都可以满帧运行。

Wechat Game 稳定版 Stable Build 1.03.2010240

所有测试用例在 Model.2/3/4/5/6 中,部分测试用例为 30 ~ 40FPS,其余都可以满帧运行。

总结

测试用例性能在各种场景下还是有着很不错的表现。

总体性能 Web > android > iOS。

FBO 在同一场景中数量多会造成性能下降的。

选择合适的组件类型,合适的更新策略,都可以帮助提高性能。

除了 FBOTarget 以外,其他三种类型通常在一个场景中只会用到 1 到 2 个。

0x07 How to use && 使用方法

  1. 将下载文件夹中的 SSRFBONodeComponent/assets/scripts/ssr 目录,复制到想要使用的项目

  2. 将下载文件夹中的 SSRFBONodeComponent/packages/ssr-fbo-node 目录,复制到想要使用的项目的 packages 目录中

  3. 在编辑器中,就可以看到 fbo 的插件选项了

  4. 增加一个给 FBO 专用的分组,名称,顺序都随意。

  5. 记得在使用 FBO 的插件时,设置 group 属性为刚才创建的分组名称

  6. FBONodeCaptureComponent 在网页端使用时,需要将 SSRFBONodeComponent/assets/scripts/ssr/fbo/download.js 文件导入为插件

0x08 Misc && 杂项

  • 下载的项目中,包含了核心的 Component 和源码,和所有的测试用例源码。
  • 下载的项目中,组件使用的 Group 名称为 fbo
  • 测试用例中的 Shader 主要是为了演示用,所以不讲究编码优雅与否,性能如何,只是为了配合演示。
25赞

为什么不把代码放在插件里面呢, 这样直接安装插件就可以使用功能了,
不用额外的文件操作
就是这个设置分组麻烦,好像只有手动

什么时候正式上线呀

大佬这波操作666666,

已经上架了,欢迎来支持一波 :sunglasses:

1赞

Mark一下,学习学习

使用targetComponent报错了 2.3.4版本

请问下这个是在网页环境下运行的吗,如果方便的话私信 下你的程序可以吗,我来看看什么问题。

我刚才用 v2.3.4 版本运行 demo 的 TargetFBO 没有发生报错的情况

大佬,想要问下,你这个东西是不是能用在 cc.Graphics 上呢?

正好我用正在写的另一个插件试了一下。
左侧的老虎是用纯 cc.Graphics 绘制的 node 对象。
右边的老虎使用插件里的 FBOTargetComponent 抓取的。

然后我简单的找了两个 shader 用在右侧的 node 上,看了下效果。

这个是模糊效果:

这个是像素化效果:

应该是没有任何问题的。

1赞

666啊 又更新了

有兴趣可以搞一个把玩一下,支持一下 :test:

下一版本 svga 什么时候发布呀

买了在商店下载不下来

我刚才自己试了下,貌似因为这个我是以源码方式提交的,所以付款后点击获取,是没有自动的下载到源码。需要自己保存一下。

你看下下面的截图步骤:

点击获取

点击侧边栏的小文件符号,会弹出对话框让你选择下载的源码保存到哪里

然后代码就会下载下来了

1赞

老哥 我现在看商店里让我用paypal付款 我没有paypal 能改成人民币吗?

你可以搜索一下 FBO,有一个是中文版的可以微信支付宝,一个是英文版的只有 PayPal,英文版的有后缀 “_EN”。

如果能像unity那样,一句话实现使用指定shader pass渲染src输出到dest就好了。

Graphics.Blit(src, dest, material, 0);

U3D 开发者表示,确实,这个 U3D 很方便

周末提交的新版本已经上线,之前购买过的朋友,记得来免费更新 :relaxed:

v1.1.0

更新内容

  • 类命名规范修改为和其他组件一样的方式
  • 把纹理旋转的实现方式,从 scaleY -1 修改为 flipY,虽然这样存在无法实时修改 flipY 的问题,但是这种需求是比较少的,配合 flipY 的方式实现纹理的翻转,才能更好的配合各种 Shader 实现特效的应用
  • 增加两个新的组件 SSRFBOCloneCompoent SSRFBOGhostComponent 已经对应的测试用例

Clone && Ghost

就像这个组件最初在介绍时说过,FBO 的一个最大的使用场景,就是配合 Shader 的使用。

由于 Shader 种类多样,其中的一种,依赖 uv 的 shader ,在合图的情况下,还可以通过坐标转换来解决。但是当作用于一些骨骼动画的时候,就只能放弃了。

Cross Shader

这里那一个很简单的,uv 依赖的 shader 做例子。

把作用对象水平,垂直的,划分成四个象限,然后染色不一样的颜色。

这个 shader 写起来是很简单的:

void main () {
		vec4 c = texture2D(texture, v_uv0);
    if (uv.y < 0.5) {
				if (uv.x < 0.5) { // 上半部分,注意这个和 cocos 的坐标系是反的
            // 左上,红色
        		gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0) * c;
        }
        else {
            // 右上,绿色
        		gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0) * c;
        }
    }
    else {
    		if (uv.x < 0.5) {
            // 左下,蓝色
        		gl_FragColor = vec4(0.0, 0.0, 1.0, 1.0) * c;
        }
        else {
            // 右下,黑色
        		gl_FragColor = vec4(0.0, 1.0, 1.0, 1.0) * c;
        }
    }
}

这样的一个 shader ,在直接运用到 spine 或是 drangonBones 对象上时,是绝对无法得到想要的结果的。

再加上有些骨骼动画的中心点也并非通常意义上的 cc.Node 的中点,而且 boundingBox 也很难派上用场。

Target && Clone && Ghost FBO

上一个版本中,就有了 SSRFBOTargetComponent,这个组件。它可以实时获取指定目标的纹理,然后进行 Shader 的特效处理。

但是在实际使用的时候,通常会希望 新产生出来的一个对象可以完全把本体所替代,只展示一个加了 shader 特效的对象,让人感觉就像是本体加了特效一样。

因此这个版本引入了 CloneComponent

GhostComponent

两者都实现了上面提到的,完全替代本体,并且可以接收任何 uv 依赖的 shader。

上面的截图可以看到,第二列都是直接使用 shader 的结果,第三列则是 Clone && Ghost 的情况。

这里 红色绿色 本该在上半部分却显示在了下半部分并不是错误,是因为 FBO 有纹理垂直翻转的情况,只要像合图那样,在 Shader 做个 uv 转换就可以解决。

Clone 和 Ghost 的区别在于:

  • 前者和 target 是并列关系,因此不受 target 的形变影响
  • 后者是 target 的子节点,受 target 的形变影响
  • 通常情况下,更推荐后者来实现骨骼动画的 uv Shader 特效

OffSet && Tranform

Clone

对于 Clone 的情况,由于是并列的节点关系,因此有需要实时同步 target 的缩放旋转的需求

这里通过调整 size / offset 属性,可以修正 boundingBox 的大小位置。如果骨骼动画有缩放旋转,也可以选择是否实时同步。

修正后,就可以正确的启用 Shader 的效果了。

但是这种方式不是很推荐,因为就像前面看到的,很多骨骼动画的中点不统一,在旋转缩放的时候,如果不及时再次调整 Offset 或是 Size,那么效果又会出现问题,因此更推荐下面的 Ghost 模式。

Ghost

由于 Ghost 是以子节点的方式实现的,所以只需要简单的调整 Offset 就可以了:

target 的任何形变,子节点都不需要做出任何修改,都可以始终保持正确。

此外,通过设置两种形式的 Offset ,还可以起到一些特殊的效果,比如做到 target 的 部分消失效果。

ghost_fbo

ghost_offset

ghost_transform

How to Use && 如何使用

使用方式和 v.1.0.0 完全一致,只是需要 再增加一个分组 Group,用于 Clone && Ghost 模式使用。演示程序中增加了一个 Clone 和 Ghost 分组。

Known Issues && 已知问题

目前的版本还存在一些已知的问题没有修复,但都是不影响实际使用的问题,在这里先记录一下,后续会努力修复。

  • TargetComponent 使用时,如果 target 在 onLoad 的时候被 .active = false 就会报错

但是将代码放到 start 中,就不会有问题,应该是调用时机方面的问题。

  • 在运行时,改变 flipY 属性不会生效 texture.setFlipY

  • Clone 和 Ghost 在编辑器中实时改变大小,有时候会出现不正确的结果,只要在编辑器中刷新当前 Scene 就可以恢复

总结 && Summary

这一次在加入了 Clone 和 Ghost 两个新的组建之后, SSRFBO 这一组件也越来越接近最初我想要的结果了。

就像我一直提到的,FBO 主要是在和 Shader 配合的使用中,会起到很大的作用。

就像我几年前写用 cocos2dx 写的 SSRShaderFX,其中有很多特效和实现方式都用到了 FBO。

分享一下最近业余时间在做的一个库 SSRShaderFX

这几天我也用 SSRFBOComponent 这个组件,在寻找 Creator 中,任何节点的 Shader 统一解决方案。

无论是否合图,静态动态,帧动画,骨骼动画,是否级联,对于任何一个 Shader ,都该有统一的表现。

配合现在 SSRFBOComponent 这个组件,应该能找到很好的解决方案(目前还有一些小的问题)。

fbo_and_shader

1赞