prev:
text: 'Plugin System'
link: './plugin-system'
next:
text: 'Screen Pass'
link: './screen-pass'
The Screen Pass is the final rendering stage in Threepipe that outputs the rendered scene to the screen or a render target. It provides multiple ways to customize the final image through custom shaders, material extensions, and shader snippets.
The Screen Pass renders the final scene by processing the diffuse and transparent render targets. It supports:
Check out the ScreenPass.glsl for the default fragment shader code used in the screen pass.
The simplest way to customize the screen pass is by providing a shader snippet as a string:
const viewer = new ThreeViewer({
canvas: document.getElementById('canvas'),
screenShader: `
// add a basic red tint
diffuseColor *= vec4(1.0, 0.0, 0.0, 1.0);
`
})
This snippet is inserted at the #glMarker position in the default screen shader and can modify the diffuseColor variable which contains the final pixel color.
Live Example: Basic Screen Shader
For more complex modifications, you can provide shader parameters and functions:
const viewer = new ThreeViewer({
canvas: document.getElementById('canvas'),
screenShader: {
pars: ` // this is added before the main function
uniform vec3 tintColor;
vec4 applyTint(vec4 color) {
return vec4(color.rgb * tintColor, color.a);
}
`,
main: ` // this is added inside the main function
diffuseColor = applyTint(diffuseColor);
`
}
})
// Add the uniform to the screen pass material
viewer.renderManager.screenPass.material.uniforms.tintColor = {
value: new Color(0, 0, 1) // blue tint
}
Live Example: Advanced Screen Shader
For complete control, you can provide a full shader material configuration:
const viewer = new ThreeViewer({
canvas: document.getElementById('canvas'),
tonemap: true,
screenShader: new ExtendedShaderMaterial({
...CopyShader,
// Custom fragment shader
fragmentShader: `
#include <packing>
varying vec2 vUv;
uniform vec3 tintColor;
void main() {
vec4 diffuseColor = tDiffuseTexelToLinear (texture2D(tDiffuse, vUv));
#glMarker
diffuseColor.rgb *= tintColor;
gl_FragColor = diffuseColor;
#include <colorspace_fragment>
}
`,
uniforms: {
tDiffuse: {value: null},
tTransparent: {value: null},
tintColor: {value: new Color(0, 1, 0)},
},
transparent: true,
blending: NoBlending,
side: FrontSide,
}, ['tDiffuse', 'tTransparent'])
})
Live Example: Custom Screen Shader Material
The #glMarker is a special placeholder in the screen shader that allows plugins and extensions to inject their own code. This enables:
When using custom screen shaders, include #glMarker to ensure compatibility with plugins:
void main() {
vec4 diffuseColor = tDiffuseTexelToLinear (texture2D(tDiffuse, vUv));
#glMarker // Plugin injection point
// Your custom modifications
diffuseColor.rgb *= tintColor;
gl_FragColor = diffuseColor;
}
Material extensions provide the most flexible way to modify the screen pass. They allow you to:
const extension = {
extraUniforms: {
tintColor: {value: new Color(0, 1, 1)} // cyan tint
},
parsFragmentSnippet: ` // added before main function
uniform vec3 tintColor;
vec4 applyTint(vec4 color) {
return vec4(color.rgb * tintColor, color.a);
}
`,
shaderExtender: (shader, material, renderer) => {
console.log('Patching shader')
shader.fragmentShader = shaderReplaceString(
shader.fragmentShader,
'#glMarker',
`diffuseColor = applyTint(diffuseColor);`,
{prepend: true} // prepend to existing #glMarker content
)
},
priority: 100, // execution order
isCompatible: (material) => material.isShaderMaterial,
computeCacheKey: (material) => 'tint-extension'
}
// Register the extension
viewer.renderManager.screenPass.material.registerMaterialExtensions([extension])
Live Example: Screen Pass Extension
For more complex effects that need UI configuration and serialization, you can create a custom screen pass extension plugin using AScreenPassExtensionPlugin. This base class provides automatic UI generation, serialization, and integration with the plugin system.
import {
AScreenPassExtensionPlugin,
Color,
glsl,
onChange,
serialize,
uiColor,
uiFolderContainer,
uiSlider,
uiToggle,
uniform,
} from 'threepipe'
@uiFolderContainer('Custom Tint Extension')
export class CustomScreenPassExtensionPlugin extends AScreenPassExtensionPlugin {
static readonly PluginType = 'CustomTint'
// Define uniforms that will be available in the shader
readonly extraUniforms = {
tintIntensity: {value: 1},
tintColor: {value: new Color(0xff0000)},
} as const
// Plugin properties with UI decorators
@onChange(CustomScreenPassExtensionPlugin.prototype.setDirty)
@uiToggle('Enable')
@serialize() enabled: boolean = true
@uiSlider('Intensity', [0.1, 4], 0.01)
@uniform({propKey: 'tintIntensity'}) // Links to extraUniforms
@serialize() intensity = 1
@uiColor('Color')
@uniform({propKey: 'tintColor'})
@serialize('tintColor') color = new Color(0xff0000)
/**
* Priority determines the order of extension application
* Lower values = applied later (after other extensions)
*/
priority = -50
/**
* Add shader code before the main function
* Use glsl`` template literal for syntax highlighting
*/
parsFragmentSnippet = () => {
if (this.isDisabled()) return ''
return glsl`
uniform float tintIntensity;
uniform vec3 tintColor;
vec4 ApplyTint(vec4 color) {
return vec4(color.rgb * tintColor * tintIntensity, color.a);
}
`
}
/**
* Shader code to inject at the #glMarker position
*/
protected _shaderPatch = 'diffuseColor = ApplyTint(diffuseColor);'
constructor(enabled = true) {
super()
this.enabled = enabled
}
}
// Register the plugin
const viewer = new ThreeViewer({
canvas: document.getElementById('canvas'),
plugins: [CustomScreenPassExtensionPlugin],
})
@serialize()@uniform() decorator links properties to shader uniforms@onChange() triggers updates when properties changeisDisabled() to conditionally apply effectsparsFragmentSnippet(): Add code before the main function_shaderPatch: Code to inject at #glMarker (can also be a function)isDisabled(): Check if the extension should be appliedsetDirty(): Mark the material for recompilationLive Example: Screen Pass Extension Plugin
Control background rendering with the clipBackground option:
// Enable background clipping
viewer.renderManager.screenPass.clipBackground = true
// Force background clipping (overrides the above which is also in the UI)
viewer.renderManager.screenPass.clipBackgroundForce = true
Configure the output color space for the final render:
import { SRGBColorSpace, LinearSRGBColorSpace } from 'threepipe'
viewer.renderManager.screenPass.outputColorSpace = SRGBColorSpace
When writing custom screen shaders, these variables are available:
diffuseColor: The final pixel color (vec4)tDiffuse: Main render target texture (sampler2D)vUv: UV coordinates (vec2)transparentColor: Transparent objects color (vec4)tTransparent: Transparent render target texture (sampler2D)::: details GBuffer Snippet
#ifdef HAS_GBUFFER
float depth = getDepth(vUv);
bool isBackground = depth > 0.99 && transparentColor.a < 0.001;
#endif
:::
// Use depth information for effects
diffuseColor.rgb = mix(diffuseColor.rgb, fogColor.rgb, depth);
Many built-in plugins extend the screen pass:
These plugins use the material extension system to inject their effects at the #glMarker position, allowing them to work together seamlessly.