Browse Source

Integration of Dual depth peeling - OIT method

giagitom 2 years ago
parent
commit
39f51bcc4f

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

@@ -39,7 +39,7 @@ import { Helper } from './helper/helper';
 import { Passes } from './passes/passes';
 import { shallowEqual } from '../mol-util';
 import { MarkingParams } from './passes/marking';
-import { GraphicsRenderVariantsBlended, GraphicsRenderVariantsWboit } from '../mol-gl/webgl/render-item';
+import { GraphicsRenderVariantsBlended, GraphicsRenderVariantsWboit, GraphicsRenderVariantsDpoit } from '../mol-gl/webgl/render-item';
 import { degToRad, radToDeg } from '../mol-math/misc';
 import { AssetManager } from '../mol-util/assets';
 
@@ -124,7 +124,8 @@ namespace Canvas3DContext {
         pickScale: 0.25,
         /** extra pixels to around target to check in case target is empty */
         pickPadding: 1,
-        enableWboit: true,
+        enableWboit: false,
+        enableDpoit: true,
         preferWebGl1: false
     };
     export type Attribs = typeof DefaultAttribs
@@ -302,8 +303,7 @@ namespace Canvas3D {
         let width = 128;
         let height = 128;
         updateViewport();
-
-        const scene = Scene.create(webgl, passes.draw.wboitEnabled ? GraphicsRenderVariantsWboit : GraphicsRenderVariantsBlended);
+        const scene = Scene.create(webgl, passes.draw.dpoitEnabled ? GraphicsRenderVariantsDpoit : (passes.draw.wboitEnabled ? GraphicsRenderVariantsWboit : GraphicsRenderVariantsBlended));
 
         function getSceneRadius() {
             return scene.boundingSphere.radius * p.sceneRadiusFactor;
@@ -855,7 +855,7 @@ namespace Canvas3D {
                 }
             },
             getImagePass: (props: Partial<ImageProps> = {}) => {
-                return new ImagePass(webgl, assetManager, renderer, scene, camera, helper, passes.draw.wboitEnabled, props);
+                return new ImagePass(webgl, assetManager, renderer, scene, camera, helper, passes.draw.wboitEnabled, passes.draw.dpoitEnabled, props);
             },
             getRenderObjects(): GraphicsRenderObject[] {
                 const renderObjects: GraphicsRenderObject[] = [];

+ 285 - 0
src/mol-canvas3d/passes/dpoit.ts

@@ -0,0 +1,285 @@
+/**
+ * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Gianluca Tomasello <giagitom@gmail.com>
+ */
+
+import { QuadSchema, QuadValues } from '../../mol-gl/compute/util';
+import { ComputeRenderable, createComputeRenderable } from '../../mol-gl/renderable';
+import { TextureSpec, UniformSpec, Values } from '../../mol-gl/renderable/schema';
+import { ShaderCode } from '../../mol-gl/shader-code';
+import { WebGLContext } from '../../mol-gl/webgl/context';
+import { createComputeRenderItem } from '../../mol-gl/webgl/render-item';
+import { Texture } from '../../mol-gl/webgl/texture';
+import { ValueCell } from '../../mol-util';
+import { quad_vert } from '../../mol-gl/shader/quad.vert';
+import { evaluateDpoit_frag } from '../../mol-gl/shader/evaluate-dpoit.frag';
+import { blendBackDpoit_frag } from '../../mol-gl/shader/blend-back-dpoit.frag';
+import { Framebuffer } from '../../mol-gl/webgl/framebuffer';
+import { Vec2 } from '../../mol-math/linear-algebra';
+import { isDebugMode, isTimingMode } from '../../mol-util/debug';
+
+const BlendBackDpoitSchema = {
+    ...QuadSchema,
+    tDpoitBackColor: TextureSpec('texture', 'rgba', 'float', 'nearest'),
+    uTexSize: UniformSpec('v2'),
+};
+const BlendBackDpoitShaderCode = ShaderCode('blend-back-dpoit', quad_vert, blendBackDpoit_frag);
+type BlendBackDpoitRenderable = ComputeRenderable<Values<typeof BlendBackDpoitSchema>>
+
+function getBlendBackDpoitRenderable(ctx: WebGLContext, dopitBlendBackTexture: Texture): BlendBackDpoitRenderable {
+    const values: Values<typeof BlendBackDpoitSchema> = {
+        ...QuadValues,
+        tDpoitBackColor: ValueCell.create(dopitBlendBackTexture),
+        uTexSize: ValueCell.create(Vec2.create(dopitBlendBackTexture.getWidth(), dopitBlendBackTexture.getHeight())),
+    };
+
+    const schema = { ...BlendBackDpoitSchema };
+    const renderItem = createComputeRenderItem(ctx, 'triangles', BlendBackDpoitShaderCode, schema, values);
+
+    return createComputeRenderable(renderItem, values);
+}
+
+const EvaluateDpoitSchema = {
+    ...QuadSchema,
+    tDpoitFrontColor: TextureSpec('texture', 'rgba', 'float', 'nearest'),
+    tDpoitBlendBackColor: TextureSpec('texture', 'rgba', 'float', 'nearest'),
+    uTexSize: UniformSpec('v2'),
+};
+const EvaluateDpoitShaderCode = ShaderCode('evaluate-dpoit', quad_vert, evaluateDpoit_frag);
+type EvaluateDpoitRenderable = ComputeRenderable<Values<typeof EvaluateDpoitSchema>>
+
+function getEvaluateDpoitRenderable(ctx: WebGLContext, dpoitFrontColorTexture: Texture, dopitBlendBackTexture: Texture): EvaluateDpoitRenderable {
+    const values: Values<typeof EvaluateDpoitSchema> = {
+        ...QuadValues,
+        tDpoitFrontColor: ValueCell.create(dpoitFrontColorTexture),
+        tDpoitBlendBackColor: ValueCell.create(dopitBlendBackTexture),
+        uTexSize: ValueCell.create(Vec2.create(dpoitFrontColorTexture.getWidth(), dpoitFrontColorTexture.getHeight())),
+    };
+
+    const schema = { ...EvaluateDpoitSchema };
+    const renderItem = createComputeRenderItem(ctx, 'triangles', EvaluateDpoitShaderCode, schema, values);
+
+    return createComputeRenderable(renderItem, values);
+}
+
+export class DpoitPass {
+
+    private readonly DEPTH_CLEAR_VALUE = -99999.0;
+    private readonly MAX_DEPTH = 1.0;
+    private readonly MIN_DEPTH = 0.0;
+
+    private passCount = 0;
+    private writeId: number;
+    private readId: number;
+
+    private readonly blendBackRenderable: BlendBackDpoitRenderable;
+    private readonly renderable: EvaluateDpoitRenderable;
+
+    private readonly depthFramebuffers: Framebuffer[];
+    private readonly colorFramebuffers: Framebuffer[];
+    private readonly blendBackFramebuffer: Framebuffer;
+
+    private readonly depthTextures: Texture[];
+    private readonly colorFrontTextures: Texture[];
+    private readonly colorBackTextures: Texture[];
+    private readonly blendBackTexture: Texture;
+
+    private _supported = false;
+    get supported() {
+        return this._supported;
+    }
+
+    bind() {
+        const { state, gl, extensions : { blendMinMax, drawBuffers } } = this.webgl;
+
+        //initialize
+        this.passCount = 0;
+
+        this.blendBackFramebuffer.bind();
+        state.clearColor(0, 0, 0, 0); //correct blending when texture is cleared with background color (for example state.clearColor(1,1,1,0) on white background)
+        gl.clear(gl.COLOR_BUFFER_BIT);
+
+        this.depthFramebuffers[0].bind()
+        drawBuffers!.drawBuffers([gl.NONE, gl.NONE, drawBuffers!.COLOR_ATTACHMENT2]);
+        state.clearColor(this.DEPTH_CLEAR_VALUE, this.DEPTH_CLEAR_VALUE, 0, 0);
+        gl.clear(gl.COLOR_BUFFER_BIT);
+
+        this.depthFramebuffers[1].bind()
+        state.clearColor(-this.MIN_DEPTH, this.MAX_DEPTH, 0, 0);
+        gl.clear(gl.COLOR_BUFFER_BIT);
+
+        this.colorFramebuffers[0].bind()
+        state.clearColor(0, 0, 0, 0);
+        gl.clear(gl.COLOR_BUFFER_BIT);
+
+        this.colorFramebuffers[1].bind()
+        state.clearColor(0, 0, 0, 0);
+        gl.clear(gl.COLOR_BUFFER_BIT);
+
+        this.depthFramebuffers[0].bind();
+        //rawBuffers!.drawBuffers([gl.NONE, gl.NONE, drawBuffers!.COLOR_ATTACHMENT2]);
+        state.blendEquation(blendMinMax!.MAX);
+
+        return { depth: this.depthTextures[1], frontColor: this.colorFrontTextures[1], backColor: this.colorBackTextures[1] }
+    }
+
+    bindDualDepthPeeling() {
+        const { state, gl, extensions : { blendMinMax, drawBuffers } } = this.webgl;
+
+        this.readId = this.passCount % 2;
+        this.writeId = 1 - this.readId;  // ping-pong: 0 or 1
+
+        this.passCount += 1 //increment for next pass
+
+        this.depthFramebuffers[this.writeId].bind()
+        drawBuffers!.drawBuffers([gl.NONE, gl.NONE, drawBuffers!.COLOR_ATTACHMENT2]);
+        state.clearColor(this.DEPTH_CLEAR_VALUE, this.DEPTH_CLEAR_VALUE, 0, 0);
+        gl.clear(gl.COLOR_BUFFER_BIT);
+
+        this.colorFramebuffers[this.writeId].bind()
+        state.clearColor(0, 0, 0, 0);
+        gl.clear(gl.COLOR_BUFFER_BIT);
+
+        this.depthFramebuffers[this.writeId].bind()
+        drawBuffers!.drawBuffers([drawBuffers!.COLOR_ATTACHMENT0, drawBuffers!.COLOR_ATTACHMENT1, drawBuffers!.COLOR_ATTACHMENT2]);
+        state.blendEquation(blendMinMax!.MAX);
+
+        return { depth: this.depthTextures[this.readId], frontColor: this.colorFrontTextures[this.readId], backColor: this.colorBackTextures[this.readId] }
+    }
+
+    bindBlendBack() {
+        const { state, gl } = this.webgl;
+
+        this.blendBackFramebuffer.bind();
+        state.blendEquation(gl.FUNC_ADD);
+    }
+
+    renderBlendBack() {
+        if (isTimingMode) this.webgl.timer.mark('DpoitPass.renderBlendBack');
+        const { state, gl } = this.webgl;
+
+        state.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
+
+        ValueCell.update(this.blendBackRenderable.values.tDpoitBackColor, this.colorBackTextures[this.writeId]);
+
+        this.blendBackRenderable.update();
+        this.blendBackRenderable.render();
+        if (isTimingMode) this.webgl.timer.markEnd('DpoitPass.renderBlendBack');
+    }
+
+    render() {
+        if (isTimingMode) this.webgl.timer.mark('DpoitPass.render');
+        const { state, gl } = this.webgl;
+
+        state.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
+
+        ValueCell.update(this.renderable.values.tDpoitFrontColor, this.colorFrontTextures[this.writeId]);
+        ValueCell.update(this.renderable.values.tDpoitBlendBackColor, this.blendBackTexture);
+
+        this.renderable.update();
+        this.renderable.render();
+        if (isTimingMode) this.webgl.timer.markEnd('DpoitPass.render');
+    }
+
+    setSize(width: number, height: number) {
+        const [w, h] = this.renderable.values.uTexSize.ref.value;
+        if (width !== w || height !== h) {
+            for (let i=0;i<2;i++){
+                this.depthTextures[i].define(width, height);
+                this.colorFrontTextures[i].define(width, height);
+                this.colorBackTextures[i].define(width, height);
+            }
+            this.blendBackTexture.define(width, height);
+            ValueCell.update(this.renderable.values.uTexSize, Vec2.set(this.renderable.values.uTexSize.ref.value, width, height));
+            ValueCell.update(this.blendBackRenderable.values.uTexSize, Vec2.set(this.blendBackRenderable.values.uTexSize.ref.value, width, height));
+        }
+    }
+
+    reset() {
+        if (this._supported) this._init();
+    }
+
+    private _init() {
+        const { extensions : { drawBuffers } } = this.webgl;
+        for (let i=0;i<2;i++){
+            //depth
+            this.depthFramebuffers[i].bind();
+            this.depthTextures[i].attachFramebuffer(this.depthFramebuffers[i], 'color2');
+            this.colorFrontTextures[i].attachFramebuffer(this.depthFramebuffers[i], 'color0');
+            this.colorBackTextures[i].attachFramebuffer(this.depthFramebuffers[i], 'color1');
+
+            //color
+            this.colorFramebuffers[i].bind();
+            drawBuffers!.drawBuffers([drawBuffers!.COLOR_ATTACHMENT0, drawBuffers!.COLOR_ATTACHMENT1]);
+            this.colorFrontTextures[i].attachFramebuffer(this.colorFramebuffers[i], 'color0');
+            this.colorBackTextures[i].attachFramebuffer(this.colorFramebuffers[i], 'color1');
+        }
+
+        //blend back
+        this.blendBackFramebuffer.bind();
+        drawBuffers!.drawBuffers([drawBuffers!.COLOR_ATTACHMENT0]);
+        this.blendBackTexture.attachFramebuffer(this.blendBackFramebuffer, 'color0');
+    }
+
+    static isSupported(webgl: WebGLContext) {
+        const { extensions: { drawBuffers, textureFloat, colorBufferFloat, depthTexture, blendMinMax } } = webgl;
+        if (!textureFloat || !colorBufferFloat || !depthTexture || !drawBuffers || !blendMinMax) {
+            if (isDebugMode) {
+                const missing: string[] = [];
+                if (!textureFloat) missing.push('textureFloat');
+                if (!colorBufferFloat) missing.push('colorBufferFloat');
+                if (!depthTexture) missing.push('depthTexture');
+                if (!drawBuffers) missing.push('drawBuffers');
+                if (!blendMinMax) missing.push('blendMinMax');
+                console.log(`Missing "${missing.join('", "')}" extensions required for "dpoit"`);
+            }
+            return false;
+        } else {
+            return true;
+        }
+    }
+
+    constructor(private webgl: WebGLContext, width: number, height: number) {
+        if (!DpoitPass.isSupported(webgl)) return;
+
+        const { resources } = webgl;
+
+        //textures
+        this.depthTextures = [
+          resources.texture('image-float32', 'rg', 'float', 'nearest'),
+          resources.texture('image-float32', 'rg', 'float', 'nearest')
+        ];
+        this.depthTextures[0].define(width, height);
+        this.depthTextures[1].define(width, height);
+
+        this.colorFrontTextures = [
+          resources.texture('image-float32', 'rgba', 'float', 'nearest'),
+          resources.texture('image-float32', 'rgba', 'float', 'nearest')
+        ];
+        this.colorFrontTextures[0].define(width, height);
+        this.colorFrontTextures[1].define(width, height);
+
+        this.colorBackTextures = [
+          resources.texture('image-float32', 'rgba', 'float', 'nearest'),
+          resources.texture('image-float32', 'rgba', 'float', 'nearest')
+        ];
+        this.colorBackTextures[0].define(width, height);
+        this.colorBackTextures[1].define(width, height);
+
+        this.blendBackTexture = resources.texture('image-float32', 'rgba', 'float', 'nearest');
+        this.blendBackTexture.define(width, height);
+
+        //framebuffers
+        this.depthFramebuffers = [resources.framebuffer(), resources.framebuffer()];
+        this.colorFramebuffers = [resources.framebuffer(), resources.framebuffer()];
+        this.blendBackFramebuffer = resources.framebuffer();
+
+        this.blendBackRenderable = getBlendBackDpoitRenderable(webgl, this.colorBackTextures[0])
+
+        this.renderable = getEvaluateDpoitRenderable(webgl, this.colorFrontTextures[0], this.blendBackTexture);
+
+        this._supported = true;
+        this._init();
+    }
+}

+ 80 - 4
src/mol-canvas3d/passes/draw.ts

@@ -17,6 +17,7 @@ import { Helper } from '../helper/helper';
 
 import { StereoCamera } from '../camera/stereo';
 import { WboitPass } from './wboit';
+import { DpoitPass } from './dpoit';
 import { AntialiasingPass, PostprocessingPass, PostprocessingProps } from './postprocessing';
 import { MarkingPass, MarkingProps } from './marking';
 import { CopyRenderable, createCopyRenderable } from '../../mol-gl/compute/util';
@@ -52,6 +53,7 @@ export class DrawPass {
     private copyFboPostprocessing: CopyRenderable;
 
     private readonly wboit: WboitPass | undefined;
+    private readonly dpoit: DpoitPass | undefined;
     private readonly marking: MarkingPass;
     readonly postprocessing: PostprocessingPass;
     private readonly antialiasing: AntialiasingPass;
@@ -60,9 +62,12 @@ export class DrawPass {
         return !!this.wboit?.supported;
     }
 
-    constructor(private webgl: WebGLContext, assetManager: AssetManager, width: number, height: number, enableWboit: boolean) {
-        const { extensions, resources, isWebGL2 } = webgl;
+    get dpoitEnabled() {
+        return !!this.dpoit?.supported;
+    }
 
+    constructor(private webgl: WebGLContext, assetManager: AssetManager, width: number, height: number, enableWboit: boolean, enableDpoit: boolean) {
+        const { extensions, resources, isWebGL2 } = webgl;
         this.drawTarget = createNullRenderTarget(webgl.gl);
         this.colorTarget = webgl.createRenderTarget(width, height, true, 'uint8', 'linear');
         this.packedDepth = !extensions.depthTexture;
@@ -78,6 +83,7 @@ export class DrawPass {
         }
 
         this.wboit = enableWboit ? new WboitPass(webgl, width, height) : undefined;
+        this.dpoit = enableDpoit ? new DpoitPass(webgl, width, height) : undefined;
         this.marking = new MarkingPass(webgl, width, height);
         this.postprocessing = new PostprocessingPass(webgl, assetManager, this);
         this.antialiasing = new AntialiasingPass(webgl, this);
@@ -88,6 +94,7 @@ export class DrawPass {
 
     reset() {
         this.wboit?.reset();
+        this.dpoit?.reset();
     }
 
     setSize(width: number, height: number) {
@@ -111,12 +118,79 @@ export class DrawPass {
                 this.wboit.setSize(width, height);
             }
 
+            if (this.dpoit?.supported) {
+                this.dpoit.setSize(width, height);
+            }
+
             this.marking.setSize(width, height);
             this.postprocessing.setSize(width, height);
             this.antialiasing.setSize(width, height);
         }
     }
 
+    private _renderDpoit(renderer: Renderer, camera: ICamera, scene: Scene, transparentBackground: boolean, postprocessingProps: PostprocessingProps) {
+        if (!this.dpoit?.supported) throw new Error('expected dpoit to be supported');
+
+        this.colorTarget.bind();
+        renderer.clear(true);
+
+        // render opaque primitives
+        this.depthTextureOpaque.attachFramebuffer(this.colorTarget.framebuffer, 'depth');
+        this.colorTarget.bind();
+        renderer.clearDepth();
+        renderer.renderDpoitOpaque(scene.primitives, camera, null);
+
+        if (PostprocessingPass.isEnabled(postprocessingProps)) {
+            if (PostprocessingPass.isOutlineEnabled(postprocessingProps)) {
+                this.depthTargetTransparent.bind();
+                renderer.clearDepth(true);
+                if (scene.opacityAverage < 1) {
+                    renderer.renderDepthTransparent(scene.primitives, camera, this.depthTextureOpaque);
+                }
+            }
+
+            this.postprocessing.render(camera, false, transparentBackground, renderer.props.backgroundColor, postprocessingProps);
+        }
+
+        let dpoitTextures
+        // render transparent primitives and volumes
+        if (scene.opacityAverage < 1 || scene.volumes.renderables.length > 0) {
+
+            dpoitTextures = this.dpoit.bind();
+
+            if (isTimingMode) this.webgl.timer.mark('DpoitPasses.render');
+
+            if (scene.opacityAverage < 1) {
+                renderer.renderDpoitTransparent(scene.primitives, camera, this.depthTextureOpaque, dpoitTextures);
+            }
+            if (scene.volumes.renderables.length > 0) {
+                renderer.renderDpoitTransparent(scene.volumes, camera, this.depthTextureOpaque, dpoitTextures);
+            }
+
+            for (let i=0;i<2;i++){ //not working with 1 pass
+                dpoitTextures = this.dpoit.bindDualDepthPeeling();
+                if (scene.opacityAverage < 1) {
+                  renderer.renderDpoitTransparent(scene.primitives, camera, this.depthTextureOpaque, dpoitTextures);
+                }
+                if (scene.volumes.renderables.length > 0) {
+                  renderer.renderDpoitTransparent(scene.volumes, camera, this.depthTextureOpaque, dpoitTextures);
+                }
+                this.dpoit.bindBlendBack();
+                this.dpoit.renderBlendBack();
+            }
+
+            if (isTimingMode) this.webgl.timer.markEnd('DpoitPasses.render');
+
+            // evaluate dpoit
+            if (PostprocessingPass.isEnabled(postprocessingProps)) {
+                this.postprocessing.target.bind();
+            } else {
+                this.colorTarget.bind();
+            }
+            this.dpoit.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');
 
@@ -254,13 +328,15 @@ export class DrawPass {
 
         if (this.wboitEnabled) {
             this._renderWboit(renderer, camera, scene, transparentBackground, props.postprocessing);
+        } else if (this.dpoitEnabled){
+              this._renderDpoit(renderer, camera, scene, transparentBackground, props.postprocessing);
         } else {
             this._renderBlended(renderer, camera, scene, !volumeRendering && !postprocessingEnabled && !antialiasingEnabled && toDrawingBuffer, transparentBackground, props.postprocessing);
         }
 
         const target = postprocessingEnabled
             ? this.postprocessing.target
-            : !toDrawingBuffer || volumeRendering || this.wboitEnabled
+            : !toDrawingBuffer || volumeRendering || this.wboitEnabled || this.dpoitEnabled
                 ? this.colorTarget
                 : this.drawTarget;
 
@@ -303,7 +379,7 @@ export class DrawPass {
             this.webgl.state.disable(this.webgl.gl.DEPTH_TEST);
             if (postprocessingEnabled) {
                 this.copyFboPostprocessing.render();
-            } else if (volumeRendering || this.wboitEnabled) {
+            } else if (volumeRendering || this.wboitEnabled || this.dpoitEnabled) {
                 this.copyFboTarget.render();
             }
         }

+ 2 - 2
src/mol-canvas3d/passes/image.ts

@@ -48,10 +48,10 @@ export class ImagePass {
     get width() { return this._width; }
     get height() { return this._height; }
 
-    constructor(private webgl: WebGLContext, assetManager: AssetManager, private renderer: Renderer, private scene: Scene, private camera: Camera, helper: Helper, enableWboit: boolean, props: Partial<ImageProps>) {
+    constructor(private webgl: WebGLContext, assetManager: AssetManager, private renderer: Renderer, private scene: Scene, private camera: Camera, helper: Helper, enableWboit: boolean, enableDpoit: boolean, props: Partial<ImageProps>) {
         this.props = { ...PD.getDefaultValues(ImageParams), ...props };
 
-        this.drawPass = new DrawPass(webgl, assetManager, 128, 128, enableWboit);
+        this.drawPass = new DrawPass(webgl, assetManager, 128, 128, enableWboit, enableDpoit);
         this.multiSamplePass = new MultiSamplePass(webgl, this.drawPass);
         this.multiSampleHelper = new MultiSampleHelper(this.multiSamplePass);
 

+ 2 - 2
src/mol-canvas3d/passes/passes.ts

@@ -15,9 +15,9 @@ export class Passes {
     readonly pick: PickPass;
     readonly multiSample: MultiSamplePass;
 
-    constructor(private webgl: WebGLContext, assetManager: AssetManager, attribs: Partial<{ pickScale: number, enableWboit: boolean }> = {}) {
+    constructor(private webgl: WebGLContext, assetManager: AssetManager, attribs: Partial<{ pickScale: number, enableWboit: boolean, enableDpoit: boolean }> = {}) {
         const { gl } = webgl;
-        this.draw = new DrawPass(webgl, assetManager, gl.drawingBufferWidth, gl.drawingBufferHeight, attribs.enableWboit || false);
+        this.draw = new DrawPass(webgl, assetManager, gl.drawingBufferWidth, gl.drawingBufferHeight, attribs.enableWboit || false, attribs.enableDpoit || false);
         this.pick = new PickPass(webgl, this.draw, attribs.pickScale || 0.25);
         this.multiSample = new MultiSamplePass(webgl, this.draw);
     }

+ 6 - 1
src/mol-gl/renderable/schema.ts

@@ -167,6 +167,11 @@ export type GlobalUniformValues = Values<GlobalUniformSchema>
 
 export const GlobalTextureSchema = {
     tDepth: TextureSpec('texture', 'depth', 'ushort', 'nearest'),
+
+    //dpoit
+    tDpoitDepth: TextureSpec('texture', 'rg', 'float', 'nearest'),
+    tDpoitFrontColor: TextureSpec('texture', 'rgba', 'float', 'nearest'),
+    tDpoitBackColor: TextureSpec('texture', 'rgba', 'float', 'nearest')
 } as const;
 export type GlobalTextureSchema = typeof GlobalTextureSchema
 export type GlobalTextureValues = Values<GlobalTextureSchema>
@@ -236,7 +241,7 @@ export const TransparencySchema = {
     uTransparencyGridDim: UniformSpec('v3'),
     uTransparencyGridTransform: UniformSpec('v4'),
     tTransparencyGrid: TextureSpec('texture', 'alpha', 'ubyte', 'linear'),
-    dTransparencyType: DefineSpec('string', ['instance', 'groupInstance', 'volumeInstance']),
+    dTransparencyType: DefineSpec('string', ['instance', 'groupInstance', 'volumeInstance'])
 } as const;
 export type TransparencySchema = typeof TransparencySchema
 export type TransparencyValues = Values<TransparencySchema>

+ 54 - 1
src/mol-gl/renderer.ts

@@ -70,6 +70,8 @@ interface Renderer {
     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
+    renderDpoitOpaque: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
+    renderDpoitTransparent: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null, dpoitTextures : { depth: Texture, frontColor: Texture, backColor: Texture }) => void
 
     setProps: (props: Partial<RendererProps>) => void
     setViewport: (x: number, y: number, width: number, height: number) => void
@@ -268,7 +270,7 @@ namespace Renderer {
             }
 
             if (r.values.dGeometryType.ref.value === 'directVolume') {
-                if (variant !== 'colorWboit' && variant !== 'colorBlended') {
+                if (variant !== 'colorDpoit' && variant !== 'colorWboit' && variant !== 'colorBlended') {
                     return; // only color supported
                 }
 
@@ -602,6 +604,55 @@ namespace Renderer {
             if (isTimingMode) ctx.timer.markEnd('Renderer.renderWboitTransparent');
         };
 
+        const renderDpoitOpaque = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => {
+            if (isTimingMode) ctx.timer.mark('Renderer.renderDpoitOpaque');
+            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];
+
+                // 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.dTransparentBackfaces?.ref.value === 'opaque') {
+                    renderObject(r, 'colorDpoit', Flag.None);
+                }
+            }
+            if (isTimingMode) ctx.timer.markEnd('Renderer.renderDpoitOpaque');
+        };
+
+        const renderDpoitTransparent = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null, dpoitTextures : { depth: Texture, frontColor: Texture, backColor: Texture }) => {
+            if (isTimingMode) ctx.timer.mark('Renderer.renderDpoitTransparent');
+
+            state.enable(gl.BLEND);
+
+            arrayMapUpsert(sharedTexturesList, 'tDpoitDepth', dpoitTextures.depth);
+            arrayMapUpsert(sharedTexturesList, 'tDpoitFrontColor', dpoitTextures.frontColor);
+            arrayMapUpsert(sharedTexturesList, 'tDpoitBackColor', dpoitTextures.backColor);
+
+            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 > 0 || r.values.dGeometryType.ref.value === 'directVolume' || r.values.dPointStyle?.ref.value === 'fuzzy' || !!r.values.uBackgroundColor || r.values.dXrayShaded?.ref.value) {
+                    renderObject(r, 'colorDpoit', Flag.None);
+                }
+            }
+            if (isTimingMode) ctx.timer.markEnd('Renderer.renderDpoitTransparent');
+        };
+
         return {
             clear: (toBackgroundColor: boolean, ignoreTransparentBackground?: boolean) => {
                 state.enable(gl.SCISSOR_TEST);
@@ -645,6 +696,8 @@ namespace Renderer {
             renderBlendedVolume,
             renderWboitOpaque,
             renderWboitTransparent,
+            renderDpoitOpaque,
+            renderDpoitTransparent,
 
             setProps: (props: Partial<RendererProps>) => {
                 if (props.backgroundColor !== undefined && props.backgroundColor !== p.backgroundColor) {

+ 2 - 2
src/mol-gl/scene.ts

@@ -45,8 +45,8 @@ function calculateBoundingSphere(renderables: GraphicsRenderable[], boundingSphe
 }
 
 function renderableSort(a: GraphicsRenderable, b: GraphicsRenderable) {
-    const drawProgramIdA = (a.getProgram('colorBlended') || a.getProgram('colorWboit')).id;
-    const drawProgramIdB = (b.getProgram('colorBlended') || a.getProgram('colorWboit')).id;
+    const drawProgramIdA = (a.getProgram('colorBlended') || a.getProgram('colorWboit') || a.getProgram('colorDpoit')).id;
+    const drawProgramIdB = (b.getProgram('colorBlended') || b.getProgram('colorWboit') || b.getProgram('colorDpoit')).id;
     const materialIdA = a.materialId;
     const materialIdB = b.materialId;
 

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

@@ -67,6 +67,7 @@ import { texture3d_from_1d_trilinear } from './shader/chunks/texture3d-from-1d-t
 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_write } from './shader/chunks/wboit-write.glsl';
+import { dpoit_write } from './shader/chunks/dpoit-write.glsl';
 
 const ShaderChunks: { [k: string]: string } = {
     apply_fog,
@@ -99,7 +100,8 @@ const ShaderChunks: { [k: string]: string } = {
     texture3d_from_1d_trilinear,
     texture3d_from_2d_linear,
     texture3d_from_2d_nearest,
-    wboit_write
+    wboit_write,
+    dpoit_write
 };
 
 const reInclude = /^(?!\/\/)\s*#include\s+(\S+)/gm;

+ 14 - 0
src/mol-gl/shader/blend-back-dpoit.frag.ts

@@ -0,0 +1,14 @@
+export const blendBackDpoit_frag = `
+  precision highp float;
+
+  uniform sampler2D tDpoitBackColor;
+  uniform vec2 uTexSize;
+
+  void main() {
+      vec2 coords = gl_FragCoord.xy / uTexSize;
+      gl_FragColor = texture2D(tDpoitBackColor, coords);
+      if (gl_FragColor.a == 0.0) {
+          discard;
+      }
+  }
+`;

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

@@ -39,6 +39,12 @@ uniform int uMarkingType;
     #endif
 #endif
 
+#if defined(dRenderVariant_colorDpoit)
+    #define MAX_DPOIT_DEPTH 99999.0
+    uniform sampler2D tDpoitDepth;
+    uniform sampler2D tDpoitFrontColor;
+#endif
+
 varying vec3 vModelPosition;
 varying vec3 vViewPosition;
 

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

@@ -1,7 +1,7 @@
 export const common = `
 // TODO find a better place for these convenience defines
 
-#if defined(dRenderVariant_colorBlended) || defined(dRenderVariant_colorWboit)
+#if defined(dRenderVariant_colorBlended) || defined(dRenderVariant_colorWboit) || defined(dRenderVariant_colorDpoit)
     #define dRenderVariant_color
 #endif
 

+ 75 - 0
src/mol-gl/shader/chunks/dpoit-write.glsl.ts

@@ -0,0 +1,75 @@
+/**
+ * Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Gianluca Tomasello <giagitom@gmail.com>
+ */
+
+export const dpoit_write = `
+#if defined(dRenderVariant_colorDpoit)
+    if (uRenderMask == MaskOpaque) {
+        if (preFogAlpha < 1.0) {
+            discard;
+        }
+    } else if (uRenderMask == MaskTransparent) {
+        // the 'fragmentDepth > 0.99' check is to handle precision issues with packed depth
+        vec2 coords = gl_FragCoord.xy / uDrawingBufferSize;
+        if (preFogAlpha != 1.0 && (fragmentDepth < getDepth(coords) || fragmentDepth > 0.99)) {
+            #ifdef dTransparentBackfaces_off
+                if (interior) discard;
+            #endif
+
+            // ---------------------------------------------------------
+            // Order Independent Transparency - Dual Depth Peeling
+            // adapted from https://github.com/tsherif/webgl2examples
+            // The MIT License, Copyright 2017 Tarek Sherif, Shuai Shao
+            // ---------------------------------------------------------
+
+            vec2 lastDepth = texture2D(tDpoitDepth, coords).rg;
+            vec4 lastFrontColor = texture2D(tDpoitFrontColor, coords);
+
+            vec4 fragColor = gl_FragColor;
+
+            // depth value always increases
+            // so we can use MAX blend equation
+            gl_FragData[2].rg = vec2(-MAX_DPOIT_DEPTH);
+
+            // front color always increases
+            // so we can use MAX blend equation
+            gl_FragColor = lastFrontColor;
+
+            // back color is separately blend afterwards each pass
+            gl_FragData[1] = vec4(0.0);
+
+            float nearestDepth = - lastDepth.x;
+            float furthestDepth = lastDepth.y;
+            float alphaMultiplier = 1.0 - lastFrontColor.a;
+
+
+            if (fragmentDepth < nearestDepth || fragmentDepth > furthestDepth) {
+                // Skip this depth since it's been peeled.
+                return;
+            }
+
+            if (fragmentDepth > nearestDepth && fragmentDepth < furthestDepth) {
+                // This needs to be peeled.
+                // The ones remaining after MAX blended for
+                // all need-to-peel will be peeled next pass.
+                gl_FragData[2].rg = vec2(-fragmentDepth, fragmentDepth);
+                return;
+            }
+
+            // write to back and front color buffer
+
+            if (fragmentDepth == nearestDepth) {
+                gl_FragColor.rgb += fragColor.rgb * fragColor.a * alphaMultiplier;
+                gl_FragColor.a = (1.0 - alphaMultiplier * (1.0 - fragColor.a)) * (uTransparentBackground ? fragColor.a : 1.0);
+            } else {
+                gl_FragData[1] += vec4(fragColor.rgb, fragColor.a * (uTransparentBackground ? fragColor.a : 1.0));
+            }
+
+        } else {
+            discard;
+        }
+    }
+#endif
+`;

+ 2 - 1
src/mol-gl/shader/cylinders.frag.ts

@@ -142,6 +142,7 @@ void main() {
         #include apply_marker_color
         #include apply_fog
         #include wboit_write
+        #include dpoit_write
     #endif
 }
-`;
+`;

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

@@ -355,5 +355,6 @@ void main() {
     float fragmentDepth = calcDepth((uModelView * vec4(start, 1.0)).xyz);
     float preFogAlpha = clamp(preFogAlphaBlended, 0.0, 1.0);
     #include wboit_write
+    #include dpoit_write
 }
-`;
+`;

+ 19 - 0
src/mol-gl/shader/evaluate-dpoit.frag.ts

@@ -0,0 +1,19 @@
+export const evaluateDpoit_frag = `
+precision highp float;
+
+uniform sampler2D tDpoitFrontColor;
+uniform sampler2D tDpoitBlendBackColor;
+uniform vec2 uTexSize;
+
+void main() {
+    vec2 coords = gl_FragCoord.xy / uTexSize;
+    vec4 frontColor = texture2D(tDpoitFrontColor, coords);
+    vec4 backColor = texture2D(tDpoitBlendBackColor, coords);
+    float alphaMultiplier = 1.0 - frontColor.a;
+
+    gl_FragColor = vec4(
+        frontColor.rgb + alphaMultiplier * backColor.rgb,
+        frontColor.a + backColor.a
+    );
+}
+`;

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

@@ -159,6 +159,7 @@ void main() {
         #include apply_marker_color
         #include apply_fog
         #include wboit_write
+        #include dpoit_write
     #endif
 }
-`;
+`;

+ 2 - 1
src/mol-gl/shader/lines.frag.ts

@@ -39,6 +39,7 @@ void main(){
         #include apply_marker_color
         #include apply_fog
         #include wboit_write
+        #include dpoit_write
     #endif
 }
-`;
+`;

+ 2 - 1
src/mol-gl/shader/mesh.frag.ts

@@ -62,6 +62,7 @@ void main() {
         #include apply_marker_color
         #include apply_fog
         #include wboit_write
+        #include dpoit_write
     #endif
 }
-`;
+`;

+ 2 - 1
src/mol-gl/shader/points.frag.ts

@@ -55,6 +55,7 @@ void main(){
         #include apply_marker_color
         #include apply_fog
         #include wboit_write
+        #include dpoit_write
     #endif
 }
-`;
+`;

+ 2 - 1
src/mol-gl/shader/spheres.frag.ts

@@ -105,6 +105,7 @@ void main(void){
         #include apply_marker_color
         #include apply_fog
         #include wboit_write
+        #include dpoit_write
     #endif
 }
-`;
+`;

+ 2 - 1
src/mol-gl/shader/text.frag.ts

@@ -83,6 +83,7 @@ void main(){
         #include apply_marker_color
         #include apply_fog
         #include wboit_write
+        #include dpoit_write
     #endif
 }
-`;
+`;

+ 4 - 3
src/mol-gl/webgl/render-item.ts

@@ -49,11 +49,12 @@ export interface RenderItem<T extends string> {
 
 //
 
-const GraphicsRenderVariant = { colorBlended: '', colorWboit: '', pick: '', depth: '', marking: '' };
+const GraphicsRenderVariant = { colorBlended: '', colorWboit: '', colorDpoit: '', pick: '', depth: '', marking: '' };
 export type GraphicsRenderVariant = keyof typeof GraphicsRenderVariant
 export const GraphicsRenderVariants = Object.keys(GraphicsRenderVariant) as GraphicsRenderVariant[];
-export const GraphicsRenderVariantsBlended = GraphicsRenderVariants.filter(v => v !== 'colorWboit');
-export const GraphicsRenderVariantsWboit = GraphicsRenderVariants.filter(v => v !== 'colorBlended');
+export const GraphicsRenderVariantsBlended = GraphicsRenderVariants.filter(v => !['colorWboit','colorDpoit'].includes(v));
+export const GraphicsRenderVariantsWboit = GraphicsRenderVariants.filter(v => !['colorBlended','colorDpoit'].includes(v));
+export const GraphicsRenderVariantsDpoit = GraphicsRenderVariants.filter(v => !['colorWboit','colorBlended'].includes(v));
 
 const ComputeRenderVariant = { compute: '' };
 export type ComputeRenderVariant = keyof typeof ComputeRenderVariant

+ 11 - 1
src/mol-gl/webgl/texture.ts

@@ -31,7 +31,7 @@ export type TextureKindValue = {
 export type TextureValueType = ValueOf<TextureKindValue>
 export type TextureKind = keyof TextureKindValue
 export type TextureType = 'ubyte' | 'ushort' | 'float' | 'fp16' | 'int'
-export type TextureFormat = 'alpha' | 'rgb' | 'rgba' | 'depth'
+export type TextureFormat = 'alpha' | 'rg' | 'rgb' | 'rgba' | 'depth'
 /** Numbers are shortcuts for color attachment */
 export type TextureAttachment = 'depth' | 'stencil' | 'color0' | 'color1' | 'color2' | 'color3' | 'color4' | 'color5' | 'color6' | 'color7' | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7
 export type TextureFilter = 'nearest' | 'linear'
@@ -63,6 +63,9 @@ export function getFormat(gl: GLRenderingContext, format: TextureFormat, type: T
         case 'rgb':
             if (isWebGL2(gl) && type === 'int') return gl.RGB_INTEGER;
             return gl.RGB;
+        case 'rg':
+            if (isWebGL2(gl) && type === 'float') return gl.RG;
+            return gl.RGBA;
         case 'rgba':
             if (isWebGL2(gl) && type === 'int') return gl.RGBA_INTEGER;
             return gl.RGBA;
@@ -80,6 +83,13 @@ export function getInternalFormat(gl: GLRenderingContext, format: TextureFormat,
                     case 'fp16': return gl.R16F;
                     case 'int': return gl.R32I;
                 }
+            case 'rg':
+                switch (type){
+                  case 'ubyte': return gl.RG;
+                  case 'float': return gl.RG32F;
+                  case 'fp16': return gl.RG16F;
+                  case 'int': return gl.RG32I;
+                }
             case 'rgb':
                 switch (type) {
                     case 'ubyte': return gl.RGB;

+ 2 - 1
src/mol-plugin/config.ts

@@ -31,7 +31,8 @@ export const PluginConfig = {
         PixelScale: item('plugin-config.pixel-scale', 1),
         PickScale: item('plugin-config.pick-scale', 0.25),
         PickPadding: item('plugin-config.pick-padding', 3),
-        EnableWboit: item('plugin-config.enable-wboit', true),
+        EnableWboit: item('plugin-config.enable-wboit', false),
+        EnableDpoit: item('plugin-config.enable-dpoit', true),
         // as of Oct 1 2021, WebGL 2 doesn't work on iOS 15.
         // TODO: check back in a few weeks to see if it was fixed
         PreferWebGl1: item('plugin-config.prefer-webgl1', PluginFeatureDetection.preferWebGl1),

+ 2 - 1
src/mol-plugin/context.ts

@@ -200,8 +200,9 @@ export class PluginContext {
                 const pickScale = this.config.get(PluginConfig.General.PickScale) || 0.25;
                 const pickPadding = this.config.get(PluginConfig.General.PickPadding) ?? 1;
                 const enableWboit = this.config.get(PluginConfig.General.EnableWboit) || false;
+                const enableDpoit = this.config.get(PluginConfig.General.EnableDpoit) || false;
                 const preferWebGl1 = this.config.get(PluginConfig.General.PreferWebGl1) || false;
-                (this.canvas3dContext as Canvas3DContext) = Canvas3DContext.fromCanvas(canvas, this.managers.asset, { antialias, preserveDrawingBuffer, pixelScale, pickScale, pickPadding, enableWboit, preferWebGl1 });
+                (this.canvas3dContext as Canvas3DContext) = Canvas3DContext.fromCanvas(canvas, this.managers.asset, { antialias, preserveDrawingBuffer, pixelScale, pickScale, pickPadding, enableWboit, enableDpoit, preferWebGl1 });
             }
             (this.canvas3d as Canvas3D) = Canvas3D.create(this.canvas3dContext!);
             this.canvas3dInit.next(true);