diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5f8bdc0fa357e93c5f9c7283ded1fe9cd0ea1315..98f295751a98b78bc0802e26d02585d848156453 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,7 @@ Note that since we don't clearly distinguish between a public and private interf
 - Add ``UnitResonance`` property with info about delocalized triplets
 - Resolve marking in main renderer loop to improve overall performance
 - Use ``throttleTime`` instead of ``debounceTime`` in sequence viewer for better responsiveness
+- Reuse occlusion for secondary passes during multi-sampling
 
 ## [v3.2.0] - 2022-02-17
 
diff --git a/src/mol-canvas3d/passes/multi-sample.ts b/src/mol-canvas3d/passes/multi-sample.ts
index dba08fb53c10aa61c3d792bbb743ed2b7197747d..6f7ca4358d657622ad81bbd9186d28857622d6f4 100644
--- a/src/mol-canvas3d/passes/multi-sample.ts
+++ b/src/mol-canvas3d/passes/multi-sample.ts
@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -157,6 +157,14 @@ export class MultiSamplePass {
             ValueCell.update(compose.values.uWeight, sampleWeight);
 
             // render scene
+            if (i === 0) {
+                drawPass.postprocessing.setOcclusionOffset(0, 0);
+            } else {
+                drawPass.postprocessing.setOcclusionOffset(
+                    offset[0] / width,
+                    offset[1] / height
+                );
+            }
             drawPass.render(ctx, props, false);
 
             // compose rendered scene with compose target
@@ -175,6 +183,8 @@ export class MultiSamplePass {
             compose.render();
         }
 
+        drawPass.postprocessing.setOcclusionOffset(0, 0);
+
         ValueCell.update(compose.values.uWeight, 1.0);
         ValueCell.update(compose.values.tColor, composeTarget.texture);
         compose.update();
@@ -236,6 +246,14 @@ export class MultiSamplePass {
                 camera.update();
 
                 // render scene
+                if (sampleIndex === 0) {
+                    drawPass.postprocessing.setOcclusionOffset(0, 0);
+                } else {
+                    drawPass.postprocessing.setOcclusionOffset(
+                        offset[0] / width,
+                        offset[1] / height
+                    );
+                }
                 drawPass.render(ctx, props, false);
 
                 // compose rendered scene with compose target
@@ -258,6 +276,8 @@ export class MultiSamplePass {
             }
         }
 
+        drawPass.postprocessing.setOcclusionOffset(0, 0);
+
         this.bindOutputTarget(toDrawingBuffer);
         gl.viewport(x, y, width, height);
         gl.scissor(x, y, width, height);
@@ -291,23 +311,23 @@ const JitterVectors = [
         [0, 0]
     ],
     [
-        [4, 4], [-4, -4]
+        [0, 0], [-4, -4]
     ],
     [
-        [-2, -6], [6, -2], [-6, 2], [2, 6]
+        [0, 0], [6, -2], [-6, 2], [2, 6]
     ],
     [
-        [1, -3], [-1, 3], [5, 1], [-3, -5],
+        [0, 0], [-1, 3], [5, 1], [-3, -5],
         [-5, 5], [-7, -1], [3, 7], [7, -7]
     ],
     [
-        [1, 1], [-1, -3], [-3, 2], [4, -1],
+        [0, 0], [-1, -3], [-3, 2], [4, -1],
         [-5, -2], [2, 5], [5, 3], [3, -5],
         [-2, 6], [0, -7], [-4, -6], [-6, 4],
         [-8, 0], [7, -4], [6, 7], [-7, -8]
     ],
     [
-        [-4, -7], [-7, -5], [-3, -5], [-5, -4],
+        [0, 0], [-7, -5], [-3, -5], [-5, -4],
         [-1, -4], [-2, -2], [-6, -1], [-4, 0],
         [-7, 1], [-1, 2], [-6, 3], [-3, 3],
         [-7, 6], [-3, 6], [-5, 7], [-1, 7],
diff --git a/src/mol-canvas3d/passes/postprocessing.ts b/src/mol-canvas3d/passes/postprocessing.ts
index e6904f4c79ad45e2e7688c53131e54d33c6445f2..dfc2ec876940042c45663041740db7754d5da514 100644
--- a/src/mol-canvas3d/passes/postprocessing.ts
+++ b/src/mol-canvas3d/passes/postprocessing.ts
@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  * @author Áron Samuel Kovács <aron.kovacs@mail.muni.cz>
@@ -199,6 +199,7 @@ const PostprocessingSchema = {
     uMaxPossibleViewZDiff: UniformSpec('f'),
 
     dOcclusionEnable: DefineSpec('boolean'),
+    uOcclusionOffset: UniformSpec('v2'),
 
     dOutlineEnable: DefineSpec('boolean'),
     dOutlineScale: DefineSpec('number'),
@@ -227,6 +228,7 @@ function getPostprocessingRenderable(ctx: WebGLContext, colorTexture: Texture, d
         uMaxPossibleViewZDiff: ValueCell.create(0.5),
 
         dOcclusionEnable: ValueCell.create(true),
+        uOcclusionOffset: ValueCell.create(Vec2.create(0, 0)),
 
         dOutlineEnable: ValueCell.create(false),
         dOutlineScale: ValueCell.create(1),
@@ -494,6 +496,13 @@ export class PostprocessingPass {
         gl.scissor(x, y, width, height);
     }
 
+    occlusionOffset: [x: number, y: number] = [0, 0];
+    setOcclusionOffset(x: number, y: number) {
+        this.occlusionOffset[0] = x;
+        this.occlusionOffset[1] = y;
+        ValueCell.update(this.renderable.values.uOcclusionOffset, Vec2.set(this.renderable.values.uOcclusionOffset.ref.value, x, y));
+    }
+
     render(camera: ICamera, toDrawingBuffer: boolean, transparentBackground: boolean, backgroundColor: Color, props: PostprocessingProps) {
         this.updateState(camera, transparentBackground, backgroundColor, props);
 
@@ -502,15 +511,9 @@ export class PostprocessingPass {
             this.outlinesRenderable.render();
         }
 
-        if (props.occlusion.name === 'on') {
-            const { x, y, width, height } = camera.viewport;
-            const sx = Math.floor(x * this.ssaoScale);
-            const sy = Math.floor(y * this.ssaoScale);
-            const sw = Math.ceil(width * this.ssaoScale);
-            const sh = Math.ceil(height * this.ssaoScale);
-            this.webgl.gl.viewport(sx, sy, sw, sh);
-            this.webgl.gl.scissor(sx, sy, sw, sh);
-
+        // don't render occlusion if offset is given,
+        // which will reuse the existing occlusion
+        if (props.occlusion.name === 'on' && this.occlusionOffset[0] === 0 && this.occlusionOffset[1] === 0) {
             this.ssaoFramebuffer.bind();
             this.ssaoRenderable.render();
 
@@ -519,9 +522,6 @@ export class PostprocessingPass {
 
             this.ssaoBlurSecondPassFramebuffer.bind();
             this.ssaoBlurSecondPassRenderable.render();
-
-            this.webgl.gl.viewport(x, y, width, height);
-            this.webgl.gl.scissor(x, y, width, height);
         }
 
         if (toDrawingBuffer) {
diff --git a/src/mol-gl/shader/postprocessing.frag.ts b/src/mol-gl/shader/postprocessing.frag.ts
index 9a50f414352179a0f5e3da916ea961020628f650..aefe55452a3007003fbb9f0ffbdc5998fd854b49 100644
--- a/src/mol-gl/shader/postprocessing.frag.ts
+++ b/src/mol-gl/shader/postprocessing.frag.ts
@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  * @author Áron Samuel Kovács <aron.kovacs@mail.muni.cz>
@@ -24,8 +24,7 @@ uniform vec3 uFogColor;
 uniform vec3 uOutlineColor;
 uniform bool uTransparentBackground;
 
-uniform float uOcclusionBias;
-uniform float uOcclusionRadius;
+uniform vec2 uOcclusionOffset;
 
 uniform float uMaxPossibleViewZDiff;
 
@@ -102,7 +101,7 @@ void main(void) {
         if (!isBackground(depth)) {
             viewDist = abs(getViewZ(depth));
             fogFactor = smoothstep(uFogNear, uFogFar, viewDist);
-            float occlusionFactor = getSsao(coords);
+            float occlusionFactor = getSsao(coords + uOcclusionOffset);
             if (!uTransparentBackground) {
                 color.rgb = mix(mix(occlusionColor, uFogColor, fogFactor), color.rgb, occlusionFactor);
             } else {