From c13350b098fe2c18caa973e6f093b8e6d51b6180 Mon Sep 17 00:00:00 2001 From: Alexander Rose <alexander.rose@weirdbyte.de> Date: Sun, 8 Dec 2019 18:41:47 -0800 Subject: [PATCH] set transparent background per render call & screenshot improvements --- src/mol-canvas3d/canvas3d.ts | 7 +++++-- src/mol-canvas3d/passes/draw.ts | 10 ++++----- src/mol-canvas3d/passes/image.ts | 11 ++++++++-- src/mol-canvas3d/passes/multi-sample.ts | 16 +++++++------- src/mol-canvas3d/passes/pick.ts | 6 +++--- src/mol-gl/renderer.ts | 21 ++++++++----------- .../skin/base/components/controls.scss | 6 ++++++ src/mol-plugin-ui/viewport/screenshot.tsx | 5 +++++ .../viewport/simple-settings.tsx | 6 +++--- src/mol-plugin/util/viewport-screenshot.ts | 12 +++++++++-- 10 files changed, 63 insertions(+), 37 deletions(-) diff --git a/src/mol-canvas3d/canvas3d.ts b/src/mol-canvas3d/canvas3d.ts index 8d15a9dee..95bd539a8 100644 --- a/src/mol-canvas3d/canvas3d.ts +++ b/src/mol-canvas3d/canvas3d.ts @@ -38,6 +38,7 @@ export const Canvas3DParams = { cameraMode: PD.Select('perspective', [['perspective', 'Perspective'], ['orthographic', 'Orthographic']]), cameraFog: PD.Numeric(50, { min: 1, max: 100, step: 1 }), cameraResetDurationMs: PD.Numeric(250, { min: 0, max: 1000, step: 1 }, { description: 'The time it takes to reset the camera.' }), + transparentBackground: PD.Boolean(false), multiSample: PD.Group(MultiSampleParams), postprocessing: PD.Group(PostprocessingParams), @@ -191,9 +192,9 @@ namespace Canvas3D { case 'draw': renderer.setViewport(0, 0, width, height) if (multiSample.enabled) { - multiSample.render(true) + multiSample.render(true, p.transparentBackground) } else { - drawPass.render(!postprocessing.enabled) + drawPass.render(!postprocessing.enabled, p.transparentBackground) if (postprocessing.enabled) postprocessing.render(true) } pickPass.pickDirty = true @@ -346,6 +347,7 @@ namespace Canvas3D { camera.setState({ fog: props.cameraFog }) } if (props.cameraResetDurationMs !== undefined) p.cameraResetDurationMs = props.cameraResetDurationMs + if (props.transparentBackground !== undefined) p.transparentBackground = props.transparentBackground if (props.postprocessing) postprocessing.setProps(props.postprocessing) if (props.multiSample) multiSample.setProps(props.multiSample) @@ -363,6 +365,7 @@ namespace Canvas3D { cameraMode: camera.state.mode, cameraFog: camera.state.fog, cameraResetDurationMs: p.cameraResetDurationMs, + transparentBackground: p.transparentBackground, postprocessing: { ...postprocessing.props }, multiSample: { ...multiSample.props }, diff --git a/src/mol-canvas3d/passes/draw.ts b/src/mol-canvas3d/passes/draw.ts index b2f5a9477..5872e3cf0 100644 --- a/src/mol-canvas3d/passes/draw.ts +++ b/src/mol-canvas3d/passes/draw.ts @@ -42,7 +42,7 @@ export class DrawPass { } } - render(toDrawingBuffer: boolean) { + render(toDrawingBuffer: boolean, transparentBackground: boolean) { const { webgl, renderer, scene, camera, debugHelper, colorTarget, depthTarget } = this if (toDrawingBuffer) { webgl.unbindFramebuffer() @@ -51,20 +51,20 @@ export class DrawPass { } renderer.setViewport(0, 0, colorTarget.width, colorTarget.height) - renderer.render(scene, camera, 'color', true) + renderer.render(scene, camera, 'color', true, transparentBackground) if (debugHelper.isEnabled) { debugHelper.syncVisibility() - renderer.render(debugHelper.scene, camera, 'color', false) + renderer.render(debugHelper.scene, camera, 'color', false, transparentBackground) } // do a depth pass if not rendering to drawing buffer and // extensions.depthTexture is unsupported (i.e. depthTarget is set) if (!toDrawingBuffer && depthTarget) { depthTarget.bind() - renderer.render(scene, camera, 'depth', true) + renderer.render(scene, camera, 'depth', true, transparentBackground) if (debugHelper.isEnabled) { debugHelper.syncVisibility() - renderer.render(debugHelper.scene, camera, 'depth', false) + renderer.render(debugHelper.scene, camera, 'depth', false, transparentBackground) } } } diff --git a/src/mol-canvas3d/passes/image.ts b/src/mol-canvas3d/passes/image.ts index dde52c99d..dc25a9cc3 100644 --- a/src/mol-canvas3d/passes/image.ts +++ b/src/mol-canvas3d/passes/image.ts @@ -17,6 +17,7 @@ import { Camera } from '../camera'; import { Viewport } from '../camera/util'; export const ImageParams = { + transparentBackground: PD.Boolean(false), multiSample: PD.Group(MultiSampleParams), postprocessing: PD.Group(PostprocessingParams), } @@ -26,6 +27,7 @@ export class ImagePass { private _width = 1024 private _height = 768 private _camera = new Camera() + private _transparentBackground = false private _colorTarget: RenderTarget get colorTarget() { return this._colorTarget } @@ -40,6 +42,8 @@ export class ImagePass { constructor(webgl: WebGLContext, private renderer: Renderer, scene: Scene, private camera: Camera, debugHelper: BoundingSphereHelper, props: Partial<ImageProps>) { const p = { ...PD.getDefaultValues(ImageParams), ...props } + this._transparentBackground = p.transparentBackground + this.drawPass = new DrawPass(webgl, renderer, scene, this._camera, debugHelper) this.postprocessing = new PostprocessingPass(webgl, this._camera, this.drawPass, p.postprocessing) this.multiSample = new MultiSamplePass(webgl, this._camera, this.drawPass, this.postprocessing, p.multiSample) @@ -48,6 +52,8 @@ export class ImagePass { } setSize(width: number, height: number) { + if (width === this._width && height === this._height) return + this._width = width this._height = height @@ -57,6 +63,7 @@ export class ImagePass { } setProps(props: Partial<ImageProps> = {}) { + if (props.transparentBackground !== undefined) this._transparentBackground = props.transparentBackground if (props.postprocessing) this.postprocessing.setProps(props.postprocessing) if (props.multiSample) this.multiSample.setProps(props.multiSample) } @@ -69,10 +76,10 @@ export class ImagePass { this.renderer.setViewport(0, 0, this._width, this._height); if (this.multiSample.enabled) { - this.multiSample.render(false) + this.multiSample.render(false, this._transparentBackground) this._colorTarget = this.multiSample.colorTarget } else { - this.drawPass.render(false) + this.drawPass.render(false, this._transparentBackground) if (this.postprocessing.enabled) { this.postprocessing.render(false) this._colorTarget = this.postprocessing.target diff --git a/src/mol-canvas3d/passes/multi-sample.ts b/src/mol-canvas3d/passes/multi-sample.ts index 94dfd4cda..f22af14af 100644 --- a/src/mol-canvas3d/passes/multi-sample.ts +++ b/src/mol-canvas3d/passes/multi-sample.ts @@ -105,15 +105,15 @@ export class MultiSamplePass { if (props.sampleLevel !== undefined) this.props.sampleLevel = props.sampleLevel } - render(toDrawingBuffer: boolean) { + render(toDrawingBuffer: boolean, transparentBackground: boolean) { if (this.props.mode === 'temporal') { - this.renderTemporalMultiSample(toDrawingBuffer) + this.renderTemporalMultiSample(toDrawingBuffer, transparentBackground) } else { - this.renderMultiSample(toDrawingBuffer) + this.renderMultiSample(toDrawingBuffer, transparentBackground) } } - private renderMultiSample(toDrawingBuffer: boolean) { + private renderMultiSample(toDrawingBuffer: boolean, transparentBackground: boolean) { const { camera, compose, composeTarget, drawPass, postprocessing, webgl } = this const { gl, state } = webgl @@ -148,7 +148,7 @@ export class MultiSamplePass { ValueCell.update(compose.values.uWeight, sampleWeight) // render scene and optionally postprocess - drawPass.render(false) + drawPass.render(false, transparentBackground) if (postprocessing.enabled) postprocessing.render(false) // compose rendered scene with compose target @@ -184,7 +184,7 @@ export class MultiSamplePass { camera.update() } - private renderTemporalMultiSample(toDrawingBuffer: boolean) { + private renderTemporalMultiSample(toDrawingBuffer: boolean, transparentBackground: boolean) { const { camera, compose, composeTarget, holdTarget, postprocessing, drawPass, webgl } = this const { gl, state } = webgl @@ -204,7 +204,7 @@ export class MultiSamplePass { const i = this.sampleIndex if (i === 0) { - drawPass.render(false) + drawPass.render(false, transparentBackground) if (postprocessing.enabled) postprocessing.render(false) ValueCell.update(compose.values.uWeight, 1.0) ValueCell.update(compose.values.tColor, postprocessing.enabled ? postprocessing.target.texture : drawPass.colorTarget.texture) @@ -233,7 +233,7 @@ export class MultiSamplePass { camera.update() // render scene and optionally postprocess - drawPass.render(false) + drawPass.render(false, transparentBackground) if (postprocessing.enabled) postprocessing.render(false) // compose rendered scene with compose target diff --git a/src/mol-canvas3d/passes/pick.ts b/src/mol-canvas3d/passes/pick.ts index 28867455f..c4f54a816 100644 --- a/src/mol-canvas3d/passes/pick.ts +++ b/src/mol-canvas3d/passes/pick.ts @@ -68,11 +68,11 @@ export class PickPass { const { renderer, scene, camera } = this renderer.setViewport(0, 0, this.pickWidth, this.pickHeight); this.objectPickTarget.bind(); - renderer.render(scene, camera, 'pickObject', true); + renderer.render(scene, camera, 'pickObject', true, false); this.instancePickTarget.bind(); - renderer.render(scene, camera, 'pickInstance', true); + renderer.render(scene, camera, 'pickInstance', true, false); this.groupPickTarget.bind(); - renderer.render(scene, camera, 'pickGroup', true); + renderer.render(scene, camera, 'pickGroup', true, false); this.pickDirty = false } diff --git a/src/mol-gl/renderer.ts b/src/mol-gl/renderer.ts index f25e539ed..7bfcad1d6 100644 --- a/src/mol-gl/renderer.ts +++ b/src/mol-gl/renderer.ts @@ -37,8 +37,8 @@ interface Renderer { readonly stats: RendererStats readonly props: Readonly<RendererProps> - clear: () => void - render: (scene: Scene, camera: Camera, variant: GraphicsRenderVariant, clear: boolean) => void + clear: (transparentBackground: boolean) => void + render: (scene: Scene, camera: Camera, variant: GraphicsRenderVariant, clear: boolean, transparentBackground: boolean) => void setProps: (props: Partial<RendererProps>) => void setViewport: (x: number, y: number, width: number, height: number) => void dispose: () => void @@ -46,7 +46,6 @@ interface Renderer { export const RendererParams = { backgroundColor: PD.Color(Color(0x000000), { description: 'Background color of the 3D canvas' }), - transparentBackground: PD.Boolean(false, { description: 'Background opacity of the 3D canvas' }), pickingAlphaThreshold: PD.Numeric(0.5, { min: 0.0, max: 1.0, step: 0.01 }, { description: 'The minimum opacity value needed for an object to be pickable.' }), interiorDarkening: PD.Numeric(0.5, { min: 0.0, max: 1.0, step: 0.01 }), interiorColorFlag: PD.Boolean(true), @@ -111,8 +110,8 @@ namespace Renderer { uFogNear: ValueCell.create(1), uFogFar: ValueCell.create(10000), uFogColor: ValueCell.create(bgColor), + uTransparentBackground: ValueCell.create(0), - uTransparentBackground: ValueCell.create(p.transparentBackground ? 1 : 0), uPickingAlphaThreshold: ValueCell.create(p.pickingAlphaThreshold), uInteriorDarkening: ValueCell.create(p.interiorDarkening), uInteriorColorFlag: ValueCell.create(p.interiorColorFlag ? 1 : 0), @@ -166,7 +165,7 @@ namespace Renderer { } } - const render = (scene: Scene, camera: Camera, variant: GraphicsRenderVariant, clear: boolean) => { + const render = (scene: Scene, camera: Camera, variant: GraphicsRenderVariant, clear: boolean, transparentBackground: boolean) => { ValueCell.update(globalUniforms.uModel, scene.view) ValueCell.update(globalUniforms.uView, camera.view) ValueCell.update(globalUniforms.uInvView, Mat4.invert(invView, camera.view)) @@ -186,6 +185,8 @@ namespace Renderer { ValueCell.update(globalUniforms.uFogFar, camera.fogFar) ValueCell.update(globalUniforms.uFogNear, camera.fogNear) + ValueCell.update(globalUniforms.uTransparentBackground, transparentBackground ? 1 : 0) + globalUniformsNeedUpdate = true state.currentRenderItemId = -1 @@ -199,7 +200,7 @@ namespace Renderer { if (clear) { if (variant === 'color') { - state.clearColor(bgColor[0], bgColor[1], bgColor[2], p.transparentBackground ? 0 : 1) + state.clearColor(bgColor[0], bgColor[1], bgColor[2], transparentBackground ? 0 : 1) } else { state.clearColor(1, 1, 1, 1) } @@ -231,10 +232,10 @@ namespace Renderer { } return { - clear: () => { + clear: (transparentBackground: boolean) => { state.depthMask(true) state.colorMask(true, true, true, true) - state.clearColor(bgColor[0], bgColor[1], bgColor[2], p.transparentBackground ? 0 : 1) + state.clearColor(bgColor[0], bgColor[1], bgColor[2], transparentBackground ? 0 : 1) gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) }, render, @@ -262,10 +263,6 @@ namespace Renderer { Color.toVec3Normalized(bgColor, p.backgroundColor) ValueCell.update(globalUniforms.uFogColor, Vec3.copy(globalUniforms.uFogColor.ref.value, bgColor)) } - if (props.transparentBackground !== undefined && props.transparentBackground !== p.transparentBackground) { - p.transparentBackground = props.transparentBackground - ValueCell.update(globalUniforms.uTransparentBackground, p.transparentBackground ? 1 : 0) - } if (props.lightIntensity !== undefined && props.lightIntensity !== p.lightIntensity) { p.lightIntensity = props.lightIntensity ValueCell.update(globalUniforms.uLightIntensity, p.lightIntensity) diff --git a/src/mol-plugin-ui/skin/base/components/controls.scss b/src/mol-plugin-ui/skin/base/components/controls.scss index c24043385..8a0f44a4c 100644 --- a/src/mol-plugin-ui/skin/base/components/controls.scss +++ b/src/mol-plugin-ui/skin/base/components/controls.scss @@ -383,6 +383,12 @@ max-height: 180px; max-width: 100%; display: 'block'; + + background-color: $default-background; + background-image: linear-gradient(45deg, lightgrey 25%, transparent 25%, transparent 75%, lightgrey 75%, lightgrey), + linear-gradient(45deg, lightgrey 25%, transparent 25%, transparent 75%, lightgrey 75%, lightgrey); + background-size: 30px 30px; + background-position: 0 0, 15px 15px; } > span { diff --git a/src/mol-plugin-ui/viewport/screenshot.tsx b/src/mol-plugin-ui/viewport/screenshot.tsx index 430e4eb21..ad22766a3 100644 --- a/src/mol-plugin-ui/viewport/screenshot.tsx +++ b/src/mol-plugin-ui/viewport/screenshot.tsx @@ -18,6 +18,7 @@ interface ImageControlsState { showPreview: boolean resolution?: ViewportScreenshotHelper.ResolutionSettings, + transparent?: boolean, isDisabled: boolean } @@ -25,6 +26,7 @@ export class DownloadScreenshotControls extends PluginUIComponent<{ close: () => state: ImageControlsState = { showPreview: true, resolution: this.plugin.helpers.viewportScreenshot?.currentResolution, + transparent: this.plugin.helpers.viewportScreenshot?.transparent, isDisabled: false } as ImageControlsState @@ -110,6 +112,9 @@ export class DownloadScreenshotControls extends PluginUIComponent<{ close: () => this.plugin.helpers.viewportScreenshot!.currentResolution.type = resolution.name; } this.setState({ resolution }); + } else if (p.name === 'transparent') { + this.plugin.helpers.viewportScreenshot!.transparent = p.value; + this.setState({ transparent: p.value }); } } diff --git a/src/mol-plugin-ui/viewport/simple-settings.tsx b/src/mol-plugin-ui/viewport/simple-settings.tsx index 2062c6f4c..8d55f0768 100644 --- a/src/mol-plugin-ui/viewport/simple-settings.tsx +++ b/src/mol-plugin-ui/viewport/simple-settings.tsx @@ -40,9 +40,9 @@ export class SimpleSettingsControl extends PluginUIComponent { const renderer = this.plugin.canvas3d.props.renderer; const color: typeof SimpleSettingsParams['background']['defaultValue'] = p.value; if (color.name === 'transparent') { - PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: { renderer: { ...renderer, backgroundColor: ColorNames.white, transparentBackground: true } } }); + PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: { renderer: { ...renderer, backgroundColor: ColorNames.white }, transparentBackground: true } }); } else { - PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: { renderer: { ...renderer, backgroundColor: color.params.color, transparentBackground: false } } }); + PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: { renderer: { ...renderer, backgroundColor: color.params.color }, transparentBackground: false } }); } } else if (p.name === 'renderStyle') { if (!this.plugin.canvas3d) return; @@ -103,7 +103,7 @@ export class SimpleSettingsControl extends PluginUIComponent { } } - if (renderer.backgroundColor === ColorNames.white && renderer.transparentBackground) { + if (renderer.backgroundColor === ColorNames.white && this.plugin.canvas3d?.props.transparentBackground) { background = { name: 'transparent', params: { } } } else { background = { name: 'opaque', params: { color: renderer.backgroundColor } } diff --git a/src/mol-plugin/util/viewport-screenshot.ts b/src/mol-plugin/util/viewport-screenshot.ts index 87f881820..be05711c5 100644 --- a/src/mol-plugin/util/viewport-screenshot.ts +++ b/src/mol-plugin/util/viewport-screenshot.ts @@ -26,6 +26,7 @@ class ViewportScreenshotHelper { private createParams() { const max = Math.min(this.plugin.canvas3d ? this.plugin.canvas3d.webgl.maxRenderbufferSize : 4096, 4096) return { + transparent: PD.Boolean(false), resolution: PD.MappedStatic('full-hd', { viewport: PD.Group({}), hd: PD.Group({}), @@ -54,8 +55,8 @@ class ViewportScreenshotHelper { get values() { return this.currentResolution.type === 'custom' - ? { resolution: { name: 'custom', params: { width: this.currentResolution.width, height: this.currentResolution.height } } } - : { resolution: { name: this.currentResolution.type, params: { } } }; + ? { transparent: this.transparent, resolution: { name: 'custom', params: { width: this.currentResolution.width, height: this.currentResolution.height } } } + : { transparent: this.transparent, resolution: { name: this.currentResolution.type, params: { } } }; } private getCanvasSize() { @@ -65,6 +66,8 @@ class ViewportScreenshotHelper { }; } + transparent = false + currentResolution = { type: 'full-hd' as ViewportScreenshotHelper.ResolutionTypes, width: 1920, @@ -88,6 +91,7 @@ class ViewportScreenshotHelper { this._imagePass = this.plugin.canvas3d!.getImagePass() this._imagePass.setProps({ + transparentBackground: this.transparent, multiSample: { mode: 'on', sampleLevel: 2 }, postprocessing: this.plugin.canvas3d!.props.postprocessing }); @@ -134,6 +138,10 @@ class ViewportScreenshotHelper { if (width <= 0 || height <= 0) return; await ctx.update('Rendering image...') + this.imagePass.setProps({ + transparentBackground: this.transparent, + postprocessing: this.plugin.canvas3d!.props.postprocessing // TODO this line should not be required, updating should work by listening to this.plugin.events.canvas3d.settingsUpdated + }); const imageData = this.imagePass.getImageData(width, height); await ctx.update('Encoding image...') -- GitLab