湘潭网站建设有名磐石网络,友情链接出售,网站建设费用一年多少钱,网站开发合同知识产权前言
在上一篇文章中#xff0c;麒麟子给大家分享了如何在 Cocos Creator 3.8 中的自定义管线中#xff0c;添加属于自己的后期效果 Shader。 但基于 BlitScreen 的方案#xff0c;我们只能编写最简单后效 Shader#xff0c;如果我们想要支持更多复杂的 Shader#xff0c…
前言
在上一篇文章中麒麟子给大家分享了如何在 Cocos Creator 3.8 中的自定义管线中添加属于自己的后期效果 Shader。 但基于 BlitScreen 的方案我们只能编写最简单后效 Shader如果我们想要支持更多复杂的 Shader比如模糊、景深等等效果就需要配合代码才能实现。
今天麒麟子就用高斯模糊来演示如何编写一个多 Pass 的后效 Shader。
准备 Shader 高斯模糊的涉及到的内容比较多什么正态分布、高斯函数、高斯卷积核。 如果对数学没兴趣上面这些统统不用管。
简单来说高斯模糊就是把图片上的每一个像素都用下面的流程处理一遍。 直白来说就是一个简单的加权求和采样目标像素的同时再采样一些周围的像素;并且每一个像素给一个权重权重和为1.0。最终像素值 所有像素 x 权重的和。 如果想要效果好就多迭代几次迭代次数越多画面越好但性能开销越高。
我们创建一个 Cocos Shader 文件命名为 “gaussian-blur.effect” 然后编写下面的内容。
CCEffect %{techniques:- passes:- vert: blur-hor-vsfrag: blur-fspass: blur-xdepthStencilState:depthTest: falsedepthWrite: false- vert: blur-vert-vsfrag: blur-fspass: blur-ydepthStencilState:depthTest: falsedepthWrite: false
}%CCProgram blur-hor-vs %{//...
}%
CCProgram blur-hor-vs %{//...
}%CCProgram blur-fs %{//...
}%//为了方便文章阅读
//完整 Shader 被放到了文末可以看到整个 Cocos Shader 只有两个 Pass一个用来水平模糊一个用来竖直模糊。 为了不影响阅读完整 Shader 放在了文末。 注意Cocos Creator 3.8.0 版本如果新增了后效 Shader需要重启编辑器才能识别。后面版本中会优化这个流程。 编写属性组件
Cocos Creator 3.8 中只需要在节点上添加对应的后期效果组件就可以启动对应的后期效果。 自定义后期管线提供了一个 postProcess.PostProcessingSetting 组件类我们可以通过继承它类来实现后效参数的可视化界面配置。
通过它自定义出来的后期效果 是完全可以达到和内置的后期效果一样的使用体验的。
新建一个 TS 脚本文件起名为 “GaussianBlur.ts”然后输入以下代码。 代码片段1 import { _decorator, gfx, postProcess, Material, EffectAsset, renderer, rendering, Vec4 } from cc;
const { Format } gfxconst { ccclass, property, menu, executeInEditMode } _decorator;ccclass(GaussianBlur)
menu(PostProcess/GaussianBlur)
executeInEditMode
export class GaussianBlur extends postProcess.PostProcessSetting{property(EffectAsset)_effectAsset: EffectAsset | undefinedproperty(EffectAsset)get effect () {return this._effectAsset;}set effect (v) {this._effectAsset v;if(this._effectAsset null){this._material null;}else{if(this._material null){this._material new Material();}this._material.reset({effectAsset:this._effectAsset});}this.updateMaterial();}propertyiterations 3;propertyget blurRadius(){return this._blurParams.x;}set blurRadius(v){this._blurParams.x v;this.updateMaterial();}private _material:Material;public get material():Material{return this._material;}propertyprivate _blurParams:Vec4 new Vec4(1.0,0.0,0.0,0.0);public get blurParams():Vec4{return this._blurParams;}updateMaterial(){if(!this._material){return;}this._material.setProperty(blurParams, this.blurParams);}protected start(): void {if(this._effectAsset){this._material new Material();this._material.initialize({effectAsset:this._effectAsset});this._material.setProperty(blurParams, this.blurParams);}}
}接下来我们就可以通过“添加组件“按钮把 PostProcess/GansussianBlur 添加到后处理结点上了。 可以看到在 Inspector 面板上它接受 3 个参数。
effect用于指定用于这个后期效果的 Shader 文件iterations用于指定需要迭代多少次迭代次数越多越模糊blurRadius采样临近像素时的偏移偏移量越大越模糊 这个时候是没有任何效果的因为还没有实现对应的渲染代码。 编写渲染代码
Cocos Creator 3.8 中的后处理管线是基于新版自定义管线的而新版的自定义管线是基于 RenderGraph 架构的。
你可以简单地认为我们想要实现的单个后处理效果对应的就是渲染流程图上的一个节点。 后处理管线提供了 postProcess.SettingPass 给我们用来编写自己的后处理渲染效果。也可以叫自定义后处理节点的资源和行为。
接下来我们来做真正的渲染实现。 我们需要继承自定义管线中的 postProcess.SettingPass 类并实现并要的代码。
get setting获取配置信息对应的就是上面实现的界面组件checkEnable用于判断此后效是否开启name后效的名字一般保持和类名一致即可outputNames最终输出的 RT 数组。临时用的 RT 不用放在这里render用于执行渲染流程
完整编码如下。 代码片段2 export class GaussianBlurPass extends postProcess.SettingPass {get setting () { return this.getSetting(GaussianBlur); }checkEnable (camera: renderer.scene.Camera) {let enable super.checkEnable(camera);if (postProcess.disablePostProcessForDebugView()) {enable false;}return enable this.setting.material ! null;}name GaussianBlurPass;outputNames [GaussianBlurMap];public render (camera: renderer.scene.Camera, ppl: rendering.Pipeline): void {const setting this.setting;if(!setting.material){return;}let passContext this.context;passContext.material setting.material;const cameraID this.getCameraUniqueID(camera);const cameraName Camera${cameraID};const passViewport passContext.passViewport;passContext.clearBlack();const format Format.RGBA8;let input this.lastPass!.slotName(camera, 0);for(let i 0; i setting.iterations; i){passContext.updatePassViewPort().addRenderPass(blur-x, blur-x${cameraID}).setPassInput(input, outputResultMap).addRasterView(GaussianBlurMap_TMP, format).blitScreen(0).version();passContext.updatePassViewPort().addRenderPass(blur-y, blur-y${cameraID}).setPassInput(GaussianBlurMap_TMP, outputResultMap).addRasterView(this.slotName(camera), format).blitScreen(1).version();input this.slotName(camera);}}
}接下来我们主要看看 render 处理了哪些事情。
准备工作
每一个 SettingPass 就是一个绘制节点。而节点的数据我们存在了 context 中。
render 函数会每帧执行所以需要调用 context.clearBack() 来清理背景。
然后我们要将材质设置给 context。
模糊的处理需要使用上一个处理流程结束后的画面内容。因此我们使用 this.lastPass.slotName(camera,0); 来获取。
一切准备就绪后就进入到了绘制环节。
绘制
这里我们使用了 iterations 属性来控制总共要迭代的次数。迭代一次绘制流程就会走一遍。
我们来看看绘制流程中每一步操作的用途。 updatePassViewPort这个函数用来指定相对分辨率大小这个根据算法需求来指定就行。如果要保持和后台缓冲区一样大传入 1.0 即可。 addRenderPass这个函数用来告诉管线需要执行一次绘制流程。 layout对应的是 Cocos Shader 中的 Pass 名称passName助记名称便于调试查看 setPassInput如果有用到自定义管线中的 RT 资源比如上一次执行的结果则需要在这里指定方便自定义管线对资源进行管理。 inputName: 自定义管线资源分配的资源名称shaderName: 对应 Cocos Shader 中的 uniform Sampler2D 名称。 addRasterView可以简单理解为输出结果 name输出的 RT 名称便于后续流程复用format输出的 RT 格式比如RGBA8888、RGBA16F 等等 blitScreen执行绘制 passIdxCocos Shader 中的 Pass 索引这个在后面的版本中会优化一下到时候后处理流程可以不用传这个值。 version无实际意义可以忽略。
添加到管线
如果只是写好了上面的代码不进行添加也是不会生效的。
我们在文件末尾加上下面代码。 代码片段3 let builder rendering.getCustomPipeline(Custom) as postProcess.PostProcessBuilder;
if (builder) {builder.insertPass(new GaussianBlurPass(),postProcess.BlitScreenPass);
}首先我们获取到了 Custom 管线然后把我们新写的效果添加进去。
回到编辑器中调节参数就可以看到我们新写的模糊效果生效咯。 后处理管线源码浅析
为了方便大家理解后处理的渲染流程麒麟子简单说明一些关键的源码。
代码集
后效相关的类大多都在 postProcess 下建议先从 “cc” 引入 postProcess再使用。
RT 管理
开启后处理管线后开启了后处理管线效果的摄像机会自动生成 RT并设置它的 targetTexture。
后效管线基于 RenderGraph 管线架构 Cocos 引擎中使用的 RenderGraph 是基于数据驱动的会每帧收集需要的 RT 资源并做统一管理。 因此不需要自己再手工新建 RT。
后效执行顺序
后处理效果的执行不是按界面添加的顺序来的而是按照处于数组中的顺序来的。我们通过源码可以看到它的内部顺序如下 // pipeline relatedthis.addPass(new HBAOPass());this.addPass(new ToneMappingPass());// user post-processingthis.addPass(new TAAPass());this.addPass(new FxaaPass());this.addPass(new ColorGradingPass());this.addPass(new BlitScreenPass());this.addPass(new BloomPass());// final outputthis.addPass(new FSRPass()); // fsr should be finalthis.addPass(forwardFinal);后处理效果在渲染时管线会遍历这个数组依次执行可用的后处理效果。
for (let i 0; i passes.length; i) {const pass passes[i];if (!pass.checkEnable(camera)) {continue;}if (i (passes.length - 1)) {passContext.isFinalPass true;}pass.lastPass lastPass;pass.render(camera, ppl);lastPass pass;
}**源码位置**engine/cocos/rendering/post-process/post-process-builder.ts 后效注意事项
自定义后效添加
在将自己定义的后效 Shader 添加到管线中时需要注意几个问题
必须添加在 ForwardFinal 之前否则没有效果。所以 builder.addPass 不建议使用使用 builder.insterPass 添加新的后效时如果新后效与旧的后效重名会先移除旧的后效builder.insterPass 会将新的后效插入到第二个参数类型指定的后效后面通常情况下建议使用 postProcess.BlitScreenPass。
效果
TAAFXAAFSR 同时开启才能达到很好的抗锯齿效果因为 TAA 主要负责动态帧间抗锯齿但会让画面略微变糊FXAA 主要负责边缘抗锯齿而 FSR 可以让糊了的画面变清晰。ColorGrading 是最划算的后期效果能够快速提升项目画面颜值不要过渡依赖 HBAO因为中低端机跑不动。场景优先使用光照图烘焙出 AO。这样即使HBAO 关闭的情况下效果依然不会差。Bloom 的阈值调小强度调低可实现全屏泛光柔和画面。 Bloom 的阈值调大强度调高可实现亮部像素曝光效果。
内存
虽然 RenderGraph会自动管理和复用 RT但是 后效依然会对内存有所消耗可自行测试当所需后效开启后上涨了多少内存。内存开销受分辨率决定请以实机测试为准。可以通过 shadingScale 来降低分辨率从而减少内存和减少渲染开销提升性能的目的。这个值可以根据当前实际分辨率与设定的机型可用的最大后效分辨率来决定。
性能 后效会多次读写帧缓存对 GPU 带宽和像素填充率纹理填充率要求高。 在中低端机型上很可能造成性能锐减。 做好高、中、低端机型管理 在不同档次的机型上开启对应的后效组合确保性能和效果的平衡。 许多单 Pass 的后效可以合成一个。 比如 ColorGrading,FXAA,Vignette,FinalPass 可以合成一个。这样可以减少 BlitScreen 次数提升性能。 HBAO 能够极大地提升空间关系但也是性能开销大户谨慎使用。
写在最后 使用后处理效果能极大地提升画面质感但也需要注意后处理效果带来的额外内存开销和填充率开销。
后效对低端机型很不友好请做好在低端机上根据性能测试结果做好分档。
另外后效的种类繁多且根据具体的项目需求又可以做特殊优化。因此引擎只能内置常见的后效供大家使用更多后效果需求还得开发者们自己来。
希望今天的分享能够对大家有帮助谢谢大家
附1完整源码
新建一个 TS 脚本将代码片段1、代码片段2、代码片段3 复制到这个脚本中即可。
附2完整 Shader
新建一个 Cocos Shader 文件将下面 Shader 代码复制到文件中即可。
CCEffect %{techniques:- passes:- vert: blur-hor-vsfrag: blur-fspass: blur-xdepthStencilState:depthTest: falsedepthWrite: false- vert: blur-vert-vsfrag: blur-fspass: blur-ydepthStencilState:depthTest: falsedepthWrite: false
}%CCProgram blur-hor-vs %{precision highp float;#include legacy/input-standard#include builtin/uniforms/cc-global#include common/common-defineuniform MyConstants {vec4 blurParams;};out vec2 v_uv;out vec2 v_uv1;out vec2 v_uv2;out vec2 v_uv3;out vec2 v_uv4;void main () {StandardVertInput In;CCVertInput(In);CC_HANDLE_GET_CLIP_FLIP(In.position.xy);gl_Position In.position;gl_Position.y gl_Position.y;v_uv a_texCoord;vec2 texelSize cc_nativeSize.zw;float blurOffsetX blurParams.x * texelSize.x;v_uv1 v_uv vec2(blurOffsetX * 1.0, 0.0);v_uv2 v_uv - vec2(blurOffsetX * 1.0, 0.0);v_uv3 v_uv vec2(blurOffsetX * 2.0, 0.0);v_uv4 v_uv - vec2(blurOffsetX * 2.0, 0.0);}
}%CCProgram blur-vert-vs %{precision highp float;#include legacy/input-standard#include builtin/uniforms/cc-global#include common/common-defineuniform MyConstants {vec4 blurParams;};out vec2 v_uv;out vec2 v_uv1;out vec2 v_uv2;out vec2 v_uv3;out vec2 v_uv4;void main () {StandardVertInput In;CCVertInput(In);CC_HANDLE_GET_CLIP_FLIP(In.position.xy);gl_Position In.position;gl_Position.y gl_Position.y;v_uv a_texCoord;vec2 texelSize cc_nativeSize.zw;float blurOffsetY blurParams.x * texelSize.y;v_uv1 v_uv vec2(0.0, blurOffsetY * 1.0);v_uv2 v_uv - vec2(0.0, blurOffsetY * 1.0);v_uv3 v_uv vec2(0.0, blurOffsetY * 2.0);v_uv4 v_uv - vec2(0.0, blurOffsetY * 2.0);}
}%CCProgram blur-fs %{precision highp float;#include builtin/uniforms/cc-globalin vec2 v_uv;in vec2 v_uv1;in vec2 v_uv2;in vec2 v_uv3;in vec2 v_uv4;#pragma rate outputResultMap passuniform sampler2D outputResultMap;layout(location 0) out vec4 fragColor;void main () {vec3 weights vec3(0.4026,0.2442,0.0545);vec3 sum texture(outputResultMap, v_uv).rgb * weights.x;sum texture(outputResultMap, v_uv1).rgb * weights.y;sum texture(outputResultMap, v_uv2).rgb * weights.y;sum texture(outputResultMap, v_uv3).rgb * weights.z;sum texture(outputResultMap, v_uv4).rgb * weights.z;fragColor vec4(sum, 1.0);}
}%关于麒麟子
深耕游戏引擎与游戏开发15年每一滴干货都源自商业项目实践。
用技术资源赋能行业商机提供实用解决方案、项目分析、技术指导与干货教程
欢迎私信