自定义后处理

在后处理系统中,特效(Effect) 负责数据层的维护,管线(Pass)负责渲染逻辑的编写,在管线中调用 getBlendEffect 方法可以拿到经过 全局/局部 混合后的最终后处理数据。

引擎内置了 PostProcessUberPass,搭配 BloomEffectTonemappingEffect 的数据使用,如果想要自定义后处理特效,我们需要新建一个 Pass,然后根据是否需要融合数据来创建 Effect。

一个 Demo

这里就简单地实现一个灰度图的后处理效果吧~

left image
Uber Pass
right image
Uber Pass + Custom Pass

1. 添加脚本

我们先创建一个脚本,接下去我们要在这个脚本文件里面编写我们的自定义后处理 Shader 和 Pass。

我们参考添加后处理方式添加全局或者局部后处理组件,并且将脚本挂到这个实体上面:

2. Shader 编写

算法没有特殊的, 需要注意 renderer_BlitTexture 这个内置变量就是上一个 Pass 的后处理渲染结果,在此处就是 Bloom 和 Tonemapping 后的结果,我们针对这个结果进行了灰度显示。

CustomPostProcessPass.ts
const customShader = Shader.create(
  "Gray Scale Shader",
  `
  attribute vec4 POSITION_UV;
  varying vec2 v_uv;
 
  void main() {	
	  gl_Position = vec4(POSITION_UV.xy, 0.0, 1.0);	
	  v_uv = POSITION_UV.zw;
  }
  `,
  `
  varying vec2 v_uv;
  uniform sampler2D renderer_BlitTexture;
 
  void main(){
    vec4 color = texture2D(renderer_BlitTexture, v_uv);
    float grayScale = 0.299 * color.r + 0.587 * color.g + 0.114 * color.b;
    gl_FragColor = vec4(vec3(grayScale), 1.0);
  }
  `
);

3. 新建 Pass

我们新建一个 Pass,在 onRender 钩子 里面直接 Blitter 到屏幕,然后将这个 Pass 添加到引擎中。

CustomPostProcessPass.ts
class CustomPass extends PostProcessPass {
  private _blitMaterial: Material;
 
  constructor(engine: Engine) {
    super(engine);
    this._blitMaterial = new Material(this.engine, customShader);
 
    // 我们这里关掉深度测试和深度写入,保证每次 Blit 都能覆盖到屏幕上。
    const depthState = this._blitMaterial.renderState.depthState;
    depthState.enabled = false;
    depthState.writeEnabled = false;
  }
 
  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,我们也可以手动修改管线的执行顺序:

CustomPostProcessPass.ts
customPass.event = PostProcessPassEvent.BeforeUber;

Pass 是否生效默认是根据 Pass 的 isActive 来决定的,我们也可以修改生效逻辑,比如强度是否大于 0 :

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,专门用来融合强度,需要融合的数据也很简单,引擎已经封装了一系列后处理参数,如浮点类型参数

CustomPostProcessPass.ts
class CustomEffect extends PostProcessEffect {
  intensity = new PostProcessEffectFloatParameter(0.8);
}
 
// 将这个 effect 添加到后处理组件中,postProcess 可以是单独新建的,
// 也可以是跟 Bloom 等 effect 同一个,具体看融合的需求~
postProcess.addEffect(CustomEffect);

定义好数据后,需要在自定义 Pass 的 onRender 钩子中,改成获取融合数据:

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 中消费这个数据即可:

CustomPostProcessPass.ts
const customShader = Shader.create(
  "Gray Scale Shader",
  `
   ......
  `,
  `
  ......
  uniform float u_intensity;
 
  void main(){
    ......
    gl_FragColor = vec4(mix(color.rgb, vec3(grayScale), u_intensity), 1.0);
  }
  `
);

如果你的后处理组件是局部模式,我们还可以通过 Blend Distance 来设置相机靠近碰撞体多少距离时开始混合:

这篇文档对您有帮助吗?