Skip to content

Custom Passes

Raoul v. R edited this page Feb 24, 2020 · 24 revisions

Introduction

At a closer look, passes can be divided into four groups. The first group consists of passes that render normal scenes like the RenderPass and MaskPass. The second type doesn't render anything, but performs supporting operations. For example, the ClearMaskPass belongs to that group. Passes that render special textures make up the third group. GPGPU passes are a good example for this group. The fourth and most prominent group contains the fullscreen effect passes. If you want to make a pass that belongs to the last group, you should consider creating an Effect instead.

There are two options for creating custom passes. You can either rely on the general-purpose ShaderPass or extend Pass.

ShaderPass

The ShaderPass expects an instance of ShaderMaterial as its first argument. The second argument specifies the name of the texture sampler uniform of the shader you provide. This name defaults to "inputBuffer" and the ShaderPass binds the inputBuffer to this uniform.

In order to render a simple ShaderMaterial, you have to pass your shader object (uniforms, defines, fragment and vertex shader code) to ShaderMaterial and then pass that material instance to ShaderPass. Depending on the material you use, you may have to adjust the name of the input texture.

ShaderPass Code Example

import { ShaderMaterial, Uniform } from "three";
import { ShaderPass } from "postprocessing";

const myShaderMaterial = new ShaderMaterial({

	defines: { LABEL: "value" },
	uniforms: { tDiffuse: new Uniform(null) },
	vertexShader: "...",
	fragmentShader: "..."

});

const myShaderPass = new ShaderPass(myShaderMaterial, "tDiffuse");

An example that uses the Kaleidoscope Shader from the three.js examples together with the ShaderPass can be found here.

Extending Pass

More complex passes sometimes require additional programming. By extending the Pass class you can decide what happens with your pass during resizing, initialization and rendering.

The minimum requirement to create a custom pass is to override the render method. You may also need to set the needsSwap flag to false if your pass never renders to the outputBuffer. If you're creating a fullscreen effect, you'll need to assign a fullscreen Material by using the setFullscreenMaterial(Material) method. You may also create custom render targets in your pass.

Inside the render method you have access to an inputBuffer as well as an outputBuffer. Reading from and writing to the same render target should be avoided. Therefore, two seperate yet identical buffers are used. The EffectComposer expects your pass to render its result to the outputBuffer. After your pass has finished rendering, the outputBuffer will be swapped with the inputBuffer so that the next pass can find the result in the inputBuffer. If your pass doesn't write to the outputBuffer, you need to set needsSwap to false. Otherwise, the image in the input buffer will be lost.

Note that Passes don't have to use the buffers that are provided in the render method. Writing self-contained render-to-texture passes is also a feasible option.

Custom Pass Code Example

shader.vert
varying vec2 vUv;

void main() {

	vUv = position.xy * 0.5 + 0.5;
	gl_Position = vec4(position.xy, 1.0, 1.0);

}
shader.frag
uniform sampler2D inputBuffer;
uniform vec3 weights;

varying vec2 vUv;

void main() {

	vec4 texel = texture2D(inputBuffer, vUv);
	gl_FragColor = vec4(texel.rgb * weights, texel.a);

	// Support automatic output encoding.
	#include <encodings_fragment>

}
CustomMaterial.js
import { ShaderMaterial, Uniform, Vector3 } from "three";

// Using rollup-plugin-glsl to import shader files.
import fragmentShader from "./shader.frag";
import vertexShader from "./shader.vert";

export class CustomMaterial extends ShaderMaterial {

	constructor() {

		super({

			type: "CustomMaterial",

			uniforms: {
				inputBuffer: new Uniform(null),
				weights: new Uniform(new Vector3())
			},

			fragmentShader,
			vertexShader,

			toneMapped: false,
			depthWrite: false,
			depthTest: false

		});

	}

}
CustomPass.js
import { Pass } from "postprocessing";
import { CustomMaterial } from "./CustomMaterial.js";

export class CustomPass extends Pass {

	constructor() {

		super("CustomPass");

		this.setFullscreenMaterial(new CustomMaterial());

	}

	render(renderer, inputBuffer, outputBuffer, deltaTime, stencilTest) {

		const material = this.getFullscreenMaterial();
		material.uniforms.inputBuffer.value = inputBuffer.texture;

		renderer.setRenderTarget(this.renderToScreen ? null : outputBuffer);
		renderer.render(this.scene, this.camera);

	}

}
Clone this wiki locally