Browse Source

set transparent background per render call & screenshot improvements

Alexander Rose 5 năm trước cách đây
mục cha
commit
c13350b098

+ 5 - 2
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 },

+ 5 - 5
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)
             }
         }
     }

+ 9 - 2
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

+ 8 - 8
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

+ 3 - 3
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
     }

+ 9 - 12
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)

+ 6 - 0
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 {

+ 5 - 0
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 });
         }
     }
 

+ 3 - 3
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 } }

+ 10 - 2
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...')