Browse Source

refactor sss

- only sss, remove other ao implementations
- tweak params
- make sss a separate renderable
Alexander Rose 2 years ago
parent
commit
e1dd74775e

+ 2 - 0
CHANGELOG.md

@@ -6,6 +6,8 @@ Note that since we don't clearly distinguish between a public and private interf
 
 ## [Unreleased]
 
+- Add screen-space shadow post-processing effect
+
 ## [v3.25.1] - 2022-11-20
 
 - Fix edge-case in `Structure.eachUnitPair` with single-element units

+ 3 - 4
src/apps/docking-viewer/viewport.tsx

@@ -31,7 +31,8 @@ function shinyStyle(plugin: PluginContext) {
         postprocessing: {
             ...plugin.canvas3d!.props.postprocessing,
             occlusion: { name: 'off', params: {} },
-            outline: { name: 'off', params: {} }
+            shadow: { name: 'off', params: {} },
+            outline: { name: 'off', params: {} },
         }
     } });
 }
@@ -49,10 +50,8 @@ function occlusionStyle(plugin: PluginContext) {
                 radius: 5,
                 samples: 32,
                 resolutionScale: 1,
-                shadow: { name: 'off', params: {} },
-                closeAO: { name: 'off', params: {} },
-                softAO: { name: 'off', params: {} }
             } },
