diff --git a/src/mol-canvas3d/passes/postprocessing.ts b/src/mol-canvas3d/passes/postprocessing.ts index b98072d9c77a3f9ee31927fe5fd091eaf05dc87a..5cae262fa056c1e7efa3f1a0fd2ce2fc0ebc678e 100644 --- a/src/mol-canvas3d/passes/postprocessing.ts +++ b/src/mol-canvas3d/passes/postprocessing.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -17,6 +17,7 @@ import { ParamDefinition as PD } from '../../mol-util/param-definition'; import { RenderTarget } from '../../mol-gl/webgl/render-target'; import { DrawPass } from './draw'; import { Camera } from '../../mol-canvas3d/camera'; +import { produce } from 'immer'; import quad_vert from '../../mol-gl/shader/quad.vert' import postprocessing_frag from '../../mol-gl/shader/postprocessing.frag' @@ -47,14 +48,21 @@ const PostprocessingSchema = { } export const PostprocessingParams = { - occlusionEnable: PD.Boolean(false), - occlusionKernelSize: PD.Numeric(4, { min: 1, max: 32, step: 1 }), - occlusionBias: PD.Numeric(0.5, { min: 0, max: 1, step: 0.01 }), - occlusionRadius: PD.Numeric(32, { min: 0, max: 256, step: 1 }), - - outlineEnable: PD.Boolean(false), - outlineScale: PD.Numeric(1, { min: 0, max: 10, step: 1 }), - outlineThreshold: PD.Numeric(0.8, { min: 0, max: 1, step: 0.01 }), + occlusion: PD.MappedStatic('off', { + on: PD.Group({ + kernelSize: PD.Numeric(4, { min: 1, max: 32, step: 1 }), + bias: PD.Numeric(0.5, { min: 0, max: 1, step: 0.01 }), + radius: PD.Numeric(64, { min: 0, max: 256, step: 1 }), + }), + off: PD.Group({}) + }, { cycle: true, description: 'Darken occluded crevices with the ambient occlusion effect' }), + outline: PD.MappedStatic('off', { + on: PD.Group({ + scale: PD.Numeric(1, { min: 0, max: 10, step: 1 }), + threshold: PD.Numeric(0.8, { min: 0, max: 1, step: 0.01 }), + }), + off: PD.Group({}) + }, { cycle: true, description: 'Draw outline around 3D objects' }) } export type PostprocessingProps = PD.Values<typeof PostprocessingParams> @@ -75,14 +83,14 @@ function getPostprocessingRenderable(ctx: WebGLContext, colorTexture: Texture, d uFogFar: ValueCell.create(10000), uFogColor: ValueCell.create(Vec3.create(1, 1, 1)), - dOcclusionEnable: ValueCell.create(p.occlusionEnable), - dOcclusionKernelSize: ValueCell.create(p.occlusionKernelSize), - uOcclusionBias: ValueCell.create(p.occlusionBias), - uOcclusionRadius: ValueCell.create(p.occlusionRadius), + dOcclusionEnable: ValueCell.create(p.occlusion.name === 'on'), + dOcclusionKernelSize: ValueCell.create(p.occlusion.name === 'on' ? p.occlusion.params.kernelSize : 4), + uOcclusionBias: ValueCell.create(p.occlusion.name === 'on' ? p.occlusion.params.bias : 0.5), + uOcclusionRadius: ValueCell.create(p.occlusion.name === 'on' ? p.occlusion.params.radius : 64), - dOutlineEnable: ValueCell.create(p.outlineEnable), - uOutlineScale: ValueCell.create(p.outlineScale * ctx.pixelRatio), - uOutlineThreshold: ValueCell.create(p.outlineThreshold), + dOutlineEnable: ValueCell.create(p.outline.name === 'on'), + uOutlineScale: ValueCell.create((p.outline.name === 'on' ? p.outline.params.scale : 1) * ctx.pixelRatio), + uOutlineThreshold: ValueCell.create(p.outline.name === 'on' ? p.outline.params.threshold : 0.8), dPackedDepth: ValueCell.create(packedDepth), } @@ -108,7 +116,7 @@ export class PostprocessingPass { } get enabled() { - return this.props.occlusionEnable || this.props.outlineEnable + return this.props.occlusion.name === 'on' || this.props.outline.name === 'on' } setSize(width: number, height: number) { @@ -117,35 +125,28 @@ export class PostprocessingPass { } setProps(props: Partial<PostprocessingProps>) { - if (props.occlusionEnable !== undefined) { - this.props.occlusionEnable = props.occlusionEnable - ValueCell.update(this.renderable.values.dOcclusionEnable, props.occlusionEnable) - } - if (props.occlusionKernelSize !== undefined) { - this.props.occlusionKernelSize = props.occlusionKernelSize - ValueCell.update(this.renderable.values.dOcclusionKernelSize, props.occlusionKernelSize) - } - if (props.occlusionBias !== undefined) { - this.props.occlusionBias = props.occlusionBias - ValueCell.update(this.renderable.values.uOcclusionBias, props.occlusionBias) - } - if (props.occlusionRadius !== undefined) { - this.props.occlusionRadius = props.occlusionRadius - ValueCell.update(this.renderable.values.uOcclusionRadius, props.occlusionRadius) - } - - if (props.outlineEnable !== undefined) { - this.props.outlineEnable = props.outlineEnable - ValueCell.update(this.renderable.values.dOutlineEnable, props.outlineEnable) - } - if (props.outlineScale !== undefined) { - this.props.outlineScale = props.outlineScale - ValueCell.update(this.renderable.values.uOutlineScale, props.outlineScale * this.webgl.pixelRatio) - } - if (props.outlineThreshold !== undefined) { - this.props.outlineThreshold = props.outlineThreshold - ValueCell.update(this.renderable.values.uOutlineThreshold, props.outlineThreshold) - } + this.props = produce(this.props, p => { + if (props.occlusion !== undefined) { + p.occlusion.name = props.occlusion.name + ValueCell.updateIfChanged(this.renderable.values.dOcclusionEnable, props.occlusion.name === 'on') + if (props.occlusion.name === 'on') { + p.occlusion.params = { ...props.occlusion.params } + ValueCell.updateIfChanged(this.renderable.values.dOcclusionKernelSize, props.occlusion.params.kernelSize) + ValueCell.updateIfChanged(this.renderable.values.uOcclusionBias, props.occlusion.params.bias) + ValueCell.updateIfChanged(this.renderable.values.uOcclusionRadius, props.occlusion.params.radius) + } + } + + if (props.outline !== undefined) { + p.outline.name = props.outline.name + ValueCell.updateIfChanged(this.renderable.values.dOutlineEnable, props.outline.name === 'on') + if (props.outline.name === 'on') { + p.outline.params = { ...props.outline.params } + ValueCell.updateIfChanged(this.renderable.values.uOutlineScale, props.outline.params.scale) + ValueCell.updateIfChanged(this.renderable.values.uOutlineThreshold, props.outline.params.threshold) + } + } + }) this.renderable.update() } diff --git a/src/mol-gl/renderer.ts b/src/mol-gl/renderer.ts index 6262d5452b47b4e2245d37f14b08df6f109e519d..57975f47c9d89dde14fb6fc64a0a58dce5b04f8a 100644 --- a/src/mol-gl/renderer.ts +++ b/src/mol-gl/renderer.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -55,22 +55,63 @@ export const RendererParams = { interiorColorFlag: PD.Boolean(true, { label: 'Use Interior Color' }), interiorColor: PD.Color(Color.fromNormalizedRgb(0.3, 0.3, 0.3)), - lightIntensity: PD.Numeric(0.6, { min: 0.0, max: 1.0, step: 0.01 }), - ambientIntensity: PD.Numeric(0.4, { min: 0.0, max: 1.0, step: 0.01 }), - - metalness: PD.Numeric(0.0, { min: 0.0, max: 1.0, step: 0.01 }), - roughness: PD.Numeric(1.0, { min: 0.0, max: 1.0, step: 0.01 }), - reflectivity: PD.Numeric(0.5, { min: 0.0, max: 1.0, step: 0.01 }), - highlightColor: PD.Color(Color.fromNormalizedRgb(1.0, 0.4, 0.6)), selectColor: PD.Color(Color.fromNormalizedRgb(0.2, 1.0, 0.1)), + + style: PD.MappedStatic('matte', { + custom: PD.Group({ + lightIntensity: PD.Numeric(0.6, { min: 0.0, max: 1.0, step: 0.01 }), + ambientIntensity: PD.Numeric(0.4, { min: 0.0, max: 1.0, step: 0.01 }), + metalness: PD.Numeric(0.0, { min: 0.0, max: 1.0, step: 0.01 }), + roughness: PD.Numeric(1.0, { min: 0.0, max: 1.0, step: 0.01 }), + reflectivity: PD.Numeric(0.5, { min: 0.0, max: 1.0, step: 0.01 }), + }, { isExpanded: true }), + flat: PD.Group({}), + matte: PD.Group({}), + glossy: PD.Group({}), + metallic: PD.Group({}), + plastic: PD.Group({}), + }, { label: 'Render Style', description: 'Style in which the 3D scene is rendered' }), } export type RendererProps = PD.Values<typeof RendererParams> +function getStyle(props: RendererProps['style']) { + switch (props.name) { + case 'custom': + return props.params + case 'flat': + return { + lightIntensity: 0, ambientIntensity: 1, + metalness: 0, roughness: 0.4, reflectivity: 0.5 + } + case 'matte': + return { + lightIntensity: 0.6, ambientIntensity: 0.4, + metalness: 0, roughness: 1, reflectivity: 0.5 + } + case 'glossy': + return { + lightIntensity: 0.6, ambientIntensity: 0.4, + metalness: 0, roughness: 0.4, reflectivity: 0.5 + } + case 'metallic': + return { + lightIntensity: 0.6, ambientIntensity: 0.4, + metalness: 0.4, roughness: 0.6, reflectivity: 0.5 + } + case 'plastic': + return { + lightIntensity: 0.6, ambientIntensity: 0.4, + metalness: 0, roughness: 0.2, reflectivity: 0.5 + } + } +} + namespace Renderer { export function create(ctx: WebGLContext, props: Partial<RendererProps> = {}): Renderer { const { gl, state, stats } = ctx const p = deepClone({ ...PD.getDefaultValues(RendererParams), ...props }) + const style = getStyle(p.style) const viewport = Viewport() const bgColor = Color.toVec3Normalized(Vec3(), p.backgroundColor) @@ -112,12 +153,12 @@ namespace Renderer { uTransparentBackground: ValueCell.create(0), // the following are general 'material' uniforms - uLightIntensity: ValueCell.create(p.lightIntensity), - uAmbientIntensity: ValueCell.create(p.ambientIntensity), + uLightIntensity: ValueCell.create(style.lightIntensity), + uAmbientIntensity: ValueCell.create(style.ambientIntensity), - uMetalness: ValueCell.create(p.metalness), - uRoughness: ValueCell.create(p.roughness), - uReflectivity: ValueCell.create(p.reflectivity), + uMetalness: ValueCell.create(style.metalness), + uRoughness: ValueCell.create(style.roughness), + uReflectivity: ValueCell.create(style.reflectivity), uPickingAlphaThreshold: ValueCell.create(p.pickingAlphaThreshold), @@ -276,28 +317,6 @@ namespace Renderer { ValueCell.update(globalUniforms.uInteriorColor, Color.toVec3Normalized(globalUniforms.uInteriorColor.ref.value, p.interiorColor)) } - if (props.lightIntensity !== undefined && props.lightIntensity !== p.lightIntensity) { - p.lightIntensity = props.lightIntensity - ValueCell.update(globalUniforms.uLightIntensity, p.lightIntensity) - } - if (props.ambientIntensity !== undefined && props.ambientIntensity !== p.ambientIntensity) { - p.ambientIntensity = props.ambientIntensity - ValueCell.update(globalUniforms.uAmbientIntensity, p.ambientIntensity) - } - - if (props.metalness !== undefined && props.metalness !== p.metalness) { - p.metalness = props.metalness - ValueCell.update(globalUniforms.uMetalness, p.metalness) - } - if (props.roughness !== undefined && props.roughness !== p.roughness) { - p.roughness = props.roughness - ValueCell.update(globalUniforms.uRoughness, p.roughness) - } - if (props.reflectivity !== undefined && props.reflectivity !== p.reflectivity) { - p.reflectivity = props.reflectivity - ValueCell.update(globalUniforms.uReflectivity, p.reflectivity) - } - if (props.highlightColor !== undefined && props.highlightColor !== p.highlightColor) { p.highlightColor = props.highlightColor ValueCell.update(globalUniforms.uHighlightColor, Color.toVec3Normalized(globalUniforms.uHighlightColor.ref.value, p.highlightColor)) @@ -306,6 +325,16 @@ namespace Renderer { p.selectColor = props.selectColor ValueCell.update(globalUniforms.uSelectColor, Color.toVec3Normalized(globalUniforms.uSelectColor.ref.value, p.selectColor)) } + + if (props.style !== undefined) { + p.style = props.style + Object.assign(style, getStyle(props.style)) + ValueCell.updateIfChanged(globalUniforms.uLightIntensity, style.lightIntensity) + ValueCell.updateIfChanged(globalUniforms.uAmbientIntensity, style.ambientIntensity) + ValueCell.updateIfChanged(globalUniforms.uMetalness, style.metalness) + ValueCell.updateIfChanged(globalUniforms.uRoughness, style.roughness) + ValueCell.updateIfChanged(globalUniforms.uReflectivity, style.reflectivity) + } }, setViewport: (x: number, y: number, width: number, height: number) => { gl.viewport(x, y, width, height) diff --git a/src/mol-plugin-ui/viewport/simple-settings.tsx b/src/mol-plugin-ui/viewport/simple-settings.tsx index 2cdc23609517c83da8be25d86b6363cdbec67944..200fd91e65acd1e6f94d1b7477325bc1209eb6d6 100644 --- a/src/mol-plugin-ui/viewport/simple-settings.tsx +++ b/src/mol-plugin-ui/viewport/simple-settings.tsx @@ -1,5 +1,5 @@ /** - * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2019-2020 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> @@ -38,9 +38,9 @@ const SimpleSettingsParams = { 'transparent': PD.EmptyGroup(), 'opaque': PD.Group({ color: PD.Color(Color(0xFCFBF9), { description: 'Custom background color' }) }, { isFlat: true }) }, { description: 'Background of the 3D canvas' }), - renderStyle: PD.Select('glossy', PD.arrayToOptions(['flat', 'matte', 'glossy', 'metallic']), { description: 'Style in which the 3D scene is rendered' }), - occlusion: PD.Boolean(false, { description: 'Darken occluded crevices with the ambient occlusion effect' }), - outline: PD.Boolean(false, { description: 'Draw outline around 3D objects' }), + renderStyle: Canvas3DParams.renderer.params.style, + occlusion: Canvas3DParams.postprocessing.params.occlusion, + outline: Canvas3DParams.postprocessing.params.outline, fog: PD.Boolean(false, { description: 'Show fog in the distance' }), clipFar: PD.Boolean(true, { description: 'Clip scene in the distance' }), }; @@ -48,43 +48,28 @@ const SimpleSettingsParams = { type SimpleSettingsParams = typeof SimpleSettingsParams const SimpleSettingsMapping = ParamMapping({ params: SimpleSettingsParams, - target(ctx: PluginContext) { + target(ctx: PluginContext) { const layout: SimpleSettingsParams['layout']['defaultValue'] = []; if (ctx.layout.state.regionState.top !== 'hidden') layout.push('sequence'); if (ctx.layout.state.regionState.bottom !== 'hidden') layout.push('log'); if (ctx.layout.state.regionState.left !== 'hidden') layout.push('left'); return { canvas: ctx.canvas3d?.props!, layout }; } -})({ +})({ values(props, ctx) { const { canvas } = props; const renderer = canvas.renderer; - let renderStyle: SimpleSettingsParams['renderStyle']['defaultValue'] = 'custom' as any; - if (renderer) { - if (renderer.lightIntensity === 0 && renderer.ambientIntensity === 1 && renderer.roughness === 0.4 && renderer.metalness === 0) { - renderStyle = 'flat' - } else if (renderer.lightIntensity === 0.6 && renderer.ambientIntensity === 0.4) { - if (renderer.roughness === 1 && renderer.metalness === 0) { - renderStyle = 'matte' - } else if (renderer.roughness === 0.4 && renderer.metalness === 0) { - renderStyle = 'glossy' - } else if (renderer.roughness === 0.6 && renderer.metalness === 0.4) { - renderStyle = 'metallic' - } - } - } - return { layout: props.layout, spin: !!canvas.trackball.spin, camera: canvas.cameraMode, - background: (renderer.backgroundColor === ColorNames.white && canvas.transparentBackground) + background: (renderer.backgroundColor === ColorNames.white && canvas.transparentBackground) ? { name: 'transparent', params: { } } : { name: 'opaque', params: { color: renderer.backgroundColor } }, - renderStyle, - occlusion: canvas.postprocessing.occlusionEnable, - outline: canvas.postprocessing.outlineEnable, + renderStyle: renderer.style, + occlusion: canvas.postprocessing.occlusion, + outline: canvas.postprocessing.outline, fog: ctx.canvas3d ? canvas.cameraFog > 1 : false, clipFar: canvas.cameraClipFar }; @@ -95,18 +80,9 @@ const SimpleSettingsMapping = ParamMapping({ canvas.cameraMode = s.camera; canvas.transparentBackground = s.background.name === 'transparent'; canvas.renderer.backgroundColor = s.background.name === 'transparent' ? ColorNames.white : s.background.params.color; - switch (s.renderStyle) { - case 'flat': Object.assign(canvas.renderer, { lightIntensity: 0, ambientIntensity: 1, roughness: 0.4, metalness: 0 }); break; - case 'matte': Object.assign(canvas.renderer, { lightIntensity: 0.6, ambientIntensity: 0.4, roughness: 1, metalness: 0 }); break; - case 'glossy': Object.assign(canvas.renderer, { lightIntensity: 0.6, ambientIntensity: 0.4, roughness: 0.4, metalness: 0 }); break; - case 'metallic': Object.assign(canvas.renderer, { lightIntensity: 0.6, ambientIntensity: 0.4, roughness: 0.6, metalness: 0.4 }); break; - } - canvas.postprocessing.occlusionEnable = s.occlusion; - if (s.occlusion) { - canvas.postprocessing.occlusionBias = 0.5; - canvas.postprocessing.occlusionRadius = 64; - } - canvas.postprocessing.outlineEnable = s.outline; + canvas.renderer.style = s.renderStyle + canvas.postprocessing.occlusion = s.occlusion; + canvas.postprocessing.outline = s.outline; canvas.cameraFog = s.fog ? 50 : 0; canvas.cameraClipFar = s.clipFar; @@ -122,7 +98,7 @@ const SimpleSettingsMapping = ParamMapping({ s.regionState.left = hideLeft ? 'hidden' : ctx.behaviors.layout.leftPanelTabName.value === 'none' ? 'collapsed' : 'full'; }); await PluginCommands.Layout.Update(ctx, { state }); - + if (hideLeft) { PluginCommands.State.SetCurrentObject(ctx, { state: ctx.state.dataState, ref: StateTransform.RootRef }); }