Browse Source

Merge pull request #427 from molstar/transparent-object-outline

Transparent object outline
Alexander Rose 2 years ago
parent
commit
2dc32be9ee

+ 5 - 0
CHANGELOG.md

@@ -7,8 +7,13 @@ 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
+- Improve per-group transparency when wboit is switched off
+- Fix ``allowTransparentBackfaces`` for per-group transparency
 - Fix ``FormatRegistry.isApplicable`` returning true for unregistered formats
 - Fix: handle building of ``GridLookup3D`` with zero cell size
+- Fix ``ignoreLight`` for direct-volume rendering with webgl1
+- Fix (non-black) outlines when using transparent background
 
 ## [v3.7.0] - 2022-04-13
 

+ 32 - 91
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.clearDepth(true);
+                if (scene.getOpacityAverage() < 1) {
+                    renderer.renderDepthTransparent(scene.primitives, camera, this.depthTexturePrimitives);
+                }
+            }
+
             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();
@@ -236,42 +183,36 @@ export class DrawPass {
             // extensions.depthTexture is unsupported (i.e. depthTarget is set)
             if (this.depthTargetPrimitives) {
                 this.depthTargetPrimitives.bind();
-                renderer.clear(false);
-                // TODO: this should only render opaque
-                renderer.renderDepth(scene.primitives, camera, null);
+                renderer.clearDepth(true);
+                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.clearDepth(true);
+                    if (scene.getOpacityAverage() < 1) {
+                        renderer.renderDepthTransparent(scene.primitives, camera, this.depthTexturePrimitives);
+                    }
+                }
 
-            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);

+ 2 - 3
src/mol-canvas3d/passes/pick.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>
  */
@@ -8,7 +8,6 @@ import { PickingId } from '../../mol-geo/geometry/picking';
 import { PickType, Renderer } from '../../mol-gl/renderer';
 import { Scene } from '../../mol-gl/scene';
 import { WebGLContext } from '../../mol-gl/webgl/context';
-import { GraphicsRenderVariant } from '../../mol-gl/webgl/render-item';
 import { RenderTarget } from '../../mol-gl/webgl/render-target';
 import { Vec3 } from '../../mol-math/linear-algebra';
 import { spiral2d } from '../../mol-math/misc';
@@ -64,7 +63,7 @@ export class PickPass {
         }
     }
 
