你相信光吗?2D 后效与光照技术分享!

原创
2023/12/20 14:23
阅读数 18

很多朋友提到后效,就会想起那些 3D 游戏大作,但实际上,后效在 2D 游戏开发中的应用也是非常广泛的。
恰当地使用后效,可以使一款 2D 游戏的画质提升好几个台阶。
今天邀请到了社区大佬 wing,给大家分享一下 2D 后效框架相关的技术要点。

什么是后处理效果

后处理效果简称后效,用于对渲染结果做进一步的处理,以实现许多高级效果和特殊效果。
通俗的解释就是对游戏画面进行 PS,在游戏中属于一种不可获缺的功能,常用于提升游戏画质与表现。

以下是一些常用后效:

  1. 模糊效果:可以用于实现景深、运动模糊等效果。
  2. 辉光效果:可以增加游戏元素的发光效果,提升游戏的视觉冲击力。
  3. 色调映射效果:可以调整游戏的色彩风格,表达不同的情绪和氛围。
  4. 扭曲效果:可以创建独特的变形效果,增加游戏的艺术感和创意。
  5. 颜色校正效果:可以调整游戏的整体色调和对比度,使画面更加饱满和生动。

2D 后效方案解析

Cocos Creator 目前没有内置支持 2D 阶段的后处理,因此需要大家各显神才能通达到渲染目标。

用得最多的方式就是将相机渲染到一张 RenderTexture(以下简称 RT) 中,再显示到一个 Sprite 上。

这是一个古老而实用的解决方案,但是这样会对开发流程有一定影响且看起来不那么优雅。

参考社区大佬 @玄烨@二喵给终极解决方案,我们可以通过代码,将相机渲染的 RT 在处理后直接显示到最终窗口上。由于是通过代码创建一个矩形渲染到最上层,对于使用者来说是无感的,既优雅又方便。

渲染流程如下所示:

这里奉上关键代码:

    let device = director.root.device;
    let renderPipeline = director.root.pipeline;
    let pso = PipelineStateManager.getOrCreatePipelineState(device, pass, pass.getShaderVariant(), __renderPass, __quad);

    let cmd = renderPipeline.commandBuffers[0];
    cmd.beginRenderPass(__renderPass, destination.window.framebuffer, renderArea, __clearColors, 00);
    cmd.bindDescriptorSet(pipeline.SetIndex.GLOBAL, renderPipeline.descriptorSet);
    cmd.bindDescriptorSet(pipeline.SetIndex.MATERIAL, pass.descriptorSet);
    cmd.bindPipelineState(pso);
    cmd.bindInputAssembler(__quad);
    cmd.draw(__quad);
    cmd.endRenderPass();

好啦!大家知道原理了,是不是特别简单,有了核心技术,剩下的就是怎么去组织它,让它用起来更趁手,于是就有了如下几个通用接口 PP_Graphics

/**
 * 通用后处理接口,通过一个材质pass处理一张至目标纹理上
 */

blitP(source: RenderTexture, 
      destination: IRenderTexture, 
      pass: renderer.Pass, 
      renderArea: gfx.Rect = null)

/**
 * 拷贝纹理
 */

blit(source: RenderTexture,  
     destination: IRenderTexture,  
     renderArea: gfx.Rect = null )

/**
 * 用一种颜色清除纹理
 */

clear(destination: IRenderTexture,
      color: IVec4Like,
      renderArea: gfx.Rect = null)

/**
 * 翻转纹理
 */

flip(source: RenderTexture,
     destination: IRenderTexture,
     flipX: boolean,
     flipY: boolean,
     renderArea: gfx.Rect = null)

整个后处理渲染框架都是基于以上这几个接口,那么我们继续完善流程,以方便我们直接使用或者扩展更多渲染效果:

其中:

  • PP_Graphics 包含上述各种基础接口
  • PostProcessingMgr 管理 PostProcessing 以及后处理用到的 RenderTexture
  • PostProcessing 绑定在相机上,用于接管相机的渲染结果,并用 RT 执行各种效果,并最终输入到屏幕上
  • IPPEffect 是最终的效果处理模块,可以按自己的需求进行排列组合

使用简介

基于以上流程,框架中包含了 10 多种基础效果,使用起来也比较简单,这里以窗口背景模糊为例:

1. 添加 Layer

先添加一个 UI 层,用于分离不同相机渲染的对象, 这里我添加了一个 UI_2D_1 的层级:

2. 创建两个 Canvas

我们需要创建两个 Canvas,并将两个 Canvas 中的节点放于不同的 layer 中:

背景放在 UI_2D_1 层中,用于模糊:

弹窗放入 UI_2D 层中:

3. 修改相机的可见层级

Canvas 下的相机只用渲染所在 Canvas 中的层级即可。

