Kaynağa Gözat

support outlines for transparent objects

- cleanup of renderer and draw pass
Alexander Rose 2 yıl önce
ebeveyn
işleme
87189cee58

+ 1 - 0
CHANGELOG.md

@@ -7,6 +7,7 @@ Note that since we don't clearly distinguish between a public and private interf
 ## [Unreleased]
 
 - Fix case handling of ``struct_conf`` mmCIF enumeration field (#425)
+- Add support for outlines around transparent objects (only per-object)
 
 ## [v3.7.0] - 2022-04-13
 

+ 31 - 90
src/mol-canvas3d/passes/draw.ts

@@ -11,48 +11,16 @@ import { Renderer } from '../../mol-gl/renderer';
 import { Scene } from '../../mol-gl/scene';
 import { Texture } from '../../mol-gl/webgl/texture';
 import { Camera, ICamera } from '../camera';
-import { QuadSchema, QuadValues } from '../../mol-gl/compute/util';
-import { DefineSpec, TextureSpec, UniformSpec, Values } from '../../mol-gl/renderable/schema';
-import { ComputeRenderable, createComputeRenderable } from '../../mol-gl/renderable';
-import { ShaderCode } from '../../mol-gl/shader-code';
-import { createComputeRenderItem } from '../../mol-gl/webgl/render-item';
 import { ValueCell } from '../../mol-util';
 import { Vec2 } from '../../mol-math/linear-algebra';
 import { Helper } from '../helper/helper';
 
-import { quad_vert } from '../../mol-gl/shader/quad.vert';
-import { depthMerge_frag } from '../../mol-gl/shader/depth-merge.frag';
 import { StereoCamera } from '../camera/stereo';
 import { WboitPass } from './wboit';
 import { AntialiasingPass, PostprocessingPass, PostprocessingProps } from './postprocessing';
 import { MarkingPass, MarkingProps } from './marking';
 import { CopyRenderable, createCopyRenderable } from '../../mol-gl/compute/util';
 
-const DepthMergeSchema = {
-    ...QuadSchema,
-    tDepthPrimitives: TextureSpec('texture', 'depth', 'ushort', 'nearest'),
-    tDepthVolumes: TextureSpec('texture', 'depth', 'ushort', 'nearest'),
-    uTexSize: UniformSpec('v2'),
-    dPackedDepth: DefineSpec('boolean'),
-};
-const DepthMergeShaderCode = ShaderCode('depth-merge', quad_vert, depthMerge_frag);
-type DepthMergeRenderable = ComputeRenderable<Values<typeof DepthMergeSchema>>
-
-function getDepthMergeRenderable(ctx: WebGLContext, depthTexturePrimitives: Texture, depthTextureVolumes: Texture, packedDepth: boolean): DepthMergeRenderable {
-    const values: Values<typeof DepthMergeSchema> = {
-        ...QuadValues,
-        tDepthPrimitives: ValueCell.create(depthTexturePrimitives),
-        tDepthVolumes: ValueCell.create(depthTextureVolumes),
-        uTexSize: ValueCell.create(Vec2.create(depthTexturePrimitives.getWidth(), depthTexturePrimitives.getHeight())),
-        dPackedDepth: ValueCell.create(packedDepth),
-    };
-
-    const schema = { ...DepthMergeSchema };
-    const renderItem = createComputeRenderItem(ctx, 'triangles', DepthMergeShaderCode, schema, values);
-
-    return createComputeRenderable(renderItem, values);
-}
-
 type Props = {
     postprocessing: PostprocessingProps
     marking: MarkingProps
@@ -79,7 +47,6 @@ export class DrawPass {
     private depthTargetPrimitives: RenderTarget | null;
     private depthTargetVolumes: RenderTarget | null;
     private depthTextureVolumes: Texture;
-    private depthMerge: DepthMergeRenderable;
 
     private copyFboTarget: CopyRenderable;
     private copyFboPostprocessing: CopyRenderable;
@@ -113,7 +80,6 @@ export class DrawPass {
             this.depthTexturePrimitives.define(width, height);
             this.depthTextureVolumes.define(width, height);
         }
-        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);
@@ -148,8 +114,6 @@ export class DrawPass {
                 this.depthTextureVolumes.define(width, height);
             }
 
-            ValueCell.update(this.depthMerge.values.uTexSize, Vec2.set(this.depthMerge.values.uTexSize.ref.value, width, height));
-
             ValueCell.update(this.copyFboTarget.values.uTexSize, Vec2.set(this.copyFboTarget.values.uTexSize.ref.value, width, height));
             ValueCell.update(this.copyFboPostprocessing.values.uTexSize, Vec2.set(this.copyFboPostprocessing.values.uTexSize.ref.value, width, height));
 
@@ -163,20 +127,6 @@ export class DrawPass {
         }
     }
 
-    private _depthMerge() {
-        const { state, gl } = this.webgl;
-
-        this.depthMerge.update();
-        this.depthTarget.bind();
-        state.disable(gl.BLEND);
-        state.disable(gl.DEPTH_TEST);
-        state.disable(gl.CULL_FACE);
-        state.depthMask(false);
-        state.clearColor(1, 1, 1, 1);
-        gl.clear(gl.COLOR_BUFFER_BIT);
-        this.depthMerge.render();
-    }
-
     private _renderWboit(renderer: Renderer, camera: ICamera, scene: Scene, transparentBackground: boolean, postprocessingProps: PostprocessingProps) {
         if (!this.wboit?.supported) throw new Error('expected wboit to be supported');
 
@@ -189,30 +139,27 @@ export class DrawPass {
         renderer.clearDepth();
         renderer.renderWboitOpaque(scene.primitives, camera, null);
 
-        // render opaque volumes
-        this.depthTextureVolumes.attachFramebuffer(this.colorTarget.framebuffer, 'depth');
-        this.colorTarget.bind();
-        renderer.clearDepth();
-        renderer.renderWboitOpaque(scene.volumes, camera, this.depthTexturePrimitives);
-
-        // merge depth of opaque primitives and volumes
-        this._depthMerge();
-
         if (PostprocessingPass.isEnabled(postprocessingProps)) {
+            if (PostprocessingPass.isOutlineEnabled(postprocessingProps)) {
+                this.depthTarget.bind();
+                renderer.clear(false);
+                if (scene.getOpacityAverage() < 1) {
+                    renderer.renderDepthTransparent(scene.primitives, camera, null);
+                }
+            }
+
             this.postprocessing.render(camera, false, transparentBackground, renderer.props.backgroundColor, postprocessingProps);
         }
 
         // render transparent primitives and volumes
         this.wboit.bind();
-        renderer.renderWboitTransparent(scene.primitives, camera, this.depthTexture);
-        renderer.renderWboitTransparent(scene.volumes, camera, this.depthTexture);
+        renderer.renderWboitTransparent(scene.primitives, camera, this.depthTexturePrimitives);
+        renderer.renderWboitTransparent(scene.volumes, camera, this.depthTexturePrimitives);
 
         // evaluate wboit
         if (PostprocessingPass.isEnabled(postprocessingProps)) {
-            this.depthTexturePrimitives.attachFramebuffer(this.postprocessing.target.framebuffer, 'depth');
             this.postprocessing.target.bind();
         } else {
-            this.depthTexturePrimitives.attachFramebuffer(this.colorTarget.framebuffer, 'depth');
             this.colorTarget.bind();
         }
         this.wboit.render();
@@ -237,41 +184,35 @@ export class DrawPass {
             if (this.depthTargetPrimitives) {
                 this.depthTargetPrimitives.bind();
                 renderer.clear(false);
-                // TODO: this should only render opaque
-                renderer.renderDepth(scene.primitives, camera, null);
+                renderer.renderDepthOpaque(scene.primitives, camera, null);
                 this.colorTarget.bind();
             }
 
-            // do direct-volume rendering
-            if (!this.packedDepth) {
-                this.depthTextureVolumes.attachFramebuffer(this.colorTarget.framebuffer, 'depth');
-                renderer.clearDepth(); // from previous frame
-            }
-            renderer.renderBlendedVolumeOpaque(scene.volumes, camera, this.depthTexturePrimitives);
-
-            // do volume depth pass if extensions.depthTexture is unsupported (i.e. depthTarget is set)
-            if (this.depthTargetVolumes) {
-                this.depthTargetVolumes.bind();
-                renderer.clear(false);
-                renderer.renderDepth(scene.volumes, camera, this.depthTexturePrimitives);
-                this.colorTarget.bind();
-            }
+            if (PostprocessingPass.isEnabled(postprocessingProps)) {
+                if (!this.packedDepth) {
+                    this.depthTexturePrimitives.detachFramebuffer(this.postprocessing.target.framebuffer, 'depth');
+                } else {
+                    this.colorTarget.depthRenderbuffer?.detachFramebuffer(this.postprocessing.target.framebuffer);
+                }
 
-            // merge depths from primitive and volume rendering
-            this._depthMerge();
-            this.colorTarget.bind();
+                if (PostprocessingPass.isOutlineEnabled(postprocessingProps)) {
+                    this.depthTarget.bind();
+                    renderer.clear(false);
+                    if (scene.getOpacityAverage() < 1) {
+                        renderer.renderDepthTransparent(scene.primitives, camera, null);
+                    }
+                }
 
-            if (PostprocessingPass.isEnabled(postprocessingProps)) {
                 this.postprocessing.render(camera, false, transparentBackground, renderer.props.backgroundColor, postprocessingProps);
-            }
-            renderer.renderBlendedVolumeTransparent(scene.volumes, camera, this.depthTexturePrimitives);
 
-            const target = PostprocessingPass.isEnabled(postprocessingProps)
-                ? this.postprocessing.target : this.colorTarget;
-            if (!this.packedDepth) {
-                this.depthTexturePrimitives.attachFramebuffer(target.framebuffer, 'depth');
+                if (!this.packedDepth) {
+                    this.depthTexturePrimitives.attachFramebuffer(this.postprocessing.target.framebuffer, 'depth');
+                } else {
+                    this.colorTarget.depthRenderbuffer?.attachFramebuffer(this.postprocessing.target.framebuffer);
+                }
             }
-            target.bind();
+
+            renderer.renderBlendedVolume(scene.volumes, camera, this.depthTexturePrimitives);
         }
 
         renderer.renderBlendedTransparent(scene.primitives, camera, null);

+ 23 - 12
src/mol-canvas3d/passes/postprocessing.ts

@@ -30,7 +30,8 @@ import { SmaaParams, SmaaPass } from './smaa';
 
 const OutlinesSchema = {
     ...QuadSchema,
-    tDepth: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
+    tDepthOpaque: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
+    tDepthTransparent: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
     uTexSize: UniformSpec('v2'),
 
     dOrthographic: DefineSpec('number'),
@@ -41,11 +42,15 @@ const OutlinesSchema = {
 };
 type OutlinesRenderable = ComputeRenderable<Values<typeof OutlinesSchema>>
 
-function getOutlinesRenderable(ctx: WebGLContext, depthTexture: Texture): OutlinesRenderable {
+function getOutlinesRenderable(ctx: WebGLContext, depthTextureOpaque: Texture, depthTextureTransparent: Texture): OutlinesRenderable {
+    const width = depthTextureOpaque.getWidth();
+    const height = depthTextureOpaque.getHeight();
+
     const values: Values<typeof OutlinesSchema> = {
         ...QuadValues,
-        tDepth: ValueCell.create(depthTexture),
-        uTexSize: ValueCell.create(Vec2.create(depthTexture.getWidth(), depthTexture.getHeight())),
+        tDepthOpaque: ValueCell.create(depthTextureOpaque),
+        tDepthTransparent: ValueCell.create(depthTextureTransparent),
+        uTexSize: ValueCell.create(Vec2.create(width, height)),
 
         dOrthographic: ValueCell.create(0),
         uNear: ValueCell.create(1),
@@ -183,7 +188,8 @@ const PostprocessingSchema = {
     ...QuadSchema,
     tSsaoDepth: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
     tColor: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
-    tDepth: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
+    tDepthOpaque: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
+    tDepthTransparent: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
     tOutlines: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
     uTexSize: UniformSpec('v2'),
 
@@ -207,12 +213,13 @@ const PostprocessingSchema = {
 };
 type PostprocessingRenderable = ComputeRenderable<Values<typeof PostprocessingSchema>>
 
-function getPostprocessingRenderable(ctx: WebGLContext, colorTexture: Texture, depthTexture: Texture, outlinesTexture: Texture, ssaoDepthTexture: Texture): PostprocessingRenderable {
+function getPostprocessingRenderable(ctx: WebGLContext, colorTexture: Texture, depthTextureOpaque: Texture, depthTextureTransparent: Texture, outlinesTexture: Texture, ssaoDepthTexture: Texture): PostprocessingRenderable {
     const values: Values<typeof PostprocessingSchema> = {
         ...QuadValues,
         tSsaoDepth: ValueCell.create(ssaoDepthTexture),
         tColor: ValueCell.create(colorTexture),
-        tDepth: ValueCell.create(depthTexture),
+        tDepthOpaque: ValueCell.create(depthTextureOpaque),
+        tDepthTransparent: ValueCell.create(depthTextureTransparent),
         tOutlines: ValueCell.create(outlinesTexture),
         uTexSize: ValueCell.create(Vec2.create(colorTexture.getWidth(), colorTexture.getHeight())),
 
@@ -274,6 +281,10 @@ export class PostprocessingPass {
         return props.occlusion.name === 'on' || props.outline.name === 'on';
     }
 
+    static isOutlineEnabled(props: PostprocessingProps) {
+        return props.outline.name === 'on';
+    }
+
     readonly target: RenderTarget;
 
     private readonly outlinesTarget: RenderTarget;
@@ -307,7 +318,7 @@ export class PostprocessingPass {
     }
 
     constructor(private webgl: WebGLContext, private drawPass: DrawPass) {
-        const { colorTarget, depthTexture } = drawPass;
+        const { colorTarget, depthTexture: depthTextureTransparent, depthTexturePrimitives: depthTextureOpaque } = drawPass;
         const width = colorTarget.getWidth();
         const height = colorTarget.getHeight();
 
@@ -320,7 +331,7 @@ export class PostprocessingPass {
         this.target = webgl.createRenderTarget(width, height, false, 'uint8', 'linear');
 
         this.outlinesTarget = webgl.createRenderTarget(width, height, false);
-        this.outlinesRenderable = getOutlinesRenderable(webgl, depthTexture);
+        this.outlinesRenderable = getOutlinesRenderable(webgl, depthTextureOpaque, depthTextureTransparent);
 
         this.randomHemisphereVector = [];
         for (let i = 0; i < 256; i++) {
@@ -340,7 +351,7 @@ export class PostprocessingPass {
         const sh = Math.floor(height * this.ssaoScale);
 
         this.downsampledDepthTarget = webgl.createRenderTarget(sw, sh, false, 'uint8', 'linear');
-        this.downsampleDepthRenderable = createCopyRenderable(webgl, depthTexture);
+        this.downsampleDepthRenderable = createCopyRenderable(webgl, depthTextureOpaque);
 
         this.ssaoDepthTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
         this.ssaoDepthTexture.define(sw, sh);
@@ -352,10 +363,10 @@ export class PostprocessingPass {
 
         this.ssaoDepthTexture.attachFramebuffer(this.ssaoBlurSecondPassFramebuffer, 'color0');
 
-        this.ssaoRenderable = getSsaoRenderable(webgl, this.downsampleFactor === 1 ? depthTexture : this.downsampledDepthTarget.texture);
+        this.ssaoRenderable = getSsaoRenderable(webgl, this.downsampleFactor === 1 ? depthTextureOpaque : this.downsampledDepthTarget.texture);
         this.ssaoBlurFirstPassRenderable = getSsaoBlurRenderable(webgl, this.ssaoDepthTexture, 'horizontal');
         this.ssaoBlurSecondPassRenderable = getSsaoBlurRenderable(webgl, this.ssaoDepthBlurProxyTexture, 'vertical');
-        this.renderable = getPostprocessingRenderable(webgl, colorTarget.texture, depthTexture, this.outlinesTarget.texture, this.ssaoDepthTexture);
+        this.renderable = getPostprocessingRenderable(webgl, colorTarget.texture, depthTextureOpaque, depthTextureTransparent, this.outlinesTarget.texture, this.ssaoDepthTexture);
     }
 
     setSize(width: number, height: number) {

+ 36 - 29
src/mol-gl/renderer.ts

@@ -59,13 +59,14 @@ interface Renderer {
 
     renderPick: (group: Scene.Group, camera: ICamera, variant: 'pick' | 'depth', depthTexture: Texture | null, pickType: PickType) => void
     renderDepth: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
+    renderDepthOpaque: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
+    renderDepthTransparent: (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
-    renderBlendedVolumeOpaque: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
-    renderBlendedVolumeTransparent: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
+    renderBlendedVolume: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
     renderWboitOpaque: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
     renderWboitTransparent: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
 
@@ -379,6 +380,34 @@ namespace Renderer {
             }
         };
 
+        const renderDepthOpaque = (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.state.opaque) renderObject(r, 'depth', Flag.None);
+            }
+        };
+
+        const renderDepthTransparent = (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.state.opaque) renderObject(r, 'depth', Flag.None);
+            }
+        };
+
         const renderMarkingDepth = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => {
             state.disable(gl.BLEND);
             state.enable(gl.DEPTH_TEST);
@@ -477,26 +506,7 @@ namespace Renderer {
             }
         };
 
-        const renderBlendedVolumeOpaque = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => {
-            state.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
-            state.enable(gl.BLEND);
-
-            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 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) {
-                    renderObject(r, 'colorBlended', Flag.None);
-                }
-            }
-        };
-
-        const renderBlendedVolumeTransparent = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => {
+        const renderBlendedVolume = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => {
             state.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
             state.enable(gl.BLEND);
 
@@ -505,11 +515,7 @@ namespace Renderer {
             const { renderables } = group;
             for (let i = 0, il = renderables.length; i < il; ++i) {
                 const r = renderables[i];
-
-                // 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) {
+                if (r.values.dGeometryType.ref.value === 'directVolume') {
                     renderObject(r, 'colorBlended', Flag.None);
                 }
             }
@@ -577,13 +583,14 @@ namespace Renderer {
 
             renderPick,
             renderDepth,
+            renderDepthOpaque,
+            renderDepthTransparent,
             renderMarkingDepth,
             renderMarkingMask,
             renderBlended,
             renderBlendedOpaque,
             renderBlendedTransparent,
-            renderBlendedVolumeOpaque,
-            renderBlendedVolumeTransparent,
+            renderBlendedVolume,
             renderWboitOpaque,
             renderWboitTransparent,
 

+ 1 - 1
src/mol-gl/shader/chunks/assign-material-color.glsl.ts

@@ -51,7 +51,7 @@ export const assign_material_color = `
             discard;
         float depthTest = 1.0;
         if (uMarkingDepthTest) {
-            depthTest = (fragmentDepth >= getDepth(gl_FragCoord.xy / uDrawingBufferSize)) ? 1.0 : 0.0;
+            depthTest = (fragmentDepth >= getDepthPacked(gl_FragCoord.xy / uDrawingBufferSize)) ? 1.0 : 0.0;
         }
         bool isHighlight = intMod(marker, 2.0) > 0.1;
         float viewZ = depthToViewZ(uIsOrtho, fragmentDepth, uNear, uFar);

+ 9 - 2
src/mol-gl/shader/chunks/common-frag-params.glsl.ts

@@ -70,11 +70,18 @@ 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
+float getDepthPacked(const in vec2 coords) {
     return unpackRGBAToDepth(texture2D(tDepth, coords));
 }
 
+float getDepth(const in vec2 coords) {
+    #ifdef depthTextureSupport
+        return texture2D(tDepth, coords).r;
+    #else
+        return unpackRGBAToDepth(texture2D(tDepth, coords));
+    #endif
+}
+
 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;

+ 1 - 1
src/mol-gl/shader/image.frag.ts

@@ -130,7 +130,7 @@ void main() {
                 discard;
             float depthTest = 1.0;
             if (uMarkingDepthTest) {
-                depthTest = (fragmentDepth >= getDepth(gl_FragCoord.xy / uDrawingBufferSize)) ? 1.0 : 0.0;
+                depthTest = (fragmentDepth >= getDepthPacked(gl_FragCoord.xy / uDrawingBufferSize)) ? 1.0 : 0.0;
             }
             bool isHighlight = intMod(marker, 2.0) > 0.1;
             gl_FragColor = vec4(0.0, depthTest, isHighlight ? 1.0 : 0.0, 1.0);

+ 33 - 10
src/mol-gl/shader/outlines.frag.ts

@@ -1,7 +1,8 @@
 /**
- * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Áron Samuel Kovács <aron.kovacs@mail.muni.cz>
+ @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
 export const outlines_frag = `
@@ -9,7 +10,8 @@ precision highp float;
 precision highp int;
 precision highp sampler2D;
 
-uniform sampler2D tDepth;
+uniform sampler2D tDepthOpaque;
+uniform sampler2D tDepthTransparent;
 uniform vec2 uTexSize;
 
 uniform float uNear;
@@ -27,8 +29,16 @@ float getViewZ(const in float depth) {
     #endif
 }
 
-float getDepth(const in vec2 coords) {
-    return unpackRGBAToDepth(texture2D(tDepth, coords));
+float getDepthOpaque(const in vec2 coords) {
+    #ifdef depthTextureSupport
+        return texture2D(tDepthOpaque, coords).r;
+    #else
+        return unpackRGBAToDepth(texture2D(tDepthOpaque, coords));
+    #endif
+}
+
+float getDepthTransparent(const in vec2 coords) {
+    return unpackRGBAToDepth(texture2D(tDepthTransparent, coords));
 }
 
 bool isBackground(const in float depth) {
@@ -41,8 +51,11 @@ void main(void) {
     vec2 coords = gl_FragCoord.xy / uTexSize;
     vec2 invTexSize = 1.0 / uTexSize;
 
-    float selfDepth = getDepth(coords);
-    float selfViewZ = isBackground(selfDepth) ? backgroundViewZ : getViewZ(getDepth(coords));
+    float selfDepthOpaque = getDepthOpaque(coords);
+    float selfViewZOpaque = isBackground(selfDepthOpaque) ? backgroundViewZ : getViewZ(selfDepthOpaque);
+
+    float selfDepthTransparent = getDepthTransparent(coords);
+    float selfViewZTransparent = isBackground(selfDepthTransparent) ? backgroundViewZ : getViewZ(selfDepthTransparent);
 
     float outline = 1.0;
     float bestDepth = 1.0;
@@ -50,12 +63,22 @@ void main(void) {
     for (int y = -1; y <= 1; y++) {
         for (int x = -1; x <= 1; x++) {
             vec2 sampleCoords = coords + vec2(float(x), float(y)) * invTexSize;
-            float sampleDepth = getDepth(sampleCoords);
-            float sampleViewZ = isBackground(sampleDepth) ? backgroundViewZ : getViewZ(sampleDepth);
 
-            if (abs(selfViewZ - sampleViewZ) > uMaxPossibleViewZDiff && selfDepth > sampleDepth && sampleDepth <= bestDepth) {
+            float sampleDepthOpaque = getDepthOpaque(sampleCoords);
+            float sampleDepthTransparent = getDepthTransparent(sampleCoords);
+
+            float sampleViewZOpaque = isBackground(sampleDepthOpaque) ? backgroundViewZ : getViewZ(sampleDepthOpaque);
+            if (abs(selfViewZOpaque - sampleViewZOpaque) > uMaxPossibleViewZDiff && selfDepthOpaque > sampleDepthOpaque && sampleDepthOpaque <= bestDepth) {
                 outline = 0.0;
-                bestDepth = sampleDepth;
+                bestDepth = sampleDepthOpaque;
+            }
+
+            if (sampleDepthTransparent < sampleDepthOpaque) {
+                float sampleViewZTransparent = isBackground(sampleDepthTransparent) ? backgroundViewZ : getViewZ(sampleDepthTransparent);
+                if (abs(selfViewZTransparent - sampleViewZTransparent) > uMaxPossibleViewZDiff && selfDepthTransparent > sampleDepthTransparent && sampleDepthTransparent <= bestDepth) {
+                    outline = 0.0;
+                    bestDepth = sampleDepthTransparent;
+                }
             }
         }
     }

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

@@ -12,7 +12,8 @@ precision highp sampler2D;
 
 uniform sampler2D tSsaoDepth;
 uniform sampler2D tColor;
-uniform sampler2D tDepth;
+uniform sampler2D tDepthOpaque;
+uniform sampler2D tDepthTransparent;
 uniform sampler2D tOutlines;
 uniform vec2 uTexSize;
 
@@ -40,8 +41,16 @@ float getViewZ(const in float depth) {
     #endif
 }
 
-float getDepth(const in vec2 coords) {
-    return unpackRGBAToDepth(texture2D(tDepth, coords));
+float getDepthOpaque(const in vec2 coords) {
+    #ifdef depthTextureSupport
+        return texture2D(tDepthOpaque, coords).r;
+    #else
+        return unpackRGBAToDepth(texture2D(tDepthOpaque, coords));
+    #endif
+}
+
+float getDepthTransparent(const in vec2 coords) {
+    return unpackRGBAToDepth(texture2D(tDepthTransparent, coords));
 }
 
 bool isBackground(const in float depth) {
@@ -52,7 +61,7 @@ float getOutline(const in vec2 coords, out float closestTexel) {
     float backgroundViewZ = uFar + 3.0 * uMaxPossibleViewZDiff;
     vec2 invTexSize = 1.0 / uTexSize;
 
-    float selfDepth = getDepth(coords);
+    float selfDepth = min(getDepthOpaque(coords), getDepthTransparent(coords));
     float selfViewZ = isBackground(selfDepth) ? backgroundViewZ : getViewZ(selfDepth);
 
     float outline = 1.0;
@@ -97,7 +106,7 @@ void main(void) {
     float fogFactor;
 
     #ifdef dOcclusionEnable
-        float depth = getDepth(coords);
+        float depth = getDepthOpaque(coords);
         if (!isBackground(depth)) {
             viewDist = abs(getViewZ(depth));
             fogFactor = smoothstep(uFogNear, uFogFar, viewDist);

+ 10 - 2
src/mol-gl/shader/ssao.frag.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  * @author Áron Samuel Kovács <aron.kovacs@mail.muni.cz>
@@ -51,7 +51,15 @@ bool outsideBounds(const in vec2 p) {
 }
 
 float getDepth(const in vec2 coords) {
-    return outsideBounds(coords) ? 1.0 : unpackRGBAToDepth(texture2D(tDepth, coords));
+    if (outsideBounds(coords)) {
+        return 1.0;
+    } else {
+        #ifdef depthTextureSupport
+            return texture2D(tDepth, coords).r;
+        #else
+            return unpackRGBAToDepth(texture2D(tDepth, coords));
+        #endif
+    }
 }
 
 vec3 normalFromDepth(const in float depth, const in float depth1, const in float depth2, vec2 offset1, vec2 offset2) {