Browse Source

transparent object rendering improvement

- changed output to be pre-multiplied alpha
- point and text tweaks
- better handle opaque vs transparent volumes in blended rendering
Alexander Rose 4 years ago
parent
commit
466308cde8

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

@@ -155,7 +155,7 @@ namespace Canvas3D {
             antialias: attribs.antialias ?? true,
             depth: true,
             preserveDrawingBuffer: true,
-            premultipliedAlpha: false,
+            premultipliedAlpha: true,
         });
         if (gl === null) throw new Error('Could not create a WebGL rendering context');
 

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

@@ -180,8 +180,7 @@ export class DrawPass {
         }
 
         renderer.clear(true);
-        // TODO: split into opaque and transparent pass to handle opaque volume isosurfaces
-        renderer.renderBlended(scene.primitives, camera, null);
+        renderer.renderBlendedOpaque(scene.primitives, camera, null);
 
         // do a depth pass if not rendering to drawing buffer and
         // extensions.depthTexture is unsupported (i.e. depthTarget is set)
@@ -198,7 +197,7 @@ export class DrawPass {
                 this.depthTextureVolumes.attachFramebuffer(this.colorTarget.framebuffer, 'depth');
                 renderer.clearDepth();
             }
-            renderer.renderBlended(scene.volumes, camera, this.depthTexturePrimitives);
+            renderer.renderBlendedVolume(scene.volumes, camera, this.depthTexturePrimitives);
 
             // do volume depth pass if extensions.depthTexture is unsupported (i.e. depthTarget is set)
             if (this.depthTargetVolumes) {
@@ -209,6 +208,8 @@ export class DrawPass {
             }
         }
 
