[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.Cameracc.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.SpriteFrameflipY 属性来实现,但是该属性配合 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 以外,其他三种类型通常在一个场景中只会用到 12 个。

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 主要是为了演示用,所以不讲究编码优雅与否,性能如何,只是为了配合演示。
24赞

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

什么时候正式上线呀

大佬这波操作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 种类多样,其中的一种,依赖 uvshader ,在合图的情况下,还可以通过坐标转换来解决。但是当作用于一些骨骼动画的时候,就只能放弃了。

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 转换就可以解决。

CloneGhost 的区别在于:

  • 前者和 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 使用时,如果 targetonLoad 的时候被 .active = false 就会报错

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

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

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

总结 && Summary

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

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

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

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

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

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

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

fbo_and_shader

1赞