+            shadow: { name: 'off', params: { } },
             outline: { name: 'on', params: {
                 scale: 1.0,
                 threshold: 0.33,

+ 9 - 3
src/extensions/cellpack/model.ts

@@ -604,9 +604,15 @@ export const LoadCellPackModel = StateAction.build({
                         bias: 1,
                         blurKernelSize: 15,
                         resolutionScale: 1,
-                        shadow: { name: 'off', params: {} },
-                        softAO: { name: 'off', params: {} },
-                        closeAO: { name: 'off', params: {} }
+                    }
+                },
+                shadow: {
+                    name: 'on',
+                    params: {
+                        bias: 0.6,
+                        maxDistance: 80,
+                        steps: 1,
+                        tolerance: 1.0,
                     }
                 },
                 outline: {

+ 120 - 151
src/mol-canvas3d/passes/postprocessing.ts

@@ -3,6 +3,7 @@
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  * @author Áron Samuel Kovács <aron.kovacs@mail.muni.cz>
+ * @author Ludovic Autin <ludovic.autin@gmail.com>
  */
 
 import { CopyRenderable, createCopyRenderable, QuadSchema, QuadValues } from '../../mol-gl/compute/util';
@@ -31,7 +32,7 @@ import { isTimingMode } from '../../mol-util/debug';
 import { BackgroundParams, BackgroundPass } from './background';
 import { AssetManager } from '../../mol-util/assets';
 import { Light } from '../../mol-gl/renderer';
-// import { debug } from 'console';
+import { shadows_frag } from '../../mol-gl/shader/shadows.frag';
 
 const OutlinesSchema = {
     ...QuadSchema,
@@ -71,6 +72,62 @@ function getOutlinesRenderable(ctx: WebGLContext, depthTextureOpaque: Texture, d
     return createComputeRenderable(renderItem, values);
 }
 
+const ShadowsSchema = {
+    ...QuadSchema,
+    tDepth: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
+    uTexSize: UniformSpec('v2'),
+
+    uProjection: UniformSpec('m4'),
+    uInvProjection: UniformSpec('m4'),
+
+    dOrthographic: DefineSpec('number'),
+    uNear: UniformSpec('f'),
+    uFar: UniformSpec('f'),
+
+    dSteps: DefineSpec('number'),
+    uMaxDistance: UniformSpec('f'),
+    uTolerance: UniformSpec('f'),
+    uBias: UniformSpec('f'),
+
+    uLightDirection: UniformSpec('v3[]'),
+    uLightColor: UniformSpec('v3[]'),
+    dLightCount: DefineSpec('number'),
+};
+type ShadowsRenderable = ComputeRenderable<Values<typeof ShadowsSchema>>
+
+function getShadowsRenderable(ctx: WebGLContext, depthTexture: Texture): ShadowsRenderable {
+    const width = depthTexture.getWidth();
+    const height = depthTexture.getHeight();
+
+    const values: Values<typeof ShadowsSchema> = {
+        ...QuadValues,
+        tDepth: ValueCell.create(depthTexture),
+        uTexSize: ValueCell.create(Vec2.create(width, height)),
+
+        uProjection: ValueCell.create(Mat4.identity()),
+        uInvProjection: ValueCell.create(Mat4.identity()),
+
+        dOrthographic: ValueCell.create(0),
+        uNear: ValueCell.create(1),
+        uFar: ValueCell.create(10000),
+
+        dSteps: ValueCell.create(1),
+        uMaxDistance: ValueCell.create(3.0),
+        uTolerance: ValueCell.create(1.0),
+        uBias: ValueCell.create(0.6),
+
+        uLightDirection: ValueCell.create([]),
+        uLightColor: ValueCell.create([]),
+        dLightCount: ValueCell.create(0),
+    };
+
+    const schema = { ...ShadowsSchema };
+    const shaderCode = ShaderCode('shadows', quad_vert, shadows_frag);
+    const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values);
+
+    return createComputeRenderable(renderItem, values);
+}
+
 const SsaoSchema = {
     ...QuadSchema,
     tDepth: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
@@ -88,34 +145,6 @@ const SsaoSchema = {
     uRadius: UniformSpec('f'),
     uBias: UniformSpec('f'),
 
-    uCloseAO: UniformSpec('i'),
-    uCloseBias: UniformSpec('f'),
-    uCloseDistance: UniformSpec('f'),
-
-    uCDistanceCutoff: UniformSpec('f'),
-    uCCutoffFalloff: UniformSpec('f'),
-    uCIntensity: UniformSpec('f'),
-    uCDistance: UniformSpec('f'),
-
-    uShadow: UniformSpec('i'),
-    dSSample: DefineSpec('number'),
-    uSDistance: UniformSpec('f'),
-    uSTolerance: UniformSpec('f'),
-    uSBias: UniformSpec('f'),
-
-    uSoftAO: UniformSpec('i'),
-    uAorange: UniformSpec('f'),
-    uDepthTolerance: UniformSpec('f'),
-    uAoMultiplier: UniformSpec('f'),
-    uAoCap: UniformSpec('f'),
-    uAScale: UniformSpec('f'),
-    uARings: UniformSpec('i'),
-    uASamples: UniformSpec('i'),
-
-    uLightDirection: UniformSpec('v3[]'),
-    uLightColor: UniformSpec('v3[]'),
-    dLightCount: DefineSpec('number'),
-
     dOrthographic: DefineSpec('number'),
     uNear: UniformSpec('f'),
     uFar: UniformSpec('f'),
@@ -138,36 +167,9 @@ function getSsaoRenderable(ctx: WebGLContext, depthTexture: Texture): SsaoRender
 
         uTexSize: ValueCell.create(Vec2.create(ctx.gl.drawingBufferWidth, ctx.gl.drawingBufferHeight)),
 
-        uLightDirection: ValueCell.create([]),
-        uLightColor: ValueCell.create([]),
-        dLightCount: ValueCell.create(0),
-
         uRadius: ValueCell.create(8.0),
         uBias: ValueCell.create(0.025),
 
-        uCloseAO: ValueCell.create(1),
-        uCloseBias: ValueCell.create(1.0),
-        uCloseDistance: ValueCell.create(0.01),
-        uCDistanceCutoff: ValueCell.create(1.0),
-        uCCutoffFalloff: ValueCell.create(1.0),
-        uCIntensity: ValueCell.create(1.0),
-        uCDistance: ValueCell.create(1.0),
-
-        uShadow: ValueCell.create(1),
-        dSSample: ValueCell.create(1),
-        uSDistance: ValueCell.create(3.0),
-        uSTolerance: ValueCell.create(0.2),
-        uSBias: ValueCell.create(1.5),
-
-        uSoftAO: ValueCell.create(1),
-        uAorange: ValueCell.create(1.0),
-        uDepthTolerance: ValueCell.create(1.0),
-        uAoMultiplier: ValueCell.create(1.0),
-        uAoCap: ValueCell.create(1.0),
-        uAScale: ValueCell.create(1.0),
-        uARings: ValueCell.create(1),
-        uASamples: ValueCell.create(1),
-
         dOrthographic: ValueCell.create(0),
         uNear: ValueCell.create(0.0),
         uFar: ValueCell.create(10000.0),
@@ -271,6 +273,7 @@ const PostprocessingSchema = {
     tColor: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
     tDepthOpaque: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
     tDepthTransparent: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
+    tShadows: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
     tOutlines: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
     uTexSize: UniformSpec('v2'),
 
@@ -288,6 +291,8 @@ const PostprocessingSchema = {
     dOcclusionEnable: DefineSpec('boolean'),
     uOcclusionOffset: UniformSpec('v2'),
 
+    dShadowEnable: DefineSpec('boolean'),
+
     dOutlineEnable: DefineSpec('boolean'),
     dOutlineScale: DefineSpec('number'),
     uOutlineThreshold: UniformSpec('f'),
@@ -298,13 +303,14 @@ const PostprocessingSchema = {
 };
 type PostprocessingRenderable = ComputeRenderable<Values<typeof PostprocessingSchema>>
 
-function getPostprocessingRenderable(ctx: WebGLContext, colorTexture: Texture, depthTextureOpaque: Texture, depthTextureTransparent: Texture, outlinesTexture: Texture, ssaoDepthTexture: Texture): PostprocessingRenderable {
+function getPostprocessingRenderable(ctx: WebGLContext, colorTexture: Texture, depthTextureOpaque: Texture, depthTextureTransparent: Texture, shadowsTexture: Texture, outlinesTexture: Texture, ssaoDepthTexture: Texture): PostprocessingRenderable {
     const values: Values<typeof PostprocessingSchema> = {
         ...QuadValues,
         tSsaoDepth: ValueCell.create(ssaoDepthTexture),
         tColor: ValueCell.create(colorTexture),
         tDepthOpaque: ValueCell.create(depthTextureOpaque),
         tDepthTransparent: ValueCell.create(depthTextureTransparent),
+        tShadows: ValueCell.create(shadowsTexture),
         tOutlines: ValueCell.create(outlinesTexture),
         uTexSize: ValueCell.create(Vec2.create(colorTexture.getWidth(), colorTexture.getHeight())),
 
@@ -322,6 +328,8 @@ function getPostprocessingRenderable(ctx: WebGLContext, colorTexture: Texture, d
         dOcclusionEnable: ValueCell.create(true),
         uOcclusionOffset: ValueCell.create(Vec2.create(0, 0)),
 
+        dShadowEnable: ValueCell.create(false),
+
         dOutlineEnable: ValueCell.create(false),
         dOutlineScale: ValueCell.create(1),
         uOutlineThreshold: ValueCell.create(0.33),
@@ -344,43 +352,20 @@ export const PostprocessingParams = {
             samples: PD.Numeric(32, { min: 1, max: 256, step: 1 }),
             radius: PD.Numeric(5, { min: 0, max: 10, step: 0.1 }, { description: 'Final occlusion radius is 2^x' }),
             bias: PD.Numeric(0.8, { min: 0, max: 3, step: 0.1 }),
-            shadow: PD.MappedStatic('on', {
-                on: PD.Group({
-                    sSamples: PD.Numeric(1, { min: 1, max: 20, step: 1 }),
-                    sbias: PD.Numeric(0.8, { min: 0.0, max: 1.0, step: 0.01 }),
-                    sdistance: PD.Numeric(3.0, { min: 0.0, max: 100.0, step: 1.0 }),
-                    stolerance: PD.Numeric(1.0, { min: 0.0, max: 100.0, step: 1.0 }),
-                }),
-                off: PD.Group({})
-            }, { cycle: true, description: 'Darken occluded crevices with the ambient occlusion effect' }),
-            closeAO: PD.MappedStatic('on', {
-                on: PD.Group({
-                    cbias: PD.Numeric(0.0, { min: 0.0, max: 1.0, step: 0.01 }),
-                    cradius: PD.Numeric(0.015, { min: 0.0, max: 0.2, step: 0.001 }),
-                    cdistancecutoff: PD.Numeric(2000.0, { min: 0.0, max: 10000.0, step: 1.0 }),
-                    ccutofffalloff: PD.Numeric(25.0, { min: 0.0, max: 10000.0, step: 0.001 }),
-                    cintensity: PD.Numeric(1.0, { min: 0.0, max: 1.0, step: 0.1 }),
-                    cdistance: PD.Numeric(0.0015, { min: 0.0, max: 0.2, step: 0.001 }),
-                }),
-                off: PD.Group({})
-            }, { cycle: true, description: 'Darken occluded crevices with the ambient occlusion effect' }),
-            softAO: PD.MappedStatic('on', {
-                on: PD.Group({
-                    uAorange: PD.Numeric(160.0, { min: 0.0, max: 500.0, step: 1.0 }),
-                    uDepthTolerance: PD.Numeric(0.0, { min: 0.0, max: 200.0, step: 0.01 }),
-                    uAoMultiplier: PD.Numeric(100.0, { min: 0.0, max: 2000.0, step: 0.01 }),
-                    uAoCap: PD.Numeric(1.0, { min: 0.0, max: 10.0, step: 0.01 }),
-                    uAScale: PD.Numeric(1.0, { min: 0.0, max: 10.0, step: 0.01 }),
-                    uARings: PD.Numeric(6.0, { min: 0.0, max: 10.0, step: 1 }),
-                    uASamples: PD.Numeric(3.0, { min: 0.0, max: 10.0, step: 1 }),
-                }),
-                off: PD.Group({})
-            }, { cycle: true, description: 'Darken occluded crevices with the ambient occlusion effect' }),
             blurKernelSize: PD.Numeric(15, { min: 1, max: 25, step: 2 }),
             resolutionScale: PD.Numeric(1, { min: 0.1, max: 1, step: 0.05 }, { description: 'Adjust resolution of occlusion calculation' })
         }),
         off: PD.Group({})
     }, { cycle: true, description: 'Darken occluded crevices with the ambient occlusion effect' }),
+    shadow: PD.MappedStatic('off', {
+        on: PD.Group({
+            steps: PD.Numeric(1, { min: 1, max: 20, step: 1 }),
+            bias: PD.Numeric(0.6, { min: 0.0, max: 1.0, step: 0.01 }),
+            maxDistance: PD.Numeric(3.0, { min: 0.0, max: 100.0, step: 1.0 }),
+            tolerance: PD.Numeric(1.0, { min: 0.0, max: 10.0, step: 0.1 }),
+        }),
+        off: PD.Group({})
+    }, { cycle: true, description: 'Simplistic shadows' }),
     outline: PD.MappedStatic('off', {
         on: PD.Group({
             scale: PD.Numeric(1, { min: 1, max: 5, step: 1 }),
@@ -413,6 +398,9 @@ export class PostprocessingPass {
     private readonly outlinesTarget: RenderTarget;
     private readonly outlinesRenderable: OutlinesRenderable;
 
+    private readonly shadowsTarget: RenderTarget;
+    private readonly shadowsRenderable: ShadowsRenderable;
+
     private readonly ssaoFramebuffer: Framebuffer;
     private readonly ssaoBlurFirstPassFramebuffer: Framebuffer;
     private readonly ssaoBlurSecondPassFramebuffer: Framebuffer;
@@ -458,6 +446,9 @@ export class PostprocessingPass {
         this.outlinesTarget = webgl.createRenderTarget(width, height, false);
         this.outlinesRenderable = getOutlinesRenderable(webgl, depthTextureOpaque, depthTextureTransparent);
 
+        this.shadowsTarget = webgl.createRenderTarget(width, height, false);
+        this.shadowsRenderable = getShadowsRenderable(webgl, depthTextureOpaque);
+
         this.ssaoFramebuffer = webgl.resources.framebuffer();
         this.ssaoBlurFirstPassFramebuffer = webgl.resources.framebuffer();
         this.ssaoBlurSecondPassFramebuffer = webgl.resources.framebuffer();
@@ -481,7 +472,7 @@ export class PostprocessingPass {
         this.ssaoRenderable = getSsaoRenderable(webgl, this.downsampleFactor === 1 ? depthTextureOpaque : this.downsampledDepthTarget.texture);
         this.ssaoBlurFirstPassRenderable = getSsaoBlurRenderable(webgl, this.ssaoDepthTexture, 'horizontal');
         this.ssaoBlurSecondPassRenderable = getSsaoBlurRenderable(webgl, this.ssaoDepthBlurProxyTexture, 'vertical');
-        this.renderable = getPostprocessingRenderable(webgl, colorTarget.texture, depthTextureOpaque, depthTextureTransparent, this.outlinesTarget.texture, this.ssaoDepthTexture);
+        this.renderable = getPostprocessingRenderable(webgl, colorTarget.texture, depthTextureOpaque, depthTextureTransparent, this.shadowsTarget.texture, this.outlinesTarget.texture, this.ssaoDepthTexture);
 
         this.background = new BackgroundPass(webgl, assetManager, width, height);
     }
@@ -497,12 +488,14 @@ export class PostprocessingPass {
             const sh = Math.floor(height * this.ssaoScale);
             this.target.setSize(width, height);
             this.outlinesTarget.setSize(width, height);
+            this.shadowsTarget.setSize(width, height);
             this.downsampledDepthTarget.setSize(sw, sh);
             this.ssaoDepthTexture.define(sw, sh);
             this.ssaoDepthBlurProxyTexture.define(sw, sh);
 
             ValueCell.update(this.renderable.values.uTexSize, Vec2.set(this.renderable.values.uTexSize.ref.value, width, height));
             ValueCell.update(this.outlinesRenderable.values.uTexSize, Vec2.set(this.outlinesRenderable.values.uTexSize.ref.value, width, height));
+            ValueCell.update(this.shadowsRenderable.values.uTexSize, Vec2.set(this.shadowsRenderable.values.uTexSize.ref.value, width, height));
             ValueCell.update(this.downsampleDepthRenderable.values.uTexSize, Vec2.set(this.downsampleDepthRenderable.values.uTexSize.ref.value, sw, sh));
             ValueCell.update(this.ssaoRenderable.values.uTexSize, Vec2.set(this.ssaoRenderable.values.uTexSize.ref.value, sw, sh));
             ValueCell.update(this.ssaoBlurFirstPassRenderable.values.uTexSize, Vec2.set(this.ssaoBlurFirstPassRenderable.values.uTexSize.ref.value, sw, sh));
@@ -513,25 +506,20 @@ export class PostprocessingPass {
     }
 
     private updateState(camera: ICamera, transparentBackground: boolean, backgroundColor: Color, props: PostprocessingProps, light: Light) {
+        let needsUpdateShadows = false;
         let needsUpdateMain = false;
         let needsUpdateSsao = false;
         let needsUpdateSsaoBlur = false;
 
         const orthographic = camera.state.mode === 'orthographic' ? 1 : 0;
         const outlinesEnabled = props.outline.name === 'on';
+        const shadowsEnabled = props.shadow.name === 'on';
         const occlusionEnabled = props.occlusion.name === 'on';
+
         const invProjection = Mat4.identity();
         Mat4.invert(invProjection, camera.projection);
 
         if (props.occlusion.name === 'on') {
-            const shadowocclusionEnabled = props.occlusion.params.shadow.name === 'on' ? 1 : 0;
-            const closeocclusionEnabled = props.occlusion.params.closeAO.name === 'on' ? 1 : 0;
-            const softocclusionEnabled = props.occlusion.params.softAO.name === 'on' ? 1 : 0;
-
-            ValueCell.update(this.ssaoRenderable.values.uShadow, shadowocclusionEnabled);
-            ValueCell.update(this.ssaoRenderable.values.uCloseAO, closeocclusionEnabled);
-            ValueCell.update(this.ssaoRenderable.values.uSoftAO, softocclusionEnabled);
-
             ValueCell.update(this.ssaoRenderable.values.uProjection, camera.projection);
             ValueCell.update(this.ssaoRenderable.values.uInvProjection, invProjection);
             ValueCell.update(this.ssaoRenderable.values.uView, camera.view);
@@ -576,54 +564,6 @@ export class PostprocessingPass {
             ValueCell.updateIfChanged(this.ssaoRenderable.values.uRadius, Math.pow(2, props.occlusion.params.radius));
             ValueCell.updateIfChanged(this.ssaoRenderable.values.uBias, props.occlusion.params.bias);
 
-            if (props.occlusion.params.shadow.name === 'on') {
-                ValueCell.update(this.ssaoRenderable.values.dSSample, props.occlusion.params.shadow.params.sSamples);
-                ValueCell.update(this.ssaoRenderable.values.uSDistance, props.occlusion.params.shadow.params.sdistance);
-                ValueCell.update(this.ssaoRenderable.values.uSTolerance, props.occlusion.params.shadow.params.stolerance);
-
-                ValueCell.update(this.ssaoRenderable.values.uSBias, props.occlusion.params.shadow.params.sbias);
-                if (this.ssaoRenderable.values.dSSample.ref.value !== props.occlusion.params.shadow.params.sSamples ||
-                    this.ssaoRenderable.values.uSDistance.ref.value !== props.occlusion.params.shadow.params.sdistance ||
-                    this.ssaoRenderable.values.uSBias.ref.value !== props.occlusion.params.shadow.params.sbias) {
-                    needsUpdateSsao = true;
-                }
-            }
-
-            if (props.occlusion.params.closeAO.name === 'on') {
-                ValueCell.update(this.ssaoRenderable.values.uCloseBias, props.occlusion.params.closeAO.params.cbias);
-                ValueCell.update(this.ssaoRenderable.values.uCloseDistance, props.occlusion.params.closeAO.params.cradius);
-                ValueCell.update(this.ssaoRenderable.values.uCDistanceCutoff, props.occlusion.params.closeAO.params.cdistancecutoff);
-                ValueCell.update(this.ssaoRenderable.values.uCCutoffFalloff, props.occlusion.params.closeAO.params.ccutofffalloff);
-                ValueCell.update(this.ssaoRenderable.values.uCIntensity, props.occlusion.params.closeAO.params.cintensity);
-                ValueCell.update(this.ssaoRenderable.values.uCDistance, props.occlusion.params.closeAO.params.cdistance);
-                if (this.ssaoRenderable.values.uCloseBias.ref.value !== props.occlusion.params.closeAO.params.cbias ||
-                    this.ssaoRenderable.values.uCloseDistance.ref.value !== props.occlusion.params.closeAO.params.cdistance) {
-                    needsUpdateSsao = true;
-                }
-            }
-
-            if (props.occlusion.params.softAO.name === 'on') {
-                ValueCell.update(this.ssaoRenderable.values.uAorange, props.occlusion.params.softAO.params.uAorange);
-                ValueCell.update(this.ssaoRenderable.values.uDepthTolerance, props.occlusion.params.softAO.params.uDepthTolerance);
-                ValueCell.update(this.ssaoRenderable.values.uAoMultiplier, props.occlusion.params.softAO.params.uAoMultiplier);
-                ValueCell.update(this.ssaoRenderable.values.uAoCap, props.occlusion.params.softAO.params.uAoCap);
-                ValueCell.update(this.ssaoRenderable.values.uAScale, props.occlusion.params.softAO.params.uAScale);
-                ValueCell.update(this.ssaoRenderable.values.uARings, props.occlusion.params.softAO.params.uARings);
-                ValueCell.update(this.ssaoRenderable.values.uASamples, props.occlusion.params.softAO.params.uASamples);
-
-                if (this.ssaoRenderable.values.uCloseBias.ref.value !== props.occlusion.params.softAO.params.uASamples ||
-                    this.ssaoRenderable.values.uCloseDistance.ref.value !== props.occlusion.params.softAO.params.uARings) {
-                    needsUpdateSsao = true;
-                }
-            }
-
-            // console.log(light.direction);
-            ValueCell.update(this.ssaoRenderable.values.uLightDirection, light.direction);
-            ValueCell.update(this.ssaoRenderable.values.uLightColor, light.color);
-            if (this.ssaoRenderable.values.dLightCount.ref.value !== light.count) {
-                ValueCell.update(this.ssaoRenderable.values.dLightCount, light.count);
-                needsUpdateSsao = true;
-            }
             if (this.blurKernelSize !== props.occlusion.params.blurKernelSize) {
                 needsUpdateSsaoBlur = true;
 
@@ -662,6 +602,30 @@ export class PostprocessingPass {
             }
         }
 
+        if (props.shadow.name === 'on') {
+            ValueCell.update(this.shadowsRenderable.values.uProjection, camera.projection);
+            ValueCell.update(this.shadowsRenderable.values.uInvProjection, invProjection);
+
+            ValueCell.updateIfChanged(this.shadowsRenderable.values.uNear, camera.near);
+            ValueCell.updateIfChanged(this.shadowsRenderable.values.uFar, camera.far);
+            ValueCell.updateIfChanged(this.shadowsRenderable.values.dOrthographic, orthographic);
+
+            ValueCell.updateIfChanged(this.shadowsRenderable.values.uMaxDistance, props.shadow.params.maxDistance);
+            ValueCell.updateIfChanged(this.shadowsRenderable.values.uTolerance, props.shadow.params.tolerance);
+            ValueCell.updateIfChanged(this.shadowsRenderable.values.uBias, props.shadow.params.bias);
+            if (this.shadowsRenderable.values.dSteps.ref.value !== props.shadow.params.steps) {
+                ValueCell.update(this.shadowsRenderable.values.dSteps, props.shadow.params.steps);
+                needsUpdateShadows = true;
+            }
+
+            ValueCell.update(this.shadowsRenderable.values.uLightDirection, light.direction);
+            ValueCell.update(this.shadowsRenderable.values.uLightColor, light.color);
+            if (this.shadowsRenderable.values.dLightCount.ref.value !== light.count) {
+                ValueCell.update(this.shadowsRenderable.values.dLightCount, light.count);
+                needsUpdateShadows = true;
+            }
+        }
+
         if (props.outline.name === 'on') {
             let { threshold } = props.outline.params;
             // orthographic needs lower threshold
@@ -690,16 +654,16 @@ export class PostprocessingPass {
         ValueCell.updateIfChanged(this.renderable.values.uTransparentBackground, transparentBackground);
         if (this.renderable.values.dOrthographic.ref.value !== orthographic) { needsUpdateMain = true; }
         ValueCell.updateIfChanged(this.renderable.values.dOrthographic, orthographic);
+
         if (this.renderable.values.dOutlineEnable.ref.value !== outlinesEnabled) { needsUpdateMain = true; }
         ValueCell.updateIfChanged(this.renderable.values.dOutlineEnable, outlinesEnabled);
+        if (this.renderable.values.dShadowEnable.ref.value !== shadowsEnabled) { needsUpdateMain = true; }
+        ValueCell.updateIfChanged(this.renderable.values.dShadowEnable, shadowsEnabled);
         if (this.renderable.values.dOcclusionEnable.ref.value !== occlusionEnabled) { needsUpdateMain = true; }
         ValueCell.updateIfChanged(this.renderable.values.dOcclusionEnable, occlusionEnabled);
 
-        ValueCell.update(this.renderable.values.uLightDirection, light.direction);
-        ValueCell.update(this.renderable.values.uLightColor, light.color);
-        if (this.renderable.values.dLightCount.ref.value !== light.count) {
-            ValueCell.update(this.renderable.values.dLightCount, light.count);
-            needsUpdateMain = true;
+        if (needsUpdateShadows) {
+            this.shadowsRenderable.update();
         }
 
         if (needsUpdateSsao) {
@@ -748,6 +712,11 @@ export class PostprocessingPass {
             this.outlinesRenderable.render();
         }
 
+        if (props.shadow.name === 'on') {
+            this.shadowsTarget.bind();
+            this.shadowsRenderable.render();
+        }
+
         // 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) {

+ 4 - 4
src/mol-gl/renderer.ts

@@ -104,13 +104,13 @@ export const RendererParams = {
     xrayEdgeFalloff: PD.Numeric(1, { min: 0.0, max: 3.0, step: 0.1 }),
 
     light: PD.ObjectList({
-        inclination: PD.Numeric(180, { min: 0, max: 180, step: 1 }),
-        azimuth: PD.Numeric(0, { min: 0, max: 360, step: 1 }),
+        inclination: PD.Numeric(150, { min: 0, max: 180, step: 1 }),
+        azimuth: PD.Numeric(320, { min: 0, max: 360, step: 1 }),
         color: PD.Color(Color.fromNormalizedRgb(1.0, 1.0, 1.0)),
         intensity: PD.Numeric(0.6, { min: 0.0, max: 1.0, step: 0.01 }),
     }, o => Color.toHexString(o.color), { defaultValue: [{
-        inclination: 180,
-        azimuth: 0,
+        inclination: 150,
+        azimuth: 320,
         color: Color.fromNormalizedRgb(1.0, 1.0, 1.0),
         intensity: 0.6
     }] }),

+ 15 - 13
src/mol-gl/shader/postprocessing.frag.ts

@@ -14,6 +14,7 @@ uniform sampler2D tSsaoDepth;
 uniform sampler2D tColor;
 uniform sampler2D tDepthOpaque;
 uniform sampler2D tDepthTransparent;
+uniform sampler2D tShadows;
 uniform sampler2D tOutlines;
 uniform vec2 uTexSize;
 
@@ -29,18 +30,6 @@ uniform vec2 uOcclusionOffset;
 
 uniform float uMaxPossibleViewZDiff;
 
-#if dLightCount != 0
-    uniform vec3 uLightDirection[dLightCount];
-    uniform vec3 uLightColor[dLightCount];
-#endif
-
-// #pragma unroll_loop_start
-// for (int i = 0; i < dLightCount; ++i) {
-//     uLightDirection[i];
-//     uLightColor[i];
-// }
-// #pragma unroll_loop_end
-
 const vec3 occlusionColor = vec3(0.0);
 
 #include common
@@ -132,7 +121,20 @@ void main(void) {
         }
     #endif
 
-    // outline needs to be handled after occlusion to keep them clean
+    #ifdef dShadowEnable
+        if (!isBackground(opaqueDepth)) {
+            viewDist = abs(getViewZ(opaqueDepth));
+            fogFactor = smoothstep(uFogNear, uFogFar, viewDist);
+            vec4 shadow = texture2D(tShadows, coords + uOcclusionOffset);
+            if (!uTransparentBackground) {
+                color.rgb = mix(mix(vec3(0), uFogColor, fogFactor), color.rgb, shadow.a);
+            } else {
+                color.rgb = mix(vec3(0) * (1.0 - fogFactor), color.rgb, shadow.a);
+            }
+        }
+    #endif
+
+    // outline needs to be handled after occlusion and shadow to keep them clean
     #ifdef dOutlineEnable
         float closestTexel;
         float outline = getOutline(coords, opaqueDepth, closestTexel);

+ 116 - 0
src/mol-gl/shader/shadows.frag.ts

@@ -0,0 +1,116 @@
+/**
+ * Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Ludovic Autin <ludovic.autin@gmail.com>
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+export const shadows_frag = `
+precision highp float;
+precision highp int;
+precision highp sampler2D;
+
+#include common
+
+uniform sampler2D tDepth;
+uniform vec2 uTexSize;
+
+uniform float uNear;
+uniform float uFar;
+
+#if dLightCount != 0
+    uniform vec3 uLightDirection[dLightCount];
+    uniform vec3 uLightColor[dLightCount];
+#endif
+
+uniform mat4 uProjection;
+uniform mat4 uInvProjection;
+
+uniform float uMaxDistance;
+uniform float uTolerance;
+uniform float uBias;
+
+bool isBackground(const in float depth) {
+    return depth == 1.0;
+}
+
+float getViewZ(in float depth) {
+    #if dOrthographic == 1
+        return orthographicDepthToViewZ(depth, uNear, uFar);
+    #else
+        return perspectiveDepthToViewZ(depth, uNear, uFar);
+    #endif
+}
+
+float getDepth(const in vec2 coords) {
+    #ifdef depthTextureSupport
+        return texture2D(tDepth, coords).r;
+    #else
+        return unpackRGBAToDepth(texture2D(tDepth, coords));
+    #endif
+}
+
+// based on https://panoskarabelas.com/posts/screen_space_shadows/
+float screenSpaceShadow(in vec2 coords, in vec3 position, in vec3 lightDirection, in float stepLength) {
+    // Ray position and direction (in view-space)
+    vec3 rayPos = position;
+    vec3 rayDir = -lightDirection;
+
+    // Compute ray step
+    vec3 rayStep = rayDir * stepLength;
+
+    // Ray march towards the light
+    float occlusion = 0.0;
+    vec4 rayCoords = vec4(0.0);
+    for (int i = 0; i < dSteps; ++i) {
+        // Step the ray
+        rayPos += rayStep;
+
+        // Compute the difference between the ray's and the camera's depth
+        rayCoords = uProjection * vec4(rayPos, 1.0);
+        rayCoords.xyz = (rayCoords.xyz / rayCoords.w) * 0.5 + 0.5;
+        float depth = getDepth(rayCoords.xy);
+        float viewZ = getViewZ(depth);
+        float zDelta = rayPos.z - viewZ;
+
+        if (zDelta < uTolerance) {
+            occlusion = 1.0;
+            break;
+        }
+    }
+
+    // Fade out as we approach the edges of the screen
+    vec2 fade = max(12.0 * abs(rayCoords.xy - 0.5) - 5.0, vec2(0.0));
+    occlusion *= saturate(1.0 - dot(fade, fade));
+
+    return 1.0 - (uBias * occlusion);
+}
+
+void main(void) {
+    vec2 invTexSize = 1.0 / uTexSize;
+    vec2 selfCoords = gl_FragCoord.xy * invTexSize;
+
+    float selfDepth = getDepth(selfCoords);
+
+    if (isBackground(selfDepth)) {
+        gl_FragColor = vec4(0.0);
+        return;
+    }
+
+    vec3 selfViewPos = screenSpaceToViewSpace(vec3(selfCoords, selfDepth), uInvProjection);
+    float stepLength = uMaxDistance / float(dSteps);
+
+    float o = 1.0;
+    #if dLightCount != 0
+        float sh[dLightCount];
+        #pragma unroll_loop_start
+        for (int i = 0; i < dLightCount; ++i) {
+            sh[i] = screenSpaceShadow(selfCoords, selfViewPos, uLightDirection[i], stepLength);
+            o = min(o, sh[i]);
+        }
+        #pragma unroll_loop_end
+    #endif
+
+    gl_FragColor = vec4(o);
+}
+`;

+ 15 - 322
src/mol-gl/shader/ssao.frag.ts

@@ -16,54 +16,14 @@ uniform sampler2D tDepth;
 uniform vec2 uTexSize;
 uniform vec4 uBounds;
 
-uniform float uNear;
-uniform float uFar;
-
-#if dLightCount != 0
-    uniform vec3 uLightDirection[dLightCount];
-    uniform vec3 uLightColor[dLightCount];
-#endif
-
 uniform vec3 uSamples[dNSamples];
 
 uniform mat4 uProjection;
-uniform mat4 uView;
 uniform mat4 uInvProjection;
 
 uniform float uRadius;
 uniform float uBias;
 
-// shadow uniform
-uniform float uSDistance;
-uniform float uSTolerance;
-uniform float uSBias;
-uniform int uShadow;
-
-//ssao-pro uniform
-uniform int uCloseAO;
-uniform float uCloseBias;
-uniform float uCloseDistance;
-uniform float uCDistanceCutoff;
-uniform float uCCutoffFalloff;
-uniform float uCIntensity;
-uniform float uCDistance;
-    
-//ssao-old-blender uniform
-uniform int uSoftAO;
-uniform float uAorange;
-uniform float uDepthTolerance;
-uniform float uAoMultiplier;
-uniform float uAoCap;
-uniform float uAScale;
-uniform int uARings;
-uniform int uASamples;
-
-#define PI 3.14159265
-#define SAMPLES_HIGH 1
-#define SAMPLES_ULTRA 0
-#define SAMPLE_NOISE 1
-
-
 float smootherstep(float edge0, float edge1, float x) {
     x = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);
     return x * x * x * (x * (x * 6.0 - 15.0) + 10.0);
@@ -90,14 +50,6 @@ bool outsideBounds(const in vec2 p) {
     return p.x < uBounds.x || p.y < uBounds.y || p.x > uBounds.z || p.y > uBounds.w;
 }
 
-float getViewZ(in float depth) {
-    #if dOrthographic == 1
-        return orthographicDepthToViewZ(depth, uNear, uFar);
-    #else
-        return perspectiveDepthToViewZ(depth, uNear, uFar);
-    #endif
-}
-
 float getDepth(const in vec2 coords) {
     if (outsideBounds(coords)) {
         return 1.0;
@@ -120,241 +72,6 @@ vec3 normalFromDepth(const in float depth, const in float depth1, const in float
     return normalize(normal);
 }
 
-float readDepth( in vec2 coord ) {
-	return (2.0 * uNear) / (uFar + uNear - getDepth(coord) * (uFar - uNear));	
-}
-
-float compareDepths( in float d1, in float d2 ){
-    float near = uNear;
-    float far = uFar;
-    //float aorange = 160.0; //uniform
-    //float depthTolerance = 0.0;//uniform
-    //float aoMultiplier = 100.0;//uniform
-    //float aoCap = 1.0;//uniform
-    //go linear ?
-    float depth1 = d1;//getViewZ(d1);
-    float depth2 = d2;//getViewZ(d2);
-    //float diff = sqrt(clamp(1.0-(depth1-depth2) / (uAorange),0.0,1.0));
-    float diff = sqrt(clamp(1.0-(depth1-depth2) / (uAorange/(far-near)),0.0,1.0));
-    float ao = min(uAoCap,max(0.0,depth1-depth2-uDepthTolerance) * uAoMultiplier) * diff;
-    return ao;
-}
-
-float computeAO(in vec2 scrCoord){
-    float depth = readDepth(scrCoord);
-    vec2 invTexSize = 1.0 / uTexSize;
-    int do_noise = 0;
-    
-    float scale = uAScale; //uniform
-    float aspect = uTexSize.x/uTexSize.y;
-    int rings = uARings;//min(6,int(uRadius)); //uniform
-    int samples = uASamples;//min(6,int(dNSamples)); //uniform
-    //vec3 randomVec = normalize(vec3(getNoiseVec2(scrCoord), 0.0));
-    vec2 noise = getNoiseVec2(scrCoord);//getRandom(srcCoord);//
-    float w;
-	float h;
-	if (do_noise == 1) {
-       w = invTexSize.x/clamp(depth,0.05,1.0)+(noise.x*(1.0-noise.x))*scale;
-       h = invTexSize.y/clamp(depth,0.05,1.0)+(noise.y*(1.0-noise.y))*scale;
-	   }
-    else {
-       w = invTexSize.x/clamp(depth,0.05,1.0)+0.001*scale;//+(noise.x*(1.0-noise.x));
-       h = invTexSize.y/clamp(depth,0.05,1.0)+0.001*scale;//+(noise.y*(1.0-noise.y));
-	}
-    float pw;
-    float ph;
-
-    float ao;
-    float s;
-
-    int ringsamples;
-    for (int i = 1; i <= rings; i += 1){
-        ringsamples = i * samples;
-        for (int j = 0 ; j < ringsamples ; j += 1)   {
-            float step = PI*2.0 / float(ringsamples);
-            pw = (cos(float(j)*step)*float(i));
-            ph = (sin(float(j)*step)*float(i))*aspect;
-            float v = readDepth( vec2(scrCoord.s+pw*w,scrCoord.t+ph*h) );
-            ao += compareDepths(depth, v);
-            s += 1.0;
-            }
-        }
-    ao /= s;
-    // ao = 1.0-ao;
-    return ao;
-}
-
-
-float computeOcclusion(in float aradius, in mat3 TBN, in vec3 selfViewPos ){
-    float occlusion = 0.0;
-    for(int i = 0; i < dNSamples; i++){
-        vec3 sampleViewPos = TBN * uSamples[i];
-        sampleViewPos = selfViewPos + sampleViewPos * aradius;
-
-        vec4 offset = vec4(sampleViewPos, 1.0);
-        offset = uProjection * offset;
-        offset.xyz = (offset.xyz / offset.w) * 0.5 + 0.5;
-
-        float sampleViewZ = screenSpaceToViewSpace(vec3(offset.xy, getDepth(offset.xy)), uInvProjection).z;
-
-        occlusion += step(sampleViewPos.z + 0.025, sampleViewZ) * smootherstep(0.0, 1.0, aradius / abs(selfViewPos.z - sampleViewZ));
-    }
-    return occlusion;
-}
-
-float calcAO(in vec2 tcoord, in vec2 uv, in vec3 p, in vec3 cnorm)
-{
-    float _Bias = uCloseBias;
-    float _Intensity = uCIntensity;
-    float _Distance = uCDistance;
-    vec2 t = tcoord + uv;
-    float depth = getDepth(t);
-    vec3 diff = screenSpaceToViewSpace(vec3(t, depth), uInvProjection) - p;
-    vec3 v = normalize(diff);
-    float d = length(diff) * _Distance;
-    // cnorm = normalize(_WorldSpaceCameraPos - p);
-    return max(0.0, dot(cnorm, v) - _Bias) * (1.0 / (1.0 + d)) * _Intensity;
-}
-
-float invlerp(float from, float to, float value)
-{
-    return (value - from) / (to - from);
-}
-
-// Gold Noise function
-float PHI = 1.61803398874989484820459 * 00000.1; // Golden Ratio   
-float PIT  = 3.14159265358979323846264 * 00000.1; // PI
-float SRT = 1.41421356237309504880169 * 10000.0; // Square Root of Two
-
-float random_0t1(in vec2 coordinate, in float seed)
-{
-    return fract(sin(dot(coordinate*seed, vec2(PHI, PIT)))*SRT);
-}
-
-float ssao(in vec2 uv, in vec3 normal)
-{
-    float _SampleRadius = 5.0;
-    float _DistanceCutoff = uCDistanceCutoff;//100.0;
-    float _CutoffFalloff = uCCutoffFalloff;//25.0;
-
-    vec2 CROSS[4] = vec2[4]( vec2(1.0, 0.0), vec2(-1.0, 0.0), vec2(0.0, 1.0), vec2(0.0, -1.0) );
-    float depth = getDepth(uv);
-    float eyeDepth = getViewZ(depth);
-    vec3 position = screenSpaceToViewSpace(vec3(uv, depth), uInvProjection);
-    float radius =  uCloseDistance; // original was max(_SampleRadius / eyeDepth, 0.005);
-    // clip(_DistanceCutoff - eyeDepth); // Skip out of range pixels
-    if (_DistanceCutoff - abs(eyeDepth) < 0.0) return 1.0;
-    #if defined(SAMPLE_NOISE)
-        float a = random_0t1(uv,depth);
-        float b = random_0t1(uv,eyeDepth);
-        vec2 random = normalize(vec2(a,b));
-        // original used a texture for noise
-        // vec2 random = normalize(tex2D(_NoiseTex, _ScreenParams.xy * uv / _NoiseSize).rg * 2.0 - 1.0);
-    #endif    
-    float ao = 0.0;
-    // Sampling
-    for (int j = 0; j < 4; j++)
-    {
-        vec2 coord1;
-
-        #if defined(SAMPLE_NOISE)
-            coord1 = reflect(CROSS[j], random) * radius;
-        #else
-            coord1 = CROSS[j] * radius;
-        #endif
-
-        // #if !SAMPLES_VERY_LOW
-        vec2 coord2 = coord1 * 0.707;
-        coord2 = vec2(coord2.x - coord2.y, coord2.x + coord2.y);
-        // #endif 
-
-        #if defined(SAMPLES_ULTRA)    // 20
-        ao += calcAO(uv, coord1 * 0.20, position, normal);
-        ao += calcAO(uv, coord2 * 0.40, position, normal);
-        ao += calcAO(uv, coord1 * 0.60, position, normal);
-        ao += calcAO(uv, coord2 * 0.80, position, normal);
-        ao += calcAO(uv, coord1, position, normal);
-        #elif defined(SAMPLES_HIGH)          // 16
-        ao += calcAO(uv, coord1 * 0.25, position, normal);
-        ao += calcAO(uv, coord2 * 0.50, position, normal);
-        ao += calcAO(uv, coord1 * 0.75, position, normal);
-        ao += calcAO(uv, coord2, position, normal);
-        #elif defined(SAMPLES_MEDIUM)        // 12
-        ao += calcAO(uv, coord1 * 0.30, position, normal);
-        ao += calcAO(uv, coord2 * 0.60, position, normal);
-        ao += calcAO(uv, coord1 * 0.90, position, normal);
-        #elif defined(SAMPLES_LOW )          // 8
-        ao += calcAO(uv, coord1 * 0.30, position, normal);
-        ao += calcAO(uv, coord2 * 0.80, position, normal);
-        #else   // 4
-        ao += calcAO(uv, coord1 * 0.50, position, normal);
-        #endif
-    }
-    
-    #if SAMPLES_ULTRA
-    ao /= 20.0;
-    #elif SAMPLES_HIGH
-    ao /= 16.0;
-    #elif SAMPLES_MEDIUM
-    ao /= 12.0;
-    #elif SAMPLES_LOW
-    ao /= 8.0;
-    #else
-    ao /= 4.0;
-    #endif
-
-    // Distance cutoff
-    ao = mix(1.0 - ao, 1.0, saturate(invlerp(_DistanceCutoff - _CutoffFalloff, _DistanceCutoff, eyeDepth)));
-
-    return ao;
-}
-
-float ScreenSpaceShadows(in vec2 uv, in vec3 position, in vec3 light_direction)
-{
-    // Settings
-    int  g_sss_steps            = dSSample; // Quality/performancedNSamples
-    float g_sss_ray_max_distance = uSDistance; // Max shadow length
-    float g_sss_tolerance        = uSTolerance; // Error in favor of reducing gaps
-    float g_sss_step_length      = g_sss_ray_max_distance / float(g_sss_steps);
-    float uvdepth = getDepth(uv);
-    
-    float eyeDepth = getViewZ(uvdepth);
-    // Compute ray position and direction (in view-space)
-    vec3 ray_pos = position;
-    vec3 ray_dir = -light_direction; //light direction in View space
-	vec2 uv_pos = uv;
-    // Compute ray step
-    vec3 ray_step = ray_dir * g_sss_step_length;
-	vec2 uv_step = ray_dir.xy * g_sss_step_length;
-    // Ray march towards the light
-    float occlusion = 0.0;
-    
-    vec4 ray_uv   = vec4(0.0,0.0,0.0,0.0);
-    for (int i = 0; i < g_sss_steps; i++)
-    {
-        // Step the ray
-        uv_pos += uv_step;
-        ray_pos += ray_step;
-        // Compute the difference between the ray's and the camera's depth
-        ray_uv  = (uProjection * vec4(ray_pos,1.0));
-        ray_uv.xyz = (ray_uv.xyz / ray_uv.w) * 0.5 + 0.5;
-        float depth = getDepth(ray_uv.xy);       
-        float depth_z = getViewZ(depth);
-        float depth_delta = ray_pos.z - depth_z;
-        if (depth_delta < g_sss_tolerance){
-        // original test : if (abs(g_sss_tolerance - depth_delta) < g_sss_tolerance){
-            occlusion = 1.0;
-            vec2 fade = max(12.0 * abs(ray_uv.xy - 0.5) - 5.0, vec2(0.0,0.0));
-            occlusion *= saturate(1.0 - dot(fade, fade));             
-            break;
-        }
-    }
-    // Fade out as we approach the edges of the screen
-    // occlusion *= screen_fade(ray_uv);return 1.0 - occlusion;
-    occlusion = 1.0 - (uSBias * occlusion);
-    return occlusion;
-}
-
 // StarCraft II Ambient Occlusion by [Filion and McNaughton 2008]
 void main(void) {
     vec2 invTexSize = 1.0 / uTexSize;
@@ -383,47 +100,23 @@ void main(void) {
     vec3 bitangent = cross(selfViewNormal, tangent);
     mat3 TBN = mat3(tangent, bitangent, selfViewNormal);
 
-    float occlusion = computeOcclusion(uRadius, TBN, selfViewPos);
-    occlusion = 1.0 - (uBias * occlusion / float(dNSamples));
-    float ao1=0.0;
-    // alternative ao algo
-    if (uSoftAO == 1)
-    {
-        ao1 = computeAO(selfCoords);
-        ao1 = clamp(ao1,0.0,1.0);
-        if ( ao1 > 1.0 ) {ao1 = 1.0 ;}
-        if ( ao1 < 0.0 ) {ao1 = 0.0 ;}
-        if (selfDepth > 1.0 ) {ao1 = 1.0 ;}
-        if (selfDepth < 0.0 ) {ao1 = 0.0 ;}
-        ao1 = 1.0 - (ao1);
-    }
-    
-    bool isClose = true;
-    if (abs(selfViewPos.z) > 1200.0) isClose = false;
+    float occlusion = 0.0;
+    for(int i = 0; i < dNSamples; i++){
+        vec3 sampleViewPos = TBN * uSamples[i];
+        sampleViewPos = selfViewPos + sampleViewPos * uRadius;
 
-    float ao = 1.0;
-    if (uCloseAO == 1){
-        ao = saturate(ssao(selfCoords, selfViewNormal));
-    }
-    float o = 9999.9;
-    if (uShadow == 1) {
-        #if dLightCount != 0
-        float sh[dLightCount];
-        #pragma unroll_loop_start
-        for (int i = 0; i < dLightCount; ++i) {
-            sh[i] = ScreenSpaceShadows(selfCoords, selfViewPos, uLightDirection[i]);
-            o = min(o,min(min(sh[i],ao),occlusion));
-        }
-        #pragma unroll_loop_end
-        #endif
-    }
-    else{ 
-        o = min(ao,occlusion);
-    }
-    if (uSoftAO==1){
-        o = min(ao1,o);
+        vec4 offset = vec4(sampleViewPos, 1.0);
+        offset = uProjection * offset;
+        offset.xyz = (offset.xyz / offset.w) * 0.5 + 0.5;
+
+        float sampleViewZ = screenSpaceToViewSpace(vec3(offset.xy, getDepth(offset.xy)), uInvProjection).z;
+
+        occlusion += step(sampleViewPos.z + 0.025, sampleViewZ) * smootherstep(0.0, 1.0, uRadius / abs(selfViewPos.z - sampleViewZ));
     }
-    vec2 packedOcclusion = packUnitIntervalToRG(o);
+    occlusion = 1.0 - (uBias * occlusion / float(dNSamples));
+
+    vec2 packedOcclusion = packUnitIntervalToRG(occlusion);
+
     gl_FragColor = vec4(packedOcclusion, selfPackedDepth);
 }
 `;

+ 6 - 2
src/mol-plugin-ui/structure/quick-styles.tsx

@@ -60,8 +60,12 @@ export class QuickStyles extends PurePluginUIComponent {
                     },
                     occlusion: {
                         name: 'on',
-                        params: { bias: 0.8, blurKernelSize: 15, radius: 5, samples: 32, resolutionScale: 1, shadow: { name: 'off', params: {} }, softAO: { name: 'off', params: {} }, closeAO: { name: 'off', params: {} } }
-                    }
+                        params: { bias: 0.8, blurKernelSize: 15, radius: 5, samples: 32, resolutionScale: 1 }
+                    },
+                    shadow: {
+                        name: 'off',
+                        params: { }
+                    },
                 }
             });
         }

+ 3 - 0
src/mol-plugin-ui/viewport/simple-settings.tsx

@@ -59,6 +59,7 @@ const SimpleSettingsParams = {
     }, { pivot: 'color' }),
     lighting: PD.Group({
         occlusion: Canvas3DParams.postprocessing.params.occlusion,
+        shadow: Canvas3DParams.postprocessing.params.shadow,
         outline: Canvas3DParams.postprocessing.params.outline,
         fog: Canvas3DParams.cameraFog,
     }, { isFlat: true }),
@@ -114,6 +115,7 @@ const SimpleSettingsMapping = ParamMapping({
             },
             lighting: {
                 occlusion: canvas.postprocessing.occlusion,
+                shadow: canvas.postprocessing.shadow,
                 outline: canvas.postprocessing.outline,
                 fog: canvas.cameraFog,
             },
@@ -129,6 +131,7 @@ const SimpleSettingsMapping = ParamMapping({
         canvas.transparentBackground = s.background.transparent;
         canvas.renderer.backgroundColor = s.background.color;
         canvas.postprocessing.occlusion = s.lighting.occlusion;
+        canvas.postprocessing.shadow = s.lighting.shadow;
         canvas.postprocessing.outline = s.lighting.outline;
         canvas.postprocessing.background = s.background.style;
         canvas.cameraFog = s.lighting.fog;