Browse Source

improve transparentBackfaces handling

- off: don't show (default)
- on: show with transparency
- opaque: show fully opaque
Alexander Rose 3 years ago
parent
commit
b23d610c94

+ 4 - 0
CHANGELOG.md

@@ -7,6 +7,10 @@ Note that since we don't clearly distinguish between a public and private interf
 ## [Unreleased]
 
 - Fix ``xrayShaded`` for texture-mesh geometries
+- [Breaking] Change ``allowTransparentBackfaces`` to ``transparentBackfaces`` with options ``off``, ``on``, ``opaque``. This was only added in 3.6.0, so allowing a breaking change here.
+    - ``off``: don't show (default)
+    - ``on``: show with transparency
+    - ``opaque``: show fully opaque
 
 ## [v3.6.2] - 2022-04-05
 

+ 3 - 1
src/mol-geo/geometry/cylinders/cylinders.ts

@@ -157,7 +157,7 @@ export namespace Cylinders {
         doubleSided: PD.Boolean(false, BaseGeometry.CustomQualityParamInfo),
         ignoreLight: PD.Boolean(false, BaseGeometry.ShadingCategory),
         xrayShaded: PD.Boolean(false, BaseGeometry.ShadingCategory),
-        allowTransparentBackfaces: PD.Boolean(false, BaseGeometry.ShadingCategory),
+        transparentBackfaces: PD.Select('off', PD.arrayToOptions(['off', 'on', 'opaque']), BaseGeometry.ShadingCategory),
         bumpFrequency: PD.Numeric(0, { min: 0, max: 10, step: 0.1 }, BaseGeometry.ShadingCategory),
         bumpAmplitude: PD.Numeric(1, { min: 0, max: 5, step: 0.1 }, BaseGeometry.ShadingCategory),
     };
@@ -242,6 +242,7 @@ export namespace Cylinders {
             uDoubleSided: ValueCell.create(props.doubleSided),
             dIgnoreLight: ValueCell.create(props.ignoreLight),
             dXrayShaded: ValueCell.create(props.xrayShaded),
+            dOpaqueBackfaces: ValueCell.create(props.transparentBackfaces === 'opaque'),
             uBumpFrequency: ValueCell.create(props.bumpFrequency),
             uBumpAmplitude: ValueCell.create(props.bumpAmplitude),
         };
@@ -259,6 +260,7 @@ export namespace Cylinders {
         ValueCell.updateIfChanged(values.uDoubleSided, props.doubleSided);
         ValueCell.updateIfChanged(values.dIgnoreLight, props.ignoreLight);
         ValueCell.updateIfChanged(values.dXrayShaded, props.xrayShaded);
+        ValueCell.updateIfChanged(values.dOpaqueBackfaces, props.transparentBackfaces === 'opaque');
         ValueCell.updateIfChanged(values.uBumpFrequency, props.bumpFrequency);
         ValueCell.updateIfChanged(values.uBumpAmplitude, props.bumpAmplitude);
     }

+ 3 - 1
src/mol-geo/geometry/mesh/mesh.ts

@@ -625,7 +625,7 @@ export namespace Mesh {
         flatShaded: PD.Boolean(false, BaseGeometry.ShadingCategory),
         ignoreLight: PD.Boolean(false, BaseGeometry.ShadingCategory),
         xrayShaded: PD.Boolean(false, BaseGeometry.ShadingCategory),
-        allowTransparentBackfaces: PD.Boolean(false, BaseGeometry.ShadingCategory),
+        transparentBackfaces: PD.Select('off', PD.arrayToOptions(['off', 'on', 'opaque']), BaseGeometry.ShadingCategory),
         bumpFrequency: PD.Numeric(0, { min: 0, max: 10, step: 0.1 }, BaseGeometry.ShadingCategory),
         bumpAmplitude: PD.Numeric(1, { min: 0, max: 5, step: 0.1 }, BaseGeometry.ShadingCategory),
     };
