diff --git a/src/mol-canvas3d/canvas3d.ts b/src/mol-canvas3d/canvas3d.ts index 5df5536af201a1b921c1a5c84a9dba55642f9afe..cac66a040bceb0ee06891e692057020c0217bb32 100644 --- a/src/mol-canvas3d/canvas3d.ts +++ b/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[] = []; diff --git a/src/mol-canvas3d/passes/dpoit.ts b/src/mol-canvas3d/passes/dpoit.ts new file mode 100644 index 0000000000000000000000000000000000000000..8bb71a02ce7cafbbf844feb183f031b474b61a77 --- /dev/null +++ b/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(); + } +} diff --git a/src/mol-canvas3d/passes/draw.ts b/src/mol-canvas3d/passes/draw.ts index 8b1d82c088a35b364fca04340d3811b55dbb5c9c..2131fcf51ef039ea20226a533aa139028ced3556 100644 --- a/src/mol-canvas3d/passes/draw.ts +++ b/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(); } } diff --git a/src/mol-canvas3d/passes/image.ts b/src/mol-canvas3d/passes/image.ts index e78aca0a31a447c5aa0f874dbf1995a13304c026..aa1d60493cd4e390a28d549d2622d1cb16b3481d 100644 --- a/src/mol-canvas3d/passes/image.ts +++ b/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); diff --git a/src/mol-canvas3d/passes/passes.ts b/src/mol-canvas3d/passes/passes.ts index 117bb6ef0526f68074b40bf08b0f437d88c5ba88..c52b1488f064302eeb7fcbc1cb6b2d056e8321bc 100644 --- a/src/mol-canvas3d/passes/passes.ts +++ b/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); } diff --git a/src/mol-gl/renderable/schema.ts b/src/mol-gl/renderable/schema.ts index 71578c4d28bf5b8aa569c38975a0d739e2e14e00..92f0635b315736003ecf33542228973216d0ef4d 100644 --- a/src/mol-gl/renderable/schema.ts +++ b/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> diff --git a/src/mol-gl/renderer.ts b/src/mol-gl/renderer.ts index 2182662f3675347955f025390e9892d948904ab4..807f8512da52047c40b5a97cfac5b190776bb9c6 100644 --- a/src/mol-gl/renderer.ts +++ b/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) { diff --git a/src/mol-gl/scene.ts b/src/mol-gl/scene.ts index a46be8ae129f3be7d75e5dd6e821181757923386..b8a8252b941c13baff32c3951cd8b1193c1f664f 100644 --- a/src/mol-gl/scene.ts +++ b/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; diff --git a/src/mol-gl/shader-code.ts b/src/mol-gl/shader-code.ts index a99cf5b31cafaac10f2d9352c82d79b3f908efeb..baeb787af1ad4243ccc46f129181c885a9cb407d 100644 --- a/src/mol-gl/shader-code.ts +++ b/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; diff --git a/src/mol-gl/shader/blend-back-dpoit.frag.ts b/src/mol-gl/shader/blend-back-dpoit.frag.ts new file mode 100644 index 0000000000000000000000000000000000000000..e60e2c8c27b6d3aa09e820697b03de98fc0b748c --- /dev/null +++ b/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; + } + } +`; diff --git a/src/mol-gl/shader/chunks/common-frag-params.glsl.ts b/src/mol-gl/shader/chunks/common-frag-params.glsl.ts index 3d694f3e2601c37631439f9e2d5a7f640aa3315b..8bb016483625fc01a967a31be76d6f26e673be58 100644 --- a/src/mol-gl/shader/chunks/common-frag-params.glsl.ts +++ b/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; diff --git a/src/mol-gl/shader/chunks/common.glsl.ts b/src/mol-gl/shader/chunks/common.glsl.ts index 0df48939f6767eadd3658d1bd4538dbdba3b6f92..49eb5b9dba4027faa2382e7cde8dabdc0757a6b0 100644 --- a/src/mol-gl/shader/chunks/common.glsl.ts +++ b/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 diff --git a/src/mol-gl/shader/chunks/dpoit-write.glsl.ts b/src/mol-gl/shader/chunks/dpoit-write.glsl.ts new file mode 100644 index 0000000000000000000000000000000000000000..511c2452380c865b005432b343a9fd81143c5a63 --- /dev/null +++ b/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 +`; diff --git a/src/mol-gl/shader/cylinders.frag.ts b/src/mol-gl/shader/cylinders.frag.ts index 8f840be543d2df2174a33c66772727d639a608a2..ba17705d7c67d751e744a3f70be36695d6087269 100644 --- a/src/mol-gl/shader/cylinders.frag.ts +++ b/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 } -`; \ No newline at end of file +`; diff --git a/src/mol-gl/shader/direct-volume.frag.ts b/src/mol-gl/shader/direct-volume.frag.ts index fd0f3af260fcac42e74d3ab34cd6a6c3203e0043..bcb2d4728825ecad05dca94e669850d40ccd12c7 100644 --- a/src/mol-gl/shader/direct-volume.frag.ts +++ b/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 } -`; \ No newline at end of file +`; diff --git a/src/mol-gl/shader/evaluate-dpoit.frag.ts b/src/mol-gl/shader/evaluate-dpoit.frag.ts new file mode 100644 index 0000000000000000000000000000000000000000..9dfe743a744a1e8444f97ddc1791c52cfc6d56fe --- /dev/null +++ b/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 + ); +} +`; diff --git a/src/mol-gl/shader/image.frag.ts b/src/mol-gl/shader/image.frag.ts index 39c8835d8c94aee896e484fdbc9149121939f974..b8a147fc13219f7c346f138fd4c9b15d4bd2da99 100644 --- a/src/mol-gl/shader/image.frag.ts +++ b/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 } -`; \ No newline at end of file +`; diff --git a/src/mol-gl/shader/lines.frag.ts b/src/mol-gl/shader/lines.frag.ts index fa62dbf5559b323f98e4e21e1e51d865e6ce900b..1639ae79df6e9d70a78b28a29515a77d7c00bfe5 100644 --- a/src/mol-gl/shader/lines.frag.ts +++ b/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 } -`; \ No newline at end of file +`; diff --git a/src/mol-gl/shader/mesh.frag.ts b/src/mol-gl/shader/mesh.frag.ts index e49049f60a8f4ddf845476d55321c3bc6aed8f83..98d0ca5445342517fd716e1030df8db3fc2c3960 100644 --- a/src/mol-gl/shader/mesh.frag.ts +++ b/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 } -`; \ No newline at end of file +`; diff --git a/src/mol-gl/shader/points.frag.ts b/src/mol-gl/shader/points.frag.ts index 299779b299cbb4049a0b8452c9ae53049c4f9e0a..84001a3d76f59427e3811a227ad0a877e0eb13e4 100644 --- a/src/mol-gl/shader/points.frag.ts +++ b/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 } -`; \ No newline at end of file +`; diff --git a/src/mol-gl/shader/spheres.frag.ts b/src/mol-gl/shader/spheres.frag.ts index 9397260a5c8606cd8f364ed15d28ab0c20a001ba..9b3def951a34d227d9211ed14fa835b9aefc5fde 100644 --- a/src/mol-gl/shader/spheres.frag.ts +++ b/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 } -`; \ No newline at end of file +`; diff --git a/src/mol-gl/shader/text.frag.ts b/src/mol-gl/shader/text.frag.ts index ad46e8f3a62a4694cebf872d63f3cc16d27687ba..4e141f1293c1b9933299ac5a0b78d4deb4300546 100644 --- a/src/mol-gl/shader/text.frag.ts +++ b/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 } -`; \ No newline at end of file +`; diff --git a/src/mol-gl/webgl/render-item.ts b/src/mol-gl/webgl/render-item.ts index 83f044f37abc7f117685b24792e4a608f2455b85..cf41b7431eb8e54f091e9584112beb91b0192a0f 100644 --- a/src/mol-gl/webgl/render-item.ts +++ b/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 diff --git a/src/mol-gl/webgl/texture.ts b/src/mol-gl/webgl/texture.ts index 26d4a2f1d20b44b89a51bc821c552efda8131e7e..9d96db22d34c6ce7aec9274102ac8f66bd7efb3f 100644 --- a/src/mol-gl/webgl/texture.ts +++ b/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; diff --git a/src/mol-plugin/config.ts b/src/mol-plugin/config.ts index e087c3468e04b818d23357059b899aa9eb031db9..3ddfe9ff795d71e655265807229ed926a0c2bc23 100644 --- a/src/mol-plugin/config.ts +++ b/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), diff --git a/src/mol-plugin/context.ts b/src/mol-plugin/context.ts index 405087080f0c118dece7b9c2ec541134ddcf23d0..7a1a345ec43a034071f5038b21e09124e1d0cf89 100644 --- a/src/mol-plugin/context.ts +++ b/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);