浏览代码

Merge branch 'master' of https://github.com/molstar/molstar into fix-qa-assignment

dsehnal 2 年之前
父节点
当前提交
9cd4aeabaa

+ 2 - 0
CHANGELOG.md

@@ -6,12 +6,14 @@ Note that since we don't clearly distinguish between a public and private interf
 
 
 ## [Unreleased]
 ## [Unreleased]
 
 
+- Add an `includeTransparent` parameter to hide/show outlines of components that are transparent
 - Fix 'once' for animations of systems with many frames
 - Fix 'once' for animations of systems with many frames
 - Better guard against issue (black fringes) with bumpiness in impostors
 - Better guard against issue (black fringes) with bumpiness in impostors
 - Improve impostor shaders
 - Improve impostor shaders
     - Fix sphere near-clipping with orthographic projection
     - Fix sphere near-clipping with orthographic projection
     - Fix cylinder near-clipping
     - Fix cylinder near-clipping
     - Add interior cylinder caps
     - Add interior cylinder caps
+    - Add per-pixel object clipping
 - Fix `QualityAssessment` assignment bug for structures with different auth vs label sequence numbering
 - Fix `QualityAssessment` assignment bug for structures with different auth vs label sequence numbering
 
 
 ## [v3.26.0] - 2022-12-04
 ## [v3.26.0] - 2022-12-04

+ 1 - 0
src/apps/docking-viewer/viewport.tsx

@@ -55,6 +55,7 @@ function occlusionStyle(plugin: PluginContext) {
                 scale: 1.0,
                 scale: 1.0,
                 threshold: 0.33,
                 threshold: 0.33,
                 color: Color(0x0000),
                 color: Color(0x0000),
+                includeTransparent: true,
             } },
             } },
             shadow: { name: 'off', params: {} },
             shadow: { name: 'off', params: {} },
         }
         }

+ 1 - 1
src/examples/lighting/index.ts

@@ -25,7 +25,7 @@ const Canvas3DPresets = {
         canvas3d: <Preset>{
         canvas3d: <Preset>{
             postprocessing: {
             postprocessing: {
                 occlusion: { name: 'on', params: { samples: 32, radius: 6, bias: 1.4, blurKernelSize: 15, resolutionScale: 1 } },
                 occlusion: { name: 'on', params: { samples: 32, radius: 6, bias: 1.4, blurKernelSize: 15, resolutionScale: 1 } },
-                outline: { name: 'on', params: { scale: 1, threshold: 0.33, color: Color(0x000000) } },
+                outline: { name: 'on', params: { scale: 1, threshold: 0.33, color: Color(0x000000), includeTransparent: true, } },
                 shadow: { name: 'off', params: {} },
                 shadow: { name: 'off', params: {} },
             },
             },
             renderer: {
             renderer: {

+ 1 - 0
src/extensions/cellpack/model.ts

@@ -621,6 +621,7 @@ export const LoadCellPackModel = StateAction.build({
                         scale: 1,
                         scale: 1,
                         threshold: 0.33,
                         threshold: 0.33,
                         color: ColorNames.black,
                         color: ColorNames.black,
+                        includeTransparent: true,
                     }
                     }
                 }
                 }
             }
             }

+ 3 - 3
src/mol-canvas3d/passes/draw.ts

@@ -142,7 +142,7 @@ export class DrawPass {
         }
         }
 
 
         if (PostprocessingPass.isEnabled(postprocessingProps)) {
         if (PostprocessingPass.isEnabled(postprocessingProps)) {
-            if (PostprocessingPass.isOutlineEnabled(postprocessingProps)) {
+            if (PostprocessingPass.isTransparentOutlineEnabled(postprocessingProps)) {
                 this.depthTargetTransparent.bind();
                 this.depthTargetTransparent.bind();
                 renderer.clearDepth(true);
                 renderer.clearDepth(true);
                 if (scene.opacityAverage < 1) {
                 if (scene.opacityAverage < 1) {
@@ -196,7 +196,7 @@ export class DrawPass {
         }
         }
 
 
         if (PostprocessingPass.isEnabled(postprocessingProps)) {
         if (PostprocessingPass.isEnabled(postprocessingProps)) {
-            if (PostprocessingPass.isOutlineEnabled(postprocessingProps)) {
+            if (PostprocessingPass.isTransparentOutlineEnabled(postprocessingProps)) {
                 this.depthTargetTransparent.bind();
                 this.depthTargetTransparent.bind();
                 renderer.clearDepth(true);
                 renderer.clearDepth(true);
                 if (scene.opacityAverage < 1) {
                 if (scene.opacityAverage < 1) {
@@ -260,7 +260,7 @@ export class DrawPass {
                     this.colorTarget.depthRenderbuffer?.detachFramebuffer(this.postprocessing.target.framebuffer);
                     this.colorTarget.depthRenderbuffer?.detachFramebuffer(this.postprocessing.target.framebuffer);
                 }
                 }
 
 
-                if (PostprocessingPass.isOutlineEnabled(postprocessingProps)) {
+                if (PostprocessingPass.isTransparentOutlineEnabled(postprocessingProps)) {
                     this.depthTargetTransparent.bind();
                     this.depthTargetTransparent.bind();
                     renderer.clearDepth(true);
                     renderer.clearDepth(true);
                     if (scene.opacityAverage < 1) {
                     if (scene.opacityAverage < 1) {

+ 27 - 7
src/mol-canvas3d/passes/postprocessing.ts

@@ -45,10 +45,12 @@ const OutlinesSchema = {
     uFar: UniformSpec('f'),
     uFar: UniformSpec('f'),
 
 
     uMaxPossibleViewZDiff: UniformSpec('f'),
     uMaxPossibleViewZDiff: UniformSpec('f'),
+
+    dTransparentOutline: DefineSpec('boolean'),
 };
 };
 type OutlinesRenderable = ComputeRenderable<Values<typeof OutlinesSchema>>
 type OutlinesRenderable = ComputeRenderable<Values<typeof OutlinesSchema>>
 
 
-function getOutlinesRenderable(ctx: WebGLContext, depthTextureOpaque: Texture, depthTextureTransparent: Texture): OutlinesRenderable {
+function getOutlinesRenderable(ctx: WebGLContext, depthTextureOpaque: Texture, depthTextureTransparent: Texture, transparentOutline: boolean): OutlinesRenderable {
     const width = depthTextureOpaque.getWidth();
     const width = depthTextureOpaque.getWidth();
     const height = depthTextureOpaque.getHeight();
     const height = depthTextureOpaque.getHeight();
 
 
@@ -63,6 +65,8 @@ function getOutlinesRenderable(ctx: WebGLContext, depthTextureOpaque: Texture, d
         uFar: ValueCell.create(10000),
         uFar: ValueCell.create(10000),
 
 
         uMaxPossibleViewZDiff: ValueCell.create(0.5),
         uMaxPossibleViewZDiff: ValueCell.create(0.5),
+
+        dTransparentOutline: ValueCell.create(transparentOutline),
     };
     };
 
 
     const schema = { ...OutlinesSchema };
     const schema = { ...OutlinesSchema };
@@ -288,10 +292,13 @@ const PostprocessingSchema = {
     dOutlineEnable: DefineSpec('boolean'),
     dOutlineEnable: DefineSpec('boolean'),
     dOutlineScale: DefineSpec('number'),
     dOutlineScale: DefineSpec('number'),
     uOutlineThreshold: UniformSpec('f'),
     uOutlineThreshold: UniformSpec('f'),
+
+    dTransparentOutline: DefineSpec('boolean'),
 };
 };
 type PostprocessingRenderable = ComputeRenderable<Values<typeof PostprocessingSchema>>
 type PostprocessingRenderable = ComputeRenderable<Values<typeof PostprocessingSchema>>
 
 
-function getPostprocessingRenderable(ctx: WebGLContext, colorTexture: Texture, depthTextureOpaque: Texture, depthTextureTransparent: Texture, shadowsTexture: Texture, outlinesTexture: Texture, ssaoDepthTexture: Texture): PostprocessingRenderable {
+
+function getPostprocessingRenderable(ctx: WebGLContext, colorTexture: Texture, depthTextureOpaque: Texture, depthTextureTransparent: Texture, shadowsTexture: Texture, outlinesTexture: Texture, ssaoDepthTexture: Texture, transparentOutline: boolean): PostprocessingRenderable {
     const values: Values<typeof PostprocessingSchema> = {
     const values: Values<typeof PostprocessingSchema> = {
         ...QuadValues,
         ...QuadValues,
         tSsaoDepth: ValueCell.create(ssaoDepthTexture),
         tSsaoDepth: ValueCell.create(ssaoDepthTexture),
@@ -321,6 +328,8 @@ function getPostprocessingRenderable(ctx: WebGLContext, colorTexture: Texture, d
         dOutlineEnable: ValueCell.create(false),
         dOutlineEnable: ValueCell.create(false),
         dOutlineScale: ValueCell.create(1),
         dOutlineScale: ValueCell.create(1),
         uOutlineThreshold: ValueCell.create(0.33),
         uOutlineThreshold: ValueCell.create(0.33),
+
+        dTransparentOutline: ValueCell.create(transparentOutline),
     };
     };
 
 
     const schema = { ...PostprocessingSchema };
     const schema = { ...PostprocessingSchema };
@@ -355,6 +364,7 @@ export const PostprocessingParams = {
             scale: PD.Numeric(1, { min: 1, max: 5, step: 1 }),
             scale: PD.Numeric(1, { min: 1, max: 5, step: 1 }),
             threshold: PD.Numeric(0.33, { min: 0.01, max: 1, step: 0.01 }),
             threshold: PD.Numeric(0.33, { min: 0.01, max: 1, step: 0.01 }),
             color: PD.Color(Color(0x000000)),
             color: PD.Color(Color(0x000000)),
+            includeTransparent: PD.Boolean(true, { description: 'Whether to show outline for transparent objects' }),
         }),
         }),
         off: PD.Group({})
         off: PD.Group({})
     }, { cycle: true, description: 'Draw outline around 3D objects' }),
     }, { cycle: true, description: 'Draw outline around 3D objects' }),
@@ -373,8 +383,8 @@ export class PostprocessingPass {
         return props.occlusion.name === 'on' || props.shadow.name === 'on' || props.outline.name === 'on' || props.background.variant.name !== 'off';
         return props.occlusion.name === 'on' || props.shadow.name === 'on' || props.outline.name === 'on' || props.background.variant.name !== 'off';
     }
     }
 
 
-    static isOutlineEnabled(props: PostprocessingProps) {
-        return props.outline.name === 'on';
+    static isTransparentOutlineEnabled(props: PostprocessingProps) {
+        return props.outline.name === 'on' && props.outline.params.includeTransparent;
     }
     }
 
 
     readonly target: RenderTarget;
     readonly target: RenderTarget;
@@ -428,7 +438,7 @@ export class PostprocessingPass {
         this.target = webgl.createRenderTarget(width, height, false, 'uint8', 'linear');
         this.target = webgl.createRenderTarget(width, height, false, 'uint8', 'linear');
 
 
         this.outlinesTarget = webgl.createRenderTarget(width, height, false);
         this.outlinesTarget = webgl.createRenderTarget(width, height, false);
-        this.outlinesRenderable = getOutlinesRenderable(webgl, depthTextureOpaque, depthTextureTransparent);
+        this.outlinesRenderable = getOutlinesRenderable(webgl, depthTextureOpaque, depthTextureTransparent, true);
 
 
         this.shadowsTarget = webgl.createRenderTarget(width, height, false);
         this.shadowsTarget = webgl.createRenderTarget(width, height, false);
         this.shadowsRenderable = getShadowsRenderable(webgl, depthTextureOpaque);
         this.shadowsRenderable = getShadowsRenderable(webgl, depthTextureOpaque);
@@ -456,7 +466,7 @@ export class PostprocessingPass {
         this.ssaoRenderable = getSsaoRenderable(webgl, this.downsampleFactor === 1 ? depthTextureOpaque : this.downsampledDepthTarget.texture);
         this.ssaoRenderable = getSsaoRenderable(webgl, this.downsampleFactor === 1 ? depthTextureOpaque : this.downsampledDepthTarget.texture);
         this.ssaoBlurFirstPassRenderable = getSsaoBlurRenderable(webgl, this.ssaoDepthTexture, 'horizontal');
         this.ssaoBlurFirstPassRenderable = getSsaoBlurRenderable(webgl, this.ssaoDepthTexture, 'horizontal');
         this.ssaoBlurSecondPassRenderable = getSsaoBlurRenderable(webgl, this.ssaoDepthBlurProxyTexture, 'vertical');
         this.ssaoBlurSecondPassRenderable = getSsaoBlurRenderable(webgl, this.ssaoDepthBlurProxyTexture, 'vertical');
-        this.renderable = getPostprocessingRenderable(webgl, colorTarget.texture, depthTextureOpaque, depthTextureTransparent, this.shadowsTarget.texture, this.outlinesTarget.texture, this.ssaoDepthTexture);
+        this.renderable = getPostprocessingRenderable(webgl, colorTarget.texture, depthTextureOpaque, depthTextureTransparent, this.shadowsTarget.texture, this.outlinesTarget.texture, this.ssaoDepthTexture, true);
 
 
         this.background = new BackgroundPass(webgl, assetManager, width, height);
         this.background = new BackgroundPass(webgl, assetManager, width, height);
     }
     }
@@ -494,6 +504,7 @@ export class PostprocessingPass {
         let needsUpdateMain = false;
         let needsUpdateMain = false;
         let needsUpdateSsao = false;
         let needsUpdateSsao = false;
         let needsUpdateSsaoBlur = false;
         let needsUpdateSsaoBlur = false;
+        let needsUpdateOutlines = false;
 
 
         const orthographic = camera.state.mode === 'orthographic' ? 1 : 0;
         const orthographic = camera.state.mode === 'orthographic' ? 1 : 0;
         const outlinesEnabled = props.outline.name === 'on';
         const outlinesEnabled = props.outline.name === 'on';
@@ -615,7 +626,8 @@ export class PostprocessingPass {
         }
         }
 
 
         if (props.outline.name === 'on') {
         if (props.outline.name === 'on') {
-            let { threshold } = props.outline.params;
+            let { threshold, includeTransparent } = props.outline.params;
+            const transparentOutline = includeTransparent ?? true;
             // orthographic needs lower threshold
             // orthographic needs lower threshold
             if (camera.state.mode === 'orthographic') threshold /= 5;
             if (camera.state.mode === 'orthographic') threshold /= 5;
             const factor = Math.pow(1000, threshold) / 1000;
             const factor = Math.pow(1000, threshold) / 1000;
@@ -626,12 +638,16 @@ export class PostprocessingPass {
             ValueCell.updateIfChanged(this.outlinesRenderable.values.uNear, camera.near);
             ValueCell.updateIfChanged(this.outlinesRenderable.values.uNear, camera.near);
             ValueCell.updateIfChanged(this.outlinesRenderable.values.uFar, camera.far);
             ValueCell.updateIfChanged(this.outlinesRenderable.values.uFar, camera.far);
             ValueCell.updateIfChanged(this.outlinesRenderable.values.uMaxPossibleViewZDiff, maxPossibleViewZDiff);
             ValueCell.updateIfChanged(this.outlinesRenderable.values.uMaxPossibleViewZDiff, maxPossibleViewZDiff);
+            if (this.renderable.values.dTransparentOutline.ref.value !== transparentOutline) { needsUpdateOutlines = true; }
+            ValueCell.updateIfChanged(this.outlinesRenderable.values.dTransparentOutline, transparentOutline);
 
 
             ValueCell.update(this.renderable.values.uOutlineColor, Color.toVec3Normalized(this.renderable.values.uOutlineColor.ref.value, props.outline.params.color));
             ValueCell.update(this.renderable.values.uOutlineColor, Color.toVec3Normalized(this.renderable.values.uOutlineColor.ref.value, props.outline.params.color));
 
 
             ValueCell.updateIfChanged(this.renderable.values.uMaxPossibleViewZDiff, maxPossibleViewZDiff);
             ValueCell.updateIfChanged(this.renderable.values.uMaxPossibleViewZDiff, maxPossibleViewZDiff);
             if (this.renderable.values.dOutlineScale.ref.value !== outlineScale) { needsUpdateMain = true; }
             if (this.renderable.values.dOutlineScale.ref.value !== outlineScale) { needsUpdateMain = true; }
             ValueCell.updateIfChanged(this.renderable.values.dOutlineScale, outlineScale);
             ValueCell.updateIfChanged(this.renderable.values.dOutlineScale, outlineScale);
+            if (this.renderable.values.dTransparentOutline.ref.value !== transparentOutline) { needsUpdateMain = true; }
+            ValueCell.updateIfChanged(this.renderable.values.dTransparentOutline, transparentOutline);
         }
         }
 
 
         ValueCell.updateIfChanged(this.renderable.values.uFar, camera.far);
         ValueCell.updateIfChanged(this.renderable.values.uFar, camera.far);
@@ -650,6 +666,10 @@ export class PostprocessingPass {
         if (this.renderable.values.dOcclusionEnable.ref.value !== occlusionEnabled) { needsUpdateMain = true; }
         if (this.renderable.values.dOcclusionEnable.ref.value !== occlusionEnabled) { needsUpdateMain = true; }
         ValueCell.updateIfChanged(this.renderable.values.dOcclusionEnable, occlusionEnabled);
         ValueCell.updateIfChanged(this.renderable.values.dOcclusionEnable, occlusionEnabled);
 
 
+        if (needsUpdateOutlines) {
+            this.outlinesRenderable.update();
+        }
+
         if (needsUpdateShadows) {
         if (needsUpdateShadows) {
             this.shadowsRenderable.update();
             this.shadowsRenderable.update();
         }
         }

+ 1 - 6
src/mol-gl/shader/chunks/clip-instance.glsl.ts

@@ -1,12 +1,7 @@
 export const clip_instance = `
 export const clip_instance = `
 #if defined(dClipVariant_instance) && dClipObjectCount != 0
 #if defined(dClipVariant_instance) && dClipObjectCount != 0
-    int flag = 0;
-    #if defined(dClipping)
-        flag = int(floor(vClipping * 255.0 + 0.5));
-    #endif
-
     vec4 mCenter = uModel * aTransform * vec4(uInvariantBoundingSphere.xyz, 1.0);
     vec4 mCenter = uModel * aTransform * vec4(uInvariantBoundingSphere.xyz, 1.0);
-    if (clipTest(vec4(mCenter.xyz, uInvariantBoundingSphere.w), flag))
+    if (clipTest(vec4(mCenter.xyz, uInvariantBoundingSphere.w)))
         // move out of [ -w, +w ] to 'discard' in vert shader
         // move out of [ -w, +w ] to 'discard' in vert shader
         gl_Position.z = 2.0 * gl_Position.w;
         gl_Position.z = 2.0 * gl_Position.w;
 #endif
 #endif

+ 1 - 7
src/mol-gl/shader/chunks/clip-pixel.glsl.ts

@@ -1,12 +1,6 @@
 export const clip_pixel = `
 export const clip_pixel = `
 #if defined(dClipVariant_pixel) && dClipObjectCount != 0
 #if defined(dClipVariant_pixel) && dClipObjectCount != 0
-    #if defined(dClipping)
-        int clippingFlag = int(floor(vClipping * 255.0 + 0.5));
-    #else
-        int clippingFlag = 0;
-    #endif
-
-    if (clipTest(vec4(vModelPosition, 0.0), clippingFlag))
+    if (clipTest(vec4(vModelPosition, 0.0)))
         discard;
         discard;
 #endif
 #endif
 `;
 `;

+ 20 - 14
src/mol-gl/shader/chunks/common-clip.glsl.ts

@@ -1,5 +1,5 @@
 /**
 /**
- * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  *
  * @author Ludovic Autin <autin@scripps.edu>
  * @author Ludovic Autin <autin@scripps.edu>
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -7,45 +7,45 @@
 
 
 export const common_clip = `
 export const common_clip = `
 #if dClipObjectCount != 0
 #if dClipObjectCount != 0
-    vec3 quaternionTransform(vec4 q, vec3 v) {
+    vec3 quaternionTransform(const in vec4 q, const in vec3 v) {
         vec3 t = 2.0 * cross(q.xyz, v);
         vec3 t = 2.0 * cross(q.xyz, v);
         return v + q.w * t + cross(q.xyz, t);
         return v + q.w * t + cross(q.xyz, t);
     }
     }
 
 
-    vec4 computePlane(vec3 normal, vec3 inPoint) {
+    vec4 computePlane(const in vec3 normal, const in vec3 inPoint) {
         return vec4(normalize(normal), -dot(normal, inPoint));
         return vec4(normalize(normal), -dot(normal, inPoint));
     }
     }
 
 
-    float planeSD(vec4 plane, vec3 center) {
+    float planeSD(const in vec4 plane, const in vec3 center) {
         return -dot(plane.xyz, center - plane.xyz * -plane.w);
         return -dot(plane.xyz, center - plane.xyz * -plane.w);
     }
     }
 
 
-    float sphereSD(vec3 position, vec4 rotation, vec3 size, vec3 center) {
+    float sphereSD(const in vec3 position, const in vec4 rotation, const in vec3 size, const in vec3 center) {
         return (
         return (
             length(quaternionTransform(vec4(-rotation.x, -rotation.y, -rotation.z, rotation.w), center - position) / size) - 1.0
             length(quaternionTransform(vec4(-rotation.x, -rotation.y, -rotation.z, rotation.w), center - position) / size) - 1.0
         ) * min(min(size.x, size.y), size.z);
         ) * min(min(size.x, size.y), size.z);
     }
     }
 
 
-    float cubeSD(vec3 position, vec4 rotation, vec3 size, vec3 center) {
+    float cubeSD(const in vec3 position, const in vec4 rotation, const in vec3 size, const in vec3 center) {
         vec3 d = abs(quaternionTransform(vec4(-rotation.x, -rotation.y, -rotation.z, rotation.w), center - position)) - size;
         vec3 d = abs(quaternionTransform(vec4(-rotation.x, -rotation.y, -rotation.z, rotation.w), center - position)) - size;
         return min(max(d.x, max(d.y, d.z)), 0.0) + length(max(d, 0.0));
         return min(max(d.x, max(d.y, d.z)), 0.0) + length(max(d, 0.0));
     }
     }
 
 
-    float cylinderSD(vec3 position, vec4 rotation, vec3 size, vec3 center) {
+    float cylinderSD(const in vec3 position, const in vec4 rotation, const in vec3 size, const in vec3 center) {
         vec3 t = quaternionTransform(vec4(-rotation.x, -rotation.y, -rotation.z, rotation.w), center - position);
         vec3 t = quaternionTransform(vec4(-rotation.x, -rotation.y, -rotation.z, rotation.w), center - position);
 
 
         vec2 d = abs(vec2(length(t.xz), t.y)) - size.xy;
         vec2 d = abs(vec2(length(t.xz), t.y)) - size.xy;
         return min(max(d.x, d.y), 0.0) + length(max(d, 0.0));
         return min(max(d.x, d.y), 0.0) + length(max(d, 0.0));
     }
     }
 
 
-    float infiniteConeSD(vec3 position, vec4 rotation, vec3 size, vec3 center) {
+    float infiniteConeSD(const in vec3 position, const in vec4 rotation, const in vec3 size, const in vec3 center) {
         vec3 t = quaternionTransform(vec4(-rotation.x, -rotation.y, -rotation.z, rotation.w), center - position);
         vec3 t = quaternionTransform(vec4(-rotation.x, -rotation.y, -rotation.z, rotation.w), center - position);
 
 
         float q = length(t.xy);
         float q = length(t.xy);
         return dot(size.xy, vec2(q, t.z));
         return dot(size.xy, vec2(q, t.z));
     }
     }
 
 
-    float getSignedDistance(vec3 center, int type, vec3 position, vec4 rotation, vec3 scale) {
+    float getSignedDistance(const in vec3 center, const in int type, const in vec3 position, const in vec4 rotation, const in vec3 scale) {
         if (type == 1) {
         if (type == 1) {
             vec3 normal = quaternionTransform(rotation, vec3(0.0, 1.0, 0.0));
             vec3 normal = quaternionTransform(rotation, vec3(0.0, 1.0, 0.0));
             vec4 plane = computePlane(normal, position);
             vec4 plane = computePlane(normal, position);
@@ -65,7 +65,7 @@ export const common_clip = `
 
 
     #if __VERSION__ == 100
     #if __VERSION__ == 100
         // 8-bit
         // 8-bit
-        int bitwiseAnd(int a, int b) {
+        int bitwiseAnd(const in int a, const in int b) {
             int d = 128;
             int d = 128;
             int result = 0;
             int result = 0;
             for (int i = 0; i < 8; ++i) {
             for (int i = 0; i < 8; ++i) {
@@ -78,17 +78,23 @@ export const common_clip = `
             return result;
             return result;
         }
         }
 
 
-        bool hasBit(int mask, int bit) {
+        bool hasBit(const in int mask, const in int bit) {
             return bitwiseAnd(mask, bit) == 0;
             return bitwiseAnd(mask, bit) == 0;
         }
         }
     #else
     #else
-        bool hasBit(int mask, int bit) {
+        bool hasBit(const in int mask, const in int bit) {
             return (mask & bit) == 0;
             return (mask & bit) == 0;
         }
         }
     #endif
     #endif
 
 
-    // flag is a bit-flag for clip-objects to ignore (note, object ids start at 1 not 0)
-    bool clipTest(vec4 sphere, int flag) {
+    bool clipTest(const in vec4 sphere) {
+        // flag is a bit-flag for clip-objects to ignore (note, object ids start at 1 not 0)
+        #if defined(dClipping)
+            int flag = int(floor(vClipping * 255.0 + 0.5));
+        #else
+            int flag = 0;
+        #endif
+
         #pragma unroll_loop_start
         #pragma unroll_loop_start
         for (int i = 0; i < dClipObjectCount; ++i) {
         for (int i = 0; i < dClipObjectCount; ++i) {
             if (flag == 0 || hasBit(flag, UNROLLED_LOOP_INDEX + 1)) {
             if (flag == 0 || hasBit(flag, UNROLLED_LOOP_INDEX + 1)) {

+ 22 - 11
src/mol-gl/shader/cylinders.frag.ts

@@ -34,7 +34,7 @@ bool CylinderImpostor(
     in vec3 rayOrigin, in vec3 rayDir,
     in vec3 rayOrigin, in vec3 rayDir,
     in vec3 start, in vec3 end, in float radius,
     in vec3 start, in vec3 end, in float radius,
     out vec3 cameraNormal, out bool interior,
     out vec3 cameraNormal, out bool interior,
-    out vec3 viewPosition, out float fragmentDepth
+    out vec3 modelPosition, out vec3 viewPosition, out float fragmentDepth
 ){
 ){
     vec3 ba = end - start;
     vec3 ba = end - start;
     vec3 oc = rayOrigin - start;
     vec3 oc = rayOrigin - start;
@@ -60,8 +60,12 @@ bool CylinderImpostor(
     if (y > 0.0 && y < baba) {
     if (y > 0.0 && y < baba) {
         interior = false;
         interior = false;
         cameraNormal = (oc + t * rayDir - ba * y / baba) / radius;
         cameraNormal = (oc + t * rayDir - ba * y / baba) / radius;
-        viewPosition = (uView * vec4(rayOrigin + t * rayDir, 1.0)).xyz;
+        modelPosition = rayOrigin + t * rayDir;
+        viewPosition = (uView * vec4(modelPosition, 1.0)).xyz;
         fragmentDepth = calcDepth(viewPosition);
         fragmentDepth = calcDepth(viewPosition);
+        #if defined(dClipVariant_pixel) && dClipObjectCount != 0
+            if (clipTest(vec4(modelPosition, 0.0))) fragmentDepth = -1.0;
+        #endif
         if (fragmentDepth > 0.0) return true;
         if (fragmentDepth > 0.0) return true;
     }
     }
 
 
@@ -71,7 +75,8 @@ bool CylinderImpostor(
         if (abs(k1 + k2 * t) < h) {
         if (abs(k1 + k2 * t) < h) {
             interior = false;
             interior = false;
             cameraNormal = -ba / baba;
             cameraNormal = -ba / baba;
-            viewPosition = (uView * vec4(rayOrigin + t * rayDir, 1.0)).xyz;
+            modelPosition = rayOrigin + t * rayDir;
+            viewPosition = (uView * vec4(modelPosition, 1.0)).xyz;
             fragmentDepth = calcDepth(viewPosition);
             fragmentDepth = calcDepth(viewPosition);
             if (fragmentDepth > 0.0) return true;
             if (fragmentDepth > 0.0) return true;
         }
         }
@@ -81,7 +86,8 @@ bool CylinderImpostor(
         if (abs(k1 + k2 * t) < h) {
         if (abs(k1 + k2 * t) < h) {
             interior = false;
             interior = false;
             cameraNormal = ba / baba;
             cameraNormal = ba / baba;
-            viewPosition = (uView * vec4(rayOrigin + t * rayDir, 1.0)).xyz;
+            modelPosition = rayOrigin + t * rayDir;
+            viewPosition = (uView * vec4(modelPosition, 1.0)).xyz;
             fragmentDepth = calcDepth(viewPosition);
             fragmentDepth = calcDepth(viewPosition);
             if (fragmentDepth > 0.0) return true;
             if (fragmentDepth > 0.0) return true;
         }
         }
@@ -95,7 +101,8 @@ bool CylinderImpostor(
         if (y > 0.0 && y < baba) {
         if (y > 0.0 && y < baba) {
             interior = true;
             interior = true;
             cameraNormal = -(oc + t * rayDir - ba * y / baba) / radius;
             cameraNormal = -(oc + t * rayDir - ba * y / baba) / radius;
-            viewPosition = (uView * vec4(rayOrigin + t * rayDir, 1.0)).xyz;
+            modelPosition = rayOrigin + t * rayDir;
+            viewPosition = (uView * vec4(modelPosition, 1.0)).xyz;
             fragmentDepth = calcDepth(viewPosition);
             fragmentDepth = calcDepth(viewPosition);
             return true;
             return true;
         }
         }
@@ -106,7 +113,8 @@ bool CylinderImpostor(
             if (abs(k1 + k2 * t) < -h) {
             if (abs(k1 + k2 * t) < -h) {
                 interior = true;
                 interior = true;
                 cameraNormal = ba / baba;
                 cameraNormal = ba / baba;
-                viewPosition = (uView * vec4(rayOrigin + t * rayDir, 1.0)).xyz;
+                modelPosition = rayOrigin + t * rayDir;
+                viewPosition = (uView * vec4(modelPosition, 1.0)).xyz;
                 fragmentDepth = calcDepth(viewPosition);
                 fragmentDepth = calcDepth(viewPosition);
                 if (fragmentDepth > 0.0) return true;
                 if (fragmentDepth > 0.0) return true;
             }
             }
@@ -116,7 +124,8 @@ bool CylinderImpostor(
             if (abs(k1 + k2 * t) < -h) {
             if (abs(k1 + k2 * t) < -h) {
                 interior = true;
                 interior = true;
                 cameraNormal = -ba / baba;
                 cameraNormal = -ba / baba;
-                viewPosition = (uView * vec4(rayOrigin + t * rayDir, 1.0)).xyz;
+                modelPosition = rayOrigin + t * rayDir;
+                viewPosition = (uView * vec4(modelPosition, 1.0)).xyz;
                 fragmentDepth = calcDepth(viewPosition);
                 fragmentDepth = calcDepth(viewPosition);
                 if (fragmentDepth > 0.0) return true;
                 if (fragmentDepth > 0.0) return true;
             }
             }
@@ -127,15 +136,14 @@ bool CylinderImpostor(
 }
 }
 
 
 void main() {
 void main() {
-    #include clip_pixel
-
     vec3 rayOrigin = vModelPosition;
     vec3 rayOrigin = vModelPosition;
     vec3 rayDir = mix(normalize(vModelPosition - uCameraPosition), uCameraDir, uIsOrtho);
     vec3 rayDir = mix(normalize(vModelPosition - uCameraPosition), uCameraDir, uIsOrtho);
 
 
     vec3 cameraNormal;
     vec3 cameraNormal;
+    vec3 modelPosition;
     vec3 viewPosition;
     vec3 viewPosition;
     float fragmentDepth;
     float fragmentDepth;
-    bool hit = CylinderImpostor(rayOrigin, rayDir, vStart, vEnd, vSize, cameraNormal, interior, viewPosition, fragmentDepth);
+    bool hit = CylinderImpostor(rayOrigin, rayDir, vStart, vEnd, vSize, cameraNormal, interior, modelPosition, viewPosition, fragmentDepth);
     if (!hit) discard;
     if (!hit) discard;
 
 
     if (fragmentDepth < 0.0) discard;
     if (fragmentDepth < 0.0) discard;
@@ -143,7 +151,10 @@ void main() {
 
 
     gl_FragDepthEXT = fragmentDepth;
     gl_FragDepthEXT = fragmentDepth;
 
 
-    vec3 vModelPosition = (uInvView * vec4(viewPosition, 1.0)).xyz;
+    vec3 vViewPosition = viewPosition;
+    vec3 vModelPosition = modelPosition;
+
+    #include clip_pixel
     #include assign_material_color
     #include assign_material_color
 
 
     #if defined(dRenderVariant_pick)
     #if defined(dRenderVariant_pick)

+ 6 - 1
src/mol-gl/shader/cylinders.vert.ts

@@ -55,7 +55,12 @@ void main() {
     vec3 camDir = -mix(normalize(vModelPosition - uCameraPosition), uCameraDir, uIsOrtho);
     vec3 camDir = -mix(normalize(vModelPosition - uCameraPosition), uCameraDir, uIsOrtho);
     vec3 dir = vEnd - vStart;
     vec3 dir = vEnd - vStart;
     // ensure cylinder 'dir' is pointing towards the camera
     // ensure cylinder 'dir' is pointing towards the camera
-    if(dot(camDir, dir) < 0.0) dir = -dir;
+    if(dot(camDir, dir) < 0.0) {
+        dir = -dir;
+        vec3 tmp = vStart;
+        vStart = vEnd;
+        vEnd = tmp;
+    }
 
 
     vec3 left = cross(camDir, dir);
     vec3 left = cross(camDir, dir);
     vec3 up = cross(left, dir);
     vec3 up = cross(left, dir);

+ 1 - 1
src/mol-gl/shader/direct-volume.frag.ts

@@ -229,7 +229,7 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 rayDir) {
 
 
         #if defined(dClipVariant_pixel) && dClipObjectCount != 0
         #if defined(dClipVariant_pixel) && dClipObjectCount != 0
             vec3 vModelPosition = v3m4(unitPos * uGridDim, modelTransform);
             vec3 vModelPosition = v3m4(unitPos * uGridDim, modelTransform);
-            if (clipTest(vec4(vModelPosition, 0.0), 0)) {
+            if (clipTest(vec4(vModelPosition, 0.0))) {
                 prevValue = value;
                 prevValue = value;
                 pos += step;
                 pos += step;
                 continue;
                 continue;

+ 5 - 1
src/mol-gl/shader/outlines.frag.ts

@@ -38,7 +38,11 @@ float getDepthOpaque(const in vec2 coords) {
 }
 }
 
 
 float getDepthTransparent(const in vec2 coords) {
 float getDepthTransparent(const in vec2 coords) {
-    return unpackRGBAToDepth(texture2D(tDepthTransparent, coords));
+    #ifdef dTransparentOutline
+        return unpackRGBAToDepth(texture2D(tDepthTransparent, coords));
+    #else
+        return 1.0;
+    #endif
 }
 }
 
 
 bool isBackground(const in float depth) {
 bool isBackground(const in float depth) {

+ 5 - 1
src/mol-gl/shader/postprocessing.frag.ts

@@ -51,7 +51,11 @@ float getDepthOpaque(const in vec2 coords) {
 }
 }
 
 
 float getDepthTransparent(const in vec2 coords) {
 float getDepthTransparent(const in vec2 coords) {
-    return unpackRGBAToDepth(texture2D(tDepthTransparent, coords));
+    #ifdef dTransparentOutline
+        return unpackRGBAToDepth(texture2D(tDepthTransparent, coords));
+    #else
+        return 1.0;
+    #endif
 }
 }
 
 
 bool isBackground(const in float depth) {
 bool isBackground(const in float depth) {

+ 18 - 7
src/mol-gl/shader/spheres.frag.ts

@@ -23,7 +23,7 @@ varying float vRadiusSq;
 varying vec3 vPoint;
 varying vec3 vPoint;
 varying vec3 vPointViewPosition;
 varying vec3 vPointViewPosition;
 
 
-bool SphereImpostor(out vec3 cameraPos, out vec3 cameraNormal, out bool interior, out float fragmentDepth){
+bool SphereImpostor(out vec3 modelPos, out vec3 cameraPos, out vec3 cameraNormal, out bool interior, out float fragmentDepth, out bool clipped){
     vec3 cameraSpherePos = -vPointViewPosition;
     vec3 cameraSpherePos = -vPointViewPosition;
 
 
     vec3 rayOrigin = mix(vec3(0.0, 0.0, 0.0), vPoint, uIsOrtho);
     vec3 rayOrigin = mix(vec3(0.0, 0.0, 0.0), vPoint, uIsOrtho);
@@ -40,14 +40,23 @@ bool SphereImpostor(out vec3 cameraPos, out vec3 cameraNormal, out bool interior
     float negT = mix(B - sqrtDet, B + sqrtDet, uIsOrtho);
     float negT = mix(B - sqrtDet, B + sqrtDet, uIsOrtho);
 
 
     cameraPos = rayDirection * negT + rayOrigin;
     cameraPos = rayDirection * negT + rayOrigin;
+    modelPos = (uInvView * vec4(cameraPos, 1.0)).xyz;
     fragmentDepth = calcDepth(cameraPos);
     fragmentDepth = calcDepth(cameraPos);
 
 
+    #if defined(dClipVariant_pixel) && dClipObjectCount != 0
+        if (clipTest(vec4(modelPos, 0.0))) {
+            clipped = true;
+            fragmentDepth = -1.0;
+        }
+    #endif
+
     if (fragmentDepth > 0.0) {
     if (fragmentDepth > 0.0) {
         cameraNormal = normalize(cameraPos - cameraSpherePos);
         cameraNormal = normalize(cameraPos - cameraSpherePos);
         interior = false;
         interior = false;
         return true;
         return true;
     } else if (uDoubleSided) {
     } else if (uDoubleSided) {
         cameraPos = rayDirection * posT + rayOrigin;
         cameraPos = rayDirection * posT + rayOrigin;
+        modelPos = (uInvView * vec4(cameraPos, 1.0)).xyz;
         fragmentDepth = calcDepth(cameraPos);
         fragmentDepth = calcDepth(cameraPos);
         cameraNormal = -normalize(cameraPos - cameraSpherePos);
         cameraNormal = -normalize(cameraPos - cameraSpherePos);
         interior = true;
         interior = true;
@@ -58,25 +67,27 @@ bool SphereImpostor(out vec3 cameraPos, out vec3 cameraNormal, out bool interior
 }
 }
 
 
 void main(void){
 void main(void){
-    #include clip_pixel
-
+    vec3 modelPos;
     vec3 cameraPos;
     vec3 cameraPos;
     vec3 cameraNormal;
     vec3 cameraNormal;
     float fragmentDepth;
     float fragmentDepth;
-    bool hit = SphereImpostor(cameraPos, cameraNormal, interior, fragmentDepth);
+    bool clipped = false;
+    bool hit = SphereImpostor(modelPos, cameraPos, cameraNormal, interior, fragmentDepth, clipped);
     if (!hit) discard;
     if (!hit) discard;
 
 
     if (fragmentDepth < 0.0) discard;
     if (fragmentDepth < 0.0) discard;
     if (fragmentDepth > 1.0) discard;
     if (fragmentDepth > 1.0) discard;
 
 
-    if (interior) {
+    vec3 vViewPosition = cameraPos;
+    vec3 vModelPosition = modelPos;
+
+    if (interior && !clipped) {
         fragmentDepth = 0.0 + (0.0000001 / vRadius);
         fragmentDepth = 0.0 + (0.0000001 / vRadius);
     }
     }
 
 
     gl_FragDepthEXT = fragmentDepth;
     gl_FragDepthEXT = fragmentDepth;
 
 
-    vec3 vViewPosition = cameraPos;
-    vec3 vModelPosition = (uInvView * vec4(vViewPosition, 1.0)).xyz;
+    #include clip_pixel
     #include assign_material_color
     #include assign_material_color
 
 
     #if defined(dRenderVariant_pick)
     #if defined(dRenderVariant_pick)

+ 2 - 2
src/mol-plugin-ui/structure/quick-styles.tsx

@@ -56,7 +56,7 @@ export class QuickStyles extends PurePluginUIComponent {
                 postprocessing: {
                 postprocessing: {
                     outline: {
                     outline: {
                         name: 'on',
                         name: 'on',
-                        params: { scale: 1, color: Color(0x000000), threshold: 0.25 }
+                        params: { scale: 1, color: Color(0x000000), threshold: 0.25, includeTransparent: true }
                     },
                     },
                     occlusion: {
                     occlusion: {
                         name: 'on',
                         name: 'on',
@@ -79,7 +79,7 @@ export class QuickStyles extends PurePluginUIComponent {
                         name: 'on',
                         name: 'on',
                         params: pp.outline.name === 'on'
                         params: pp.outline.name === 'on'
                             ? pp.outline.params
                             ? pp.outline.params
-                            : { scale: 1, color: Color(0x000000), threshold: 0.33 }
+                            : { scale: 1, color: Color(0x000000), threshold: 0.33, includeTransparent: true }
                     },
                     },
                     occlusion: {
                     occlusion: {
                         name: 'on',
                         name: 'on',