@@ -701,6 +701,7 @@ export namespace Mesh {
             dFlipSided: ValueCell.create(props.flipSided),
             dIgnoreLight: ValueCell.create(props.ignoreLight),
             dXrayShaded: ValueCell.create(props.xrayShaded),
+            dOpaqueBackfaces: ValueCell.create(props.transparentBackfaces === 'opaque'),
             uBumpFrequency: ValueCell.create(props.bumpFrequency),
             uBumpAmplitude: ValueCell.create(props.bumpAmplitude),
 
@@ -721,6 +722,7 @@ export namespace Mesh {
         ValueCell.updateIfChanged(values.dFlipSided, props.flipSided);
         ValueCell.updateIfChanged(values.dIgnoreLight, props.ignoreLight);
         ValueCell.updateIfChanged(values.dXrayShaded, props.xrayShaded);
+        ValueCell.updateIfChanged(values.dOpaqueBackfaces, props.transparentBackfaces === 'opaque');
         ValueCell.updateIfChanged(values.uBumpFrequency, props.bumpFrequency);
         ValueCell.updateIfChanged(values.uBumpAmplitude, props.bumpAmplitude);
     }

+ 3 - 1
src/mol-geo/geometry/spheres/spheres.ts

@@ -129,7 +129,7 @@ export namespace Spheres {
         doubleSided: PD.Boolean(false, BaseGeometry.CustomQualityParamInfo),
         ignoreLight: PD.Boolean(false, BaseGeometry.ShadingCategory),
         xrayShaded: PD.Boolean(false, BaseGeometry.ShadingCategory),
-        allowTransparentBackfaces: PD.Boolean(false, BaseGeometry.ShadingCategory),
+        transparentBackfaces: PD.Select('off', PD.arrayToOptions(['off', 'on', 'opaque']), BaseGeometry.ShadingCategory),
         bumpFrequency: PD.Numeric(0, { min: 0, max: 10, step: 0.1 }, BaseGeometry.ShadingCategory),
         bumpAmplitude: PD.Numeric(1, { min: 0, max: 5, step: 0.1 }, BaseGeometry.ShadingCategory),
     };
@@ -209,6 +209,7 @@ export namespace Spheres {
             uDoubleSided: ValueCell.create(props.doubleSided),
             dIgnoreLight: ValueCell.create(props.ignoreLight),
             dXrayShaded: ValueCell.create(props.xrayShaded),
+            dOpaqueBackfaces: ValueCell.create(props.transparentBackfaces === 'opaque'),
             uBumpFrequency: ValueCell.create(props.bumpFrequency),
             uBumpAmplitude: ValueCell.create(props.bumpAmplitude),
         };
@@ -226,6 +227,7 @@ export namespace Spheres {
         ValueCell.updateIfChanged(values.uDoubleSided, props.doubleSided);
         ValueCell.updateIfChanged(values.dIgnoreLight, props.ignoreLight);
         ValueCell.updateIfChanged(values.dXrayShaded, props.xrayShaded);
+        ValueCell.updateIfChanged(values.dOpaqueBackfaces, props.transparentBackfaces === 'opaque');
         ValueCell.updateIfChanged(values.uBumpFrequency, props.bumpFrequency);
         ValueCell.updateIfChanged(values.uBumpAmplitude, props.bumpAmplitude);
     }

+ 3 - 1
src/mol-geo/geometry/texture-mesh/texture-mesh.ts

@@ -114,7 +114,7 @@ export namespace TextureMesh {
         flatShaded: PD.Boolean(false, BaseGeometry.ShadingCategory),
         ignoreLight: PD.Boolean(false, BaseGeometry.ShadingCategory),
         xrayShaded: PD.Boolean(false, BaseGeometry.ShadingCategory),
-        allowTransparentBackfaces: PD.Boolean(false, BaseGeometry.ShadingCategory),
+        transparentBackfaces: PD.Select('off', PD.arrayToOptions(['off', 'on', 'opaque']), BaseGeometry.ShadingCategory),
         bumpFrequency: PD.Numeric(0, { min: 0, max: 10, step: 0.1 }, BaseGeometry.ShadingCategory),
         bumpAmplitude: PD.Numeric(1, { min: 0, max: 5, step: 0.1 }, BaseGeometry.ShadingCategory),
     };
@@ -174,6 +174,7 @@ export namespace TextureMesh {
             dFlipSided: ValueCell.create(props.flipSided),
             dIgnoreLight: ValueCell.create(props.ignoreLight),
             dXrayShaded: ValueCell.create(props.xrayShaded),
+            dOpaqueBackfaces: ValueCell.create(props.transparentBackfaces === 'opaque'),
             uBumpFrequency: ValueCell.create(props.bumpFrequency),
             uBumpAmplitude: ValueCell.create(props.bumpAmplitude),
 
@@ -194,6 +195,7 @@ export namespace TextureMesh {
         ValueCell.updateIfChanged(values.dFlipSided, props.flipSided);
         ValueCell.updateIfChanged(values.dIgnoreLight, props.ignoreLight);
         ValueCell.updateIfChanged(values.dXrayShaded, props.xrayShaded);
+        ValueCell.updateIfChanged(values.dOpaqueBackfaces, props.transparentBackfaces === 'opaque');
         ValueCell.updateIfChanged(values.uBumpFrequency, props.bumpFrequency);
         ValueCell.updateIfChanged(values.uBumpAmplitude, props.bumpAmplitude);
     }

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

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -26,6 +26,7 @@ export const CylindersSchema = {
     uDoubleSided: UniformSpec('b'),
     dIgnoreLight: DefineSpec('boolean'),
     dXrayShaded: DefineSpec('boolean'),
+    dOpaqueBackfaces: DefineSpec('boolean'),
     uBumpFrequency: UniformSpec('f'),
     uBumpAmplitude: UniformSpec('f'),
 };

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

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -22,6 +22,7 @@ export const MeshSchema = {
     dFlipSided: DefineSpec('boolean'),
     dIgnoreLight: DefineSpec('boolean'),
     dXrayShaded: DefineSpec('boolean'),
+    dOpaqueBackfaces: DefineSpec('boolean'),
     uBumpFrequency: UniformSpec('f'),
     uBumpAmplitude: UniformSpec('f'),
     meta: ValueSpec('unknown')

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

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -23,6 +23,7 @@ export const SpheresSchema = {
     uDoubleSided: UniformSpec('b'),
     dIgnoreLight: DefineSpec('boolean'),
     dXrayShaded: DefineSpec('boolean'),
+    dOpaqueBackfaces: DefineSpec('boolean'),
     uBumpFrequency: UniformSpec('f'),
     uBumpAmplitude: UniformSpec('f'),
 };

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

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -23,6 +23,7 @@ export const TextureMeshSchema = {
     dFlipSided: DefineSpec('boolean'),
     dIgnoreLight: DefineSpec('boolean'),
     dXrayShaded: DefineSpec('boolean'),
+    dOpaqueBackfaces: DefineSpec('boolean'),
     uBumpFrequency: UniformSpec('f'),
     uBumpAmplitude: UniformSpec('f'),
     meta: ValueSpec('unknown')

+ 50 - 21
src/mol-gl/renderer.ts

@@ -135,6 +135,12 @@ function getLight(props: RendererProps['light'], light?: Light): Light {
 }
 
 namespace Renderer {
+    const enum Flag {
+        None = 0,
+        BlendedFront = 1,
+        BlendedBack = 2
+    }
+
     export function create(ctx: WebGLContext, props: Partial<RendererProps> = {}): Renderer {
         const { gl, state, stats } = ctx;
         const p = PD.merge(RendererParams, PD.getDefaultValues(RendererParams), props);
@@ -220,7 +226,7 @@ namespace Renderer {
 
         let globalUniformsNeedUpdate = true;
 
-        const renderObject = (r: GraphicsRenderable, variant: GraphicsRenderVariant) => {
+        const renderObject = (r: GraphicsRenderable, variant: GraphicsRenderVariant, flag: Flag) => {
             if (r.state.disposed || !r.state.visible || (!r.state.pickable && variant === 'pick')) {
                 return;
             }
@@ -259,6 +265,24 @@ namespace Renderer {
                     state.disable(gl.DEPTH_TEST);
                     state.depthMask(false);
                 }
+            } else if (flag === Flag.BlendedFront) {
+                state.enable(gl.CULL_FACE);
+                if (r.values.dFlipSided?.ref.value) {
+                    state.frontFace(gl.CW);
+                    state.cullFace(gl.FRONT);
+                } else {
+                    state.frontFace(gl.CCW);
+                    state.cullFace(gl.BACK);
+                }
+            } else if (flag === Flag.BlendedBack) {
+                state.enable(gl.CULL_FACE);
+                if (r.values.dFlipSided?.ref.value) {
+                    state.frontFace(gl.CW);
+                    state.cullFace(gl.BACK);
+                } else {
+                    state.frontFace(gl.CCW);
+                    state.cullFace(gl.FRONT);
+                }
             } else {
                 if (r.values.uDoubleSided) {
                     if (r.values.uDoubleSided.ref.value || r.values.hasReflection.ref.value) {
@@ -271,14 +295,9 @@ namespace Renderer {
                     state.disable(gl.CULL_FACE);
                 }
 
-                if (r.values.dFlipSided) {
-                    if (r.values.dFlipSided.ref.value) {
-                        state.frontFace(gl.CW);
-                        state.cullFace(gl.FRONT);
-                    } else {
-                        state.frontFace(gl.CCW);
-                        state.cullFace(gl.BACK);
-                    }
+                if (r.values.dFlipSided?.ref.value) {
+                    state.frontFace(gl.CW);
+                    state.cullFace(gl.FRONT);
                 } else {
                     // webgl default
                     state.frontFace(gl.CCW);
@@ -342,7 +361,7 @@ namespace Renderer {
             const { renderables } = group;
             for (let i = 0, il = renderables.length; i < il; ++i) {
                 if (!renderables[i].state.colorOnly) {
-                    renderObject(renderables[i], variant);
+                    renderObject(renderables[i], variant, Flag.None);
                 }
             }
         };
@@ -356,7 +375,7 @@ namespace Renderer {
 
             const { renderables } = group;
             for (let i = 0, il = renderables.length; i < il; ++i) {
-                renderObject(renderables[i], 'depth');
+                renderObject(renderables[i], 'depth', Flag.None);
             }
         };
 
@@ -373,7 +392,7 @@ namespace Renderer {
                 const r = renderables[i];
 
                 if (r.values.markerAverage.ref.value !== 1) {
-                    renderObject(renderables[i], 'marking');
+                    renderObject(renderables[i], 'marking', Flag.None);
                 }
             }
         };
@@ -391,7 +410,7 @@ namespace Renderer {
                 const r = renderables[i];
 
                 if (r.values.markerAverage.ref.value > 0) {
-                    renderObject(renderables[i], 'marking');
+                    renderObject(renderables[i], 'marking', Flag.None);
                 }
             }
         };
@@ -412,7 +431,9 @@ namespace Renderer {
             for (let i = 0, il = renderables.length; i < il; ++i) {
                 const r = renderables[i];
                 if (r.state.opaque) {
-                    renderObject(r, 'colorBlended');
+                    renderObject(r, 'colorBlended', Flag.None);
+                } else if (r.values.uDoubleSided?.ref.value && r.values.dOpaqueBackfaces?.ref.value) {
+                    renderObject(r, 'colorBlended', Flag.BlendedBack);
                 }
             }
         };
@@ -435,7 +456,7 @@ namespace Renderer {
             for (let i = 0, il = renderables.length; i < il; ++i) {
                 const r = renderables[i];
                 if (!r.state.opaque && r.state.writeDepth) {
-                    renderObject(r, 'colorBlended');
+                    renderObject(r, 'colorBlended', Flag.None);
                 }
             }
 
@@ -443,7 +464,15 @@ namespace Renderer {
             for (let i = 0, il = renderables.length; i < il; ++i) {
                 const r = renderables[i];
                 if (!r.state.opaque && !r.state.writeDepth) {
-                    renderObject(r, 'colorBlended');
+                    if (r.values.uDoubleSided?.ref.value) {
+                        // render frontfaces and backfaces separately to avoid artefacts
+                        if (!r.values.dOpaqueBackfaces?.ref.value) {
+                            renderObject(r, 'colorBlended', Flag.BlendedBack);
+                        }
+                        renderObject(r, 'colorBlended', Flag.BlendedFront);
+                    } else {
+                        renderObject(r, 'colorBlended', Flag.None);
+                    }
                 }
             }
         };
@@ -462,7 +491,7 @@ namespace Renderer {
                 // uAlpha is updated in "render" so we need to recompute it here
                 const alpha = clamp(r.values.alpha.ref.value * r.state.alphaFactor, 0, 1);
                 if (alpha === 1 && r.values.transparencyAverage.ref.value !== 1 && !r.values.dXrayShaded?.ref.value) {
-                    renderObject(r, 'colorBlended');
+                    renderObject(r, 'colorBlended', Flag.None);
                 }
             }
         };
@@ -481,7 +510,7 @@ namespace Renderer {
                 // uAlpha is updated in "render" so we need to recompute it here
                 const alpha = clamp(r.values.alpha.ref.value * r.state.alphaFactor, 0, 1);
                 if (alpha < 1 || r.values.transparencyAverage.ref.value > 0 || r.values.dXrayShaded?.ref.value) {
-                    renderObject(r, 'colorBlended');
+                    renderObject(r, 'colorBlended', Flag.None);
                 }
             }
         };
@@ -500,8 +529,8 @@ namespace Renderer {
                 // TODO: simplify, handle in renderable.state???
                 // uAlpha is updated in "render" so we need to recompute it here
                 const alpha = clamp(r.values.alpha.ref.value * r.state.alphaFactor, 0, 1);
-                if (alpha === 1 && r.values.transparencyAverage.ref.value !== 1 && r.values.dGeometryType.ref.value !== 'directVolume' && r.values.dPointStyle?.ref.value !== 'fuzzy' && !r.values.dXrayShaded?.ref.value) {
-                    renderObject(r, 'colorWboit');
+                if ((alpha === 1 && r.values.transparencyAverage.ref.value !== 1 && r.values.dGeometryType.ref.value !== 'directVolume' && r.values.dPointStyle?.ref.value !== 'fuzzy' && !r.values.dXrayShaded?.ref.value) || r.values.dOpaqueBackfaces?.ref.value) {
+                    renderObject(r, 'colorWboit', Flag.None);
                 }
             }
         };
@@ -517,7 +546,7 @@ namespace Renderer {
                 // 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, 'colorWboit');
+                    renderObject(r, 'colorWboit', Flag.None);
                 }
             }
         };

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

@@ -5,5 +5,9 @@ if (interior) {
     } else {
         gl_FragColor.rgb *= 1.0 - uInteriorDarkening;
     }
+
+    #ifdef dOpaqueBackfaces
+        gl_FragColor.a = 1.0;
+    #endif
 }
 `;

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

@@ -2,6 +2,12 @@ export const check_picking_alpha = `
 float viewZ = depthToViewZ(uIsOrtho, fragmentDepth, uNear, uFar);
 float fogFactor = smoothstep(uFogNear, uFogFar, abs(viewZ));
 float alpha = (1.0 - fogFactor) * uAlpha;
-if (uAlpha < uPickingAlphaThreshold || alpha < 0.1)
-    discard; // ignore so the element below can be picked
+// if not opaque enough ignore so the element below can be picked
+if (uAlpha < uPickingAlphaThreshold || alpha < 0.1) {
+    #ifdef dOpaqueBackfaces
+        if (!interior) discard;
+    #else
+        discard;
+    #endif
+}
 `;

+ 2 - 2
src/mol-repr/util.ts

@@ -57,7 +57,7 @@ export interface QualityProps {
     doubleSided: boolean
     xrayShaded: boolean
     alpha: number
-    allowTransparentBackfaces: boolean
+    transparentBackfaces: 'off' | 'on' | 'opaque'
 }
 
 export const DefaultQualityThresholds = {
@@ -193,7 +193,7 @@ export function getQualityProps(props: Partial<QualityProps>, data?: any) {
     resolution = Math.max(resolution, volume / 500_000_000);
     resolution = Math.min(resolution, 20);
 
-    if (!props.allowTransparentBackfaces && ((props.alpha !== undefined && props.alpha < 1) || !!props.xrayShaded)) {
+    if (props.transparentBackfaces === 'off' && ((props.alpha !== undefined && props.alpha < 1) || !!props.xrayShaded)) {
         doubleSided = false;
     }