+        renderer.renderBlendedTransparent(scene.primitives, camera, null);
+
         // merge depths from primitive and volume rendering
         if (!toDrawingBuffer) {
             this._depthMerge();

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

@@ -73,7 +73,7 @@ export class WboitPass {
     render() {
         const { state, gl } = this.webgl;
 
-        state.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE);
+        state.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
         state.enable(gl.BLEND);
 
         this.renderable.update();

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

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

+ 46 - 9
src/mol-gl/renderer.ts

@@ -49,6 +49,9 @@ interface Renderer {
     renderPick: (group: Scene.Group, camera: ICamera, variant: GraphicsRenderVariant, depthTexture: Texture | null) => void
     renderDepth: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
     renderBlended: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
+    renderBlendedOpaque: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
+    renderBlendedTransparent: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void
+    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
 
@@ -224,7 +227,7 @@ namespace Renderer {
             uFogFar: ValueCell.create(10000),
             uFogColor: ValueCell.create(bgColor),
 
-            uRenderWboit: ValueCell.create(0),
+            uRenderWboit: ValueCell.create(false),
 
             uTransparentBackground: ValueCell.create(false),
 
@@ -300,7 +303,7 @@ namespace Renderer {
                         state.depthMask(false);
                     } else {
                         state.enable(gl.DEPTH_TEST);
-                        state.depthMask(r.state.writeDepth);
+                        state.depthMask(r.values.uAlpha.ref.value === 1.0);
                     }
                 }
             } else {
@@ -328,8 +331,6 @@ namespace Renderer {
                     state.frontFace(gl.CCW);
                     state.cullFace(gl.BACK);
                 }
-
-                if (variant === 'colorBlended') state.depthMask(r.state.writeDepth);
             }
 
             r.render(variant, sharedTexturesList);
@@ -363,7 +364,7 @@ namespace Renderer {
             ValueCell.update(globalUniforms.uModelViewProjection, Mat4.mul(modelViewProjection, modelView, camera.projection));
             ValueCell.update(globalUniforms.uInvModelViewProjection, Mat4.invert(invModelViewProjection, modelViewProjection));
 
-            ValueCell.updateIfChanged(globalUniforms.uRenderWboit, renderWboit ? 1 : 0);
+            ValueCell.updateIfChanged(globalUniforms.uRenderWboit, renderWboit);
 
             state.enable(gl.SCISSOR_TEST);
             state.colorMask(true, true, true, true);
@@ -405,6 +406,11 @@ namespace Renderer {
         };
 
         const renderBlended = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => {
+            renderBlendedOpaque(group, camera, depthTexture);
+            renderBlendedTransparent(group, camera, depthTexture);
+        };
+
+        const renderBlendedOpaque = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => {
             state.disable(gl.BLEND);
             state.enable(gl.DEPTH_TEST);
             state.depthMask(true);
@@ -418,8 +424,21 @@ namespace Renderer {
                     renderObject(r, 'colorBlended');
                 }
             }
+        };
+
+        const renderBlendedTransparent = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => {
+            state.enable(gl.DEPTH_TEST);
+            state.depthMask(true);
+
+            updateInternal(group, camera, depthTexture, false);
 
-            state.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE);
+            const { renderables } = group;
+
+            if (transparentBackground) {
+                state.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
+            } else {
+                state.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
+            }
             state.enable(gl.BLEND);
 
             state.depthMask(true);
@@ -439,6 +458,19 @@ namespace Renderer {
             }
         };
 
+        const renderBlendedVolume = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => {
+            state.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
+            state.enable(gl.BLEND);
+
+            updateInternal(group, camera, depthTexture, false);
+
+            const { renderables } = group;
+            for (let i = 0, il = renderables.length; i < il; ++i) {
+                const r = renderables[i];
+                renderObject(r, 'colorBlended');
+            }
+        };
+
         const renderWboitOpaque = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => {
             state.disable(gl.BLEND);
             state.enable(gl.DEPTH_TEST);
@@ -449,7 +481,8 @@ namespace Renderer {
             const { renderables } = group;
             for (let i = 0, il = renderables.length; i < il; ++i) {
                 const r = renderables[i];
-                if (r.values.uAlpha.ref.value === 1 && r.values.transparencyAverage.ref.value !== 1 && r.values?.dRenderMode?.ref.value !== 'volume') {
+                // TODO: simplify, handle on renderable.state???
+                if (r.values.uAlpha.ref.value === 1 && r.values.transparencyAverage.ref.value !== 1 && r.values.dRenderMode?.ref.value !== 'volume' && !r.values.dPointFilledCircle?.ref.value) {
                     renderObject(r, 'colorWboit');
                 }
             }
@@ -461,7 +494,8 @@ namespace Renderer {
             const { renderables } = group;
             for (let i = 0, il = renderables.length; i < il; ++i) {
                 const r = renderables[i];
-                if (r.values.uAlpha.ref.value < 1 || r.values.transparencyAverage.ref.value > 0 || r.values?.dRenderMode?.ref.value === 'volume') {
+                // TODO: simplify, handle on renderable.state???
+                if (r.values.uAlpha.ref.value < 1 || r.values.transparencyAverage.ref.value > 0 || r.values.dRenderMode?.ref.value === 'volume' || r.values.dPointFilledCircle?.ref.value || !!r.values.uBackgroundColor) {
                     renderObject(r, 'colorWboit');
                 }
             }
@@ -475,7 +509,7 @@ namespace Renderer {
                 state.depthMask(true);
 
                 if (transparentBackground) {
-                    state.clearColor(1, 1, 1, 0);
+                    state.clearColor(0, 0, 0, 0);
                 } else if (toBackgroundColor) {
                     state.clearColor(bgColor[0], bgColor[1], bgColor[2], 1);
                 } else {
@@ -494,6 +528,9 @@ namespace Renderer {
             renderPick,
             renderDepth,
             renderBlended,
+            renderBlendedOpaque,
+            renderBlendedTransparent,
+            renderBlendedVolume,
             renderWboitOpaque,
             renderWboitTransparent,
 

+ 9 - 3
src/mol-gl/shader/chunks/apply-fog.glsl.ts

@@ -2,12 +2,18 @@ export default `
 float fogDepth = length(vViewPosition);
 float fogFactor = smoothstep(uFogNear, uFogFar, fogDepth);
 float fogAlpha = (1.0 - fogFactor) * gl_FragColor.a;
-float preFogAlpha = gl_FragColor.a < 1.0 ? fogAlpha : 1.0;
+float preFogAlpha = gl_FragColor.a;
 if (!uTransparentBackground) {
-    gl_FragColor.rgb = mix(gl_FragColor.rgb, uFogColor, fogFactor);
-    if (gl_FragColor.a < 1.0)
+    if (gl_FragColor.a < 1.0) {
+        // transparent objects are blended with background color
         gl_FragColor.a = fogAlpha;
+    } else {
+        // mix opaque objects with background color
+        gl_FragColor.rgb = mix(gl_FragColor.rgb, uFogColor, fogFactor);
+    }
 } else {
+    // pre-multiplied alpha expected for transparent background
+    gl_FragColor.rgb *= fogAlpha;
     gl_FragColor.a = fogAlpha;
 }
 `;

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

@@ -11,7 +11,7 @@ export default `
     #endif
 #endif
 
-uniform int uRenderWboit;
+uniform bool uRenderWboit;
 
 float calcDepth(const in vec3 pos) {
     vec2 clipZW = pos.z * uProjection[2].zw + uProjection[3].zw;

+ 9 - 4
src/mol-gl/shader/chunks/wboit-write.glsl.ts

@@ -1,15 +1,20 @@
 export default `
 #if defined(dRenderVariant_colorWboit)
-    if (uRenderWboit == 0) {
+    if (!uRenderWboit) {
         if (preFogAlpha < 1.0) {
             discard;
         }
-    } else if (uRenderWboit == 1) {
+    } else if (uRenderWboit) {
         if (preFogAlpha != 1.0 && !interior && fragmentDepth < getDepth(gl_FragCoord.xy / uDrawingBufferSize)) {
-            float alpha = preFogAlpha;
+            float alpha = gl_FragColor.a;
             float wboitWeight = alpha * clamp(pow(1.0 - fragmentDepth, 2.0), 0.01, 1.0);
             gl_FragColor = vec4(gl_FragColor.rgb * alpha * wboitWeight, alpha);
-            gl_FragData[1] = vec4(alpha * wboitWeight);
+            // extra alpha is to handle pre-multiplied alpha
+            #if !defined(dRenderMode_volume) && !defined(dRenderMode_isosurface)
+                gl_FragData[1] = vec4((uTransparentBackground ? alpha : 1.0) * alpha * wboitWeight);
+            #else
+                gl_FragData[1] = vec4(alpha * alpha * wboitWeight);
+            #endif
         } else {
             discard;
         }

+ 11 - 4
src/mol-gl/shader/direct-volume.frag.ts

@@ -129,7 +129,7 @@ vec4 transferFunction(float value) {
 
 float getDepth(const in vec2 coords) {
     #ifdef depthTextureSupport
-        if (uRenderWboit == 0) {
+        if (!uRenderWboit) {
             // in case of opaque volumes (and depth texture support)
             return texture2D(tDepth, coords).r;
         } else {
@@ -240,7 +240,6 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 rayDir) {
                 #ifdef enabledFragDepth
                     if (!hit) {
                         gl_FragDepthEXT = depth;
-                        hit = true;
                     }
                 #endif
 
@@ -330,13 +329,18 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 rayDir) {
 
                     src = gl_FragColor;
 
-                    src.rgb *= src.a;
+                    if (!uTransparentBackground) {
+                        // done in 'apply_fog' otherwise
+                        src.rgb *= src.a;
+                    }
                     dst = (1.0 - dst.a) * src + dst; // standard blending
                 #endif
 
                 #ifdef dSingleLayer
                     break;
                 #endif
+
+                hit = true;
             }
             prevValue = value;
         #elif defined(dRenderMode_volume)
@@ -393,7 +397,10 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 rayDir) {
 
                 src = gl_FragColor;
 
-                src.rgb *= src.a;
+                if (!uTransparentBackground) {
+                    // done in 'apply_fog' otherwise
+                    src.rgb *= src.a;
+                }
                 dst = (1.0 - dst.a) * src + dst; // standard blending
             #endif
         #endif

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

@@ -12,6 +12,7 @@ void main() {
     float r = 1.0 - accum.a;
 
     accum.a = texture2D(tWboitB, coords).r;
-    gl_FragColor = vec4(accum.rgb / clamp(accum.a, 0.0001, 50000.0), r);
+    // divisor needs to allow very small values for nice fading
+    gl_FragColor = vec4(accum.rgb / clamp(accum.a, 0.00000001, 50000.0), r);
 }
 `;

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

@@ -35,7 +35,7 @@ void main(){
     #include assign_material_color
 
     if (vTexCoord.x > 1.0) {
-        gl_FragColor = vec4(uBackgroundColor, uBackgroundOpacity);
+        gl_FragColor = vec4(uBackgroundColor, uBackgroundOpacity * material.a);
     } else {
         // retrieve signed distance
         float sdf = texture2D(tFont, vTexCoord).a + uBorderWidth;