--- order: 2 title: 自定义后处理 --- 在后处理系统中,特效([Effect](/apis/core/#PostProcessEffect)) 负责数据层的维护,管线([Pass](/apis/core/#PostProcessPass))负责渲染逻辑的编写,在管线中调用 [getBlendEffect](/apis/core/#PostProcessManager-getBlendEffect) 方法可以拿到经过 **全局/局部** 混合后的最终后处理数据。 ```mermaid graph TD; subgraph Pass A1[Custom Pass ...] A2[Uber Pass] A3[Custom Pass ...] end subgraph Effect B1[Bloom Effect] B2[Tonemapping Effect] B3[Custom Effect] end A1 --> A2 --> A3 B1 --> B2 --> B3 A1 ---|Blend| B1 ``` 引擎内置了 [PostProcessUberPass](/apis/core/#PostProcessUberPass),搭配 [BloomEffect](/apis/core/#BloomEffect) 和 [TonemappingEffect](/apis/core/#TonemappingEffect) 的数据使用,如果想要自定义后处理特效,我们需要新建一个 Pass,然后根据是否需要融合数据来创建 Effect。 ## 一个 Demo 这里就简单地实现一个灰度图的后处理效果吧~ ### 1. 添加脚本 我们先创建一个脚本,接下去我们要在这个脚本文件里面编写我们的自定义后处理 Shader 和 Pass。 我们参考[添加后处理方式](/docs/graphics/postProcess/postProcess/#1添加后处理组件)添加全局或者局部后处理组件,并且将脚本挂到这个实体上面: ### 2. Shader 编写 算法没有特殊的, 需要注意 `renderer_BlitTexture` 这个内置变量就是上一个 Pass 的后处理渲染结果,在此处就是 Bloom 和 Tonemapping 后的结果,我们针对这个结果进行了灰度显示。 ```glsl showLineNumbers filename="GrayScale.shader" Shader "Custom/GrayScale" { SubShader "Default" { Pass "0" { DepthState = { Enabled = false; WriteEnabled = false; } VertexShader = vert; FragmentShader = frag; struct Attributes { vec4 POSITION_UV; }; struct Varyings { vec2 v_uv; }; sampler2D renderer_BlitTexture; Varyings vert(Attributes attr) { Varyings v; gl_Position = vec4(attr.POSITION_UV.xy, 0.0, 1.0); v.v_uv = attr.POSITION_UV.zw; return v; } void frag(Varyings v) { vec4 color = texture2D(renderer_BlitTexture, v.v_uv); float grayScale = 0.299 * color.r + 0.587 * color.g + 0.114 * color.b; gl_FragColor = vec4(vec3(grayScale), 1.0); } } } } ``` 编写好上面的 Galacean Shader 源码后(如在编辑器中创建 `.shader` 文件),在代码中注册: ```ts filename="CustomPostProcessPass.ts" Shader.create(grayScaleShaderSource); // 传入上面的 Galacean Shader 字符串 const customShader = Shader.find("Custom/GrayScale"); ``` ### 3. 新建 Pass 我们新建一个 Pass,在 [onRender 钩子](/apis/core/#PostProcessUberPass-onRender) 里面直接 [Blitter](/apis/core/#Blitter) 到屏幕,然后将这个 Pass 添加到引擎中。 ```ts showLineNumbers {6,10} filename="CustomPostProcessPass.ts" class CustomPass extends PostProcessPass { private _blitMaterial: Material; constructor(engine: Engine) { super(engine); this._blitMaterial = new Material(this.engine, Shader.find("Custom/GrayScale")); } onRender(_, srcTexture: Texture2D, dst: RenderTarget): void { Blitter.blitTexture(this.engine, srcTexture, dst, undefined, undefined, this._blitMaterial, 0); } } const customPass = new CustomPass(engine); engine.addPostProcessPass(customPass); ``` Pass 的执行顺序默认在 Uber Pass 的后面执行,即 [`PostProcessPassEvent.AfterUber`](/apis/core/#PostProcessPassEvent-AfterUber),我们也可以手动修改管线的执行顺序: ```ts filename="CustomPostProcessPass.ts" customPass.event = PostProcessPassEvent.BeforeUber; ``` Pass 是否生效默认是根据 Pass 的 [isActive](/apis/core/#PostProcessUberPass-isActive) 来决定的,我们也可以修改生效逻辑,比如强度是否大于 0 : ```ts showLineNumbers {2-9} filename="CustomPostProcessPass.ts" class CustomPass extends PostProcessPass { override isValid(postProcessManager: PostProcessManager): boolean { if (!this.isActive) { return false; } const customEffectBlend = postProcessManager.getBlendEffect(CustomEffect); return customEffectBlend?.intensity > 0; } } ``` ### 4. 融合数据 上述 2、3 步骤已经能够自定义后处理效果,这里再来一个进阶版的融合数据。 拿 `intensity` 举例,我们定义一个 `CustomEffect`,专门用来融合强度,需要融合的数据也很简单,引擎已经封装了一系列后处理参数,如[浮点类型参数](/apis/core/#PostProcessEffectFloatParameter)。 ```ts showLineNumbers {2,7} filename="CustomPostProcessPass.ts" class CustomEffect extends PostProcessEffect { intensity = new PostProcessEffectFloatParameter(0.8); } // 将这个 effect 添加到后处理组件中,postProcess 可以是单独新建的, // 也可以是跟 Bloom 等 effect 同一个,具体看融合的需求~ postProcess.addEffect(CustomEffect); ``` 定义好数据后,需要在自定义 Pass 的 `onRender` 钩子中,改成获取融合数据: ```ts showLineNumbers {3-6} filename="CustomPostProcessPass.ts" class CustomPass extends PostProcessPass { onRender(camera: Camera, srcTexture: Texture2D, dst: RenderTarget): void { const postProcessManager = camera.scene.postProcessManager; const customEffectBlend = postProcessManager.getBlendEffect(CustomEffect); if (customEffectBlend) { this._blitMaterial.shaderData.setFloat("u_intensity", customEffectBlend.intensity.value); } Blitter.blitTexture(this.engine, srcTexture, dst, undefined, undefined, this._blitMaterial, 0); } } ``` 可以看到,我们在自定义管线的 `onRender` 钩子中,不断设置融合后的强度,然后在 shader 中消费这个数据即可。只需在 Pass 中添加 `u_intensity` uniform 并修改片元着色器: ```glsl showLineNumbers {7,12} filename="GrayScale.shader" // ...同上,在 Pass 中做如下修改: float u_intensity; void frag(Varyings v) { vec4 color = texture2D(renderer_BlitTexture, v.v_uv); float grayScale = 0.299 * color.r + 0.587 * color.g + 0.114 * color.b; gl_FragColor = vec4(mix(color.rgb, vec3(grayScale), u_intensity), 1.0); } ``` 如果你的后处理组件是局部模式,我们还可以通过 [Blend Distance](/apis/core/#PostProcess-blendDistance) 来设置相机靠近碰撞体多少距离时开始混合: