From 3a5a12af665dfe409e16f1b6481468c5e0c7afae Mon Sep 17 00:00:00 2001 From: Alexander Rose <alex.rose@rcsb.org> Date: Fri, 15 Mar 2019 12:02:38 -0700 Subject: [PATCH] single and multi layer screen-door transparency --- src/mol-geo/geometry/transparency-data.ts | 7 ++- src/mol-gl/renderable/direct-volume.ts | 1 + src/mol-gl/renderable/schema.ts | 3 ++ .../shader/chunks/assign-material-color.glsl | 53 ++++++------------- src/mol-plugin/state/transforms/helpers.ts | 37 +++++-------- .../state/transforms/representation.ts | 25 +++------ src/mol-repr/visual.ts | 22 ++++---- src/mol-theme/transparency.ts | 21 ++++---- 8 files changed, 68 insertions(+), 101 deletions(-) diff --git a/src/mol-geo/geometry/transparency-data.ts b/src/mol-geo/geometry/transparency-data.ts index 2d1ba6390..65e2d17c2 100644 --- a/src/mol-geo/geometry/transparency-data.ts +++ b/src/mol-geo/geometry/transparency-data.ts @@ -7,11 +7,13 @@ import { ValueCell } from 'mol-util/value-cell' import { Vec2 } from 'mol-math/linear-algebra' import { TextureImage, createTextureImage } from 'mol-gl/renderable/util'; +import { Transparency } from 'mol-theme/transparency'; export type TransparencyData = { tTransparency: ValueCell<TextureImage<Uint8Array>> uTransparencyTexDim: ValueCell<Vec2> dTransparency: ValueCell<boolean>, + dTransparencyVariant: ValueCell<string>, } export function applyTransparencyValue(array: Uint8Array, start: number, end: number, value: number) { @@ -25,18 +27,20 @@ export function clearTransparency(array: Uint8Array, start: number, end: number) array.fill(0, start, end) } -export function createTransparency(count: number, transparencyData?: TransparencyData): TransparencyData { +export function createTransparency(count: number, variant: Transparency.Variant, transparencyData?: TransparencyData): TransparencyData { const transparency = createTextureImage(Math.max(1, count), 1, transparencyData && transparencyData.tTransparency.ref.value.array) if (transparencyData) { ValueCell.update(transparencyData.tTransparency, transparency) ValueCell.update(transparencyData.uTransparencyTexDim, Vec2.create(transparency.width, transparency.height)) ValueCell.update(transparencyData.dTransparency, count > 0) + ValueCell.update(transparencyData.dTransparencyVariant, variant) return transparencyData } else { return { tTransparency: ValueCell.create(transparency), uTransparencyTexDim: ValueCell.create(Vec2.create(transparency.width, transparency.height)), dTransparency: ValueCell.create(count > 0), + dTransparencyVariant: ValueCell.create(variant), } } } @@ -52,6 +56,7 @@ export function createEmptyTransparency(transparencyData?: TransparencyData): Tr tTransparency: ValueCell.create(emptyTransparencyTexture), uTransparencyTexDim: ValueCell.create(Vec2.create(1, 1)), dTransparency: ValueCell.create(false), + dTransparencyVariant: ValueCell.create('single'), } } } \ No newline at end of file diff --git a/src/mol-gl/renderable/direct-volume.ts b/src/mol-gl/renderable/direct-volume.ts index 50b4b3e5d..53583cbdf 100644 --- a/src/mol-gl/renderable/direct-volume.ts +++ b/src/mol-gl/renderable/direct-volume.ts @@ -27,6 +27,7 @@ export const DirectVolumeSchema = { uTransparencyTexDim: UniformSpec('v2'), tTransparency: TextureSpec('image-uint8', 'alpha', 'ubyte', 'nearest'), dTransparency: DefineSpec('boolean'), + dTransparencyVariant: DefineSpec('string', ['single', 'multi']), uInstanceCount: UniformSpec('i'), uGroupCount: UniformSpec('i'), diff --git a/src/mol-gl/renderable/schema.ts b/src/mol-gl/renderable/schema.ts index 139282f13..e243a0293 100644 --- a/src/mol-gl/renderable/schema.ts +++ b/src/mol-gl/renderable/schema.ts @@ -208,9 +208,12 @@ export type OverpaintSchema = typeof OverpaintSchema export type OverpaintValues = Values<OverpaintSchema> export const TransparencySchema = { + // aTransparency: AttributeSpec('float32', 1, 0), // TODO uTransparencyTexDim: UniformSpec('v2'), tTransparency: TextureSpec('image-uint8', 'alpha', 'ubyte', 'nearest'), dTransparency: DefineSpec('boolean'), + // dTransparencyType: DefineSpec('string', ['uniform', 'attribute', 'instance', 'group', 'group_instance']), // TODO + dTransparencyVariant: DefineSpec('string', ['single', 'multi']), } export type TransparencySchema = typeof TransparencySchema export type TransparencyValues = Values<TransparencySchema> diff --git a/src/mol-gl/shader/chunks/assign-material-color.glsl b/src/mol-gl/shader/chunks/assign-material-color.glsl index 76b82ed1c..dceb69e78 100644 --- a/src/mol-gl/shader/chunks/assign-material-color.glsl +++ b/src/mol-gl/shader/chunks/assign-material-color.glsl @@ -11,44 +11,23 @@ material.rgb = mix(material.rgb, vOverpaint.rgb, vOverpaint.a); #endif -// apply transparency +// apply screendoor transparency #if defined(dTransparency) && (defined(dColorType_uniform) || defined(dColorType_attribute) || defined(dColorType_instance) || defined(dColorType_group) || defined(dColorType_groupInstance)) - float ma = material.a * (1.0 - vTransparency); - ivec2 pixelCoord = ivec2(gl_FragCoord.xy); + float ta = 1.0 - vTransparency; + float at = 0.0; - // const mat4 thresholdMatrix = mat4( - // 1.0 / 17.0, 9.0 / 17.0, 3.0 / 17.0, 11.0 / 17.0, - // 13.0 / 17.0, 5.0 / 17.0, 15.0 / 17.0, 7.0 / 17.0, - // 4.0 / 17.0, 12.0 / 17.0, 2.0 / 17.0, 10.0 / 17.0, - // 16.0 / 17.0, 8.0 / 17.0, 14.0 / 17.0, 6.0 / 17.0 - // ); - // float at = thresholdMatrix[pixelCoord.x % 4][pixelCoord.y % 4]; + #if defined(dTransparencyVariant_single) + ivec2 pixelCoord = ivec2(gl_FragCoord.xy); + const mat4 thresholdMatrix = mat4( + 1.0 / 17.0, 9.0 / 17.0, 3.0 / 17.0, 11.0 / 17.0, + 13.0 / 17.0, 5.0 / 17.0, 15.0 / 17.0, 7.0 / 17.0, + 4.0 / 17.0, 12.0 / 17.0, 2.0 / 17.0, 10.0 / 17.0, + 16.0 / 17.0, 8.0 / 17.0, 14.0 / 17.0, 6.0 / 17.0 + ); + at = thresholdMatrix[pixelCoord.x % 4][pixelCoord.y % 4]; + #elif defined(dTransparencyVariant_multi) + at = fract(dot(vec3(gl_FragCoord.xy, vGroup + 0.5), vec3(2.0, 7.0, 23.0) / 17.0f)); + #endif - // // https://research.nvidia.com/publication/hashed-alpha-testing - // // Find the discretized derivatives of our coordinates - // float maxDeriv = max(length(dFdx(vViewPosition)), length(dFdy(vViewPosition))); - // float pixScale = 1.0 / maxDeriv; - // // Find two nearest log-discretized noise scales - // vec2 pixScales = vec2(exp2(floor(log2(pixScale))), exp2(ceil(log2(pixScale)))); - // // Compute alpha thresholds at our two noise scales - // vec2 alpha = vec2(hash3d(floor(pixScales.x * vViewPosition)), hash3d(floor(pixScales.y * vViewPosition))); - // // Factor to interpolate lerp with - // float lerpFactor = fract(log2(pixScale)); - // // Interpolate alpha threshold from noise at two scales - // float x = (1.0 - lerpFactor) * alpha.x + lerpFactor * alpha.y; - // // Pass into CDF to compute uniformly distrib threshold - // float a = min(lerpFactor, 1.0 - lerpFactor); - // vec3 cases = vec3( - // x * x / (2.0 * a * (1.0 - a)), - // (x - 0.5 * a) / (1.0 - a), - // 1.0 - ((1.0 - x) * (1.0 - x) / (2.0 * a * (1.0 - a))) - // ); - // // Find our final, uniformly distributed alpha threshold - // float at = (x < (1.0 - a)) ? ((x < a) ? cases.x : cases.y) : cases.z; - // // Avoids ατ == 0. Could also do - // at = clamp(at, 1.0e-6, 1.0); - - float at = fract(dot(vec3(gl_FragCoord.xy, vGroup + 0.5), vec3(2.0, 7.0, 23.0) / 17.0f)); - - if (ma < 0.99 && (ma < 0.01 || ma < at)) discard; + if (ta < 0.99 && (ta < 0.01 || ta < at)) discard; #endif \ No newline at end of file diff --git a/src/mol-plugin/state/transforms/helpers.ts b/src/mol-plugin/state/transforms/helpers.ts index e5a55427d..6a58d384a 100644 --- a/src/mol-plugin/state/transforms/helpers.ts +++ b/src/mol-plugin/state/transforms/helpers.ts @@ -14,36 +14,25 @@ import { Transparency } from 'mol-theme/transparency'; type Script = { language: string, expression: string } +function scriptToLoci(structure: Structure, script: Script) { + const parsed = parseMolScript(script.expression) + if (parsed.length === 0) throw new Error('No query') + const query = transpileMolScript(parsed[0]) + + const compiled = compile<StructureSelection>(query) + const result = compiled(new QueryContext(structure)) + return StructureSelection.toLoci2(result) +} + export function getStructureOverpaint(structure: Structure, scriptLayers: { script: Script, color: Color }[], alpha: number): Overpaint { const layers: Overpaint.Layer[] = [] for (let i = 0, il = scriptLayers.length; i < il; ++i) { const { script, color } = scriptLayers[i] - const parsed = parseMolScript(script.expression) - if (parsed.length === 0) throw new Error('No query') - const query = transpileMolScript(parsed[0]) - - const compiled = compile<StructureSelection>(query) - const result = compiled(new QueryContext(structure)) - const loci = StructureSelection.toLoci2(result) - - layers.push({ loci, color }) + layers.push({ loci: scriptToLoci(structure, script), color }) } return { layers, alpha } } -export function getStructureTransparency(structure: Structure, scriptLayers: { script: Script, value: number }[]): Transparency { - const layers: Transparency.Layer[] = [] - for (let i = 0, il = scriptLayers.length; i < il; ++i) { - const { script, value } = scriptLayers[i] - const parsed = parseMolScript(script.expression) - if (parsed.length === 0) throw new Error('No query') - const query = transpileMolScript(parsed[0]) - - const compiled = compile<StructureSelection>(query) - const result = compiled(new QueryContext(structure)) - const loci = StructureSelection.toLoci2(result) - - layers.push({ loci, value }) - } - return { layers } +export function getStructureTransparency(structure: Structure, script: Script, value: number, variant: Transparency.Variant): Transparency { + return { loci: scriptToLoci(structure, script), value, variant } } \ No newline at end of file diff --git a/src/mol-plugin/state/transforms/representation.ts b/src/mol-plugin/state/transforms/representation.ts index 79af0ae63..0d99a98fc 100644 --- a/src/mol-plugin/state/transforms/representation.ts +++ b/src/mol-plugin/state/transforms/representation.ts @@ -376,20 +376,9 @@ const TransparencyStructureRepresentation3D = PluginStateTransform.BuiltIn({ from: SO.Molecule.Structure.Representation3D, to: SO.Molecule.Structure.Representation3DState, params: { - layers: PD.ObjectList({ - script: PD.ScriptExpression({ language: 'mol-script', expression: '(sel.atom.atom-groups :chain-test (= atom.label_asym_id A))' }), - value: PD.Numeric(0.5) - }, e => `Transparency ${e.value}`, { - defaultValue: [ - { - script: { - language: 'mol-script', - expression: '(sel.atom.atom-groups :chain-test (= atom.label_asym_id A))' - }, - value: 0.5 - } - ] - }), + script: PD.ScriptExpression({ language: 'mol-script', expression: '(sel.atom.atom-groups :chain-test (= atom.label_asym_id A))' }), + value: PD.Numeric(0.75, { min: 0, max: 1, step: 0.01 }, { label: 'Transparency' }), + variant: PD.Select('single', [['single', 'Single-layer'], ['multi', 'Multi-layer']]) } })({ canAutoUpdate() { @@ -397,25 +386,25 @@ const TransparencyStructureRepresentation3D = PluginStateTransform.BuiltIn({ }, apply({ a, params }) { const structure = a.data.source.data - const transparency = getStructureTransparency(structure, params.layers) + const transparency = getStructureTransparency(structure, params.script, params.value, params.variant) return new SO.Molecule.Structure.Representation3DState({ state: { transparency }, initialState: { transparency: Transparency.Empty }, info: structure, source: a - }, { label: `Transparency (${transparency.layers.length} Layers)` }) + }, { label: `Transparency (${transparency.value})` }) }, update({ a, b, newParams, oldParams }) { const structure = b.data.info as Structure if (a.data.source.data !== structure) return StateTransformer.UpdateResult.Recreate const oldTransparency = b.data.state.transparency! - const newTransparency = getStructureTransparency(structure, newParams.layers) + const newTransparency = getStructureTransparency(structure, newParams.script, newParams.value, newParams.variant) if (Transparency.areEqual(oldTransparency, newTransparency)) return StateTransformer.UpdateResult.Unchanged b.data.state.transparency = newTransparency b.data.source = a - b.label = `Transparency (${newTransparency.layers.length} Layers)` + b.label = `Transparency (${newTransparency.value})` return StateTransformer.UpdateResult.Updated } }); diff --git a/src/mol-repr/visual.ts b/src/mol-repr/visual.ts index 0e120b6e6..8f399e5aa 100644 --- a/src/mol-repr/visual.ts +++ b/src/mol-repr/visual.ts @@ -7,7 +7,7 @@ import { RuntimeContext } from 'mol-task' import { GraphicsRenderObject } from 'mol-gl/render-object' import { PickingId } from '../mol-geo/geometry/picking'; -import { Loci } from 'mol-model/loci'; +import { Loci, isEmptyLoci } from 'mol-model/loci'; import { MarkerAction, applyMarkerAction } from '../mol-geo/geometry/marker-data'; import { ParamDefinition as PD } from 'mol-util/param-definition'; import { WebGLContext } from 'mol-gl/webgl/context'; @@ -106,21 +106,21 @@ namespace Visual { const { tTransparency, uGroupCount, instanceCount } = renderObject.values const count = uGroupCount.ref.value * instanceCount.ref.value - // ensure texture has right size - createTransparency(transparency.layers.length ? count : 0, renderObject.values) + const { loci, value, variant } = transparency + + // ensure texture has right size and variant + createTransparency(value && !isEmptyLoci(loci) ? count : 0, variant, renderObject.values) // clear if requested if (clear) clearTransparency(tTransparency.ref.value.array, 0, count) - for (let i = 0, il = transparency.layers.length; i < il; ++i) { - const { loci, value } = transparency.layers[i] - const apply = (interval: Interval) => { - const start = Interval.start(interval) - const end = Interval.end(interval) - return applyTransparencyValue(tTransparency.ref.value.array, start, end, value) - } - lociApply(loci, apply) + const apply = (interval: Interval) => { + const start = Interval.start(interval) + const end = Interval.end(interval) + return applyTransparencyValue(tTransparency.ref.value.array, start, end, value) } + lociApply(loci, apply) + ValueCell.update(tTransparency, tTransparency.ref.value) } diff --git a/src/mol-theme/transparency.ts b/src/mol-theme/transparency.ts index 0fad49f7f..404a55b22 100644 --- a/src/mol-theme/transparency.ts +++ b/src/mol-theme/transparency.ts @@ -4,23 +4,24 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { Loci } from 'mol-model/loci'; +import { Loci, EmptyLoci } from 'mol-model/loci'; export { Transparency } -type Transparency = { layers: ReadonlyArray<Transparency.Layer> } +interface Transparency { + readonly loci: Loci + readonly value: number + readonly variant: Transparency.Variant +} namespace Transparency { - export type Layer = { readonly loci: Loci, readonly value: number } - export const Empty: Transparency = { layers: [] } + export type Variant = 'single' | 'multi' + export const Empty: Transparency = { loci: EmptyLoci, value: 0, variant: 'single' } export function areEqual(tA: Transparency, tB: Transparency) { - if (tA.layers.length === 0 && tB.layers.length === 0) return true - if (tA.layers.length !== tB.layers.length) return false - for (let i = 0, il = tA.layers.length; i < il; ++i) { - if (tA.layers[i].value !== tB.layers[i].value) return false - if (!Loci.areEqual(tA.layers[i].loci, tB.layers[i].loci)) return false - } + if (tA.value !== tB.value) return false + if (tA.variant !== tB.variant) return false + if (!Loci.areEqual(tA.loci, tB.loci)) return false return true } } \ No newline at end of file -- GitLab