diff --git a/src/mol-canvas3d/canvas3d.ts b/src/mol-canvas3d/canvas3d.ts
index 3a9bcfc99deafbe50c2767e94012db7fd1292e89..83bd67d0a4813e6a73592e7100d1f9df02efb4a6 100644
--- a/src/mol-canvas3d/canvas3d.ts
+++ b/src/mol-canvas3d/canvas3d.ts
@@ -30,7 +30,7 @@ import { SetUtils } from 'mol-util/set';
 import { Canvas3dInteractionHelper } from './helper/interaction-events';
 import { createTexture } from 'mol-gl/webgl/texture';
 import { ValueCell } from 'mol-util';
-import { getPostprocessingRenderable, PostprocessingParams, setPostprocessingProps } from './helper/postprocessing';
+import { PostprocessingParams, PostprocessingPass } from './helper/postprocessing';
 import { JitterVectors, getComposeRenderable } from './helper/multi-sample';
 import { GLRenderingContext } from 'mol-gl/webgl/compat';
 import { PixelData } from 'mol-util/image';
@@ -144,8 +144,7 @@ namespace Canvas3D {
             depthTexture.attachFramebuffer(drawTarget.framebuffer, 'depth')
         }
 
-        const postprocessingTarget = createRenderTarget(webgl, width, height)
-        const postprocessing = getPostprocessingRenderable(webgl, drawTarget.texture, depthTexture, !!depthTarget, p.postprocessing)
+        const postprocessing = new PostprocessingPass(webgl, drawTarget.texture, depthTexture, !!depthTarget, p.postprocessing)
 
         const composeTarget = createRenderTarget(webgl, width, height)
         const holdTarget = createRenderTarget(webgl, width, height)
@@ -232,7 +231,7 @@ namespace Canvas3D {
             }
         }
 
