Ver Fonte

Merge pull request #386 from molstar/improve-ao

Improve AO performance
Alexander Rose há 3 anos atrás
pai
commit
6e5a41879f

+ 3 - 0
CHANGELOG.md

@@ -15,6 +15,9 @@ 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
+- Check if marking passes are needed before doing them
+- Add ``scaleFactor`` parameter to adjust resolution of occlusion calculation
 
 ## [v3.2.0] - 2022-02-17
 

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

@@ -44,10 +44,11 @@ function occlusionStyle(plugin: PluginContext) {
         postprocessing: {
             ...plugin.canvas3d!.props.postprocessing,
             occlusion: { name: 'on', params: {
-                samples: 64,
-                radius: 8,
-                bias: 1.0,
-                blurKernelSize: 13
+                bias: 0.8,
+                blurKernelSize: 15,
+                radius: 5,
+                samples: 32,
+                scaleFactor: 1
             } },
             outline: { name: 'on', params: {
                 scale: 1.0,

+ 2 - 2
src/examples/lighting/index.ts

@@ -24,7 +24,7 @@ const Canvas3DPresets = {
     illustrative: {
         canvas3d: <Preset>{
             postprocessing: {
-                occlusion: { name: 'on', params: { samples: 32, radius: 6, bias: 1.4, blurKernelSize: 15 } },
+                occlusion: { name: 'on', params: { samples: 32, radius: 6, bias: 1.4, blurKernelSize: 15, scaleFactor: 1 } },
                 outline: { name: 'on', params: { scale: 1, threshold: 0.33, color: Color(0x000000) } }
             },
             renderer: {
@@ -36,7 +36,7 @@ const Canvas3DPresets = {
     occlusion: {
         canvas3d: <Preset>{
             postprocessing: {
-                occlusion: { name: 'on', params: { samples: 32, radius: 6, bias: 1.4, blurKernelSize: 15 } },
+                occlusion: { name: 'on', params: { samples: 32, radius: 6, bias: 1.4, blurKernelSize: 15, scaleFactor: 1 } },
                 outline: { name: 'off', params: {} }
             },
             renderer: {

+ 14 - 11
src/mol-canvas3d/passes/draw.ts

@@ -307,19 +307,22 @@ export class DrawPass {
         }
 
         if (markingEnabled) {
-            const markingDepthTest = props.marking.ghostEdgeStrength < 1;
-            if (markingDepthTest) {
-                this.marking.depthTarget.bind();
+            const markerAverage = scene.getMarkerAverage();
+            if (markerAverage > 0) {
+                const markingDepthTest = props.marking.ghostEdgeStrength < 1;
+                if (markingDepthTest && markerAverage !== 1) {
+                    this.marking.depthTarget.bind();
+                    renderer.clear(false, true);
+                    renderer.renderMarkingDepth(scene.primitives, camera, null);
+                }
+
+                this.marking.maskTarget.bind();
                 renderer.clear(false, true);
-                renderer.renderMarkingDepth(scene.primitives, camera, null);
-            }
-
-            this.marking.maskTarget.bind();
-            renderer.clear(false, true);
-            renderer.renderMarkingMask(scene.primitives, camera, markingDepthTest ? this.marking.depthTarget.texture : null);
+                renderer.renderMarkingMask(scene.primitives, camera, markingDepthTest ? this.marking.depthTarget.texture : null);
 
-            this.marking.update(props.marking);
-            this.marking.render(camera.viewport, postprocessingEnabled ? this.postprocessing.target : this.colorTarget);
+                this.marking.update(props.marking);
+                this.marking.render(camera.viewport, postprocessingEnabled ? this.postprocessing.target : this.colorTarget);
+            }
         }
 
         if (helper.debug.isEnabled) {

+ 26 - 6
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],

+ 60 - 21
src/mol-canvas3d/passes/postprocessing.ts

@@ -1,11 +1,11 @@
 /**
- * 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>
  */
 
-import { QuadSchema, QuadValues } from '../../mol-gl/compute/util';
+import { CopyRenderable, createCopyRenderable, QuadSchema, QuadValues } from '../../mol-gl/compute/util';
 import { TextureSpec, Values, UniformSpec, DefineSpec } from '../../mol-gl/renderable/schema';
 import { ShaderCode } from '../../mol-gl/shader-code';
 import { WebGLContext } from '../../mol-gl/webgl/context';
@@ -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),
@@ -244,9 +246,10 @@ export const PostprocessingParams = {
     occlusion: PD.MappedStatic('on', {
         on: PD.Group({
             samples: PD.Numeric(32, { min: 1, max: 256, step: 1 }),
-            radius: PD.Numeric(5, { min: 0, max: 10, step: 0.1 }, { description: 'Final radius is 2^x.' }),
+            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 }),
             blurKernelSize: PD.Numeric(15, { min: 1, max: 25, step: 2 }),
+            scaleFactor: 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' }),
@@ -281,6 +284,9 @@ export class PostprocessingPass {
     private readonly ssaoBlurFirstPassFramebuffer: Framebuffer;
     private readonly ssaoBlurSecondPassFramebuffer: Framebuffer;
 
+    private readonly downsampledDepthTarget: RenderTarget;
+    private readonly downsampleDepthRenderable: CopyRenderable;
+
     private readonly ssaoDepthTexture: Texture;
     private readonly ssaoDepthBlurProxyTexture: Texture;
 
@@ -290,24 +296,25 @@ export class PostprocessingPass {
 
     private nSamples: number;
     private blurKernelSize: number;
+    private downsampleFactor: number;
 
     private readonly renderable: PostprocessingRenderable;
 
     private ssaoScale: number;
     private calcSsaoScale() {
         // downscale ssao for high pixel-ratios
-        return Math.min(1, 1 / this.webgl.pixelRatio);
+        return Math.min(1, 1 / this.webgl.pixelRatio) * this.downsampleFactor;
     }
 
-    constructor(private webgl: WebGLContext, drawPass: DrawPass) {
-        this.ssaoScale = this.calcSsaoScale();
-
+    constructor(private webgl: WebGLContext, private drawPass: DrawPass) {
         const { colorTarget, depthTexture } = drawPass;
         const width = colorTarget.getWidth();
         const height = colorTarget.getHeight();
 
         this.nSamples = 1;
         this.blurKernelSize = 1;
+        this.downsampleFactor = 1;
+        this.ssaoScale = this.calcSsaoScale();
 
         // needs to be linear for anti-aliasing pass
         this.target = webgl.createRenderTarget(width, height, false, 'uint8', 'linear');
@@ -332,17 +339,20 @@ export class PostprocessingPass {
         const sw = Math.floor(width * this.ssaoScale);
         const sh = Math.floor(height * this.ssaoScale);
 
-        this.ssaoDepthTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
+        this.downsampledDepthTarget = webgl.createRenderTarget(sw, sh, false, 'uint8', 'linear');
+        this.downsampleDepthRenderable = createCopyRenderable(webgl, depthTexture);
+
+        this.ssaoDepthTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
         this.ssaoDepthTexture.define(sw, sh);
         this.ssaoDepthTexture.attachFramebuffer(this.ssaoFramebuffer, 'color0');
 
-        this.ssaoDepthBlurProxyTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
+        this.ssaoDepthBlurProxyTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
         this.ssaoDepthBlurProxyTexture.define(sw, sh);
         this.ssaoDepthBlurProxyTexture.attachFramebuffer(this.ssaoBlurFirstPassFramebuffer, 'color0');
 
         this.ssaoDepthTexture.attachFramebuffer(this.ssaoBlurSecondPassFramebuffer, 'color0');
 
-        this.ssaoRenderable = getSsaoRenderable(webgl, depthTexture);
+        this.ssaoRenderable = getSsaoRenderable(webgl, this.downsampleFactor === 1 ? depthTexture : this.downsampledDepthTarget.texture);
         this.ssaoBlurFirstPassRenderable = getSsaoBlurRenderable(webgl, this.ssaoDepthTexture, 'horizontal');
         this.ssaoBlurSecondPassRenderable = getSsaoBlurRenderable(webgl, this.ssaoDepthBlurProxyTexture, 'vertical');
         this.renderable = getPostprocessingRenderable(webgl, colorTarget.texture, depthTexture, this.outlinesTarget.texture, this.ssaoDepthTexture);
@@ -359,11 +369,13 @@ export class PostprocessingPass {
             const sh = Math.floor(height * this.ssaoScale);
             this.target.setSize(width, height);
             this.outlinesTarget.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.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));
             ValueCell.update(this.ssaoBlurSecondPassRenderable.values.uTexSize, Vec2.set(this.ssaoBlurSecondPassRenderable.values.uTexSize.ref.value, sw, sh));
@@ -434,6 +446,30 @@ export class PostprocessingPass {
                 ValueCell.updateIfChanged(this.ssaoBlurSecondPassRenderable.values.dOcclusionKernelSize, this.blurKernelSize);
             }
 
+            if (this.downsampleFactor !== props.occlusion.params.scaleFactor) {
+                needsUpdateSsao = true;
+
+                this.downsampleFactor = props.occlusion.params.scaleFactor;
+                this.ssaoScale = this.calcSsaoScale();
+
+                const sw = Math.floor(w * this.ssaoScale);
+                const sh = Math.floor(h * this.ssaoScale);
+
+                this.downsampledDepthTarget.setSize(sw, sh);
+                this.ssaoDepthTexture.define(sw, sh);
+                this.ssaoDepthBlurProxyTexture.define(sw, sh);
+
+                if (this.ssaoScale === 1) {
+                    ValueCell.update(this.ssaoRenderable.values.tDepth, this.drawPass.depthTexture);
+                } else {
+                    ValueCell.update(this.ssaoRenderable.values.tDepth, this.downsampledDepthTarget.texture);
+                }
+
+                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));
+                ValueCell.update(this.ssaoBlurSecondPassRenderable.values.uTexSize, Vec2.set(this.ssaoBlurSecondPassRenderable.values.uTexSize.ref.value, sw, sh));
+            }
         }
 
         if (props.outline.name === 'on') {
@@ -494,6 +530,13 @@ export class PostprocessingPass {
         gl.scissor(x, y, width, height);
     }
 
+    private 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,14 +545,13 @@ 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) {
+            if (this.ssaoScale < 1) {
+                this.downsampledDepthTarget.bind();
+                this.downsampleDepthRenderable.render();
+            }
 
             this.ssaoFramebuffer.bind();
             this.ssaoRenderable.render();
@@ -519,9 +561,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) {

+ 14 - 2
src/mol-gl/scene.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  * @author David Sehnal <david.sehnal@gmail.com>
@@ -79,6 +79,7 @@ interface Scene extends Object3D {
     has: (o: GraphicsRenderObject) => boolean
     clear: () => void
     forEach: (callbackFn: (value: GraphicsRenderable, key: GraphicsRenderObject) => void) => void
+    getMarkerAverage: () => number
 }
 
 namespace Scene {
@@ -243,7 +244,18 @@ namespace Scene {
                     visibleHash = computeVisibleHash();
                 }
                 return boundingSphereVisible;
-            }
+            },
+            getMarkerAverage() {
+                if (primitives.length === 0 && volumes.length === 0) return 0;
+                let markerAverage = 0;
+                for (let i = 0, il = primitives.length; i < il; ++i) {
+                    markerAverage += primitives[i].values.markerAverage.ref.value;
+                }
+                for (let i = 0, il = volumes.length; i < il; ++i) {
+                    markerAverage += volumes[i].values.markerAverage.ref.value;
+                }
+                return markerAverage / (primitives.length + volumes.length);
+            },
         };
     }
 }

+ 3 - 4
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 {

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

@@ -60,7 +60,7 @@ export class QuickStyles extends PurePluginUIComponent {
                     },
                     occlusion: {
                         name: 'on',
-                        params: { bias: 0.9, blurKernelSize: 15, radius: 5, samples: 32 }
+                        params: { bias: 0.8, blurKernelSize: 15, radius: 5, samples: 32, scaleFactor: 1 }
                     },
                 }
             });
@@ -84,7 +84,7 @@ export class QuickStyles extends PurePluginUIComponent {
                         name: 'on',
                         params: pp.occlusion.name === 'on'
                             ? pp.occlusion.params
-                            : { bias: 0.9, blurKernelSize: 15, radius: 5, samples: 32 }
+                            : { bias: 0.8, blurKernelSize: 15, radius: 5, samples: 32, scaleFactor: 1 }
                     },
                 }
             });

+ 2 - 2
src/mol-plugin/util/viewport-screenshot.ts

@@ -119,7 +119,7 @@ class ViewportScreenshotHelper extends PluginComponent {
             postprocessing: {
                 ...c.props.postprocessing,
                 occlusion: aoProps.name === 'on'
-                    ? { name: 'on', params: { ...aoProps.params, samples: 128 } }
+                    ? { name: 'on', params: { ...aoProps.params, samples: 128, scaleFactor: 1 } }
                     : aoProps
             },
             marking: { ...c.props.marking }
@@ -143,7 +143,7 @@ class ViewportScreenshotHelper extends PluginComponent {
                 postprocessing: {
                     ...c.props.postprocessing,
                     occlusion: aoProps.name === 'on'
-                        ? { name: 'on', params: { ...aoProps.params, samples: 128 } }
+                        ? { name: 'on', params: { ...aoProps.params, samples: 128, scaleFactor: 1 } }
                         : aoProps
                 },
                 marking: { ...c.props.marking }