这里注意下,一般一个流程(2d、3d)的开始只需清屏方式为 DEPTH_ONLY。但是前面如果没有任何相机渲染,这时就需要 SOLID_COLOR 咯;这里相机 1 可见设置为 SolidColor, 相机 2 则为DepthOnly:

4. 对相机添加后处理处理

这里只用对背景模糊,即只用处理 camera1 渲染内容,在 camera 上添加组件。

PostProcessing 用于开启后处理 PPE_Blur 用于模糊:

启动预览,可以看到模糊功能就加好啦。

自定义后效

后效的扩展也很方便,只需要如下几个资源、代码:

  • 一个后处理 shader
  • 一个绑定 shader 的材质球
  • 一个后处理扩展 ts 文件,用于给材质球传参

以下用一个屏幕变灰的效果做为例子:

1. 添加一个后处理 shader

precision highp float;  
#include <pp-shared-ubos>

in vec2 v_uv;
uniform sampler2D mainTexture;
#pragma define intensity matParams.x

const vec3 weight = vec3(0.2126, 0.7152, 0.0722);
vec3 Grayscale(in vec3 o, float value){
float lumin = dot(o, weight);
vec3 final = mix(o, vec3(lumin), value);
return final;
}

vec4 frag() {
vec4 pixel = texture(mainTexture, v_uv);
pixel.rgb = Grayscale(pixel.rgb, intensity);
return pixel;
}

2. 添加并编写脚本

编写对应的后处理脚本文件,用于控制材质球参数或者对图形进行多次处理,并暴露相关属性。

在框架中,后处理组件只需要继承 PPE_Base 类即可,一下是图像变灰的后处理组件的实现,找到相关材质球,并在 apply 函数中设置材质的属性即可:

**
 * 灰度
 */
@ccclass('PPE_Grayscale')
export class PPE_Grayscale extends PPE_Base {
    @property({ range: [0, 1], slide: true, step: 0.01, override: true })
    intensity: number = 1;

    get materialPath() {
        return "materials/
pp-grayscale";
    }

    public apply(source: RenderTexture, destination: IRenderTexture): void {
        let pass = this.material.passes[0];
        PP_Graphics.setUniform(pass, "
intensity", this.intensity);
        PP_Graphics.blitP(source, destination, pass);
    }
}

3. 创建材质

使用对应的 Shader 创建材质球,放入统一的材质文件夹下,方便后处理框架加载。也可以在编辑器中将材质球拖拽到模块的材质槽中。

4. 调参、预览

在组件中调整好参数后,启动预览,即可获得自己想要的效果。

意外的惊喜(2D 光照效果)

相信不久之前,有人已经看过这张动图了。这是用 Cocos Creator 创建的一个 2D 灯光效果,虽然不是正儿八经的光照系统,但是对于不少项目已经完全够用了,毕竟光嘛,就是图片的局部提亮。

其实早在论坛中就有不少人需要增加 2D 光照,大家给的方案也基本就是后处理中加光照点位。技术不复杂,但是点位的数量或多或少是有限制的,而且调试起来也比较麻烦。

当我将 2D 后处理框架做出来后的一天,我忽然有想到这个问题,既然我的 mask 已经实现,那不就一个妥妥的一个灯光效果嘛,只是数量太少,只支持一个。

那如果我一张图来做为 mask 的遮罩呢?是不是灯光数量就无限了?这和延迟渲染是一个逻辑呀!基于这个想法,当晚就进行了一波测试,事实上这个技术方案更简单,不会儿就验证成功(当然中间有颜色叠加的坑,需要一顿调试),现在流程就变成了:

现在我们就得到一个 Cocos Creator 的 2D 光照系统,我找了张图测试了一番。

嘿,效果还不错,支持无限灯光数量不说,连粒子都可以作为光源,还支持各种异型灯光效果:

  • 一个相机渲染背景

  • 一个相机只渲染灯光,并绑定上后处理器,添加 2D 灯光后处理模块

  • 运行就可以看到效果

结语

大家好,我是 wing,坐标成都,一个 35+ 的老年人。曾经游走于各种引擎之间,目前主用 Cocos。

平时主要工作是解决公司各个项目中的技术难题,也会在业余时间研究和储备各类技术方案,让公司的 Cocos 框架更通用。

这个项目本来也是无心插柳,一开始只是为了解决自己项目中的一个小问题。@二喵觉得将这个做成一个通用的解决方案,能让我更了解引擎的渲染流程,我就试试做了看。

没想到做了以后发现后处理是一个如此好玩的事情,可以让画面朝自己想要的效果表现,只需要控制各种参数即可。

最终灯光系统也是意外之喜,我获得了一次技能提升,也为社区做了少许贡献,也让需要此功能来表现游戏氛围的开发者和项目有了一条捷径。

感谢大家的阅读,希望能够给有需要的开发者们带来一些帮助!

听懂,掌声!

本文分享自微信公众号 - COCOS(CocosEngine)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部