In the post-processing system, the effect (Effect) is responsible for maintaining the data layer, and the pipeline (Pass) is responsible for writing the rendering logic. In the pipeline, calling the getBlendEffect method can get the final post-processing data after global/local mixing.
The engine has built-in PostProcessUberPass, which is used with BloomEffect and TonemappingEffect data. If you want to customize post-processing effects, we need to create a new Pass and then create an Effect based on whether the data needs to be fused.
Here we will simply implement a grayscale image post-processing effect~
Let's create a script first. Next, we will write our custom post-processing Shader and Pass in this script file.
We refer to Adding post-processing methods to add global or local post-processing components, and hang the script on this entity:
The algorithm is not special, but you need to pay attention to renderer_BlitTexture
This built-in variable is the post-processing rendering result of the previous Pass. Here it is the result after Bloom and Tonemapping. We display this result in grayscale.
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);
}
`
);
We create a new Pass, directly Blitter to the screen in the onRender hook, and then add this Pass to the engine.
class CustomPass extends PostProcessPass {
private _blitMaterial: Material;
constructor(engine: Engine) {
super(engine);
this._blitMaterial = new Material(this.engine, customShader);
}
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);
The execution order of Pass is executed after Uber Pass by default, that is PostProcessPassEvent.AfterUber
, we can also manually modify the execution order of the pipeline:
customPass.event = PostProcessPassEvent.BeforeUber;
Whether the Pass is effective is determined by the Pass's isActive by default. We can also modify the effectiveness logic, such as whether the intensity is greater than 0:
class CustomPass extends PostProcessPass {
override isValid(postProcessManager: PostProcessManager): boolean {
if (!this.isActive) {
return false;
}
const customEffectBlend = postProcessManager.getBlendEffect(CustomEffect);
return customEffectBlend?.intensity > 0;
}
}
The above steps 2 and 3 can already customize the post-processing effect. Here is an advanced version of Blend data.
Take intensity
as an example. We define a CustomEffect
, which is specifically used to fuse intensity. The data to be fused is also very simple. The engine has encapsulated a series of post-processing parameters, such as floating point type parameters.
class CustomEffect extends PostProcessEffect {
intensity = new PostProcessEffectFloatParameter(0.8);
}
// Add this effect to the post-processing component. postProcess can be created separately,
// or it can be the same as Bloom and other effects. It depends on the Blend requirements~
postProcess.addEffect(CustomEffect);
After defining the data, you need to change the onRender
hook of the custom Pass to get the Blend data:
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);
}
}
As you can see, we continuously set the fused intensity in the onRender
hook of the custom pipeline, and then consume this data in the shader:
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);
}
`
);
If your post-processing component is in local mode, we can also use Blend Distance to set the distance at which the camera is close to the collision body to start blending: