From c62f19623ccb5110c82ac5356405847b2422c560 Mon Sep 17 00:00:00 2001 From: Alexander Rose <alexander.rose@weirdbyte.de> Date: Sun, 22 Aug 2021 12:08:15 -0700 Subject: [PATCH] add optional marking pass - outlines visible and hidden parts of highlighted/selected groups - add highlightStrength/selectStrength renderer params --- CHANGELOG.md | 3 + src/mol-canvas3d/canvas3d.ts | 9 +- src/mol-canvas3d/passes/draw.ts | 31 ++- src/mol-canvas3d/passes/image.ts | 4 +- src/mol-canvas3d/passes/marking.ts | 194 ++++++++++++++++++ src/mol-canvas3d/passes/multi-sample.ts | 14 +- src/mol-geo/geometry/marker-data.ts | 23 ++- src/mol-gl/_spec/renderer.spec.ts | 12 +- src/mol-gl/renderable/schema.ts | 6 +- src/mol-gl/renderer.ts | 80 ++++++-- src/mol-gl/shader-code.ts | 2 - .../shader/chunks/apply-marker-color.glsl.ts | 7 +- .../chunks/assign-material-color.glsl.ts | 17 ++ .../shader/chunks/common-frag-params.glsl.ts | 18 ++ src/mol-gl/shader/chunks/common.glsl.ts | 4 + src/mol-gl/shader/chunks/wboit-params.glsl.ts | 20 -- src/mol-gl/shader/cylinders.frag.ts | 5 +- src/mol-gl/shader/direct-volume.frag.ts | 14 +- src/mol-gl/shader/image.frag.ts | 23 ++- src/mol-gl/shader/lines.frag.ts | 5 +- src/mol-gl/shader/marking/edge.frag.ts | 28 +++ src/mol-gl/shader/marking/overlay.frag.ts | 23 +++ src/mol-gl/shader/mesh.frag.ts | 5 +- src/mol-gl/shader/points.frag.ts | 5 +- src/mol-gl/shader/spheres.frag.ts | 5 +- src/mol-gl/shader/text.frag.ts | 5 +- src/mol-gl/webgl/render-item.ts | 2 +- src/mol-plugin/util/viewport-screenshot.ts | 22 +- src/mol-repr/visual.ts | 24 ++- src/mol-util/marker-action.ts | 85 ++++++++ 30 files changed, 601 insertions(+), 94 deletions(-) create mode 100644 src/mol-canvas3d/passes/marking.ts delete mode 100644 src/mol-gl/shader/chunks/wboit-params.glsl.ts create mode 100644 src/mol-gl/shader/marking/edge.frag.ts create mode 100644 src/mol-gl/shader/marking/overlay.frag.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 008c8afef..72167849c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,9 @@ Note that since we don't clearly distinguish between a public and private interf - Improved ``StructureElement.Loci.size`` performance (for marking large cellpack models) - Fix new ``TransformData`` issues (camera/bounding helper not showing up) - Improve marking performance (avoid superfluous calls to ``StructureElement.Loci.isWholeStructure``) +- Add optional marking pass + - Outlines visible and hidden parts of highlighted/selected groups + - Add highlightStrength/selectStrength renderer params ## [v2.2.2] - 2021-08-11 diff --git a/src/mol-canvas3d/canvas3d.ts b/src/mol-canvas3d/canvas3d.ts index 94e9c45f1..bae279c26 100644 --- a/src/mol-canvas3d/canvas3d.ts +++ b/src/mol-canvas3d/canvas3d.ts @@ -38,6 +38,7 @@ import { StereoCamera, StereoCameraParams } from './camera/stereo'; import { Helper } from './helper/helper'; import { Passes } from './passes/passes'; import { shallowEqual } from '../mol-util'; +import { MarkingParams } from './passes/marking'; export const Canvas3DParams = { camera: PD.Group({ @@ -80,6 +81,7 @@ export const Canvas3DParams = { multiSample: PD.Group(MultiSampleParams), postprocessing: PD.Group(PostprocessingParams), + marking: PD.Group(MarkingParams), renderer: PD.Group(RendererParams), trackball: PD.Group(TrackballControlsParams), debug: PD.Group(DebugHelperParams), @@ -390,7 +392,7 @@ namespace Canvas3D { if (MultiSamplePass.isEnabled(p.multiSample)) { multiSampleHelper.render(renderer, cam, scene, helper, true, p.transparentBackground, p); } else { - passes.draw.render(renderer, cam, scene, helper, true, p.transparentBackground, p.postprocessing); + passes.draw.render(renderer, cam, scene, helper, true, p.transparentBackground, p.postprocessing, p.marking); } pickHelper.dirty = true; didRender = true; @@ -636,6 +638,7 @@ namespace Canvas3D { viewport: p.viewport, postprocessing: { ...p.postprocessing }, + marking: { ...p.marking }, multiSample: { ...p.multiSample }, renderer: { ...renderer.props }, trackball: { ...controls.props }, @@ -771,6 +774,7 @@ namespace Canvas3D { } if (props.postprocessing) Object.assign(p.postprocessing, props.postprocessing); + if (props.marking) Object.assign(p.marking, props.marking); if (props.multiSample) Object.assign(p.multiSample, props.multiSample); if (props.renderer) renderer.setProps(props.renderer); if (props.trackball) controls.setProps(props.trackball); @@ -835,9 +839,6 @@ namespace Canvas3D { height = Math.round(p.viewport.params.height * gl.drawingBufferHeight); y = Math.round(gl.drawingBufferHeight - height - p.viewport.params.y * gl.drawingBufferHeight); width = Math.round(p.viewport.params.width * gl.drawingBufferWidth); - // if (x + width >= gl.drawingBufferWidth) width = gl.drawingBufferWidth - x; - // if (y + height >= gl.drawingBufferHeight) height = gl.drawingBufferHeight - y - 1; - // console.log({ x, y, width, height }); } if (oldX !== x || oldY !== y || oldWidth !== width || oldHeight !== height) { diff --git a/src/mol-canvas3d/passes/draw.ts b/src/mol-canvas3d/passes/draw.ts index 8fbebe985..90373c0df 100644 --- a/src/mol-canvas3d/passes/draw.ts +++ b/src/mol-canvas3d/passes/draw.ts @@ -26,6 +26,7 @@ import { copy_frag } from '../../mol-gl/shader/copy.frag'; import { StereoCamera } from '../camera/stereo'; import { WboitPass } from './wboit'; import { AntialiasingPass, PostprocessingPass, PostprocessingProps } from './postprocessing'; +import { MarkingPass, MarkingProps } from './marking'; const DepthMergeSchema = { ...QuadSchema, @@ -92,6 +93,7 @@ export class DrawPass { private copyFboPostprocessing: CopyRenderable private wboit: WboitPass | undefined + private readonly marking: MarkingPass readonly postprocessing: PostprocessingPass private readonly antialiasing: AntialiasingPass @@ -122,6 +124,7 @@ export class DrawPass { this.depthMerge = getDepthMergeRenderable(webgl, this.depthTexturePrimitives, this.depthTextureVolumes, this.packedDepth); this.wboit = enableWboit ? new WboitPass(webgl, width, height) : undefined; + this.marking = new MarkingPass(webgl, width, height); this.postprocessing = new PostprocessingPass(webgl, this); this.antialiasing = new AntialiasingPass(webgl, this); @@ -162,6 +165,7 @@ export class DrawPass { this.wboit.setSize(width, height); } + this.marking.setSize(width, height); this.postprocessing.setSize(width, height); this.antialiasing.setSize(width, height); } @@ -281,10 +285,11 @@ export class DrawPass { renderer.renderBlendedTransparent(scene.primitives, camera, null); } - private _render(renderer: Renderer, camera: ICamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean, postprocessingProps: PostprocessingProps) { + private _render(renderer: Renderer, camera: ICamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean, postprocessingProps: PostprocessingProps, markingProps: MarkingProps) { const volumeRendering = scene.volumes.renderables.length > 0; const postprocessingEnabled = PostprocessingPass.isEnabled(postprocessingProps); const antialiasingEnabled = AntialiasingPass.isEnabled(postprocessingProps); + const markingEnabled = MarkingPass.isEnabled(markingProps); const { x, y, width, height } = camera.viewport; renderer.setViewport(x, y, width, height); @@ -309,6 +314,22 @@ export class DrawPass { this.drawTarget.bind(); } + if (markingEnabled) { + const markingDepthTest = markingProps.ghostEdgeStrength < 1; + if (markingDepthTest) { + this.marking.depthTarget.bind(); + renderer.clear(false); + renderer.renderMarkingDepth(scene.primitives, camera, null); + } + + this.marking.maskTarget.bind(); + renderer.clear(false); + renderer.renderMarkingMask(scene.primitives, camera, markingDepthTest ? this.marking.depthTarget.texture : null); + + this.marking.update(markingProps); + this.marking.render(camera.viewport, postprocessingEnabled ? this.postprocessing.target : this.colorTarget); + } + if (helper.debug.isEnabled) { helper.debug.syncVisibility(); renderer.renderBlended(helper.debug.scene, camera, null); @@ -338,15 +359,15 @@ export class DrawPass { this.webgl.gl.flush(); } - render(renderer: Renderer, camera: Camera | StereoCamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean, postprocessingProps: PostprocessingProps) { + render(renderer: Renderer, camera: Camera | StereoCamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean, postprocessingProps: PostprocessingProps, markingProps: MarkingProps) { renderer.setTransparentBackground(transparentBackground); renderer.setDrawingBufferSize(this.colorTarget.getWidth(), this.colorTarget.getHeight()); if (StereoCamera.is(camera)) { - this._render(renderer, camera.left, scene, helper, toDrawingBuffer, transparentBackground, postprocessingProps); - this._render(renderer, camera.right, scene, helper, toDrawingBuffer, transparentBackground, postprocessingProps); + this._render(renderer, camera.left, scene, helper, toDrawingBuffer, transparentBackground, postprocessingProps, markingProps); + this._render(renderer, camera.right, scene, helper, toDrawingBuffer, transparentBackground, postprocessingProps, markingProps); } else { - this._render(renderer, camera, scene, helper, toDrawingBuffer, transparentBackground, postprocessingProps); + this._render(renderer, camera, scene, helper, toDrawingBuffer, transparentBackground, postprocessingProps, markingProps); } } diff --git a/src/mol-canvas3d/passes/image.ts b/src/mol-canvas3d/passes/image.ts index f27ff5122..bf56b9300 100644 --- a/src/mol-canvas3d/passes/image.ts +++ b/src/mol-canvas3d/passes/image.ts @@ -17,11 +17,13 @@ import { Viewport } from '../camera/util'; import { PixelData } from '../../mol-util/image'; import { Helper } from '../helper/helper'; import { CameraHelper, CameraHelperParams } from '../helper/camera-helper'; +import { MarkingParams } from './marking'; export const ImageParams = { transparentBackground: PD.Boolean(false), multiSample: PD.Group(MultiSampleParams), postprocessing: PD.Group(PostprocessingParams), + marking: PD.Group(MarkingParams), cameraHelper: PD.Group(CameraHelperParams), }; @@ -85,7 +87,7 @@ export class ImagePass { this.multiSampleHelper.render(this.renderer, this._camera, this.scene, this.helper, false, this.props.transparentBackground, this.props); this._colorTarget = this.multiSamplePass.colorTarget; } else { - this.drawPass.render(this.renderer, this._camera, this.scene, this.helper, false, this.props.transparentBackground, this.props.postprocessing); + this.drawPass.render(this.renderer, this._camera, this.scene, this.helper, false, this.props.transparentBackground, this.props.postprocessing, this.props.marking); this._colorTarget = this.drawPass.getColorTarget(this.props.postprocessing); } } diff --git a/src/mol-canvas3d/passes/marking.ts b/src/mol-canvas3d/passes/marking.ts new file mode 100644 index 000000000..df19ea731 --- /dev/null +++ b/src/mol-canvas3d/passes/marking.ts @@ -0,0 +1,194 @@ +/** + * Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { QuadSchema, QuadValues } from '../../mol-gl/compute/util'; +import { ComputeRenderable, createComputeRenderable } from '../../mol-gl/renderable'; +import { DefineSpec, TextureSpec, UniformSpec, Values } from '../../mol-gl/renderable/schema'; +import { ShaderCode } from '../../mol-gl/shader-code'; +import { WebGLContext } from '../../mol-gl/webgl/context'; +import { createComputeRenderItem } from '../../mol-gl/webgl/render-item'; +import { Texture } from '../../mol-gl/webgl/texture'; +import { Vec2, Vec3 } from '../../mol-math/linear-algebra'; +import { ValueCell } from '../../mol-util'; +import { ParamDefinition as PD } from '../../mol-util/param-definition'; +import { quad_vert } from '../../mol-gl/shader/quad.vert'; +import { overlay_frag } from '../../mol-gl/shader/marking/overlay.frag'; +import { Viewport } from '../camera/util'; +import { RenderTarget } from '../../mol-gl/webgl/render-target'; +import { Color } from '../../mol-util/color'; +import { edge_frag } from '../../mol-gl/shader/marking/edge.frag'; + +export const MarkingParams = { + enabled: PD.Boolean(false), + highlightEdgeColor: PD.Color(Color.darken(Color.fromNormalizedRgb(1.0, 0.4, 0.6), 1.0)), + selectEdgeColor: PD.Color(Color.darken(Color.fromNormalizedRgb(0.2, 1.0, 0.1), 1.0)), + edgeScale: PD.Numeric(1, { min: 1, max: 3, step: 1 }, { description: 'Thickness of the edge.' }), + ghostEdgeStrength: PD.Numeric(0.3, { min: 0, max: 1, step: 0.1 }, { description: 'Opacity of the hidden edges that are covered by other geometry. When set to 1, one less geometry render pass is done.' }), + innerEdgeFactor: PD.Numeric(1.5, { min: 0, max: 3, step: 0.1 }, { description: 'Factor to multiply the inner edge color with - for added contrast.' }), +}; +export type MarkingProps = PD.Values<typeof MarkingParams> + +export class MarkingPass { + static isEnabled(props: MarkingProps) { + return props.enabled; + } + + readonly depthTarget: RenderTarget + readonly maskTarget: RenderTarget + private readonly edgesTarget: RenderTarget + + private readonly edge: EdgeRenderable + private readonly overlay: OverlayRenderable + + constructor(private webgl: WebGLContext, width: number, height: number) { + this.depthTarget = webgl.createRenderTarget(width, height); + this.maskTarget = webgl.createRenderTarget(width, height); + this.edgesTarget = webgl.createRenderTarget(width, height); + + this.edge = getEdgeRenderable(webgl, this.maskTarget.texture); + this.overlay = getOverlayRenderable(webgl, this.edgesTarget.texture); + } + + private setEdgeState(viewport: Viewport) { + const { gl, state } = this.webgl; + + state.enable(gl.SCISSOR_TEST); + state.enable(gl.BLEND); + state.blendFunc(gl.ONE, gl.ONE); + state.blendEquation(gl.FUNC_ADD); + state.disable(gl.DEPTH_TEST); + state.depthMask(false); + + const { x, y, width, height } = viewport; + gl.viewport(x, y, width, height); + gl.scissor(x, y, width, height); + + state.clearColor(0, 0, 0, 0); + gl.clear(gl.COLOR_BUFFER_BIT); + } + + private setOverlayState(viewport: Viewport) { + const { gl, state } = this.webgl; + + state.enable(gl.SCISSOR_TEST); + state.enable(gl.BLEND); + state.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); + state.blendEquation(gl.FUNC_ADD); + state.disable(gl.DEPTH_TEST); + state.depthMask(false); + + const { x, y, width, height } = viewport; + gl.viewport(x, y, width, height); + gl.scissor(x, y, width, height); + } + + setSize(width: number, height: number) { + const w = this.depthTarget.getWidth(); + const h = this.depthTarget.getHeight(); + + if (width !== w || height !== h) { + this.depthTarget.setSize(width, height); + this.maskTarget.setSize(width, height); + this.edgesTarget.setSize(width, height); + + ValueCell.update(this.edge.values.uTexSizeInv, Vec2.set(this.edge.values.uTexSizeInv.ref.value, 1 / width, 1 / height)); + ValueCell.update(this.overlay.values.uTexSizeInv, Vec2.set(this.overlay.values.uTexSizeInv.ref.value, 1 / width, 1 / height)); + } + } + + update(props: MarkingProps) { + const { highlightEdgeColor, selectEdgeColor, edgeScale, innerEdgeFactor, ghostEdgeStrength } = props; + + const { values: edgeValues } = this.edge; + const _edgeScale = Math.round(edgeScale * this.webgl.pixelRatio); + if (edgeValues.dEdgeScale.ref.value !== _edgeScale) { + ValueCell.update(edgeValues.dEdgeScale, _edgeScale); + this.edge.update(); + } + + const { values: overlayValues } = this.overlay; + ValueCell.update(overlayValues.uHighlightEdgeColor, Color.toVec3Normalized(overlayValues.uHighlightEdgeColor.ref.value, highlightEdgeColor)); + ValueCell.update(overlayValues.uSelectEdgeColor, Color.toVec3Normalized(overlayValues.uSelectEdgeColor.ref.value, selectEdgeColor)); + ValueCell.update(overlayValues.uInnerEdgeFactor, innerEdgeFactor); + ValueCell.update(overlayValues.uGhostEdgeStrength, ghostEdgeStrength); + } + + render(viewport: Viewport, target: RenderTarget | undefined) { + this.edgesTarget.bind(); + this.setEdgeState(viewport); + this.edge.render(); + + if (target) { + target.bind(); + } else { + this.webgl.unbindFramebuffer(); + } + this.setOverlayState(viewport); + this.overlay.render(); + } +} + +// + +const EdgeSchema = { + ...QuadSchema, + tMaskTexture: TextureSpec('texture', 'rgba', 'ubyte', 'linear'), + uTexSizeInv: UniformSpec('v2'), + dEdgeScale: DefineSpec('number'), +}; +const EdgeShaderCode = ShaderCode('edge', quad_vert, edge_frag); +type EdgeRenderable = ComputeRenderable<Values<typeof EdgeSchema>> + +function getEdgeRenderable(ctx: WebGLContext, maskTexture: Texture): EdgeRenderable { + const width = maskTexture.getWidth(); + const height = maskTexture.getHeight(); + + const values: Values<typeof EdgeSchema> = { + ...QuadValues, + tMaskTexture: ValueCell.create(maskTexture), + uTexSizeInv: ValueCell.create(Vec2.create(1 / width, 1 / height)), + dEdgeScale: ValueCell.create(1), + }; + + const schema = { ...EdgeSchema }; + const renderItem = createComputeRenderItem(ctx, 'triangles', EdgeShaderCode, schema, values); + + return createComputeRenderable(renderItem, values); +} + +// + +const OverlaySchema = { + ...QuadSchema, + tEdgeTexture: TextureSpec('texture', 'rgba', 'ubyte', 'linear'), + uTexSizeInv: UniformSpec('v2'), + uHighlightEdgeColor: UniformSpec('v3'), + uSelectEdgeColor: UniformSpec('v3'), + uGhostEdgeStrength: UniformSpec('f'), + uInnerEdgeFactor: UniformSpec('f'), +}; +const OverlayShaderCode = ShaderCode('overlay', quad_vert, overlay_frag); +type OverlayRenderable = ComputeRenderable<Values<typeof OverlaySchema>> + +function getOverlayRenderable(ctx: WebGLContext, edgeTexture: Texture): OverlayRenderable { + const width = edgeTexture.getWidth(); + const height = edgeTexture.getHeight(); + + const values: Values<typeof OverlaySchema> = { + ...QuadValues, + tEdgeTexture: ValueCell.create(edgeTexture), + uTexSizeInv: ValueCell.create(Vec2.create(1 / width, 1 / height)), + uHighlightEdgeColor: ValueCell.create(Vec3()), + uSelectEdgeColor: ValueCell.create(Vec3()), + uGhostEdgeStrength: ValueCell.create(0), + uInnerEdgeFactor: ValueCell.create(0), + }; + + const schema = { ...OverlaySchema }; + const renderItem = createComputeRenderItem(ctx, 'triangles', OverlayShaderCode, schema, values); + + return createComputeRenderable(renderItem, values); +} \ No newline at end of file diff --git a/src/mol-canvas3d/passes/multi-sample.ts b/src/mol-canvas3d/passes/multi-sample.ts index a3b9c6249..a4b56b204 100644 --- a/src/mol-canvas3d/passes/multi-sample.ts +++ b/src/mol-canvas3d/passes/multi-sample.ts @@ -22,9 +22,9 @@ import { Renderer } from '../../mol-gl/renderer'; import { Scene } from '../../mol-gl/scene'; import { Helper } from '../helper/helper'; import { StereoCamera } from '../camera/stereo'; - import { quad_vert } from '../../mol-gl/shader/quad.vert'; import { compose_frag } from '../../mol-gl/shader/compose.frag'; +import { MarkingProps } from './marking'; const ComposeSchema = { ...QuadSchema, @@ -55,7 +55,11 @@ export const MultiSampleParams = { }; export type MultiSampleProps = PD.Values<typeof MultiSampleParams> -type Props = { multiSample: MultiSampleProps, postprocessing: PostprocessingProps } +type Props = { + multiSample: MultiSampleProps + postprocessing: PostprocessingProps + marking: MarkingProps +} export class MultiSamplePass { static isEnabled(props: MultiSampleProps) { @@ -144,7 +148,7 @@ export class MultiSamplePass { ValueCell.update(compose.values.uWeight, sampleWeight); // render scene - drawPass.render(renderer, camera, scene, helper, false, transparentBackground, props.postprocessing); + drawPass.render(renderer, camera, scene, helper, false, transparentBackground, props.postprocessing, props.marking); // compose rendered scene with compose target composeTarget.bind(); @@ -194,7 +198,7 @@ export class MultiSamplePass { const sampleWeight = 1.0 / offsetList.length; if (sampleIndex === -1) { - drawPass.render(renderer, camera, scene, helper, false, transparentBackground, props.postprocessing); + drawPass.render(renderer, camera, scene, helper, false, transparentBackground, props.postprocessing, props.marking); ValueCell.update(compose.values.uWeight, 1.0); ValueCell.update(compose.values.tColor, drawPass.getColorTarget(props.postprocessing).texture); compose.update(); @@ -222,7 +226,7 @@ export class MultiSamplePass { camera.update(); // render scene - drawPass.render(renderer, camera, scene, helper, false, transparentBackground, props.postprocessing); + drawPass.render(renderer, camera, scene, helper, false, transparentBackground, props.postprocessing, props.marking); // compose rendered scene with compose target composeTarget.bind(); diff --git a/src/mol-geo/geometry/marker-data.ts b/src/mol-geo/geometry/marker-data.ts index b22e0ce9d..4112a80cf 100644 --- a/src/mol-geo/geometry/marker-data.ts +++ b/src/mol-geo/geometry/marker-data.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -11,18 +11,35 @@ import { TextureImage, createTextureImage } from '../../mol-gl/renderable/util'; export type MarkerData = { tMarker: ValueCell<TextureImage<Uint8Array>> uMarkerTexDim: ValueCell<Vec2> + markerAverage: ValueCell<number> + markerStatus: ValueCell<number> +} + +export function getMarkersAverage(array: Uint8Array, count: number): number { + if (count === 0) return 0; + let sum = 0; + for (let i = 0; i < count; ++i) { + if (array[i]) sum += 1; + } + return sum / count; } export function createMarkers(count: number, markerData?: MarkerData): MarkerData { const markers = createTextureImage(Math.max(1, count), 1, Uint8Array, markerData && markerData.tMarker.ref.value.array); + const average = getMarkersAverage(markers.array, count); + const status = average === 0 ? 0 : -1; if (markerData) { ValueCell.update(markerData.tMarker, markers); ValueCell.update(markerData.uMarkerTexDim, Vec2.create(markers.width, markers.height)); + ValueCell.updateIfChanged(markerData.markerAverage, average); + ValueCell.updateIfChanged(markerData.markerStatus, status); return markerData; } else { return { tMarker: ValueCell.create(markers), uMarkerTexDim: ValueCell.create(Vec2.create(markers.width, markers.height)), + markerAverage: ValueCell.create(average), + markerStatus: ValueCell.create(status), }; } } @@ -32,11 +49,15 @@ export function createEmptyMarkers(markerData?: MarkerData): MarkerData { if (markerData) { ValueCell.update(markerData.tMarker, emptyMarkerTexture); ValueCell.update(markerData.uMarkerTexDim, Vec2.create(1, 1)); + ValueCell.updateIfChanged(markerData.markerAverage, 0); + ValueCell.updateIfChanged(markerData.markerStatus, 0); return markerData; } else { return { tMarker: ValueCell.create(emptyMarkerTexture), uMarkerTexDim: ValueCell.create(Vec2.create(1, 1)), + markerAverage: ValueCell.create(0), + markerStatus: ValueCell.create(0), }; } } \ No newline at end of file diff --git a/src/mol-gl/_spec/renderer.spec.ts b/src/mol-gl/_spec/renderer.spec.ts index b2bcd93b1..3f1ba5f25 100644 --- a/src/mol-gl/_spec/renderer.spec.ts +++ b/src/mol-gl/_spec/renderer.spec.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -134,17 +134,17 @@ describe('renderer', () => { scene.commit(); expect(ctx.stats.resourceCounts.attribute).toBe(ctx.isWebGL2 ? 4 : 5); expect(ctx.stats.resourceCounts.texture).toBe(7); - expect(ctx.stats.resourceCounts.vertexArray).toBe(6); - expect(ctx.stats.resourceCounts.program).toBe(6); - expect(ctx.stats.resourceCounts.shader).toBe(12); + expect(ctx.stats.resourceCounts.vertexArray).toBe(8); + expect(ctx.stats.resourceCounts.program).toBe(8); + expect(ctx.stats.resourceCounts.shader).toBe(16); scene.remove(points); scene.commit(); expect(ctx.stats.resourceCounts.attribute).toBe(0); expect(ctx.stats.resourceCounts.texture).toBe(0); expect(ctx.stats.resourceCounts.vertexArray).toBe(0); - expect(ctx.stats.resourceCounts.program).toBe(6); - expect(ctx.stats.resourceCounts.shader).toBe(12); + expect(ctx.stats.resourceCounts.program).toBe(8); + expect(ctx.stats.resourceCounts.shader).toBe(16); ctx.resources.destroy(); expect(ctx.stats.resourceCounts.program).toBe(0); diff --git a/src/mol-gl/renderable/schema.ts b/src/mol-gl/renderable/schema.ts index 9c8686e76..9d869d04b 100644 --- a/src/mol-gl/renderable/schema.ts +++ b/src/mol-gl/renderable/schema.ts @@ -106,7 +106,6 @@ export type RenderableSchema = { } export type RenderableValues = { readonly [k: string]: ValueCell<any> } - // export const GlobalUniformSchema = { @@ -161,10 +160,13 @@ export const GlobalUniformSchema = { uHighlightColor: UniformSpec('v3'), uSelectColor: UniformSpec('v3'), + uHighlightStrength: UniformSpec('f'), + uSelectStrength: UniformSpec('f'), uXrayEdgeFalloff: UniformSpec('f'), uRenderWboit: UniformSpec('b'), + uMarkingDepthTest: UniformSpec('b'), } as const; export type GlobalUniformSchema = typeof GlobalUniformSchema export type GlobalUniformValues = Values<GlobalUniformSchema> @@ -210,6 +212,8 @@ export type SizeValues = Values<SizeSchema> export const MarkerSchema = { uMarkerTexDim: UniformSpec('v2'), tMarker: TextureSpec('image-uint8', 'alpha', 'ubyte', 'nearest'), + markerAverage: ValueSpec('number'), + markerStatus: ValueSpec('number'), } as const; export type MarkerSchema = typeof MarkerSchema export type MarkerValues = Values<MarkerSchema> diff --git a/src/mol-gl/renderer.ts b/src/mol-gl/renderer.ts index 372a4815d..82991710f 100644 --- a/src/mol-gl/renderer.ts +++ b/src/mol-gl/renderer.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -48,6 +48,8 @@ interface Renderer { renderPick: (group: Scene.Group, camera: ICamera, variant: GraphicsRenderVariant, depthTexture: Texture | null) => void renderDepth: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void + renderMarkingDepth: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void + renderMarkingMask: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void renderBlended: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void renderBlendedOpaque: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void renderBlendedTransparent: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void @@ -76,6 +78,8 @@ export const RendererParams = { highlightColor: PD.Color(Color.fromNormalizedRgb(1.0, 0.4, 0.6)), selectColor: PD.Color(Color.fromNormalizedRgb(0.2, 1.0, 0.1)), + highlightStrength: PD.Numeric(0.7, { min: 0.0, max: 1.0, step: 0.1 }), + selectStrength: PD.Numeric(0.7, { min: 0.0, max: 1.0, step: 0.1 }), xrayEdgeFalloff: PD.Numeric(1, { min: 0.0, max: 3.0, step: 0.1 }), @@ -242,6 +246,7 @@ namespace Renderer { uFogColor: ValueCell.create(bgColor), uRenderWboit: ValueCell.create(false), + uMarkingDepthTest: ValueCell.create(false), uTransparentBackground: ValueCell.create(false), @@ -267,6 +272,8 @@ namespace Renderer { uHighlightColor: ValueCell.create(Color.toVec3Normalized(Vec3(), p.highlightColor)), uSelectColor: ValueCell.create(Color.toVec3Normalized(Vec3(), p.selectColor)), + uHighlightStrength: ValueCell.create(p.highlightStrength), + uSelectStrength: ValueCell.create(p.selectStrength), uXrayEdgeFalloff: ValueCell.create(p.xrayEdgeFalloff), }; @@ -375,7 +382,7 @@ namespace Renderer { ValueCell.updateIfChanged(globalUniforms.uTransparentBackground, transparentBackground); }; - const updateInternal = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null, renderWboit: boolean) => { + const updateInternal = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null, renderWboit: boolean, markingDepthTest: boolean) => { arrayMapUpsert(sharedTexturesList, 'tDepth', depthTexture || nullDepthTexture); ValueCell.update(globalUniforms.uModel, group.view); @@ -385,6 +392,7 @@ namespace Renderer { ValueCell.update(globalUniforms.uInvModelViewProjection, Mat4.invert(invModelViewProjection, modelViewProjection)); ValueCell.updateIfChanged(globalUniforms.uRenderWboit, renderWboit); + ValueCell.updateIfChanged(globalUniforms.uMarkingDepthTest, markingDepthTest); state.enable(gl.SCISSOR_TEST); state.colorMask(true, true, true, true); @@ -402,7 +410,7 @@ namespace Renderer { state.enable(gl.DEPTH_TEST); state.depthMask(true); - updateInternal(group, camera, depthTexture, false); + updateInternal(group, camera, depthTexture, false, false); const { renderables } = group; for (let i = 0, il = renderables.length; i < il; ++i) { @@ -417,7 +425,7 @@ namespace Renderer { state.enable(gl.DEPTH_TEST); state.depthMask(true); - updateInternal(group, camera, depthTexture, false); + updateInternal(group, camera, depthTexture, false, false); const { renderables } = group; for (let i = 0, il = renderables.length; i < il; ++i) { @@ -425,6 +433,40 @@ namespace Renderer { } }; + const renderMarkingDepth = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => { + state.disable(gl.BLEND); + state.enable(gl.DEPTH_TEST); + state.depthMask(true); + + updateInternal(group, camera, depthTexture, false, false); + + const { renderables } = group; + for (let i = 0, il = renderables.length; i < il; ++i) { + const r = renderables[i]; + + if (r.values.markerAverage.ref.value !== 1) { + renderObject(renderables[i], 'markingDepth'); + } + } + }; + + const renderMarkingMask = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => { + state.disable(gl.BLEND); + state.enable(gl.DEPTH_TEST); + state.depthMask(true); + + updateInternal(group, camera, depthTexture, false, !!depthTexture); + + const { renderables } = group; + for (let i = 0, il = renderables.length; i < il; ++i) { + const r = renderables[i]; + + if (r.values.markerAverage.ref.value > 0) { + renderObject(renderables[i], 'markingMask'); + } + } + }; + const renderBlended = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => { renderBlendedOpaque(group, camera, depthTexture); renderBlendedTransparent(group, camera, depthTexture); @@ -435,7 +477,7 @@ namespace Renderer { state.enable(gl.DEPTH_TEST); state.depthMask(true); - updateInternal(group, camera, depthTexture, false); + updateInternal(group, camera, depthTexture, false, false); const { renderables } = group; for (let i = 0, il = renderables.length; i < il; ++i) { @@ -449,7 +491,7 @@ namespace Renderer { const renderBlendedTransparent = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => { state.enable(gl.DEPTH_TEST); - updateInternal(group, camera, depthTexture, false); + updateInternal(group, camera, depthTexture, false, false); const { renderables } = group; @@ -481,13 +523,13 @@ namespace Renderer { state.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); state.enable(gl.BLEND); - updateInternal(group, camera, depthTexture, false); + updateInternal(group, camera, depthTexture, false, false); const { renderables } = group; for (let i = 0, il = renderables.length; i < il; ++i) { const r = renderables[i]; - // TODO: simplify, handle on renderable.state??? + // TODO: simplify, handle in renderable.state??? // uAlpha is updated in "render" so we need to recompute it here const alpha = clamp(r.values.alpha.ref.value * r.state.alphaFactor, 0, 1); if (alpha === 1 && r.values.transparencyAverage.ref.value !== 1 && !r.values.dXrayShaded?.ref.value) { @@ -500,13 +542,13 @@ namespace Renderer { state.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); state.enable(gl.BLEND); - updateInternal(group, camera, depthTexture, false); + updateInternal(group, camera, depthTexture, false, false); const { renderables } = group; for (let i = 0, il = renderables.length; i < il; ++i) { const r = renderables[i]; - // TODO: simplify, handle on renderable.state??? + // TODO: simplify, handle in renderable.state??? // uAlpha is updated in "render" so we need to recompute it here const alpha = clamp(r.values.alpha.ref.value * r.state.alphaFactor, 0, 1); if (alpha < 1 || r.values.transparencyAverage.ref.value > 0 || r.values.dXrayShaded?.ref.value) { @@ -520,13 +562,13 @@ namespace Renderer { state.enable(gl.DEPTH_TEST); state.depthMask(true); - updateInternal(group, camera, depthTexture, false); + updateInternal(group, camera, depthTexture, false, false); const { renderables } = group; for (let i = 0, il = renderables.length; i < il; ++i) { const r = renderables[i]; - // TODO: simplify, handle on renderable.state??? + // TODO: simplify, handle in renderable.state??? // uAlpha is updated in "render" so we need to recompute it here const alpha = clamp(r.values.alpha.ref.value * r.state.alphaFactor, 0, 1); if (alpha === 1 && r.values.transparencyAverage.ref.value !== 1 && r.values.dRenderMode?.ref.value !== 'volume' && !r.values.dPointFilledCircle?.ref.value && !r.values.dXrayShaded?.ref.value) { @@ -536,13 +578,13 @@ namespace Renderer { }; const renderWboitTransparent = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => { - updateInternal(group, camera, depthTexture, true); + updateInternal(group, camera, depthTexture, true, false); const { renderables } = group; for (let i = 0, il = renderables.length; i < il; ++i) { const r = renderables[i]; - // TODO: simplify, handle on renderable.state??? + // TODO: simplify, handle in renderable.state??? // uAlpha is updated in "render" so we need to recompute it here const alpha = clamp(r.values.alpha.ref.value * r.state.alphaFactor, 0, 1); if (alpha < 1 || r.values.transparencyAverage.ref.value > 0 || r.values.dRenderMode?.ref.value === 'volume' || r.values.dPointFilledCircle?.ref.value || !!r.values.uBackgroundColor || r.values.dXrayShaded?.ref.value) { @@ -577,6 +619,8 @@ namespace Renderer { renderPick, renderDepth, + renderMarkingDepth, + renderMarkingMask, renderBlended, renderBlendedOpaque, renderBlendedTransparent, @@ -618,6 +662,14 @@ namespace Renderer { p.selectColor = props.selectColor; ValueCell.update(globalUniforms.uSelectColor, Color.toVec3Normalized(globalUniforms.uSelectColor.ref.value, p.selectColor)); } + if (props.highlightStrength !== undefined && props.highlightStrength !== p.highlightStrength) { + p.highlightStrength = props.highlightStrength; + ValueCell.update(globalUniforms.uHighlightStrength, p.highlightStrength); + } + if (props.selectStrength !== undefined && props.selectStrength !== p.selectStrength) { + p.selectStrength = props.selectStrength; + ValueCell.update(globalUniforms.uSelectStrength, p.selectStrength); + } if (props.xrayEdgeFalloff !== undefined && props.xrayEdgeFalloff !== p.xrayEdgeFalloff) { p.xrayEdgeFalloff = props.xrayEdgeFalloff; diff --git a/src/mol-gl/shader-code.ts b/src/mol-gl/shader-code.ts index 153f2043c..2e507084f 100644 --- a/src/mol-gl/shader-code.ts +++ b/src/mol-gl/shader-code.ts @@ -65,7 +65,6 @@ import { size_vert_params } from './shader/chunks/size-vert-params.glsl'; import { texture3d_from_1d_trilinear } from './shader/chunks/texture3d-from-1d-trilinear.glsl'; import { texture3d_from_2d_linear } from './shader/chunks/texture3d-from-2d-linear.glsl'; import { texture3d_from_2d_nearest } from './shader/chunks/texture3d-from-2d-nearest.glsl'; -import { wboit_params } from './shader/chunks/wboit-params.glsl'; import { wboit_write } from './shader/chunks/wboit-write.glsl'; const ShaderChunks: { [k: string]: string } = { @@ -99,7 +98,6 @@ const ShaderChunks: { [k: string]: string } = { texture3d_from_1d_trilinear, texture3d_from_2d_linear, texture3d_from_2d_nearest, - wboit_params, wboit_write }; diff --git a/src/mol-gl/shader/chunks/apply-marker-color.glsl.ts b/src/mol-gl/shader/chunks/apply-marker-color.glsl.ts index ad121dcc8..53291a3a4 100644 --- a/src/mol-gl/shader/chunks/apply-marker-color.glsl.ts +++ b/src/mol-gl/shader/chunks/apply-marker-color.glsl.ts @@ -2,10 +2,11 @@ export const apply_marker_color = ` float marker = floor(vMarker * 255.0 + 0.5); // rounding required to work on some cards on win if (marker > 0.1) { if (intMod(marker, 2.0) > 0.1) { - gl_FragColor.rgb = mix(uHighlightColor, gl_FragColor.rgb, 0.3); - gl_FragColor.a = max(0.02, gl_FragColor.a); // for direct-volume rendering + gl_FragColor.rgb = mix(gl_FragColor.rgb, uHighlightColor, uHighlightStrength); + gl_FragColor.a = max(gl_FragColor.a, uHighlightStrength * 0.002); // for direct-volume rendering } else { - gl_FragColor.rgb = mix(uSelectColor, gl_FragColor.rgb, 0.3); + gl_FragColor.rgb = mix(gl_FragColor.rgb, uSelectColor, uSelectStrength); + gl_FragColor.a = max(gl_FragColor.a, uSelectStrength * 0.002); // for direct-volume rendering } } `; \ No newline at end of file diff --git a/src/mol-gl/shader/chunks/assign-material-color.glsl.ts b/src/mol-gl/shader/chunks/assign-material-color.glsl.ts index 247a2b6ca..aac6bc42f 100644 --- a/src/mol-gl/shader/chunks/assign-material-color.glsl.ts +++ b/src/mol-gl/shader/chunks/assign-material-color.glsl.ts @@ -20,6 +20,23 @@ export const assign_material_color = ` #else vec4 material = packDepthToRGBA(gl_FragCoord.z); #endif +#elif defined(dRenderVariant_markingDepth) + if (vMarker > 0.0) + discard; + #ifdef enabledFragDepth + vec4 material = packDepthToRGBA(gl_FragDepthEXT); + #else + vec4 material = packDepthToRGBA(gl_FragCoord.z); + #endif +#elif defined(dRenderVariant_markingMask) + if (vMarker == 0.0) + discard; + float depthTest = 1.0; + if (uMarkingDepthTest) { + depthTest = (fragmentDepth >= getDepth(gl_FragCoord.xy / uDrawingBufferSize)) ? 1.0 : 0.0; + } + bool isHighlight = intMod(floor(vMarker * 255.0 + 0.5), 2.0) > 0.1; + vec4 material = vec4(0.0, depthTest, isHighlight ? 1.0 : 0.0, 1.0); #endif // apply screendoor transparency diff --git a/src/mol-gl/shader/chunks/common-frag-params.glsl.ts b/src/mol-gl/shader/chunks/common-frag-params.glsl.ts index 416535c82..380fcf2a0 100644 --- a/src/mol-gl/shader/chunks/common-frag-params.glsl.ts +++ b/src/mol-gl/shader/chunks/common-frag-params.glsl.ts @@ -21,6 +21,8 @@ uniform int uGroupCount; uniform vec3 uHighlightColor; uniform vec3 uSelectColor; +uniform float uHighlightStrength; +uniform float uSelectStrength; #if __VERSION__ == 100 varying float vMarker; #else @@ -52,4 +54,20 @@ bool interior; uniform float uXrayEdgeFalloff; uniform mat4 uProjection; + +uniform bool uRenderWboit; +uniform bool uMarkingDepthTest; + +uniform sampler2D tDepth; +uniform vec2 uDrawingBufferSize; + +float getDepth(const in vec2 coords) { + // always packed due to merged depth from primitives and volumes + return unpackRGBAToDepth(texture2D(tDepth, coords)); +} + +float calcDepth(const in vec3 pos) { + vec2 clipZW = pos.z * uProjection[2].zw + uProjection[3].zw; + return 0.5 + 0.5 * clipZW.x / clipZW.y; +} `; \ No newline at end of file diff --git a/src/mol-gl/shader/chunks/common.glsl.ts b/src/mol-gl/shader/chunks/common.glsl.ts index 14296e294..e67f2d784 100644 --- a/src/mol-gl/shader/chunks/common.glsl.ts +++ b/src/mol-gl/shader/chunks/common.glsl.ts @@ -9,6 +9,10 @@ export const common = ` #define dRenderVariant_pick #endif +#if defined(dRenderVariant_markingDepth) || defined(dRenderVariant_markingMask) + #define dRenderVariant_marking +#endif + #if defined(dColorType_instance) || defined(dColorType_group) || defined(dColorType_groupInstance) || defined(dColorType_vertex) || defined(dColorType_vertexInstance) #define dColorType_texture #endif diff --git a/src/mol-gl/shader/chunks/wboit-params.glsl.ts b/src/mol-gl/shader/chunks/wboit-params.glsl.ts deleted file mode 100644 index 53b070225..000000000 --- a/src/mol-gl/shader/chunks/wboit-params.glsl.ts +++ /dev/null @@ -1,20 +0,0 @@ -export const wboit_params = ` -#if defined(dRenderVariant_colorWboit) - #if !defined(dRenderMode_volume) && !defined(dRenderMode_isosurface) - uniform sampler2D tDepth; - uniform vec2 uDrawingBufferSize; - - float getDepth(const in vec2 coords) { - // always packed due to merged depth from primitives and volumes - return unpackRGBAToDepth(texture2D(tDepth, coords)); - } - #endif -#endif - -uniform bool uRenderWboit; - -float calcDepth(const in vec3 pos) { - vec2 clipZW = pos.z * uProjection[2].zw + uProjection[3].zw; - return 0.5 + 0.5 * clipZW.x / clipZW.y; -} -`; \ No newline at end of file diff --git a/src/mol-gl/shader/cylinders.frag.ts b/src/mol-gl/shader/cylinders.frag.ts index aeedc563e..6efcca93f 100644 --- a/src/mol-gl/shader/cylinders.frag.ts +++ b/src/mol-gl/shader/cylinders.frag.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -24,7 +24,6 @@ uniform vec3 uCameraPosition; #include color_frag_params #include light_frag_params #include common_clip -#include wboit_params // adapted from https://www.shadertoy.com/view/4lcSRn // The MIT License, Copyright 2016 Inigo Quilez @@ -121,6 +120,8 @@ void main() { gl_FragColor = material; #elif defined(dRenderVariant_depth) gl_FragColor = material; + #elif defined(dRenderVariant_marking) + gl_FragColor = material; #elif defined(dRenderVariant_color) #ifdef dIgnoreLight gl_FragColor = material; diff --git a/src/mol-gl/shader/direct-volume.frag.ts b/src/mol-gl/shader/direct-volume.frag.ts index 98649b770..fe59807de 100644 --- a/src/mol-gl/shader/direct-volume.frag.ts +++ b/src/mol-gl/shader/direct-volume.frag.ts @@ -53,6 +53,8 @@ uniform int uGroupCount; uniform vec3 uHighlightColor; uniform vec3 uSelectColor; +uniform float uHighlightStrength; +uniform float uSelectStrength; uniform vec2 uMarkerTexDim; uniform sampler2D tMarker; @@ -69,6 +71,8 @@ uniform bool uInteriorColorFlag; uniform vec3 uInteriorColor; bool interior; +uniform bool uRenderWboit; + uniform float uNear; uniform float uFar; uniform float uIsOrtho; @@ -122,7 +126,10 @@ uniform mat4 uCartnToUnit; } #endif -#include wboit_params +float calcDepth(const in vec3 pos) { + vec2 clipZW = pos.z * uProjection[2].zw + uProjection[3].zw; + return 0.5 + 0.5 * clipZW.x / clipZW.y; +} vec4 transferFunction(float value) { return texture2D(tTransferTex, vec2(value, 0.0)); @@ -432,6 +439,11 @@ void main() { if (gl_FrontFacing) discard; + #ifdef dRenderVariant_marking + // not supported + discard; + #endif + #if defined(dRenderVariant_pick) || defined(dRenderVariant_depth) #if defined(dRenderMode_volume) // always ignore pick & depth for volume diff --git a/src/mol-gl/shader/image.frag.ts b/src/mol-gl/shader/image.frag.ts index f6e26255b..bf6ec0692 100644 --- a/src/mol-gl/shader/image.frag.ts +++ b/src/mol-gl/shader/image.frag.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -11,7 +11,6 @@ precision highp int; #include read_from_texture #include common_frag_params #include common_clip -#include wboit_params uniform vec2 uImageTexDim; uniform sampler2D tImageTex; @@ -105,7 +104,6 @@ void main() { #if defined(dRenderVariant_pick) if (imageData.a < 0.3) discard; - #if defined(dRenderVariant_pickObject) gl_FragColor = vec4(encodeFloatRGB(float(uObjectId)), 1.0); #elif defined(dRenderVariant_pickInstance) @@ -116,12 +114,27 @@ void main() { #elif defined(dRenderVariant_depth) if (imageData.a < 0.05) discard; - gl_FragColor = packDepthToRGBA(gl_FragCoord.z); + #elif defined(dRenderVariant_marking) + float group = decodeFloatRGB(texture2D(tGroupTex, vUv).rgb); + float vMarker = readFromTexture(tMarker, vInstance * float(uGroupCount) + group, uMarkerTexDim).a; + #if defined(dRenderVariant_markingDepth) + if (vMarker > 0.0 || imageData.a < 0.05) + discard; + gl_FragColor = packDepthToRGBA(gl_FragCoord.z); + #elif defined(dRenderVariant_markingMask) + if (vMarker == 0.0 || imageData.a < 0.05) + discard; + float depthTest = 1.0; + if (uMarkingDepthTest) { + depthTest = (fragmentDepth >= getDepth(gl_FragCoord.xy / uDrawingBufferSize)) ? 1.0 : 0.0; + } + bool isHighlight = intMod(floor(vMarker * 255.0 + 0.5), 2.0) > 0.1; + gl_FragColor = vec4(0.0, depthTest, isHighlight ? 1.0 : 0.0, 1.0); + #endif #elif defined(dRenderVariant_color) if (imageData.a < 0.05) discard; - gl_FragColor = imageData; gl_FragColor.a *= uAlpha; diff --git a/src/mol-gl/shader/lines.frag.ts b/src/mol-gl/shader/lines.frag.ts index bc509c148..1340a5efa 100644 --- a/src/mol-gl/shader/lines.frag.ts +++ b/src/mol-gl/shader/lines.frag.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -12,7 +12,6 @@ precision highp int; #include common_frag_params #include color_frag_params #include common_clip -#include wboit_params void main(){ #include clip_pixel @@ -26,6 +25,8 @@ void main(){ gl_FragColor = material; #elif defined(dRenderVariant_depth) gl_FragColor = material; + #elif defined(dRenderVariant_marking) + gl_FragColor = material; #elif defined(dRenderVariant_color) gl_FragColor = material; diff --git a/src/mol-gl/shader/marking/edge.frag.ts b/src/mol-gl/shader/marking/edge.frag.ts new file mode 100644 index 000000000..475db2b0d --- /dev/null +++ b/src/mol-gl/shader/marking/edge.frag.ts @@ -0,0 +1,28 @@ +export const edge_frag = ` +precision highp float; +precision highp sampler2D; + +uniform sampler2D tMaskTexture; +uniform vec2 uTexSizeInv; + +void main() { + vec2 coords = gl_FragCoord.xy * uTexSizeInv; + vec4 offset = vec4(float(dEdgeScale), 0.0, 0.0, float(dEdgeScale)) * vec4(uTexSizeInv, uTexSizeInv); + vec4 c0 = texture2D(tMaskTexture, coords); + vec4 c1 = texture2D(tMaskTexture, coords + offset.xy); + vec4 c2 = texture2D(tMaskTexture, coords - offset.xy); + vec4 c3 = texture2D(tMaskTexture, coords + offset.yw); + vec4 c4 = texture2D(tMaskTexture, coords - offset.yw); + float diff1 = (c1.r - c2.r) * 0.5; + float diff2 = (c3.r - c4.r) * 0.5; + float d = length(vec2(diff1, diff2)); + if (d <= 0.0) + discard; + float a1 = min(c1.g, c2.g); + float a2 = min(c3.g, c4.g); + float visibility = min(a1, a2) > 0.001 ? 1.0 : 0.0; + float mask = c0.r; + float marker = min(c1.b, min(c2.b, min(c3.b, c4.b))); + gl_FragColor = vec4(visibility, mask, marker, 1.0); +} +`; \ No newline at end of file diff --git a/src/mol-gl/shader/marking/overlay.frag.ts b/src/mol-gl/shader/marking/overlay.frag.ts new file mode 100644 index 000000000..2c8450ee9 --- /dev/null +++ b/src/mol-gl/shader/marking/overlay.frag.ts @@ -0,0 +1,23 @@ +export const overlay_frag = ` +precision highp float; +precision highp sampler2D; + +uniform vec2 uTexSizeInv; +uniform sampler2D tEdgeTexture; +uniform vec3 uHighlightEdgeColor; +uniform vec3 uSelectEdgeColor; +uniform float uGhostEdgeStrength; +uniform float uInnerEdgeFactor; + +void main() { + vec2 coords = gl_FragCoord.xy * uTexSizeInv; + vec4 edgeValue = texture2D(tEdgeTexture, coords); + if (edgeValue.a > 0.0) { + vec3 edgeColor = edgeValue.b == 1.0 ? uHighlightEdgeColor : uSelectEdgeColor; + gl_FragColor.rgb = edgeValue.g > 0.0 ? edgeColor : edgeColor * uInnerEdgeFactor; + gl_FragColor.a = edgeValue.r == 1.0 ? uGhostEdgeStrength : 1.0; + } else { + gl_FragColor = vec4(0.0); + } +} +`; \ No newline at end of file diff --git a/src/mol-gl/shader/mesh.frag.ts b/src/mol-gl/shader/mesh.frag.ts index d4f99f471..987a68e53 100644 --- a/src/mol-gl/shader/mesh.frag.ts +++ b/src/mol-gl/shader/mesh.frag.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -14,7 +14,6 @@ precision highp int; #include light_frag_params #include normal_frag_params #include common_clip -#include wboit_params void main() { #include clip_pixel @@ -43,6 +42,8 @@ void main() { gl_FragColor = material; #elif defined(dRenderVariant_depth) gl_FragColor = material; + #elif defined(dRenderVariant_marking) + gl_FragColor = material; #elif defined(dRenderVariant_color) #ifdef dIgnoreLight gl_FragColor = material; diff --git a/src/mol-gl/shader/points.frag.ts b/src/mol-gl/shader/points.frag.ts index 5328d5aee..c18506882 100644 --- a/src/mol-gl/shader/points.frag.ts +++ b/src/mol-gl/shader/points.frag.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -12,7 +12,6 @@ precision highp int; #include common_frag_params #include color_frag_params #include common_clip -#include wboit_params #ifdef dPointFilledCircle uniform float uPointEdgeBleach; @@ -33,6 +32,8 @@ void main(){ gl_FragColor = material; #elif defined(dRenderVariant_depth) gl_FragColor = material; + #elif defined(dRenderVariant_marking) + gl_FragColor = material; #elif defined(dRenderVariant_color) gl_FragColor = material; diff --git a/src/mol-gl/shader/spheres.frag.ts b/src/mol-gl/shader/spheres.frag.ts index 72d51ddd0..47f3c63bd 100644 --- a/src/mol-gl/shader/spheres.frag.ts +++ b/src/mol-gl/shader/spheres.frag.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -13,7 +13,6 @@ precision highp int; #include color_frag_params #include light_frag_params #include common_clip -#include wboit_params varying float vRadius; varying float vRadiusSq; @@ -86,6 +85,8 @@ void main(void){ gl_FragColor = material; #elif defined(dRenderVariant_depth) gl_FragColor = material; + #elif defined(dRenderVariant_marking) + gl_FragColor = material; #elif defined(dRenderVariant_color) #ifdef dIgnoreLight gl_FragColor = material; diff --git a/src/mol-gl/shader/text.frag.ts b/src/mol-gl/shader/text.frag.ts index 807282c10..390886e55 100644 --- a/src/mol-gl/shader/text.frag.ts +++ b/src/mol-gl/shader/text.frag.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -12,7 +12,6 @@ precision highp int; #include common_frag_params #include color_frag_params #include common_clip -#include wboit_params uniform sampler2D tFont; @@ -66,6 +65,8 @@ void main(){ #include check_picking_alpha #elif defined(dRenderVariant_depth) gl_FragColor = material; + #elif defined(dRenderVariant_marking) + gl_FragColor = material; #elif defined(dRenderVariant_color) #include apply_marker_color #include apply_fog diff --git a/src/mol-gl/webgl/render-item.ts b/src/mol-gl/webgl/render-item.ts index bcbfca4dc..c9086c9a6 100644 --- a/src/mol-gl/webgl/render-item.ts +++ b/src/mol-gl/webgl/render-item.ts @@ -49,7 +49,7 @@ export interface RenderItem<T extends string> { // -const GraphicsRenderVariant = { 'colorBlended': '', 'colorWboit': '', 'pickObject': '', 'pickInstance': '', 'pickGroup': '', 'depth': '' }; +const GraphicsRenderVariant = { 'colorBlended': '', 'colorWboit': '', 'pickObject': '', 'pickInstance': '', 'pickGroup': '', 'depth': '', 'markingDepth': '', 'markingMask': '' }; export type GraphicsRenderVariant = keyof typeof GraphicsRenderVariant const GraphicsRenderVariants = Object.keys(GraphicsRenderVariant) as GraphicsRenderVariant[]; diff --git a/src/mol-plugin/util/viewport-screenshot.ts b/src/mol-plugin/util/viewport-screenshot.ts index f24a6d4b1..b1454feb1 100644 --- a/src/mol-plugin/util/viewport-screenshot.ts +++ b/src/mol-plugin/util/viewport-screenshot.ts @@ -1,11 +1,11 @@ -import { Viewport } from '../../mol-canvas3d/camera/util'; /** - * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal <david.sehnal@gmail.com> * @author Alexander Rose <alexander.rose@weirdbyte.de> */ +import { Viewport } from '../../mol-canvas3d/camera/util'; import { CameraHelperParams } from '../../mol-canvas3d/helper/camera-helper'; import { ImagePass } from '../../mol-canvas3d/passes/image'; import { canvasToBlob } from '../../mol-canvas3d/util'; @@ -108,8 +108,8 @@ class ViewportScreenshotHelper extends PluginComponent { private createPass(mutlisample: boolean) { const c = this.plugin.canvas3d!; const { colorBufferFloat, textureFloat } = c.webgl.extensions; - const aoProps = this.plugin.canvas3d!.props.postprocessing.occlusion; - return this.plugin.canvas3d!.getImagePass({ + const aoProps = c.props.postprocessing.occlusion; + return c.getImagePass({ transparentBackground: this.values.transparent, cameraHelper: { axes: this.values.axes }, multiSample: { @@ -121,7 +121,8 @@ class ViewportScreenshotHelper extends PluginComponent { occlusion: aoProps.name === 'on' ? { name: 'on', params: { ...aoProps.params, samples: 128 } } : aoProps - } + }, + marking: { ...c.props.marking } }); } @@ -133,17 +134,19 @@ class ViewportScreenshotHelper extends PluginComponent { private _imagePass: ImagePass; get imagePass() { if (this._imagePass) { - const aoProps = this.plugin.canvas3d!.props.postprocessing.occlusion; + const c = this.plugin.canvas3d!; + const aoProps = c.props.postprocessing.occlusion; this._imagePass.setProps({ cameraHelper: { axes: this.values.axes }, transparentBackground: this.values.transparent, // TODO: optimize because this creates a copy of a large object! postprocessing: { - ...this.plugin.canvas3d!.props.postprocessing, + ...c.props.postprocessing, occlusion: aoProps.name === 'on' ? { name: 'on', params: { ...aoProps.params, samples: 128 } } : aoProps - } + }, + marking: { ...c.props.marking } }); return this._imagePass; } @@ -266,7 +269,8 @@ class ViewportScreenshotHelper extends PluginComponent { cameraHelper: { axes: this.values.axes }, transparentBackground: this.values.transparent, // TODO: optimize because this creates a copy of a large object! - postprocessing: canvasProps.postprocessing + postprocessing: canvasProps.postprocessing, + marking: canvasProps.marking }); const imageData = this.previewPass.getImageData(w, h); const canvas = this.previewCanvas; diff --git a/src/mol-repr/visual.ts b/src/mol-repr/visual.ts index 5dcae09a3..334d52fca 100644 --- a/src/mol-repr/visual.ts +++ b/src/mol-repr/visual.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -8,7 +8,7 @@ import { RuntimeContext } from '../mol-task'; import { GraphicsRenderObject } from '../mol-gl/render-object'; import { PickingId } from '../mol-geo/geometry/picking'; import { Loci, isEmptyLoci, isEveryLoci } from '../mol-model/loci'; -import { MarkerAction, applyMarkerAction } from '../mol-util/marker-action'; +import { MarkerAction, applyMarkerAction, getMarkerInfo } from '../mol-util/marker-action'; import { ParamDefinition as PD } from '../mol-util/param-definition'; import { WebGLContext } from '../mol-gl/webgl/context'; import { Theme } from '../mol-theme/theme'; @@ -23,6 +23,7 @@ import { Transparency } from '../mol-theme/transparency'; import { createTransparency, clearTransparency, applyTransparencyValue, getTransparencyAverage } from '../mol-geo/geometry/transparency-data'; import { Clipping } from '../mol-theme/clipping'; import { createClipping, applyClippingGroups, clearClipping } from '../mol-geo/geometry/clipping-data'; +import { getMarkersAverage } from '../mol-geo/geometry/marker-data'; export interface VisualContext { readonly runtime: RuntimeContext @@ -70,17 +71,32 @@ namespace Visual { export function mark(renderObject: GraphicsRenderObject | undefined, loci: Loci, action: MarkerAction, lociApply: LociApply) { if (!renderObject) return false; - const { tMarker, uGroupCount, instanceCount } = renderObject.values; + const { tMarker, markerAverage, markerStatus, uGroupCount, instanceCount } = renderObject.values; const count = uGroupCount.ref.value * instanceCount.ref.value; const { array } = tMarker.ref.value; let changed = false; + let average = -1; + let status = -1; if (isEveryLoci(loci)) { changed = applyMarkerAction(array, Interval.ofLength(count), action); + if (changed) { + const info = getMarkerInfo(action, markerStatus.ref.value); + average = info.average; + status = info.status; + } } else if (!isEmptyLoci(loci)) { changed = lociApply(loci, interval => applyMarkerAction(array, interval, action), true); } - if (changed) ValueCell.update(tMarker, tMarker.ref.value); + if (changed) { + if (average === -1) { + average = getMarkersAverage(array, count); + if (average === 0) status = 0; + } + ValueCell.update(tMarker, tMarker.ref.value); + ValueCell.updateIfChanged(markerAverage, average); + ValueCell.updateIfChanged(markerStatus, status); + } return changed; } diff --git a/src/mol-util/marker-action.ts b/src/mol-util/marker-action.ts index fe87ae380..3964968ce 100644 --- a/src/mol-util/marker-action.ts +++ b/src/mol-util/marker-action.ts @@ -120,3 +120,88 @@ export function applyMarkerAction(array: Uint8Array, set: OrderedSet, action: Ma } return true; } + + +export interface MarkerInfo { + /** + * 0: none marked; + * 1: all marked; + * -1: unclear, need to be calculated + */ + average: 0 | 1 | -1 + /** + * 0: none marked; + * 1: all highlighted; + * 2: all selected; + * 3: all highlighted and selected + * -1: mixed/unclear + */ + status: 0 | 1 | 2 | 3 | -1 +} + +export function getMarkerInfo(action: MarkerAction, currentStatus: number): MarkerInfo { + let average: MarkerInfo['average'] = -1; + let status: MarkerInfo['status'] = -1; + switch (action) { + case MarkerAction.Highlight: + if (currentStatus === 0 || currentStatus === 1) { + average = 1; + status = 1; + } else if (currentStatus === 2 || currentStatus === 3) { + average = 1; + status = 3; + } else { + average = 1; + } + break; + case MarkerAction.RemoveHighlight: + if (currentStatus === 0 || currentStatus === 1) { + average = 0; + status = 0; + } else if (currentStatus === 2 || currentStatus === 3) { + average = 1; + status = 2; + } + break; + case MarkerAction.Select: + if (currentStatus === 1 || currentStatus === 3) { + average = 1; + status = 3; + } else if (currentStatus === 0 || currentStatus === 2) { + average = 1; + status = 2; + } else { + average = 1; + } + break; + case MarkerAction.Deselect: + if (currentStatus === 1 || currentStatus === 3) { + average = 1; + status = 1; + } else if (currentStatus === 0 || currentStatus === 2) { + average = 0; + status = 0; + } + break; + case MarkerAction.Toggle: + if (currentStatus === 1) { + average = 1; + status = 3; + } else if (currentStatus === 2) { + average = 0; + status = 0; + } else if (currentStatus === 3) { + average = 1; + status = 1; + } else if (currentStatus === 0) { + average = 1; + status = 2; + } + break; + case MarkerAction.Clear: + average = 0; + status = 0; + break; + } + return { average, status }; +} \ No newline at end of file -- GitLab