Browse Source

WBOIT init

Aron Kovacs 4 years ago
parent
commit
a75dc11427

+ 1 - 1
src/mol-canvas3d/canvas3d.ts

@@ -288,7 +288,7 @@ namespace Canvas3D {
                 if (MultiSamplePass.isEnabled(p.multiSample)) {
                     passes.multiSample.render(renderer, cam, scene, helper, true, p.transparentBackground, p);
                 } else {
-                    const toDrawingBuffer = !PostprocessingPass.isEnabled(p.postprocessing) && scene.volumes.renderables.length === 0;
+                    const toDrawingBuffer = false; //!PostprocessingPass.isEnabled(p.postprocessing) && scene.volumes.renderables.length === 0;
                     passes.draw.render(renderer, cam, scene, helper, toDrawingBuffer, p.transparentBackground);
                     if (!toDrawingBuffer) passes.postprocessing.render(cam, true, p.postprocessing);
                 }

+ 53 - 23
src/mol-canvas3d/passes/draw.ts

@@ -52,10 +52,12 @@ export class DrawPass {
     readonly colorTarget: RenderTarget
     readonly depthTexture: Texture
     readonly depthTexturePrimitives: Texture
+    readonly depthTexturePrimitivesTransparent: Texture
 
     private readonly packedDepth: boolean
     private depthTarget: RenderTarget
     private depthTargetPrimitives: RenderTarget | null
+    private depthTargetPrimitivesTransparent: RenderTarget | null
     private depthTargetVolumes: RenderTarget | null
     private depthTextureVolumes: Texture
     private depthMerge: DepthMergeRenderable
@@ -70,9 +72,11 @@ export class DrawPass {
         this.depthTexture = this.depthTarget.texture;
 
         this.depthTargetPrimitives = this.packedDepth ? webgl.createRenderTarget(width, height) : null;
+        this.depthTargetPrimitivesTransparent = this.packedDepth ? webgl.createRenderTarget(width, height) : null;
         this.depthTargetVolumes = this.packedDepth ? webgl.createRenderTarget(width, height) : null;
 
         this.depthTexturePrimitives = this.depthTargetPrimitives ? this.depthTargetPrimitives.texture : resources.texture('image-depth', 'depth', 'ushort', 'nearest');
+        this.depthTexturePrimitivesTransparent = this.depthTargetPrimitivesTransparent ? this.depthTargetPrimitivesTransparent.texture : resources.texture('image-depth', 'depth', 'ushort', 'nearest');
         this.depthTextureVolumes = this.depthTargetVolumes ? this.depthTargetVolumes.texture : resources.texture('image-depth', 'depth', 'ushort', 'nearest');
         if (!this.packedDepth) {
             this.depthTexturePrimitives.define(width, height);
@@ -94,6 +98,11 @@ export class DrawPass {
             } else {
                 this.depthTexturePrimitives.define(width, height);
             }
+            if (this.depthTargetPrimitivesTransparent) {
+                this.depthTargetPrimitivesTransparent.setSize(width, height);
+            } else {
+                this.depthTexturePrimitivesTransparent.define(width, height);
+            }
 
             if (this.depthTargetVolumes) {
                 this.depthTargetVolumes.setSize(width, height);
@@ -109,23 +118,58 @@ export class DrawPass {
         const { x, y, width, height } = camera.viewport;
         renderer.setViewport(x, y, width, height);
 
+        let renderTarget;
         if (toDrawingBuffer) {
-            this.webgl.unbindFramebuffer();
+            renderTarget = null;
         } else {
-            this.colorTarget.bind();
             if (!this.packedDepth) {
                 this.depthTexturePrimitives.attachFramebuffer(this.colorTarget.framebuffer, 'depth');
             }
+            renderTarget = this.colorTarget;
         }
 
-        renderer.render(scene.primitives, camera, 'color', true, transparentBackground, 1, null);
-
         // do a depth pass if not rendering to drawing buffer and
         // extensions.depthTexture is unsupported (i.e. depthTarget is set)
         if (!toDrawingBuffer && this.depthTargetPrimitives) {
-            this.depthTargetPrimitives.bind();
-            renderer.render(scene.primitives, camera, 'depth', true, transparentBackground, 1, null);
-            this.colorTarget.bind();
+            renderer.render(this.depthTargetPrimitives, scene.primitives, camera, 'depth', true, transparentBackground, 1, null, false);
+        }
+
+        this.depthTexturePrimitives.attachFramebuffer(renderTarget!.framebuffer, 'depth');
+        renderer.setViewport(0, 0, this.colorTarget.getWidth(), this.colorTarget.getHeight());
+        renderer.render(renderTarget, scene.primitives, camera, 'color', true, transparentBackground, 1, null, false);
+
+        if (helper.debug.isEnabled) {
+            helper.debug.syncVisibility();
+            renderer.render(renderTarget, helper.debug.scene, camera, 'color', false, transparentBackground, 1, null, false);
+        }
+        if (helper.handle.isEnabled) {
+            renderer.render(renderTarget, helper.handle.scene, camera, 'color', false, transparentBackground, 1, null, false);
+        }
+        if (helper.camera.isEnabled) {
+            helper.camera.update(camera);
+            renderer.render(renderTarget, helper.camera.scene, helper.camera.camera, 'color', false, transparentBackground, 1, null, false);
+        }
+
+        if (!toDrawingBuffer && this.depthTargetPrimitivesTransparent) {
+            renderer.render(this.depthTargetPrimitivesTransparent, scene.primitives, camera, 'depth', true, transparentBackground, 1, null, true);
+        }
+
+        if (renderTarget !== null) {
+            // this.depthTexturePrimitivesTransparent.attachFramebuffer(renderTarget.framebuffer, 'depth');
+            renderer.setViewport(0, 0, this.colorTarget.getWidth(), this.colorTarget.getHeight());
+            renderer.render(renderTarget, scene.primitives, camera, 'color', false, transparentBackground, 1, this.depthTexturePrimitives, true);
+        }
+
+        if (helper.debug.isEnabled) {
+            helper.debug.syncVisibility();
+            renderer.render(renderTarget, helper.debug.scene, camera, 'color', false, transparentBackground, 1, null, true);
+        }
+        if (helper.handle.isEnabled) {
+            renderer.render(renderTarget, helper.handle.scene, camera, 'color', false, transparentBackground, 1, null, true);
+        }
+        if (helper.camera.isEnabled) {
+            helper.camera.update(camera);
+            renderer.render(renderTarget, helper.camera.scene, helper.camera.camera, 'color', false, transparentBackground, 1, null, true);
         }
 
         // do direct-volume rendering
@@ -137,13 +181,11 @@ export class DrawPass {
                 this.webgl.gl.scissor(x, y, width, height);
                 this.webgl.gl.clear(this.webgl.gl.DEPTH_BUFFER_BIT);
             }
-            renderer.render(scene.volumes, camera, 'color', false, transparentBackground, 1, this.depthTexturePrimitives);
+            renderer.render(renderTarget, scene.volumes, camera, 'color', false, transparentBackground, 1, this.depthTexturePrimitives, true);
 
             // do volume depth pass if extensions.depthTexture is unsupported (i.e. depthTarget is set)
             if (this.depthTargetVolumes) {
-                this.depthTargetVolumes.bind();
-                renderer.render(scene.volumes, camera, 'depth', true, transparentBackground, 1, this.depthTexturePrimitives);
-                this.colorTarget.bind();
+                renderer.render(this.depthTargetVolumes, scene.volumes, camera, 'depth', true, transparentBackground, 1, this.depthTexturePrimitives, false);
             }
         }
 
@@ -163,18 +205,6 @@ export class DrawPass {
             this.depthMerge.render();
             this.colorTarget.bind();
         }
-
-        if (helper.debug.isEnabled) {
-            helper.debug.syncVisibility();
-            renderer.render(helper.debug.scene, camera, 'color', false, transparentBackground, 1, null);
-        }
-        if (helper.handle.isEnabled) {
-            renderer.render(helper.handle.scene, camera, 'color', false, transparentBackground, 1, null);
-        }
-        if (helper.camera.isEnabled) {
-            helper.camera.update(camera);
-            renderer.render(helper.camera.scene, helper.camera.camera, 'color', false, transparentBackground, 1, null);
-        }
     }
 
     render(renderer: Renderer, camera: Camera | StereoCamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean) {

+ 8 - 8
src/mol-canvas3d/passes/pick.ts

@@ -63,26 +63,26 @@ export class PickPass {
         }
     }
 
-    private renderVariant(renderer: Renderer, camera: ICamera, scene: Scene, helper: Helper, variant: GraphicsRenderVariant) {
+    private renderVariant(renderTarget: RenderTarget, renderer: Renderer, camera: ICamera, scene: Scene, helper: Helper, variant: GraphicsRenderVariant) {
         const pickScale = this.pickBaseScale / this.webgl.pixelRatio;
         const depth = this.drawPass.depthTexturePrimitives;
-        renderer.render(scene.primitives, camera, variant, true, false, pickScale, null);
-        renderer.render(scene.volumes, camera, variant, false, false, pickScale, depth);
-        renderer.render(helper.handle.scene, camera, variant, false, false, pickScale, null);
+        renderer.render(renderTarget, scene.primitives, camera, variant, true, false, pickScale, null, false);
+        renderer.render(renderTarget, scene.volumes, camera, variant, false, false, pickScale, depth, false);
+        renderer.render(renderTarget, helper.handle.scene, camera, variant, false, false, pickScale, null, false);
     }
 
     render(renderer: Renderer, camera: ICamera, scene: Scene, helper: Helper) {
         this.objectPickTarget.bind();
-        this.renderVariant(renderer, camera, scene, helper, 'pickObject');
+        this.renderVariant(this.objectPickTarget, renderer, camera, scene, helper, 'pickObject');
 
         this.instancePickTarget.bind();
-        this.renderVariant(renderer, camera, scene, helper, 'pickInstance');
+        this.renderVariant(this.instancePickTarget, renderer, camera, scene, helper, 'pickInstance');
 
         this.groupPickTarget.bind();
-        this.renderVariant(renderer, camera, scene, helper, 'pickGroup');
+        this.renderVariant(this.groupPickTarget, renderer, camera, scene, helper, 'pickGroup');
 
         this.depthPickTarget.bind();
-        this.renderVariant(renderer, camera, scene, helper, 'depth');
+        this.renderVariant(this.depthPickTarget, renderer, camera, scene, helper, 'depth');
     }
 }
 

+ 2 - 0
src/mol-geo/geometry/direct-volume/direct-volume.ts

@@ -286,6 +286,8 @@ export namespace DirectVolume {
             dFlatShaded: ValueCell.create(props.flatShaded),
             dFlipSided: ValueCell.create(props.flipSided),
             dIgnoreLight: ValueCell.create(props.ignoreLight),
+
+            uRenderWboit: ValueCell.create(1),
         };
     }
 

+ 4 - 3
src/mol-gl/renderable.ts

@@ -10,6 +10,7 @@ import { GraphicsRenderItem, ComputeRenderItem, GraphicsRenderVariant } from './
 import { ValueCell } from '../mol-util';
 import { idFactory } from '../mol-util/id-factory';
 import { clamp } from '../mol-math/interpolate';
+import { Textures } from './webgl/texture';
 
 const getNextRenderableId = idFactory();
 
@@ -28,7 +29,7 @@ export interface Renderable<T extends RenderableValues> {
     readonly values: T
     readonly state: RenderableState
 
-    render: (variant: GraphicsRenderVariant) => void
+    render: (variant: GraphicsRenderVariant, sharedTexturesList?: Textures) => void
     getProgram: (variant: GraphicsRenderVariant) => Program
     update: () => void
     dispose: () => void
@@ -41,11 +42,11 @@ export function createRenderable<T extends Values<RenderableSchema>>(renderItem:
         values,
         state,
 
-        render: (variant: GraphicsRenderVariant) => {
+        render: (variant: GraphicsRenderVariant, sharedTexturesList?: Textures) => {
             if (values.uAlpha && values.alpha) {
                 ValueCell.updateIfChanged(values.uAlpha, clamp(values.alpha.ref.value * state.alphaFactor, 0, 1));
             }
-            renderItem.render(variant);
+            renderItem.render(variant, sharedTexturesList);
         },
         getProgram: (variant: GraphicsRenderVariant) => renderItem.getProgram(variant),
         update: () => renderItem.update(),

+ 2 - 0
src/mol-gl/renderable/direct-volume.ts

@@ -61,6 +61,8 @@ export const DirectVolumeSchema = {
 
     uAlpha: UniformSpec('f'),
 
+    uRenderWboit: UniformSpec('i'),
+
     uIsoValue: UniformSpec('v2'),
     uBboxMin: UniformSpec('v3'),
     uBboxMax: UniformSpec('v3'),

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

@@ -7,7 +7,7 @@
 import { Renderable, RenderableState, createRenderable } from '../renderable';
 import { WebGLContext } from '../webgl/context';
 import { createGraphicsRenderItem } from '../webgl/render-item';
-import { GlobalUniformSchema, BaseSchema, AttributeSpec, ElementsSpec, DefineSpec, Values, InternalSchema, InternalValues } from './schema';
+import { GlobalUniformSchema, BaseSchema, AttributeSpec, ElementsSpec, DefineSpec, Values, InternalSchema, InternalValues, GlobalTextureSchema } from './schema';
 import { MeshShaderCode } from '../shader-code';
 import { ValueCell } from '../../mol-util';
 
@@ -25,7 +25,7 @@ export type MeshSchema = typeof MeshSchema
 export type MeshValues = Values<MeshSchema>
 
 export function MeshRenderable(ctx: WebGLContext, id: number, values: MeshValues, state: RenderableState, materialId: number): Renderable<MeshValues> {
-    const schema = { ...GlobalUniformSchema, ...InternalSchema, ...MeshSchema };
+    const schema = { ...GlobalUniformSchema, ...GlobalTextureSchema, ...InternalSchema, ...MeshSchema };
     const internalValues: InternalValues = {
         uObjectId: ValueCell.create(id),
     };

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

@@ -155,6 +155,8 @@ export const GlobalUniformSchema = {
 
     uHighlightColor: UniformSpec('v3'),
     uSelectColor: UniformSpec('v3'),
+
+    uRenderWboit: UniformSpec('i'),
 } as const;
 export type GlobalUniformSchema = typeof GlobalUniformSchema
 export type GlobalUniformValues = Values<GlobalUniformSchema>

+ 200 - 32
src/mol-gl/renderer.ts

@@ -9,17 +9,23 @@ import { ICamera } from '../mol-canvas3d/camera';
 import Scene from './scene';
 import { WebGLContext } from './webgl/context';
 import { Mat4, Vec3, Vec4, Vec2, Quat } from '../mol-math/linear-algebra';
-import { Renderable } from './renderable';
+import { ComputeRenderable, createComputeRenderable, Renderable } from './renderable';
 import { Color } from '../mol-util/color';
 import { ValueCell, deepEqual } from '../mol-util';
-import { RenderableValues, GlobalUniformValues, BaseValues } from './renderable/schema';
-import { GraphicsRenderVariant } from './webgl/render-item';
+import { RenderableValues, GlobalUniformValues, BaseValues, TextureSpec, Values } from './renderable/schema';
+import { createComputeRenderItem, GraphicsRenderVariant } from './webgl/render-item';
 import { ParamDefinition as PD } from '../mol-util/param-definition';
 import { Clipping } from '../mol-theme/clipping';
 import { stringToWords } from '../mol-util/string';
 import { Transparency } from '../mol-theme/transparency';
 import { degToRad } from '../mol-math/misc';
-import { Texture } from './webgl/texture';
+import { Texture, Textures } from './webgl/texture';
+import { RenderTarget } from './webgl/render-target';
+import { QuadSchema, QuadValues } from './compute/util';
+
+import quad_vert from '../mol-gl/shader/quad.vert';
+import evaluate_wboit_frag from '../mol-gl/shader/evaluate-wboit.frag';
+import { ShaderCode } from './shader-code';
 
 export interface RendererStats {
     programCount: number
@@ -42,7 +48,7 @@ interface Renderer {
     readonly props: Readonly<RendererProps>
 
     clear: (transparentBackground: boolean) => void
-    render: (group: Scene.Group, camera: ICamera, variant: GraphicsRenderVariant, clear: boolean, transparentBackground: boolean, drawingBufferScale: number, depthTexture: Texture | null) => void
+    render: (renderTarget: RenderTarget | null, group: Scene.Group, camera: ICamera, variant: GraphicsRenderVariant, clear: boolean, transparentBackground: boolean, drawingBufferScale: number, depthTexture: Texture | null, renderTransparent: boolean) => void
     setProps: (props: Partial<RendererProps>) => void
     setViewport: (x: number, y: number, width: number, height: number) => void
     dispose: () => void
@@ -158,14 +164,46 @@ function getClip(props: RendererProps['clip'], clip?: Clip): Clip {
 
 namespace Renderer {
     export function create(ctx: WebGLContext, props: Partial<RendererProps> = {}): Renderer {
-        const { gl, state, stats, extensions: { fragDepth } } = ctx;
+        const { gl, state, resources, stats, extensions: { fragDepth } } = ctx;
         const p = PD.merge(RendererParams, PD.getDefaultValues(RendererParams), props);
         const style = getStyle(p.style);
         const clip = getClip(p.clip);
 
+        const { drawBuffers, textureFloat, colorBufferFloat, depthTexture } = ctx.extensions;
+
         const viewport = Viewport();
         const drawingBufferSize = Vec2.create(gl.drawingBufferWidth, gl.drawingBufferHeight);
         const bgColor = Color.toVec3Normalized(Vec3(), p.backgroundColor);
+        
+        const sharedTexturesList: Textures = [];
+
+        let enableWboit = textureFloat !== null && colorBufferFloat !== null && depthTexture !== null;
+
+        let wboitATexture = enableWboit ? resources.texture('image-float32', 'rgba', 'float', 'nearest') : null;
+        wboitATexture?.define(viewport.width, viewport.height);
+        let wboitBTexture = enableWboit ? resources.texture('image-float32', 'rgba', 'float', 'nearest') : null;
+        wboitBTexture?.define(viewport.width, viewport.height);
+
+        let evaluateWboitRenderable = enableWboit ? getEvaluateWboitRenderable(ctx, wboitATexture!, wboitBTexture!) : null;
+
+        let wboitFramebuffers = [resources.framebuffer()];
+        if (drawBuffers) {
+            wboitFramebuffers.push(resources.framebuffer());
+
+            wboitFramebuffers[0].bind();
+            drawBuffers?.drawBuffers([
+                drawBuffers.COLOR_ATTACHMENT0,
+                drawBuffers.COLOR_ATTACHMENT1,
+            ]);
+
+            wboitATexture?.attachFramebuffer(wboitFramebuffers[0], 'color0')
+            wboitBTexture?.attachFramebuffer(wboitFramebuffers[0], 'color1');
+        } else {
+            wboitFramebuffers.push(resources.framebuffer(), resources.framebuffer());
+
+            wboitATexture?.attachFramebuffer(wboitFramebuffers[0], 'color0')
+            wboitBTexture?.attachFramebuffer(wboitFramebuffers[1], 'color0');
+        }
 
         const view = Mat4();
         const invView = Mat4();
@@ -204,6 +242,9 @@ namespace Renderer {
             uFogNear: ValueCell.create(1),
             uFogFar: ValueCell.create(10000),
             uFogColor: ValueCell.create(bgColor),
+
+            uRenderWboit: ValueCell.create(0),
+
             uTransparentBackground: ValueCell.create(false),
 
             uClipObjectType: ValueCell.create(clip.objects.type),
@@ -232,7 +273,7 @@ namespace Renderer {
 
         let globalUniformsNeedUpdate = true;
 
-        const renderObject = (r: Renderable<RenderableValues & BaseValues>, variant: GraphicsRenderVariant, depthTexture: Texture | null) => {
+        const renderObject = (r: Renderable<RenderableValues & BaseValues>, variant: GraphicsRenderVariant, sharedTexturesList?: Textures) => {
             if (!r.state.visible || (!r.state.pickable && variant[0] === 'p')) {
                 return;
             }
@@ -265,8 +306,6 @@ namespace Renderer {
                 globalUniformsNeedUpdate = false;
             }
 
-            if (depthTexture) program.bindTextures([['tDepth', depthTexture]]);
-
             if (r.values.uStepFactor) { // indicates direct-volume
                 // always cull front
                 state.enable(gl.CULL_FACE);
@@ -313,10 +352,15 @@ namespace Renderer {
                 state.depthMask(r.state.writeDepth);
             }
 
-            r.render(variant);
+            r.render(variant, sharedTexturesList);
         };
 
-        const render = (group: Scene.Group, camera: ICamera, variant: GraphicsRenderVariant, clear: boolean, transparentBackground: boolean, drawingBufferScale: number, depthTexture: Texture | null) => {
+        const render = (renderTarget: RenderTarget | null, group: Scene.Group, camera: ICamera, variant: GraphicsRenderVariant, clear: boolean, transparentBackground: boolean, drawingBufferScale: number, depthTexture: Texture | null, renderTransparent: boolean) => {
+            let localSharedTexturesList = sharedTexturesList;
+            if (depthTexture) {
+                localSharedTexturesList = [...localSharedTexturesList, ['tDepth', depthTexture]];
+            }
+
             ValueCell.update(globalUniforms.uModel, group.view);
             ValueCell.update(globalUniforms.uView, camera.view);
             ValueCell.update(globalUniforms.uInvView, Mat4.invert(invView, camera.view));
@@ -339,6 +383,8 @@ namespace Renderer {
             ValueCell.updateIfChanged(globalUniforms.uFogNear, camera.fogNear);
             ValueCell.updateIfChanged(globalUniforms.uTransparentBackground, transparentBackground);
 
+            ValueCell.update(globalUniforms.uRenderWboit, 0);
+
             if (gl.drawingBufferWidth * drawingBufferScale !== drawingBufferSize[0] ||
                 gl.drawingBufferHeight * drawingBufferScale !== drawingBufferSize[1]
             ) {
@@ -358,10 +404,16 @@ namespace Renderer {
             state.colorMask(true, true, true, true);
             state.enable(gl.DEPTH_TEST);
 
+            if (renderTarget) {
+                renderTarget.bind();
+            } else {
+                gl.bindFramebuffer(gl.FRAMEBUFFER, null);
+            }
+
             const { x, y, width, height } = viewport;
             gl.viewport(x, y, width, height);
             gl.scissor(x, y, width, height);
-
+            
             if (clear) {
                 state.depthMask(true);
                 if (variant === 'color') {
@@ -373,31 +425,99 @@ namespace Renderer {
             }
 
             if (variant === 'color') {
-                for (let i = 0, il = renderables.length; i < il; ++i) {
-                    const r = renderables[i];
-                    if (r.state.opaque) {
-                        renderObject(r, variant, depthTexture);
+                if (enableWboit) {
+                    if (!renderTransparent) {
+                        for (let i = 0, il = renderables.length; i < il; ++i) {
+                            const r = renderables[i];
+                            if (r.state.opaque) {
+                                renderObject(r, variant, localSharedTexturesList);
+                            }
+                        }
+                        for (let i = 0, il = renderables.length; i < il; ++i) {
+                            const r = renderables[i];
+                            if (!r.state.opaque && r.state.writeDepth) {
+                                renderObject(r, variant, localSharedTexturesList);
+                            }
+                        }
+                        for (let i = 0, il = renderables.length; i < il; ++i) {
+                            const r = renderables[i];
+                            if (!r.state.opaque && !r.state.writeDepth) {
+                                renderObject(r, variant, localSharedTexturesList);
+                            }
+                        }
+                    } else {
+                        wboitFramebuffers[0].bind();
+
+                        state.clearColor(0, 0, 0, 1);
+                        gl.clear(gl.COLOR_BUFFER_BIT);
+
+                        ValueCell.update(globalUniforms.uRenderWboit, 1);
+                        globalUniformsNeedUpdate = true;
+
+                        state.disable(gl.DEPTH_TEST);
+                        state.enable(gl.BLEND);
+                        state.blendFuncSeparate(gl.ONE, gl.ONE, gl.ZERO, gl.ONE_MINUS_SRC_ALPHA);
+                        
+                        for (let i = 0, il = renderables.length; i < il; ++i) {
+                            const r = renderables[i];
+                            if (r.state.opaque) {
+                                renderObject(r, variant, localSharedTexturesList);
+                            }
+                        }
+                        for (let i = 0, il = renderables.length; i < il; ++i) {
+                            const r = renderables[i];
+                            if (!r.state.opaque && r.state.writeDepth) {
+                                renderObject(r, variant, localSharedTexturesList);
+                            }
+                        }
+                        for (let i = 0, il = renderables.length; i < il; ++i) {
+                            const r = renderables[i];
+                            if (!r.state.opaque && !r.state.writeDepth) {
+                                renderObject(r, variant, localSharedTexturesList);
+                            }
+                        }
+                        if (renderTarget) {
+                            renderTarget.bind();
+                        } else {
+                            gl.bindFramebuffer(gl.FRAMEBUFFER, null);
+                        }
+
+                        state.blendFuncSeparate(gl.ONE_MINUS_SRC_ALPHA, gl.SRC_ALPHA, gl.ZERO, gl.ONE);
+                        state.enable(gl.BLEND);
+                        state.disable(gl.DEPTH_TEST);
+                        
+                        evaluateWboitRenderable?.update();
+                        evaluateWboitRenderable?.render();
                     }
-                }
-
-                state.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE);
-                state.enable(gl.BLEND);
-                for (let i = 0, il = renderables.length; i < il; ++i) {
-                    const r = renderables[i];
-                    if (!r.state.opaque && r.state.writeDepth) {
-                        renderObject(r, variant, depthTexture);
+                } else {
+                    for (let i = 0, il = renderables.length; i < il; ++i) {
+                        const r = renderables[i];
+                        if (r.state.opaque) {
+                            renderObject(r, variant, sharedTexturesList);
+                        }
                     }
-                }
-                for (let i = 0, il = renderables.length; i < il; ++i) {
-                    const r = renderables[i];
-                    if (!r.state.opaque && !r.state.writeDepth) {
-                        renderObject(r, variant, depthTexture);
+    
+                    state.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE);
+                    state.enable(gl.BLEND);
+                    for (let i = 0, il = renderables.length; i < il; ++i) {
+                        const r = renderables[i];
+                        if (!r.state.opaque && r.state.writeDepth) {
+                            renderObject(r, variant, sharedTexturesList);
+                        }
+                    }
+                    for (let i = 0, il = renderables.length; i < il; ++i) {
+                        const r = renderables[i];
+                        if (!r.state.opaque && !r.state.writeDepth) {
+                            renderObject(r, variant, sharedTexturesList);
+                        }
                     }
                 }
             } else { // picking & depth
-                for (let i = 0, il = renderables.length; i < il; ++i) {
-                    if (!renderables[i].state.colorOnly) {
-                        renderObject(renderables[i], variant, depthTexture);
+                if (!renderTransparent) {
+                    for (let i = 0, il = renderables.length; i < il; ++i) {
+                        if (!renderables[i].state.colorOnly) {
+                            renderObject(renderables[i], variant, sharedTexturesList);
+                        }
                     }
                 }
             }
@@ -479,6 +599,32 @@ namespace Renderer {
                     Viewport.set(viewport, x, y, width, height);
                     ValueCell.update(globalUniforms.uViewportHeight, height);
                     ValueCell.update(globalUniforms.uViewport, Vec4.set(globalUniforms.uViewport.ref.value, x, y, width, height));
+
+                    wboitATexture?.define(viewport.width, viewport.height);
+                    wboitBTexture?.define(viewport.width, viewport.height);
+
+                    if (drawBuffers) {
+                        wboitFramebuffers[0].destroy();
+                        wboitFramebuffers = [];
+                        wboitFramebuffers.push(resources.framebuffer());
+            
+                        wboitFramebuffers[0].bind();
+                        drawBuffers?.drawBuffers([
+                            drawBuffers.COLOR_ATTACHMENT0,
+                            drawBuffers.COLOR_ATTACHMENT1,
+                        ]);
+            
+                        wboitATexture?.attachFramebuffer(wboitFramebuffers[0], 'color0')
+                        wboitBTexture?.attachFramebuffer(wboitFramebuffers[0], 'color1');
+                    } else {
+                        wboitFramebuffers[0].destroy();
+                        wboitFramebuffers[1].destroy();
+                        wboitFramebuffers = [];
+                        wboitFramebuffers.push(resources.framebuffer(), resources.framebuffer());
+            
+                        wboitATexture?.attachFramebuffer(wboitFramebuffers[0], 'color0')
+                        wboitBTexture?.attachFramebuffer(wboitFramebuffers[1], 'color0');
+                    }
                 }
             },
 
@@ -509,4 +655,26 @@ namespace Renderer {
     }
 }
 
+const EvaluateWboitSchema = {
+    ...QuadSchema,
+    tWboitA: TextureSpec('texture', 'rgba', 'float', 'nearest'),
+    tWboitB: TextureSpec('texture', 'rgba', 'float', 'nearest'),
+};
+
+type EvaluateWboitRenderable = ComputeRenderable<Values<typeof EvaluateWboitSchema>>
+
+function getEvaluateWboitRenderable(ctx: WebGLContext, wboitATexture: Texture, wboitBTexture: Texture): EvaluateWboitRenderable {
+    const values: Values<typeof EvaluateWboitSchema> = {
+        ...QuadValues,
+        tWboitA: ValueCell.create(wboitATexture),
+        tWboitB: ValueCell.create(wboitBTexture),
+    };
+
+    const schema = { ...EvaluateWboitSchema };
+    const shaderCode = ShaderCode('ssao', quad_vert, evaluate_wboit_frag);
+    const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values);
+
+    return createComputeRenderable(renderItem, values);
+}
+
 export default Renderer;

+ 5 - 1
src/mol-gl/shader-code.ts

@@ -59,6 +59,8 @@ import size_vert_params from './shader/chunks/size-vert-params.glsl';
 import texture3d_from_1d_trilinear from './shader/chunks/texture3d-from-1d-trilinear.glsl';
 import texture3d_from_2d_linear from './shader/chunks/texture3d-from-2d-linear.glsl';
 import texture3d_from_2d_nearest from './shader/chunks/texture3d-from-2d-nearest.glsl';
+import wboit_params from './shader/chunks/wboit-params.glsl';
+import wboit_write from './shader/chunks/wboit-write.glsl';
 
 const ShaderChunks: { [k: string]: string } = {
     apply_fog,
@@ -88,7 +90,9 @@ const ShaderChunks: { [k: string]: string } = {
     size_vert_params,
     texture3d_from_1d_trilinear,
     texture3d_from_2d_linear,
-    texture3d_from_2d_nearest
+    texture3d_from_2d_nearest,
+    wboit_params,
+    wboit_write
 };
 
 const reInclude = /^(?!\/\/)\s*#include\s+(\S+)/gmi;

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

@@ -45,8 +45,10 @@ export default `
             at = fract(dot(vec3(coord, vGroup + 0.5), vec3(2.0, 7.0, 23.0) / 17.0f));
         #endif
 
-        if (ta < 0.99 && (ta < 0.01 || ta < at))
-            discard;
+        if (ta < 0.99 && (ta < 0.01 || ta < at)) {
+            //discard;
+        }
+        material.a = ta;
     #endif
 #endif
 `;

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

@@ -43,4 +43,6 @@ uniform float uInteriorDarkening;
 uniform bool uInteriorColorFlag;
 uniform vec3 uInteriorColor;
 bool interior;
+
+uniform mat4 uProjection;
 `;

+ 22 - 0
src/mol-gl/shader/chunks/wboit-params.glsl.ts

@@ -0,0 +1,22 @@
+export default `
+#if defined(dRenderVariant_color)
+#if !defined(dRenderMode_volume)
+    uniform sampler2D tDepth;
+    
+    float getDepth(const in vec2 coords) {
+        #ifdef dPackedDepth
+            return unpackRGBAToDepth(texture2D(tDepth, coords));
+        #else
+            return texture2D(tDepth, coords).r;
+        #endif
+    }
+#endif
+    uniform int uRenderWboit;
+    uniform vec4 uViewport;
+#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;
+}
+`;

+ 16 - 0
src/mol-gl/shader/chunks/wboit-write.glsl.ts

@@ -0,0 +1,16 @@
+export default `
+if (uRenderWboit == 0) {
+    if (gl_FragColor.a != 1.0) {
+        discard;
+    }
+} else if (uRenderWboit == 1) {
+    if (gl_FragColor.a != 1.0 && abs(calcDepth(vViewPosition) + 0.01) < getDepth(gl_FragCoord.xy / uViewport.zw)) {
+        float alpha = gl_FragColor.a;
+        float wboitWeight = alpha * clamp(pow(1.0 - absFragDepth, 2.0), 0.01, 1.0);
+        gl_FragColor = vec4(gl_FragColor.rgb * alpha * wboitWeight, alpha);
+        out_FragData1 = vec4(alpha * wboitWeight);
+    } else {
+        discard;
+    }
+}
+`;

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

@@ -119,16 +119,12 @@ uniform mat4 uUnitToCartn;
     }
 #endif
 
+#include wboit_params
+
 vec4 transferFunction(float value) {
     return texture2D(tTransferTex, vec2(value, 0.0));
 }
 
-// Calculate depth based on the given camera position.
-float calcDepth(const in vec3 cameraPos){
-    vec2 clipZW = cameraPos.z * uProjection[2].zw + uProjection[3].zw;
-    return 0.5 + 0.5 * clipZW.x / clipZW.y;
-}
-
 float getDepth(const in vec2 coords) {
     #ifdef dPackedDepth
         return unpackRGBAToDepth(texture2D(tDepth, coords));
@@ -405,5 +401,11 @@ void main () {
         if (gl_FragColor == vec4(0.0))
             discard;
     #endif
+
+    #if defined(dRenderVariant_color)
+        vec3 vViewPosition = vOrigPos;
+        float absFragDepth = abs(calcDepth(vViewPosition));
+        #include wboit_write
+    #endif
 }
 `;

+ 15 - 0
src/mol-gl/shader/evaluate-wboit.frag.ts

@@ -0,0 +1,15 @@
+export default `
+precision highp float;
+
+uniform sampler2D tWboitA;
+uniform sampler2D tWboitB;
+
+void main() {
+    ivec2 coords = ivec2(gl_FragCoord.xy);
+    
+    vec4 accum = texelFetch(tWboitA, coords, 0);
+    float r = accum.a;
+    accum.a = texelFetch(tWboitB, coords, 0).r;
+    gl_FragColor = vec4(accum.rgb / clamp(accum.a, 0.0001, 50000.0), r);
+}
+`;

+ 4 - 0
src/mol-gl/shader/lines.frag.ts

@@ -12,6 +12,7 @@ precision highp int;
 #include common_frag_params
 #include color_frag_params
 #include common_clip
+#include wboit_params
 
 void main(){
     #include clip_pixel
@@ -27,6 +28,9 @@ void main(){
 
         #include apply_marker_color
         #include apply_fog
+
+        float absFragDepth = abs(gl_FragCoord.z);
+        #include wboit_write
     #endif
 }
 `;

+ 4 - 0
src/mol-gl/shader/mesh.frag.ts

@@ -14,6 +14,7 @@ precision highp int;
 #include light_frag_params
 #include normal_frag_params
 #include common_clip
+#include wboit_params
 
 void main() {
     #include clip_pixel
@@ -59,6 +60,9 @@ void main() {
         #include apply_interior_color
         #include apply_marker_color
         #include apply_fog
+
+        float absFragDepth = abs(gl_FragCoord.z);
+        #include wboit_write
     #endif
 }
 `;

+ 4 - 0
src/mol-gl/shader/points.frag.ts

@@ -12,6 +12,7 @@ precision highp int;
 #include common_frag_params
 #include color_frag_params
 #include common_clip
+#include wboit_params
 
 #ifdef dPointFilledCircle
     uniform float uPointEdgeBleach;
@@ -41,6 +42,9 @@ void main(){
 
         #include apply_marker_color
         #include apply_fog
+
+        float absFragDepth = abs(gl_FragCoord.z);
+        #include wboit_write
     #endif
 }
 `;

+ 4 - 8
src/mol-gl/shader/spheres.frag.ts

@@ -13,8 +13,7 @@ precision highp int;
 #include color_frag_params
 #include light_frag_params
 #include common_clip
-
-uniform mat4 uProjection;
+#include wboit_params
 
 uniform float uClipNear;
 uniform float uIsOrtho;
@@ -27,12 +26,6 @@ varying vec3 vPointViewPosition;
 vec3 cameraPos;
 vec3 cameraNormal;
 
-// Calculate depth based on the given camera position.
-float calcDepth(const in vec3 cameraPos){
-    vec2 clipZW = cameraPos.z * uProjection[2].zw + uProjection[3].zw;
-    return 0.5 + 0.5 * clipZW.x / clipZW.y;
-}
-
 float calcClip(const in vec3 cameraPos) {
     return dot(vec4(cameraPos, 1.0), vec4(0.0, 0.0, 1.0, uClipNear - 0.5));
 }
@@ -114,6 +107,9 @@ void main(void){
         #include apply_interior_color
         #include apply_marker_color
         #include apply_fog
+
+        float absFragDepth = abs(gl_FragDepthEXT);
+        #include wboit_write
     #endif
 }
 `;

+ 10 - 14
src/mol-gl/webgl/program.ts

@@ -24,7 +24,7 @@ export interface Program {
     use: () => void
     setUniforms: (uniformValues: UniformsList) => void
     bindAttributes: (attribueBuffers: AttributeBuffers) => void
-    bindTextures: (textures: Textures) => void
+    bindTextures: (textures: Textures, startingTargetUnit?: number) => void
 
     reset: () => void
     destroy: () => void
@@ -166,8 +166,8 @@ export function createProgram(gl: GLRenderingContext, state: WebGLState, extensi
         uniformSetters = getUniformSetters(schema);
 
         if (isDebugMode) {
-            checkActiveAttributes(gl, program, schema);
-            checkActiveUniforms(gl, program, schema);
+            // checkActiveAttributes(gl, program, schema);
+            // checkActiveUniforms(gl, program, schema);
         }
     }
     init();
@@ -198,21 +198,17 @@ export function createProgram(gl: GLRenderingContext, state: WebGLState, extensi
                 if (l !== -1) buffer.bind(l);
             }
         },
-        bindTextures: (textures: Textures) => {
+        bindTextures: (textures: Textures, startingTargetUnit?: number) => {
+            startingTargetUnit = startingTargetUnit ?? 0;
+
             for (let i = 0, il = textures.length; i < il; ++i) {
                 const [k, texture] = textures[i];
                 const l = locations[k];
                 if (l !== null && l !== undefined) {
-                    if (k === 'tDepth') {
-                        // TODO find more explicit way?
-                        texture.bind(15 as TextureId);
-                        uniformSetters[k](gl, l, 15 as TextureId);
-                    } else {
-                        // TODO if the order and count of textures in a material can be made invariant
-                        //      bind needs to be called only when the material changes
-                        texture.bind(i as TextureId);
-                        uniformSetters[k](gl, l, i as TextureId);
-                    }
+                    // TODO if the order and count of textures in a material can be made invariant
+                    //      bind needs to be called only when the material changes
+                    texture.bind((i + startingTargetUnit) as TextureId);
+                    uniformSetters[k](gl, l, (i + startingTargetUnit) as TextureId);
                 }
             }
         },

+ 15 - 5
src/mol-gl/webgl/render-item.ts

@@ -5,7 +5,7 @@
  */
 
 import { createAttributeBuffers, ElementsBuffer, AttributeKind } from './buffer';
-import { createTextures, Texture } from './texture';
+import { createTextures, Texture, Textures } from './texture';
 import { WebGLContext, checkError } from './context';
 import { ShaderCode, DefineValues } from '../shader-code';
 import { Program } from './program';
@@ -40,7 +40,7 @@ export interface RenderItem<T extends string> {
     readonly materialId: number
     getProgram: (variant: T) => Program
 
-    render: (variant: T) => void
+    render: (variant: T, sharedTexturesList?: Textures) => void
     update: () => Readonly<ValueChanges>
     destroy: () => void
 }
@@ -164,12 +164,17 @@ export function createRenderItem<T extends string>(ctx: WebGLContext, drawMode:
         materialId,
         getProgram: (variant: T) => programs[variant],
 
-        render: (variant: T) => {
+        render: (variant: T, sharedTexturesList?: Textures) => {
             if (drawCount === 0 || instanceCount === 0 || ctx.isContextLost) return;
             const program = programs[variant];
             if (program.id === currentProgramId && state.currentRenderItemId === id) {
                 program.setUniforms(uniformValueEntries);
-                program.bindTextures(textures);
+                if (sharedTexturesList && sharedTexturesList.length > 0) {
+                    program.bindTextures(sharedTexturesList, 0);
+                    program.bindTextures(textures, sharedTexturesList.length);
+                } else {
+                    program.bindTextures(textures);
+                }
             } else {
                 const vertexArray = vertexArrays[variant];
                 if (program.id !== state.currentProgramId || program.id !== currentProgramId ||
@@ -182,7 +187,12 @@ export function createRenderItem<T extends string>(ctx: WebGLContext, drawMode:
                     currentProgramId = program.id;
                 }
                 program.setUniforms(uniformValueEntries);
-                program.bindTextures(textures);
+                if (sharedTexturesList && sharedTexturesList.length > 0) {
+                    program.bindTextures(sharedTexturesList, 0);
+                    program.bindTextures(textures, sharedTexturesList.length);
+                } else {
+                    program.bindTextures(textures);
+                }
                 if (vertexArray) {
                     vertexArray.bind();
                     // need to bind elements buffer explicitly since it is not always recorded in the VAO