-    private renderVariant(renderer: Renderer, camera: ICamera, scene: Scene, helper: Helper, variant: GraphicsRenderVariant, pickType: number) {
+    private renderVariant(renderer: Renderer, camera: ICamera, scene: Scene, helper: Helper, variant: 'pick' | 'depth', pickType: number) {
         const depth = this.drawPass.depthTexturePrimitives;
         renderer.clear(false);
 

+ 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) {

+ 2 - 2
src/mol-geo/geometry/cylinders/cylinders.ts

@@ -242,7 +242,7 @@ export namespace Cylinders {
             uDoubleSided: ValueCell.create(props.doubleSided),
             dIgnoreLight: ValueCell.create(props.ignoreLight),
             dXrayShaded: ValueCell.create(props.xrayShaded),
-            dOpaqueBackfaces: ValueCell.create(props.transparentBackfaces === 'opaque'),
+            dTransparentBackfaces: ValueCell.create(props.transparentBackfaces),
             uBumpFrequency: ValueCell.create(props.bumpFrequency),
             uBumpAmplitude: ValueCell.create(props.bumpAmplitude),
         };
@@ -260,7 +260,7 @@ export namespace Cylinders {
         ValueCell.updateIfChanged(values.uDoubleSided, props.doubleSided);
         ValueCell.updateIfChanged(values.dIgnoreLight, props.ignoreLight);
         ValueCell.updateIfChanged(values.dXrayShaded, props.xrayShaded);
-        ValueCell.updateIfChanged(values.dOpaqueBackfaces, props.transparentBackfaces === 'opaque');
+        ValueCell.updateIfChanged(values.dTransparentBackfaces, props.transparentBackfaces);
         ValueCell.updateIfChanged(values.uBumpFrequency, props.bumpFrequency);
         ValueCell.updateIfChanged(values.uBumpAmplitude, props.bumpAmplitude);
     }

+ 2 - 2
src/mol-geo/geometry/mesh/mesh.ts

@@ -701,7 +701,7 @@ export namespace Mesh {
             dFlipSided: ValueCell.create(props.flipSided),
             dIgnoreLight: ValueCell.create(props.ignoreLight),
             dXrayShaded: ValueCell.create(props.xrayShaded),
-            dOpaqueBackfaces: ValueCell.create(props.transparentBackfaces === 'opaque'),
+            dTransparentBackfaces: ValueCell.create(props.transparentBackfaces),
             uBumpFrequency: ValueCell.create(props.bumpFrequency),
             uBumpAmplitude: ValueCell.create(props.bumpAmplitude),
 
@@ -722,7 +722,7 @@ export namespace Mesh {
         ValueCell.updateIfChanged(values.dFlipSided, props.flipSided);
         ValueCell.updateIfChanged(values.dIgnoreLight, props.ignoreLight);
         ValueCell.updateIfChanged(values.dXrayShaded, props.xrayShaded);
-        ValueCell.updateIfChanged(values.dOpaqueBackfaces, props.transparentBackfaces === 'opaque');
+        ValueCell.updateIfChanged(values.dTransparentBackfaces, props.transparentBackfaces);
         ValueCell.updateIfChanged(values.uBumpFrequency, props.bumpFrequency);
         ValueCell.updateIfChanged(values.uBumpAmplitude, props.bumpAmplitude);
     }

+ 2 - 2
src/mol-geo/geometry/spheres/spheres.ts

@@ -209,7 +209,7 @@ export namespace Spheres {
             uDoubleSided: ValueCell.create(props.doubleSided),
             dIgnoreLight: ValueCell.create(props.ignoreLight),
             dXrayShaded: ValueCell.create(props.xrayShaded),
-            dOpaqueBackfaces: ValueCell.create(props.transparentBackfaces === 'opaque'),
+            dTransparentBackfaces: ValueCell.create(props.transparentBackfaces),
             uBumpFrequency: ValueCell.create(props.bumpFrequency),
             uBumpAmplitude: ValueCell.create(props.bumpAmplitude),
         };
@@ -227,7 +227,7 @@ export namespace Spheres {
         ValueCell.updateIfChanged(values.uDoubleSided, props.doubleSided);
         ValueCell.updateIfChanged(values.dIgnoreLight, props.ignoreLight);
         ValueCell.updateIfChanged(values.dXrayShaded, props.xrayShaded);
-        ValueCell.updateIfChanged(values.dOpaqueBackfaces, props.transparentBackfaces === 'opaque');
+        ValueCell.updateIfChanged(values.dTransparentBackfaces, props.transparentBackfaces);
         ValueCell.updateIfChanged(values.uBumpFrequency, props.bumpFrequency);
         ValueCell.updateIfChanged(values.uBumpAmplitude, props.bumpAmplitude);
     }

+ 2 - 2
src/mol-geo/geometry/texture-mesh/texture-mesh.ts

@@ -174,7 +174,7 @@ export namespace TextureMesh {
             dFlipSided: ValueCell.create(props.flipSided),
             dIgnoreLight: ValueCell.create(props.ignoreLight),
             dXrayShaded: ValueCell.create(props.xrayShaded),
-            dOpaqueBackfaces: ValueCell.create(props.transparentBackfaces === 'opaque'),
+            dTransparentBackfaces: ValueCell.create(props.transparentBackfaces),
             uBumpFrequency: ValueCell.create(props.bumpFrequency),
             uBumpAmplitude: ValueCell.create(props.bumpAmplitude),
 
@@ -195,7 +195,7 @@ export namespace TextureMesh {
         ValueCell.updateIfChanged(values.dFlipSided, props.flipSided);
         ValueCell.updateIfChanged(values.dIgnoreLight, props.ignoreLight);
         ValueCell.updateIfChanged(values.dXrayShaded, props.xrayShaded);
-        ValueCell.updateIfChanged(values.dOpaqueBackfaces, props.transparentBackfaces === 'opaque');
+        ValueCell.updateIfChanged(values.dTransparentBackfaces, props.transparentBackfaces);
         ValueCell.updateIfChanged(values.uBumpFrequency, props.bumpFrequency);
         ValueCell.updateIfChanged(values.uBumpAmplitude, props.bumpAmplitude);
     }

+ 2 - 1
src/mol-geo/geometry/transparency-data.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>
  */
@@ -29,6 +29,7 @@ export function applyTransparencyValue(array: Uint8Array, start: number, end: nu
 }
 
 export function getTransparencyAverage(array: Uint8Array, count: number): number {
+    if (count === 0 || array.length < count) return 0;
     let sum = 0;
     for (let i = 0; i < count; ++i) {
         sum += array[i];

+ 1 - 1
src/mol-gl/renderable/cylinders.ts

@@ -26,7 +26,7 @@ export const CylindersSchema = {
     uDoubleSided: UniformSpec('b'),
     dIgnoreLight: DefineSpec('boolean'),
     dXrayShaded: DefineSpec('boolean'),
-    dOpaqueBackfaces: DefineSpec('boolean'),
+    dTransparentBackfaces: DefineSpec('string', ['off', 'on', 'opaque']),
     uBumpFrequency: UniformSpec('f'),
     uBumpAmplitude: UniformSpec('f'),
 };

+ 1 - 1
src/mol-gl/renderable/mesh.ts

@@ -22,7 +22,7 @@ export const MeshSchema = {
     dFlipSided: DefineSpec('boolean'),
     dIgnoreLight: DefineSpec('boolean'),
     dXrayShaded: DefineSpec('boolean'),
-    dOpaqueBackfaces: DefineSpec('boolean'),
+    dTransparentBackfaces: DefineSpec('string', ['off', 'on', 'opaque']),
     uBumpFrequency: UniformSpec('f'),
     uBumpAmplitude: UniformSpec('f'),
     meta: ValueSpec('unknown')

+ 2 - 2
src/mol-gl/renderable/schema.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -153,7 +153,7 @@ export const GlobalUniformSchema = {
 
     uXrayEdgeFalloff: UniformSpec('f'),
 
-    uRenderWboit: UniformSpec('b'),
+    uRenderMask: UniformSpec('i'),
     uMarkingDepthTest: UniformSpec('b'),
     uMarkingType: UniformSpec('i'),
     uPickType: UniformSpec('i'),

+ 1 - 1
src/mol-gl/renderable/spheres.ts

@@ -23,7 +23,7 @@ export const SpheresSchema = {
     uDoubleSided: UniformSpec('b'),
     dIgnoreLight: DefineSpec('boolean'),
     dXrayShaded: DefineSpec('boolean'),
-    dOpaqueBackfaces: DefineSpec('boolean'),
+    dTransparentBackfaces: DefineSpec('string', ['off', 'on', 'opaque']),
     uBumpFrequency: UniformSpec('f'),
     uBumpAmplitude: UniformSpec('f'),
 };

+ 1 - 1
src/mol-gl/renderable/texture-mesh.ts

@@ -23,7 +23,7 @@ export const TextureMeshSchema = {
     dFlipSided: DefineSpec('boolean'),
     dIgnoreLight: DefineSpec('boolean'),
     dXrayShaded: DefineSpec('boolean'),
-    dOpaqueBackfaces: DefineSpec('boolean'),
+    dTransparentBackfaces: DefineSpec('string', ['off', 'on', 'opaque']),
     uBumpFrequency: UniformSpec('f'),
     uBumpAmplitude: UniformSpec('f'),
     meta: ValueSpec('unknown')

+ 78 - 52
src/mol-gl/renderer.ts

@@ -54,18 +54,19 @@ interface Renderer {
     readonly props: Readonly<RendererProps>
 
     clear: (toBackgroundColor: boolean, ignoreTransparentBackground?: boolean) => void
-    clearDepth: () => void
+    clearDepth: (packed?: boolean) => void
     update: (camera: ICamera) => void
 
-    renderPick: (group: Scene.Group, camera: ICamera, variant: GraphicsRenderVariant, depthTexture: Texture | null, pickType: PickType) => void
+    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
 
@@ -141,6 +142,12 @@ namespace Renderer {
         BlendedBack = 2
     }
 
+    const enum Mask {
+        All = 0,
+        Opaque = 1,
+        Transparent = 2,
+    }
+
     export function create(ctx: WebGLContext, props: Partial<RendererProps> = {}): Renderer {
         const { gl, state, stats } = ctx;
         const p = PD.merge(RendererParams, PD.getDefaultValues(RendererParams), props);
@@ -152,7 +159,9 @@ namespace Renderer {
 
         let transparentBackground = false;
 
-        const emptyDepthTexture = ctx.resources.texture('image-depth', 'depth', 'ushort', 'nearest');
+        const emptyDepthTexture = ctx.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
+        emptyDepthTexture.define(1, 1);
+        emptyDepthTexture.load({ array: new Uint8Array([255, 255, 255, 255]), width: 1, height: 1 });
         const sharedTexturesList: Textures = [
             ['tDepth', emptyDepthTexture]
         ];
@@ -197,7 +206,7 @@ namespace Renderer {
             uFogFar: ValueCell.create(10000),
             uFogColor: ValueCell.create(bgColor),
 
-            uRenderWboit: ValueCell.create(false),
+            uRenderMask: ValueCell.create(0),
             uMarkingDepthTest: ValueCell.create(false),
             uPickType: ValueCell.create(PickType.None),
             uMarkingType: ValueCell.create(MarkingType.None),
@@ -327,7 +336,7 @@ namespace Renderer {
             ValueCell.updateIfChanged(globalUniforms.uTransparentBackground, transparentBackground);
         };
 
-        const updateInternal = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null, renderWboit: boolean, markingDepthTest: boolean) => {
+        const updateInternal = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null, renderMask: Mask, markingDepthTest: boolean) => {
             arrayMapUpsert(sharedTexturesList, 'tDepth', depthTexture || emptyDepthTexture);
 
             ValueCell.update(globalUniforms.uModel, group.view);
@@ -336,7 +345,7 @@ namespace Renderer {
             ValueCell.update(globalUniforms.uModelViewProjection, Mat4.mul(modelViewProjection, modelView, camera.projection));
             ValueCell.update(globalUniforms.uInvModelViewProjection, Mat4.invert(invModelViewProjection, modelViewProjection));
 
-            ValueCell.updateIfChanged(globalUniforms.uRenderWboit, renderWboit);
+            ValueCell.updateIfChanged(globalUniforms.uRenderMask, renderMask);
             ValueCell.updateIfChanged(globalUniforms.uMarkingDepthTest, markingDepthTest);
 
             state.enable(gl.SCISSOR_TEST);
@@ -355,7 +364,7 @@ namespace Renderer {
             state.enable(gl.DEPTH_TEST);
             state.depthMask(true);
 
-            updateInternal(group, camera, depthTexture, false, false);
+            updateInternal(group, camera, depthTexture, Mask.All, false);
             ValueCell.updateIfChanged(globalUniforms.uPickType, pickType);
 
             const { renderables } = group;
@@ -371,7 +380,7 @@ namespace Renderer {
             state.enable(gl.DEPTH_TEST);
             state.depthMask(true);
 
-            updateInternal(group, camera, depthTexture, false, false);
+            updateInternal(group, camera, depthTexture, Mask.All, false);
 
             const { renderables } = group;
             for (let i = 0, il = renderables.length; i < il; ++i) {
@@ -379,12 +388,44 @@ 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, Mask.Opaque, false);
+
+            const { renderables } = group;
+            for (let i = 0, il = renderables.length; i < il; ++i) {
+                const r = renderables[i];
+                if (r.state.opaque && r.values.transparencyAverage.ref.value !== 1 && !r.values.dXrayShaded?.ref.value) {
+                    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, Mask.Transparent, false);
+
+            const { renderables } = group;
+            for (let i = 0, il = renderables.length; i < il; ++i) {
+                const r = renderables[i];
+                if (!r.state.opaque || r.values.transparencyAverage.ref.value > 0 || r.values.dXrayShaded?.ref.value) {
+                    renderObject(r, 'depth', Flag.None);
+                }
+            }
+        };
+
         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);
+            updateInternal(group, camera, depthTexture, Mask.All, false);
             ValueCell.updateIfChanged(globalUniforms.uMarkingType, MarkingType.Depth);
 
             const { renderables } = group;
@@ -402,7 +443,7 @@ namespace Renderer {
             state.enable(gl.DEPTH_TEST);
             state.depthMask(true);
 
-            updateInternal(group, camera, depthTexture, false, !!depthTexture);
+            updateInternal(group, camera, depthTexture, Mask.All, !!depthTexture);
             ValueCell.updateIfChanged(globalUniforms.uMarkingType, MarkingType.Mask);
 
             const { renderables } = group;
@@ -425,14 +466,14 @@ namespace Renderer {
             state.enable(gl.DEPTH_TEST);
             state.depthMask(true);
 
-            updateInternal(group, camera, depthTexture, false, false);
+            updateInternal(group, camera, depthTexture, Mask.Opaque, false);
 
             const { renderables } = group;
             for (let i = 0, il = renderables.length; i < il; ++i) {
                 const r = renderables[i];
                 if (r.state.opaque) {
                     renderObject(r, 'colorBlended', Flag.None);
-                } else if (r.values.uDoubleSided?.ref.value && r.values.dOpaqueBackfaces?.ref.value) {
+                } else if (r.values.uDoubleSided?.ref.value && r.values.dTransparentBackfaces?.ref.value === 'opaque') {
                     renderObject(r, 'colorBlended', Flag.BlendedBack);
                 }
             }
@@ -441,7 +482,7 @@ namespace Renderer {
         const renderBlendedTransparent = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => {
             state.enable(gl.DEPTH_TEST);
 
-            updateInternal(group, camera, depthTexture, false, false);
+            updateInternal(group, camera, depthTexture, Mask.Transparent, false);
 
             const { renderables } = group;
 
@@ -463,10 +504,10 @@ namespace Renderer {
             state.depthMask(false);
             for (let i = 0, il = renderables.length; i < il; ++i) {
                 const r = renderables[i];
-                if (!r.state.opaque && !r.state.writeDepth) {
+                if ((!r.state.opaque && !r.state.writeDepth) || r.values.transparencyAverage.ref.value > 0) {
                     if (r.values.uDoubleSided?.ref.value) {
                         // render frontfaces and backfaces separately to avoid artefacts
-                        if (!r.values.dOpaqueBackfaces?.ref.value) {
+                        if (r.values.dTransparentBackfaces?.ref.value !== 'opaque') {
                             renderObject(r, 'colorBlended', Flag.BlendedBack);
                         }
                         renderObject(r, 'colorBlended', Flag.BlendedFront);
@@ -477,39 +518,16 @@ namespace Renderer {
             }
         };
 
-        const renderBlendedVolumeOpaque = (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);
 
-            updateInternal(group, camera, depthTexture, false, false);
+            updateInternal(group, camera, depthTexture, Mask.Transparent, 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) => {
-            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 > 0 || r.values.dXrayShaded?.ref.value) {
+                if (r.values.dGeometryType.ref.value === 'directVolume') {
                     renderObject(r, 'colorBlended', Flag.None);
                 }
             }
@@ -520,7 +538,7 @@ namespace Renderer {
             state.enable(gl.DEPTH_TEST);
             state.depthMask(true);
 
-            updateInternal(group, camera, depthTexture, false, false);
+            updateInternal(group, camera, depthTexture, Mask.Opaque, false);
 
             const { renderables } = group;
             for (let i = 0, il = renderables.length; i < il; ++i) {
@@ -529,14 +547,14 @@ namespace Renderer {
                 // 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.dGeometryType.ref.value !== 'directVolume' && r.values.dPointStyle?.ref.value !== 'fuzzy' && !r.values.dXrayShaded?.ref.value) || r.values.dOpaqueBackfaces?.ref.value) {
+                if ((alpha === 1 && r.values.transparencyAverage.ref.value !== 1 && r.values.dGeometryType.ref.value !== 'directVolume' && r.values.dPointStyle?.ref.value !== 'fuzzy' && !r.values.dXrayShaded?.ref.value) || r.values.dTransparentBackfaces?.ref.value === 'opaque') {
                     renderObject(r, 'colorWboit', Flag.None);
                 }
             }
         };
 
         const renderWboitTransparent = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => {
-            updateInternal(group, camera, depthTexture, true, false);
+            updateInternal(group, camera, depthTexture, Mask.Transparent, false);
 
             const { renderables } = group;
             for (let i = 0, il = renderables.length; i < il; ++i) {
@@ -567,23 +585,31 @@ namespace Renderer {
                 }
                 gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
             },
-            clearDepth: () => {
+            clearDepth: (packed = false) => {
                 state.enable(gl.SCISSOR_TEST);
-                state.enable(gl.DEPTH_TEST);
-                state.depthMask(true);
-                gl.clear(gl.DEPTH_BUFFER_BIT);
+
+                if (packed) {
+                    state.colorMask(true, true, true, true);
+                    state.clearColor(1, 1, 1, 1);
+                    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+                } else {
+                    state.enable(gl.DEPTH_TEST);
+                    state.depthMask(true);
+                    gl.clear(gl.DEPTH_BUFFER_BIT);
+                }
             },
             update,
 
             renderPick,
             renderDepth,
+            renderDepthOpaque,
+            renderDepthTransparent,
             renderMarkingDepth,
             renderMarkingMask,
             renderBlended,
             renderBlendedOpaque,
             renderBlendedTransparent,
-            renderBlendedVolumeOpaque,
-            renderBlendedVolumeTransparent,
+            renderBlendedVolume,
             renderWboitOpaque,
             renderWboitTransparent,
 

+ 24 - 1
src/mol-gl/scene.ts

@@ -16,6 +16,7 @@ import { BoundaryHelper } from '../mol-math/geometry/boundary-helper';
 import { hash1 } from '../mol-data/util';
 import { GraphicsRenderable } from './renderable';
 import { GraphicsRenderVariants } from './webgl/render-item';
+import { clamp } from '../mol-math/interpolate';
 
 const boundaryHelper = new BoundaryHelper('98');
 
@@ -80,6 +81,7 @@ interface Scene extends Object3D {
     clear: () => void
     forEach: (callbackFn: (value: GraphicsRenderable, key: GraphicsRenderObject) => void) => void
     getMarkerAverage: () => number
+    getOpacityAverage: () => number
 }
 
 namespace Scene {
@@ -247,14 +249,35 @@ namespace Scene {
             },
             getMarkerAverage() {
                 if (primitives.length === 0 && volumes.length === 0) return 0;
+                let count = 0;
                 let markerAverage = 0;
                 for (let i = 0, il = primitives.length; i < il; ++i) {
+                    if (!primitives[i].state.visible) continue;
                     markerAverage += primitives[i].values.markerAverage.ref.value;
+                    count += 1;
                 }
                 for (let i = 0, il = volumes.length; i < il; ++i) {
+                    if (!volumes[i].state.visible) continue;
                     markerAverage += volumes[i].values.markerAverage.ref.value;
+                    count += 1;
                 }
-                return markerAverage / (primitives.length + volumes.length);
+                return count > 0 ? markerAverage / count : 0;
+            },
+            getOpacityAverage() {
+                if (primitives.length === 0) return 0;
+                let count = 0;
+                let opacityAverage = 0;
+                for (let i = 0, il = primitives.length; i < il; ++i) {
+                    const p = primitives[i];
+                    if (!p.state.visible) continue;
+
+                    // TODO: simplify, handle in renderable.state???
+                    // uAlpha is updated in "render" so we need to recompute it here
+                    const alpha = clamp(p.values.alpha.ref.value * p.state.alphaFactor, 0, 1);
+                    opacityAverage += (1 - p.values.transparencyAverage.ref.value) * alpha;
+                    count += 1;
+                }
+                return count > 0 ? opacityAverage / count : 0;
             },
         };
     }

+ 1 - 1
src/mol-gl/shader/chunks/apply-interior-color.glsl.ts

@@ -6,7 +6,7 @@ if (interior) {
         gl_FragColor.rgb *= 1.0 - uInteriorDarkening;
     }
 
-    #ifdef dOpaqueBackfaces
+    #ifdef dTransparentBackfaces_opaque
         gl_FragColor.a = 1.0;
     #endif
 }

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

@@ -31,11 +31,34 @@ export const assign_material_color = `
 #elif defined(dRenderVariant_pick)
     vec4 material = vColor;
 #elif defined(dRenderVariant_depth)
-    #ifdef enabledFragDepth
-        vec4 material = packDepthToRGBA(gl_FragDepthEXT);
+    if (fragmentDepth > getDepth(gl_FragCoord.xy / uDrawingBufferSize)) {
+        discard;
+    }
+
+    #ifndef dXrayShaded
+        #if defined(dTransparency)
+            float dta = 1.0 - vTransparency;
+            if (vTransparency < 0.2) dta = 1.0; // hard cutoff looks better
+
+            if (uRenderMask == MaskTransparent && uAlpha * dta == 1.0) {
+                discard;
+            } else if (uRenderMask == MaskOpaque && uAlpha * dta < 1.0) {
+                discard;
+            }
+        #else
+            if (uRenderMask == MaskTransparent && uAlpha == 1.0) {
+                discard;
+            } else if (uRenderMask == MaskOpaque && uAlpha < 1.0) {
+                discard;
+            }
+        #endif
     #else
-        vec4 material = packDepthToRGBA(gl_FragCoord.z);
+        if (uRenderMask == MaskOpaque) {
+            discard;
+        }
     #endif
+
+    vec4 material = packDepthToRGBA(fragmentDepth);
 #elif defined(dRenderVariant_marking)
     vec4 material;
     if(uMarkingType == 1) {
@@ -51,7 +74,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);
@@ -62,47 +85,41 @@ export const assign_material_color = `
     }
 #endif
 
-// apply screendoor transparency
-#if defined(dTransparency)
+// apply per-group transparency
+#if defined(dTransparency) && (defined(dRenderVariant_pick) || defined(dRenderVariant_color))
     float ta = 1.0 - vTransparency;
-    #if defined(dRenderVariant_colorWboit)
-        if (vTransparency < 0.2) ta = 1.0; // hard cutoff looks better with wboit
-    #endif
+    if (vTransparency < 0.2) ta = 1.0; // hard cutoff looks better
 
     #if defined(dRenderVariant_pick)
         if (ta < uPickingAlphaThreshold)
             discard; // ignore so the element below can be picked
-    #else
-        #if defined(dRenderVariant_colorBlended)
-            float at = 0.0;
+    #elif defined(dRenderVariant_color)
+        material.a *= ta;
 
-            // shift by view-offset during multi-sample rendering to allow for blending
-            vec2 coord = gl_FragCoord.xy + uViewOffset * 0.25;
-
-            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
-            );
-            int ci = int(intMod(coord.x, 4.0));
-            int ri = int(intMod(coord.y, 4.0));
-            #if __VERSION__ == 100
-                vec4 i = vec4(float(ci * 4 + ri));
-                vec4 v = thresholdMatrix[0] * vec4(equal(i, vec4(0.0, 1.0, 2.0, 3.0))) +
-                    thresholdMatrix[1] * vec4(equal(i, vec4(4.0, 5.0, 6.0, 7.0))) +
-                    thresholdMatrix[2] * vec4(equal(i, vec4(8.0, 9.0, 10.0, 11.0))) +
-                    thresholdMatrix[3] * vec4(equal(i, vec4(12.0, 13.0, 14.0, 15.0)));
-                at = v.x + v.y + v.z + v.w;
-            #else
-                at = thresholdMatrix[ci][ri];
+        #if defined(dRenderVariant_colorBlended)
+            #if defined(dTransparentBackfaces_off)
+                if ((uRenderMask == MaskOpaque && material.a < 1.0) ||
+                    (uRenderMask == MaskTransparent && material.a == 1.0) ||
+                    (interior && material.a < 1.0)
+                ) {
+                    discard;
+                }
+            #elif defined(dTransparentBackfaces_on)
+                if ((uRenderMask == MaskOpaque && material.a < 1.0) ||
+                    (uRenderMask == MaskTransparent && material.a == 1.0)
+                ) {
+                    discard;
+                }
+            #elif defined(dTransparentBackfaces_opaque)
+                if (interior) {
+                    material.a = 1.0;
+                } else if (
+                    (uRenderMask == MaskOpaque && material.a < 1.0) ||
+                    (uRenderMask == MaskTransparent && material.a == 1.0)
+                ) {
+                    discard;
+                }
             #endif
-
-            if (ta < 0.99 && (ta < 0.01 || ta < at)) {
-                discard;
-            }
-        #elif defined(dRenderVariant_colorWboit)
-            material.a *= ta;
         #endif
     #endif
 #endif

+ 1 - 1
src/mol-gl/shader/chunks/check-picking-alpha.glsl.ts

@@ -4,7 +4,7 @@ float fogFactor = smoothstep(uFogNear, uFogFar, abs(viewZ));
 float alpha = (1.0 - fogFactor) * uAlpha;
 // if not opaque enough ignore so the element below can be picked
 if (uAlpha < uPickingAlphaThreshold || alpha < 0.1) {
-    #ifdef dOpaqueBackfaces
+    #ifdef dTransparentBackfaces_opaque
         if (!interior) discard;
     #else
         discard;

+ 10 - 3
src/mol-gl/shader/chunks/common-frag-params.glsl.ts

@@ -64,17 +64,24 @@ uniform float uXrayEdgeFalloff;
 
 uniform mat4 uProjection;
 
-uniform bool uRenderWboit;
+uniform int uRenderMask;
 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;

+ 4 - 0
src/mol-gl/shader/chunks/common.glsl.ts

@@ -17,6 +17,10 @@ export const common = `
     #define dColorType_varying
 #endif
 
+#define MaskAll 0
+#define MaskOpaque 1
+#define MaskTransparent 2
+
 //
 
 #define PI 3.14159265

+ 5 - 2
src/mol-gl/shader/chunks/wboit-write.glsl.ts

@@ -7,13 +7,16 @@
 
 export const wboit_write = `
 #if defined(dRenderVariant_colorWboit)
-    if (!uRenderWboit) {
+    if (uRenderMask == MaskOpaque) {
         if (preFogAlpha < 1.0) {
             discard;
         }
-    } else if (uRenderWboit) {
+    } else if (uRenderMask == MaskTransparent) {
         // the 'fragmentDepth > 0.99' check is to handle precision issues with packed depth
         if (preFogAlpha != 1.0 && (fragmentDepth < getDepth(gl_FragCoord.xy / uDrawingBufferSize) || fragmentDepth > 0.99)) {
+            #ifdef dTransparentBackfaces_off
+                if (interior) discard;
+            #endif
             float alpha = gl_FragColor.a;
             float wboitWeight = alpha * clamp(pow(1.0 - fragmentDepth, 2.0), 0.01, 1.0);
             gl_FragColor = vec4(gl_FragColor.rgb * alpha * wboitWeight, alpha);

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

@@ -72,7 +72,7 @@ uniform float uPickingAlphaThreshold;
 uniform bool uTransparentBackground;
 uniform float uXrayEdgeFalloff;
 
-uniform bool uRenderWboit;
+uniform int uRenderMask;
 
 uniform float uNear;
 uniform float uFar;
@@ -141,12 +141,7 @@ float transferFunction(float value) {
 
 float getDepth(const in vec2 coords) {
     #ifdef depthTextureSupport
-        if (!uRenderWboit) {
-            // in case of opaque volumes (and depth texture support)
-            return texture2D(tDepth, coords).r;
-        } else {
-            return unpackRGBAToDepth(texture2D(tDepth, coords));
-        }
+        return texture2D(tDepth, coords).r;
     #else
         return unpackRGBAToDepth(texture2D(tDepth, coords));
     #endif
@@ -289,20 +284,24 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 rayDir) {
             material.rgb = mix(material.rgb, overpaint.rgb, overpaint.a);
         #endif
 
-        if (material.a >= 0.01) {
-            #ifdef dPackedGroup
-                // compute gradient by central differences
-                gradient.x = textureVal(unitPos - dx).a - textureVal(unitPos + dx).a;
-                gradient.y = textureVal(unitPos - dy).a - textureVal(unitPos + dy).a;
-                gradient.z = textureVal(unitPos - dz).a - textureVal(unitPos + dz).a;
-            #else
-                gradient = cell.xyz * 2.0 - 1.0;
-            #endif
-            vec3 normal = -normalize(normalMatrix * normalize(gradient));
-            #include apply_light_color
-        } else {
+        #ifdef dIgnoreLight
             gl_FragColor.rgb = material.rgb;
-        }
+        #else
+            if (material.a >= 0.01) {
+                #ifdef dPackedGroup
+                    // compute gradient by central differences
+                    gradient.x = textureVal(unitPos - dx).a - textureVal(unitPos + dx).a;
+                    gradient.y = textureVal(unitPos - dy).a - textureVal(unitPos + dy).a;
+                    gradient.z = textureVal(unitPos - dz).a - textureVal(unitPos + dz).a;
+                #else
+                    gradient = cell.xyz * 2.0 - 1.0;
+                #endif
+                vec3 normal = -normalize(normalMatrix * normalize(gradient));
+                #include apply_light_color
+            } else {
+                gl_FragColor.rgb = material.rgb;
+            }
+        #endif
 
         gl_FragColor.a = material.a * uAlpha * uTransferScale;
 

+ 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;
+                }
             }
         }
     }

+ 23 - 14
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,19 +41,27 @@ 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) {
     return depth == 1.0;
 }
 
-float getOutline(const in vec2 coords, out float closestTexel) {
+float getOutline(const in vec2 coords, const in float opaqueDepth, out float closestTexel) {
     float backgroundViewZ = uFar + 3.0 * uMaxPossibleViewZDiff;
     vec2 invTexSize = 1.0 / uTexSize;
 
-    float selfDepth = getDepth(coords);
+    float selfDepth = min(opaqueDepth, getDepthTransparent(coords));
     float selfViewZ = isBackground(selfDepth) ? backgroundViewZ : getViewZ(selfDepth);
 
     float outline = 1.0;
@@ -68,14 +77,15 @@ float getOutline(const in vec2 coords, out float closestTexel) {
             vec4 sampleOutlineCombined = texture2D(tOutlines, sampleCoords);
             float sampleOutline = sampleOutlineCombined.r;
             float sampleOutlineDepth = unpackRGToUnitInterval(sampleOutlineCombined.gb);
+            float sampleOutlineViewZ = isBackground(sampleOutlineDepth) ? backgroundViewZ : getViewZ(sampleOutlineDepth);
 
-            if (sampleOutline == 0.0 && sampleOutlineDepth < closestTexel && abs(selfViewZ - sampleOutlineDepth) > uMaxPossibleViewZDiff) {
+            if (sampleOutline == 0.0 && sampleOutlineDepth < closestTexel && abs(selfViewZ - sampleOutlineViewZ) > uMaxPossibleViewZDiff) {
                 outline = 0.0;
                 closestTexel = sampleOutlineDepth;
             }
         }
     }
-    return outline;
+    return closestTexel < opaqueDepth ? outline : 1.0;
 }
 
 float getSsao(vec2 coords) {
@@ -95,11 +105,11 @@ void main(void) {
 
     float viewDist;
     float fogFactor;
+    float opaqueDepth = getDepthOpaque(coords);
 
     #ifdef dOcclusionEnable
-        float depth = getDepth(coords);
-        if (!isBackground(depth)) {
-            viewDist = abs(getViewZ(depth));
+        if (!isBackground(opaqueDepth)) {
+            viewDist = abs(getViewZ(opaqueDepth));
             fogFactor = smoothstep(uFogNear, uFogFar, viewDist);
             float occlusionFactor = getSsao(coords + uOcclusionOffset);
             if (!uTransparentBackground) {
@@ -113,16 +123,15 @@ void main(void) {
     // outline needs to be handled after occlusion to keep them clean
     #ifdef dOutlineEnable
         float closestTexel;
-        float outline = getOutline(coords, closestTexel);
-
+        float outline = getOutline(coords, opaqueDepth, closestTexel);
         if (outline == 0.0) {
-            color.rgb = mix(uOutlineColor, color.rgb, outline);
             viewDist = abs(getViewZ(closestTexel));
             fogFactor = smoothstep(uFogNear, uFogFar, viewDist);
             if (!uTransparentBackground) {
-                color.rgb = mix(color.rgb, uFogColor, fogFactor);
+                color.rgb = mix(uOutlineColor, uFogColor, fogFactor);
             } else {
                 color.a = 1.0 - fogFactor;
+                color.rgb = mix(uOutlineColor, color.rgb, fogFactor);
             }
         }
     #endif

+ 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) {

+ 5 - 1
src/mol-gl/webgl/render-target.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -9,6 +9,7 @@ import { createNullTexture, Texture, TextureFilter } from './texture';
 import { createNullFramebuffer, Framebuffer } from './framebuffer';
 import { WebGLResources } from './resources';
 import { GLRenderingContext, isWebGL2 } from './compat';
+import { Renderbuffer } from './renderbuffer';
 
 const getNextRenderTargetId = idFactory();
 
@@ -16,6 +17,7 @@ export interface RenderTarget {
     readonly id: number
     readonly texture: Texture
     readonly framebuffer: Framebuffer
+    readonly depthRenderbuffer: Renderbuffer | null
 
     getWidth: () => number
     getHeight: () => number
@@ -54,6 +56,7 @@ export function createRenderTarget(gl: GLRenderingContext, resources: WebGLResou
         id: getNextRenderTargetId(),
         texture: targetTexture,
         framebuffer,
+        depthRenderbuffer,
 
         getWidth: () => _width,
         getHeight: () => _height,
@@ -90,6 +93,7 @@ export function createNullRenderTarget(gl: GLRenderingContext): RenderTarget {
         id: getNextRenderTargetId(),
         texture: createNullTexture(gl),
         framebuffer: createNullFramebuffer(),
+        depthRenderbuffer: null,
 
         getWidth: () => 0,
         getHeight: () => 0,

+ 8 - 1
src/mol-gl/webgl/renderbuffer.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -40,6 +40,7 @@ export interface Renderbuffer {
 
     bind: () => void
     attachFramebuffer: (framebuffer: Framebuffer) => void
+    detachFramebuffer: (framebuffer: Framebuffer) => void
     setSize: (width: number, height: number) => void
     reset: () => void
     destroy: () => void
@@ -78,6 +79,12 @@ export function createRenderbuffer(gl: GLRenderingContext, format: RenderbufferF
             gl.framebufferRenderbuffer(gl.FRAMEBUFFER, _attachment, gl.RENDERBUFFER, _renderbuffer);
             if (isDebugMode) checkFramebufferStatus(gl);
         },
+        detachFramebuffer: (framebuffer: Framebuffer) => {
+            framebuffer.bind();
+            bind();
+            gl.framebufferRenderbuffer(gl.FRAMEBUFFER, _attachment, gl.RENDERBUFFER, null);
+            if (isDebugMode) checkFramebufferStatus(gl);
+        },
         setSize: (width: number, height: number) => {
             _width = width;
             _height = height;

+ 34 - 2
src/mol-gl/webgl/state.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -44,6 +44,10 @@ export type WebGLState = {
     cullFace: (mode: number) => void
     /** sets whether writing into the depth buffer is enabled or disabled */
     depthMask: (flag: boolean) => void
+    /** specifies the depth value used when clearing depth buffer, used when calling `gl.clear` */
+    clearDepth: (depth: number) => void
+    /** sets the depth comparison function */
+    depthFunc: (func: number) => void
     /** sets which color components to enable or to disable */
     colorMask: (red: boolean, green: boolean, blue: boolean, alpha: boolean) => void
     /** specifies the color values used when clearing color buffers, used when calling `gl.clear`, clamped to [0, 1] */
@@ -58,6 +62,8 @@ export type WebGLState = {
     blendEquation: (mode: number) => void
     /** set the RGB blend equation and alpha blend equation separately, determines how a new pixel is combined with an existing */
     blendEquationSeparate: (modeRGB: number, modeAlpha: number) => void
+    /** specifies the source and destination blending factors, clamped to [0, 1] */
+    blendColor: (red: number, green: number, blue: number, alpha: number) => void
 
     enableVertexAttrib: (index: number) => void
     clearVertexAttribsState: () => void
@@ -72,6 +78,8 @@ export function createState(gl: GLRenderingContext): WebGLState {
     let currentFrontFace = gl.getParameter(gl.FRONT_FACE);
     let currentCullFace = gl.getParameter(gl.CULL_FACE_MODE);
     let currentDepthMask = gl.getParameter(gl.DEPTH_WRITEMASK);
+    let currentClearDepth = gl.getParameter(gl.DEPTH_CLEAR_VALUE);
+    let currentDepthFunc = gl.getParameter(gl.DEPTH_FUNC);
     let currentColorMask = gl.getParameter(gl.COLOR_WRITEMASK);
     let currentClearColor = gl.getParameter(gl.COLOR_CLEAR_VALUE);
 
@@ -79,6 +87,7 @@ export function createState(gl: GLRenderingContext): WebGLState {
     let currentBlendDstRGB = gl.getParameter(gl.BLEND_DST_RGB);
     let currentBlendSrcAlpha = gl.getParameter(gl.BLEND_SRC_ALPHA);
     let currentBlendDstAlpha = gl.getParameter(gl.BLEND_DST_ALPHA);
+    let currentBlendColor = gl.getParameter(gl.BLEND_COLOR);
 
     let currentBlendEqRGB = gl.getParameter(gl.BLEND_EQUATION_RGB);
     let currentBlendEqAlpha = gl.getParameter(gl.BLEND_EQUATION_ALPHA);
@@ -129,6 +138,18 @@ export function createState(gl: GLRenderingContext): WebGLState {
                 currentDepthMask = flag;
             }
         },
+        clearDepth: (depth: number) => {
+            if (depth !== currentClearDepth) {
+                gl.clearDepth(depth);
+                currentClearDepth = depth;
+            }
+        },
+        depthFunc: (func: number) => {
+            if (func !== currentDepthFunc) {
+                gl.depthFunc(func);
+                currentDepthFunc = func;
+            }
+        },
         colorMask: (red: boolean, green: boolean, blue: boolean, alpha: boolean) => {
             if (red !== currentColorMask[0] || green !== currentColorMask[1] || blue !== currentColorMask[2] || alpha !== currentColorMask[3]) {
                 gl.colorMask(red, green, blue, alpha);
@@ -166,7 +187,6 @@ export function createState(gl: GLRenderingContext): WebGLState {
                 currentBlendDstAlpha = dstAlpha;
             }
         },
-
         blendEquation: (mode: number) => {
             if (mode !== currentBlendEqRGB || mode !== currentBlendEqAlpha) {
                 gl.blendEquation(mode);
@@ -181,6 +201,15 @@ export function createState(gl: GLRenderingContext): WebGLState {
                 currentBlendEqAlpha = modeAlpha;
             }
         },
+        blendColor: (red: number, green: number, blue: number, alpha: number) => {
+            if (red !== currentBlendColor[0] || green !== currentBlendColor[1] || blue !== currentBlendColor[2] || alpha !== currentBlendColor[3]) {
+                gl.blendColor(red, green, blue, alpha);
+                currentBlendColor[0] = red;
+                currentBlendColor[1] = green;
+                currentBlendColor[2] = blue;
+                currentBlendColor[3] = alpha;
+            }
+        },
 
         enableVertexAttrib: (index: number) => {
             gl.enableVertexAttribArray(index);
@@ -199,6 +228,8 @@ export function createState(gl: GLRenderingContext): WebGLState {
             currentFrontFace = gl.getParameter(gl.FRONT_FACE);
             currentCullFace = gl.getParameter(gl.CULL_FACE_MODE);
             currentDepthMask = gl.getParameter(gl.DEPTH_WRITEMASK);
+            currentClearDepth = gl.getParameter(gl.DEPTH_CLEAR_VALUE);
+            currentDepthFunc = gl.getParameter(gl.DEPTH_FUNC);
             currentColorMask = gl.getParameter(gl.COLOR_WRITEMASK);
             currentClearColor = gl.getParameter(gl.COLOR_CLEAR_VALUE);
 
@@ -206,6 +237,7 @@ export function createState(gl: GLRenderingContext): WebGLState {
             currentBlendDstRGB = gl.getParameter(gl.BLEND_DST_RGB);
             currentBlendSrcAlpha = gl.getParameter(gl.BLEND_SRC_ALPHA);
             currentBlendDstAlpha = gl.getParameter(gl.BLEND_DST_ALPHA);
+            currentBlendColor = gl.getParameter(gl.BLEND_COLOR);
 
             currentBlendEqRGB = gl.getParameter(gl.BLEND_EQUATION_RGB);
             currentBlendEqAlpha = gl.getParameter(gl.BLEND_EQUATION_ALPHA);

+ 4 - 0
src/mol-gl/webgl/texture.ts

@@ -238,6 +238,10 @@ export function createTexture(gl: GLRenderingContext, extensions: WebGLExtension
         throw new Error(`texture kind '${kind}' and type '${_type}' are incompatible`);
     }
 
+    if (!extensions.depthTexture && _format === 'depth') {
+        throw new Error(`extension 'WEBGL_depth_texture' needed for 'depth' texture format`);
+    }
+
     const target = getTarget(gl, kind);
     const filter = getFilter(gl, _filter);
     const format = getFormat(gl, _format, _type);