Browse Source

adjust outlines based on view distance

Alexander Rose 2 years ago
parent
commit
3baa03ccdc

+ 1 - 0
CHANGELOG.md

@@ -13,6 +13,7 @@ Note that since we don't clearly distinguish between a public and private interf
 - Improve boundary calculation performance
 - Add option to create & include images in state snapshots
 - Fix SSAO artefacts with high bias values
+- Improve outlines, visually more stable at different view distances
 
 ## [v3.29.0] - 2023-01-15
 

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

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  * @author Áron Samuel Kovács <aron.kovacs@mail.muni.cz>
@@ -283,6 +283,7 @@ const PostprocessingSchema = {
     uTransparentBackground: UniformSpec('b'),
 
     uMaxPossibleViewZDiff: UniformSpec('f'),
+    uInvProjection: UniformSpec('m4'),
 
     dOcclusionEnable: DefineSpec('boolean'),
     uOcclusionOffset: UniformSpec('v2'),
@@ -319,6 +320,7 @@ function getPostprocessingRenderable(ctx: WebGLContext, colorTexture: Texture, d
         uTransparentBackground: ValueCell.create(false),
 
         uMaxPossibleViewZDiff: ValueCell.create(0.5),
+        uInvProjection: ValueCell.create(Mat4.identity()),
 
         dOcclusionEnable: ValueCell.create(true),
         uOcclusionOffset: ValueCell.create(Vec2.create(0, 0)),
@@ -630,7 +632,7 @@ export class PostprocessingPass {
             const transparentOutline = includeTransparent ?? true;
             // orthographic needs lower threshold
             if (camera.state.mode === 'orthographic') threshold /= 5;
-            const factor = Math.pow(1000, threshold) / 1000;
+            const factor = Math.pow(1000, threshold / 10) / 1000;
             // use radiusMax for stable outlines when zooming
             const maxPossibleViewZDiff = factor * camera.state.radiusMax;
             const outlineScale = props.outline.params.scale - 1;
@@ -644,6 +646,8 @@ export class PostprocessingPass {
             ValueCell.update(this.renderable.values.uOutlineColor, Color.toVec3Normalized(this.renderable.values.uOutlineColor.ref.value, props.outline.params.color));
 
             ValueCell.updateIfChanged(this.renderable.values.uMaxPossibleViewZDiff, maxPossibleViewZDiff);
+            ValueCell.update(this.renderable.values.uInvProjection, invProjection);
+
             if (this.renderable.values.dOutlineScale.ref.value !== outlineScale) { needsUpdateMain = true; }
             ValueCell.updateIfChanged(this.renderable.values.dOutlineScale, outlineScale);
             if (this.renderable.values.dTransparentOutline.ref.value !== transparentOutline) { needsUpdateMain = true; }

+ 11 - 2
src/mol-gl/shader/postprocessing.frag.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  * @author Áron Samuel Kovács <aron.kovacs@mail.muni.cz>
@@ -29,7 +29,9 @@ uniform bool uTransparentBackground;
 uniform vec2 uOcclusionOffset;
 
 uniform float uMaxPossibleViewZDiff;
+uniform mat4 uInvProjection;
 
+const float outlineDistanceFactor = 5.0;
 const vec3 occlusionColor = vec3(0.0);
 
 #include common
@@ -62,12 +64,19 @@ bool isBackground(const in float depth) {
     return depth == 1.0;
 }
 
+float getPixelSize(const in vec2 coords, const in float depth) {
+    vec3 viewPos0 = screenSpaceToViewSpace(vec3(coords, depth), uInvProjection);
+    vec3 viewPos1 = screenSpaceToViewSpace(vec3(coords + vec2(1.0, 0.0) / uTexSize, depth), uInvProjection);
+    return distance(viewPos0, viewPos1);
+}
+
 float getOutline(const in vec2 coords, const in float opaqueDepth, out float closestTexel) {
     float backgroundViewZ = uFar + 3.0 * uMaxPossibleViewZDiff;
     vec2 invTexSize = 1.0 / uTexSize;
 
     float selfDepth = min(opaqueDepth, getDepthTransparent(coords));
     float selfViewZ = isBackground(selfDepth) ? backgroundViewZ : getViewZ(selfDepth);
+    float pixelSize = getPixelSize(coords, selfDepth);
 
     float outline = 1.0;
     closestTexel = 1.0;
@@ -84,7 +93,7 @@ float getOutline(const in vec2 coords, const in float opaqueDepth, out float clo
             float sampleOutlineDepth = unpackRGToUnitInterval(sampleOutlineCombined.gb);
             float sampleOutlineViewZ = isBackground(sampleOutlineDepth) ? backgroundViewZ : getViewZ(sampleOutlineDepth);
 
-            if (sampleOutline == 0.0 && sampleOutlineDepth < closestTexel && abs(selfViewZ - sampleOutlineViewZ) > uMaxPossibleViewZDiff) {
+            if (sampleOutline == 0.0 && sampleOutlineDepth < closestTexel && abs(selfViewZ - sampleOutlineViewZ) > uMaxPossibleViewZDiff + (pixelSize * outlineDistanceFactor)) {
                 outline = 0.0;
                 closestTexel = sampleOutlineDepth;
             }