diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f8bdc0fa357e93c5f9c7283ded1fe9cd0ea1315..f9dfd4beaa3ee2555fdf37ae0ea88529bddb638c 100644 --- a/CHANGELOG.md +++ b/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 diff --git a/src/apps/docking-viewer/viewport.tsx b/src/apps/docking-viewer/viewport.tsx index d34d58701f1d60761e71a40834daa7c4e0b0c042..b988b49e3e3dcf1ae7c89fec944e04bd445dfcfa 100644 --- a/src/apps/docking-viewer/viewport.tsx +++ b/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, diff --git a/src/examples/lighting/index.ts b/src/examples/lighting/index.ts index c296b1c8fa8505b5a561a1cc9b6eaaf6a2a036a9..97b2db364e45c6e590f48684f7749207bc674057 100644 --- a/src/examples/lighting/index.ts +++ b/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: { diff --git a/src/mol-canvas3d/passes/draw.ts b/src/mol-canvas3d/passes/draw.ts index b32caf23453b1ec7250c0e1733627fa15c39bae1..469393ad983b4bf1b0aacd1532fe37f1b9fda577 100644 --- a/src/mol-canvas3d/passes/draw.ts +++ b/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) { 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..2b81018aa0fd8d08d647577a64024c705f368ebe 100644 --- a/src/mol-canvas3d/passes/postprocessing.ts +++ b/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) { diff --git a/src/mol-gl/scene.ts b/src/mol-gl/scene.ts index f41c9ac35e3046f565fd4053064b499f78632b41..7cc2f3c7cd1ac9fc4c2860852370adaa9cf469a0 100644 --- a/src/mol-gl/scene.ts +++ b/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); + }, }; } } 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 { diff --git a/src/mol-plugin-ui/structure/quick-styles.tsx b/src/mol-plugin-ui/structure/quick-styles.tsx index bc5cf66123d7db4a82441d7c129af33ebde8b417..1449eea3e44c7cabb63573643aa8bcbbb2d1eba0 100644 --- a/src/mol-plugin-ui/structure/quick-styles.tsx +++ b/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 } }, } }); diff --git a/src/mol-plugin/util/viewport-screenshot.ts b/src/mol-plugin/util/viewport-screenshot.ts index fc95938fae5b9e77fa459c6d9982dcd2b90ca851..722c605fdf5cd2fb93fe790fefe0e2d2721b887c 100644 --- a/src/mol-plugin/util/viewport-screenshot.ts +++ b/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 }