-        function renderDraw(postprocessingEnabled: boolean) {
+        function renderDraw() {
             renderer.setViewport(0, 0, width, height)
             renderer.render(scene, 'draw', true)
             if (debugHelper.isEnabled) {
@@ -240,7 +239,7 @@ namespace Canvas3D {
                 renderer.render(debugHelper.scene, 'draw', false)
             }
 
-            if (postprocessingEnabled && depthTarget) {
+            if (postprocessing.enabled && depthTarget) {
                 depthTarget.bind()
                 renderer.render(scene, 'depth', true)
                 if (debugHelper.isEnabled) {
@@ -250,16 +249,7 @@ namespace Canvas3D {
             }
         }
 
-        function renderPostprocessing() {
-            gl.viewport(0, 0, width, height)
-            state.disable(gl.SCISSOR_TEST)
-            state.disable(gl.BLEND)
-            state.disable(gl.DEPTH_TEST)
-            state.depthMask(false)
-            postprocessing.render()
-        }
-
-        function renderTemporalMultiSample(postprocessingEnabled: boolean) {
+        function renderTemporalMultiSample() {
             // based on the Multisample Anti-Aliasing Render Pass
             // contributed to three.js by bhouston / http://clara.io/
             //
@@ -277,13 +267,10 @@ namespace Canvas3D {
 
             if (i === 0) {
                 drawTarget.bind()
-                renderDraw(postprocessingEnabled)
-                if (postprocessingEnabled) {
-                    postprocessingTarget.bind()
-                    renderPostprocessing()
-                }
+                renderDraw()
+                if (postprocessing.enabled) postprocessing.render(false)
                 ValueCell.update(compose.values.uWeight, 1.0)
-                ValueCell.update(compose.values.tColor, postprocessingEnabled ? postprocessingTarget.texture : drawTarget.texture)
+                ValueCell.update(compose.values.tColor, postprocessing.enabled ? postprocessing.target.texture : drawTarget.texture)
                 compose.update()
 
                 holdTarget.bind()
@@ -294,7 +281,7 @@ namespace Canvas3D {
             const sampleWeight = 1.0 / offsetList.length
 
             camera.viewOffset.enabled = true
-            ValueCell.update(compose.values.tColor, postprocessingEnabled ? postprocessingTarget.texture : drawTarget.texture)
+            ValueCell.update(compose.values.tColor, postprocessing.enabled ? postprocessing.target.texture : drawTarget.texture)
             ValueCell.update(compose.values.uWeight, sampleWeight)
             compose.update()
 
@@ -310,11 +297,8 @@ namespace Canvas3D {
 
                 // render scene and optionally postprocess
                 drawTarget.bind()
-                renderDraw(postprocessingEnabled)
-                if (postprocessingEnabled) {
-                    postprocessingTarget.bind()
-                    renderPostprocessing()
-                }
+                renderDraw()
+                if (postprocessing.enabled) postprocessing.render(false)
 
                 // compose rendered scene with compose target
                 composeTarget.bind()
@@ -360,7 +344,7 @@ namespace Canvas3D {
             if (multiSampleIndex >= offsetList.length) multiSampleIndex = -1
         }
 
-        function renderMultiSample(postprocessingEnabled: boolean) {
+        function renderMultiSample() {
             // based on the Multisample Anti-Aliasing Render Pass
             // contributed to three.js by bhouston / http://clara.io/
             //
@@ -372,7 +356,7 @@ namespace Canvas3D {
             const roundingRange = 1 / 32
 
             camera.viewOffset.enabled = true
-            ValueCell.update(compose.values.tColor, postprocessingEnabled ? postprocessingTarget.texture : drawTarget.texture)
+            ValueCell.update(compose.values.tColor, postprocessing.enabled ? postprocessing.target.texture : drawTarget.texture)
             compose.update()
 
             const { width, height } = drawTarget
@@ -393,11 +377,8 @@ namespace Canvas3D {
 
                 // render scene and optionally postprocess
                 drawTarget.bind()
-                renderDraw(postprocessingEnabled)
-                if (postprocessingEnabled) {
-                    postprocessingTarget.bind()
-                    renderPostprocessing()
-                }
+                renderDraw()
+                if (postprocessing.enabled) postprocessing.render(false)
 
                 // compose rendered scene with compose target
                 composeTarget.bind()
@@ -449,7 +430,6 @@ namespace Canvas3D {
             } else {
                 multiSample = false
             }
-            const postprocessingEnabled = p.postprocessing.occlusionEnable || p.postprocessing.outlineEnable
 
             if (force || cameraChanged || multiSample) {
                 switch (variant) {
@@ -466,18 +446,15 @@ namespace Canvas3D {
                         renderer.setViewport(0, 0, width, height);
                         if (multiSample) {
                             if (p.multiSample === 'temporal') {
-                                renderTemporalMultiSample(postprocessingEnabled)
+                                renderTemporalMultiSample()
                             } else {
-                                renderMultiSample(postprocessingEnabled)
+                                renderMultiSample()
                             }
                         } else {
-                            if (postprocessingEnabled) drawTarget.bind()
+                            if (postprocessing.enabled) drawTarget.bind()
                             else webgl.unbindFramebuffer()
-                            renderDraw(postprocessingEnabled)
-                            if (postprocessingEnabled) {
-                                webgl.unbindFramebuffer()
-                                renderPostprocessing()
-                            }
+                            renderDraw()
+                            if (postprocessing.enabled) postprocessing.render(true)
                         }
                         pickDirty = true
                         break;
@@ -662,9 +639,7 @@ namespace Canvas3D {
                 if (props.multiSample !== undefined) p.multiSample = props.multiSample
                 if (props.sampleLevel !== undefined) p.sampleLevel = props.sampleLevel
 
-                if (props.postprocessing) {
-                    setPostprocessingProps(props.postprocessing, postprocessing, p.postprocessing, webgl)
-                }
+                if (props.postprocessing) postprocessing.setProps(props.postprocessing)
 
                 if (props.renderer) renderer.setProps(props.renderer)
                 if (props.trackball) controls.setProps(props.trackball)
@@ -682,7 +657,7 @@ namespace Canvas3D {
                     multiSample: p.multiSample,
                     sampleLevel: p.sampleLevel,
 
-                    postprocessing: { ...p.postprocessing },
+                    postprocessing: { ...postprocessing.props },
                     renderer: { ...renderer.props },
                     trackball: { ...controls.props },
                     debug: { ...debugHelper.props }
@@ -717,7 +692,7 @@ namespace Canvas3D {
             Viewport.set(controls.viewport, 0, 0, width, height)
 
             drawTarget.setSize(width, height)
-            postprocessingTarget.setSize(width, height)
+            postprocessing.setSize(width, height)
             composeTarget.setSize(width, height)
             holdTarget.setSize(width, height)
             if (depthTarget) {
@@ -725,7 +700,6 @@ namespace Canvas3D {
             } else {
                 depthTexture.define(width, height)
             }
-            ValueCell.update(postprocessing.values.uTexSize, Vec2.set(postprocessing.values.uTexSize.ref.value, width, height))
             ValueCell.update(compose.values.uTexSize, Vec2.set(compose.values.uTexSize.ref.value, width, height))
 
             pickScale = pickBaseScale / webgl.pixelRatio
diff --git a/src/mol-canvas3d/helper/postprocessing.ts b/src/mol-canvas3d/helper/postprocessing.ts
index 3f56ebad935764d9b656f3447cff702d0dcc5163..39fc3965988e334f2e17d7716e4303202e14220f 100644
--- a/src/mol-canvas3d/helper/postprocessing.ts
+++ b/src/mol-canvas3d/helper/postprocessing.ts
@@ -14,6 +14,7 @@ import { createComputeRenderItem } from 'mol-gl/webgl/render-item';
 import { createComputeRenderable, ComputeRenderable } from 'mol-gl/renderable';
 import { Vec2 } from 'mol-math/linear-algebra';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
+import { createRenderTarget, RenderTarget } from 'mol-gl/webgl/render-target';
 
 const PostprocessingSchema = {
     ...QuadSchema,
@@ -77,36 +78,73 @@ export function getPostprocessingRenderable(ctx: WebGLContext, colorTexture: Tex
     return createComputeRenderable(renderItem, values)
 }
 
-export function setPostprocessingProps(props: Partial<PostprocessingProps>, postprocessing: PostprocessingRenderable, currentProps: PostprocessingProps, webgl: WebGLContext) {
-    if (props.occlusionEnable !== undefined) {
-        currentProps.occlusionEnable = props.occlusionEnable
-        ValueCell.update(postprocessing.values.dOcclusionEnable, props.occlusionEnable)
-    }
-    if (props.occlusionKernelSize !== undefined) {
-        currentProps.occlusionKernelSize = props.occlusionKernelSize
-        ValueCell.update(postprocessing.values.dOcclusionKernelSize, props.occlusionKernelSize)
-    }
-    if (props.occlusionBias !== undefined) {
-        currentProps.occlusionBias = props.occlusionBias
-        ValueCell.update(postprocessing.values.uOcclusionBias, props.occlusionBias)
-    }
-    if (props.occlusionRadius !== undefined) {
-        currentProps.occlusionRadius = props.occlusionRadius
-        ValueCell.update(postprocessing.values.uOcclusionRadius, props.occlusionRadius)
+export class PostprocessingPass {
+    target: RenderTarget
+    props: PostprocessingProps
+    renderable: PostprocessingRenderable
+
+    constructor(private webgl: WebGLContext, colorTexture: Texture, depthTexture: Texture, packedDepth: boolean, props: Partial<PostprocessingProps>) {
+        const { gl } = webgl
+        this.target = createRenderTarget(webgl, gl.drawingBufferWidth, gl.drawingBufferHeight)
+        this.props = { ...PD.getDefaultValues(PostprocessingParams), ...props }
+        this.renderable = getPostprocessingRenderable(webgl, colorTexture, depthTexture, packedDepth, this.props)
     }
 
-    if (props.outlineEnable !== undefined) {
-        currentProps.outlineEnable = props.outlineEnable
-        ValueCell.update(postprocessing.values.dOutlineEnable, props.outlineEnable)
+    get enabled() {
+        return this.props.occlusionEnable || this.props.outlineEnable
     }
-    if (props.outlineScale !== undefined) {
-        currentProps.outlineScale = props.outlineScale
-        ValueCell.update(postprocessing.values.uOutlineScale, props.outlineScale * webgl.pixelRatio)
+
+    setSize(width: number, height: number) {
+        this.target.setSize(width, height)
+        ValueCell.update(this.renderable.values.uTexSize, Vec2.set(this.renderable.values.uTexSize.ref.value, width, height))
     }
-    if (props.outlineThreshold !== undefined) {
-        currentProps.outlineThreshold = props.outlineThreshold
-        ValueCell.update(postprocessing.values.uOutlineThreshold, props.outlineThreshold)
+
+    setProps(props: Partial<PostprocessingProps>) {
+        if (props.occlusionEnable !== undefined) {
+            this.props.occlusionEnable = props.occlusionEnable
+            ValueCell.update(this.renderable.values.dOcclusionEnable, props.occlusionEnable)
+        }
+        if (props.occlusionKernelSize !== undefined) {
+            this.props.occlusionKernelSize = props.occlusionKernelSize
+            ValueCell.update(this.renderable.values.dOcclusionKernelSize, props.occlusionKernelSize)
+        }
+        if (props.occlusionBias !== undefined) {
+            this.props.occlusionBias = props.occlusionBias
+            ValueCell.update(this.renderable.values.uOcclusionBias, props.occlusionBias)
+        }
+        if (props.occlusionRadius !== undefined) {
+            this.props.occlusionRadius = props.occlusionRadius
+            ValueCell.update(this.renderable.values.uOcclusionRadius, props.occlusionRadius)
+        }
+
+        if (props.outlineEnable !== undefined) {
+            this.props.outlineEnable = props.outlineEnable
+            ValueCell.update(this.renderable.values.dOutlineEnable, props.outlineEnable)
+        }
+        if (props.outlineScale !== undefined) {
+            this.props.outlineScale = props.outlineScale
+            ValueCell.update(this.renderable.values.uOutlineScale, props.outlineScale * this.webgl.pixelRatio)
+        }
+        if (props.outlineThreshold !== undefined) {
+            this.props.outlineThreshold = props.outlineThreshold
+            ValueCell.update(this.renderable.values.uOutlineThreshold, props.outlineThreshold)
+        }
+
+        this.renderable.update()
     }
 
-    postprocessing.update()
+    render(toDrawingBuffer: boolean) {
+        const { gl, state } = this.webgl
+        if (toDrawingBuffer) {
+            this.webgl.unbindFramebuffer()
+            gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight)
+        } else {
+            this.target.bind()
+        }
+        state.disable(gl.SCISSOR_TEST)
+        state.disable(gl.BLEND)
+        state.disable(gl.DEPTH_TEST)
+        state.depthMask(false)
+        this.renderable.render()
+    }
 }
\ No newline at end of file