Forráskód Böngészése

Merge pull request #290 from molstar/smoothing2

Smooth Overpaint and Transparency
Alexander Rose 3 éve
szülő
commit
6655672d11
28 módosított fájl, 843 hozzáadás és 292 törlés
  1. 1 0
      CHANGELOG.md
  2. 32 18
      src/extensions/geo-export/glb-exporter.ts
  3. 101 7
      src/extensions/geo-export/mesh-exporter.ts
  4. 31 15
      src/extensions/geo-export/obj-exporter.ts
  5. 31 14
      src/extensions/geo-export/usdz-exporter.ts
  6. 36 1
      src/mol-geo/geometry/base.ts
  7. 166 37
      src/mol-geo/geometry/mesh/color-smoothing.ts
  8. 18 2
      src/mol-geo/geometry/overpaint-data.ts
  9. 185 27
      src/mol-geo/geometry/texture-mesh/color-smoothing.ts
  10. 18 2
      src/mol-geo/geometry/transparency-data.ts
  11. 10 0
      src/mol-gl/renderable/schema.ts
  12. 28 6
      src/mol-gl/shader/chunks/assign-color-varying.glsl.ts
  13. 3 0
      src/mol-gl/shader/chunks/assign-material-color.glsl.ts
  14. 22 6
      src/mol-gl/shader/chunks/color-vert-params.glsl.ts
  15. 4 2
      src/mol-gl/shader/compute/color-smoothing/accumulate.frag.ts
  16. 3 3
      src/mol-gl/shader/compute/color-smoothing/accumulate.vert.ts
  17. 3 1
      src/mol-gl/shader/compute/color-smoothing/normalize.frag.ts
  18. 16 3
      src/mol-gl/shader/direct-volume.frag.ts
  19. 1 4
      src/mol-gl/shader/mesh.vert.ts
  20. 41 15
      src/mol-plugin-state/transforms/representation.ts
  21. 2 2
      src/mol-repr/structure/complex-representation.ts
  22. 6 4
      src/mol-repr/structure/complex-visual.ts
  23. 2 2
      src/mol-repr/structure/units-representation.ts
  24. 6 4
      src/mol-repr/structure/units-visual.ts
  25. 7 5
      src/mol-repr/structure/visual/gaussian-surface-mesh.ts
  26. 3 2
      src/mol-repr/structure/visual/molecular-surface-mesh.ts
  27. 0 104
      src/mol-repr/structure/visual/util/color.ts
  28. 67 6
      src/mol-repr/visual.ts

+ 1 - 0
CHANGELOG.md

@@ -11,6 +11,7 @@ Note that since we don't clearly distinguish between a public and private interf
     - StructureSelectionQuery helpers 'helix' & 'beta' were not ensuring property availability
 - Re-enable VAO with better workaround (bind null elements buffer before deleting)
 - Add ``Representation.geometryVersion`` (increments whenever the geometry of any of its visuals changes)
+- Add support for grid-based smoothing of Overpaint and Transparency visual state for surfaces
 
 ## [v2.3.9] - 2021-11-20
 

+ 32 - 18
src/extensions/geo-export/glb-exporter.ts

@@ -2,9 +2,9 @@
  * Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { BaseValues } from '../../mol-gl/renderable/schema';
 import { Style } from '../../mol-gl/renderer';
 import { asciiWrite } from '../../mol-io/common/ascii';
 import { IsNativeEndianLittle, flipByteOrder } from '../../mol-io/common/binary';
@@ -15,7 +15,7 @@ import { RuntimeContext } from '../../mol-task';
 import { Color } from '../../mol-util/color/color';
 import { fillSerial } from '../../mol-util/array';
 import { NumberArray } from '../../mol-util/type-helpers';
-import { MeshExporter, AddMeshInput } from './mesh-exporter';
+import { MeshExporter, AddMeshInput, MeshGeoData } from './mesh-exporter';
 
 // avoiding namespace lookup improved performance in Chrome (Aug 2020)
 const v3fromArray = Vec3.fromArray;
@@ -30,6 +30,12 @@ const FLOAT = 5126;
 const ARRAY_BUFFER = 34962;
 const ELEMENT_ARRAY_BUFFER = 34963;
 
+const GLTF_MAGIC_BYTE = 0x46546C67;
+const JSON_CHUNK_TYPE = 0x4E4F534A;
+const BIN_CHUNK_TYPE = 0x004E4942;
+const JSON_PAD_CHAR = 0x20;
+const BIN_PAD_CHAR = 0x00;
+
 export type GlbData = {
     glb: Uint8Array
 }
@@ -126,23 +132,17 @@ export class GlbExporter extends MeshExporter<GlbData> {
         };
     }
 
-    private addColorBuffer(values: BaseValues, groups: Float32Array | Uint8Array, vertexCount: number, instanceIndex: number, isGeoTexture: boolean, interpolatedColors: Uint8Array | undefined) {
-        const groupCount = values.uGroupCount.ref.value;
+    private addColorBuffer(geoData: MeshGeoData, interpolatedColors: Uint8Array | undefined, interpolatedOverpaint: Uint8Array | undefined, interpolatedTransparency: Uint8Array | undefined) {
+        const { values, vertexCount } = geoData;
         const uAlpha = values.uAlpha.ref.value;
-        const dTransparency = values.dTransparency.ref.value;
-        const tTransparency = values.tTransparency.ref.value;
 
         const colorArray = new Uint8Array(vertexCount * 4);
 
         for (let i = 0; i < vertexCount; ++i) {
-            let color = GlbExporter.getColor(values, groups, vertexCount, instanceIndex, isGeoTexture, interpolatedColors, i);
+            let color = GlbExporter.getColor(i, geoData, interpolatedColors, interpolatedOverpaint);
 
-            let alpha = uAlpha;
-            if (dTransparency) {
-                const group = isGeoTexture ? GlbExporter.getGroup(groups, i) : groups[i];
-                const transparency = tTransparency.array[instanceIndex * groupCount + group] / 255;
-                alpha *= 1 - transparency;
-            }
+            const transparency = GlbExporter.getTransparency(i, geoData, interpolatedTransparency);
+            const alpha = uAlpha * (1 - transparency);
 
             color = Color.sRGBToLinear(color);
             Color.toArray(color, colorArray, i * 4);
@@ -163,6 +163,8 @@ export class GlbExporter extends MeshExporter<GlbData> {
         const t = Mat4();
 
         const colorType = values.dColorType.ref.value;
+        const overpaintType = values.dOverpaintType.ref.value;
+        const transparencyType = values.dTransparencyType.ref.value;
         const dTransparency = values.dTransparency.ref.value;
         const aTransform = values.aTransform.ref.value;
         const instanceCount = values.uInstanceCount.ref.value;
@@ -170,7 +172,19 @@ export class GlbExporter extends MeshExporter<GlbData> {
         let interpolatedColors: Uint8Array | undefined;
         if (colorType === 'volume' || colorType === 'volumeInstance') {
             const stride = isGeoTexture ? 4 : 3;
-            interpolatedColors = GlbExporter.getInterpolatedColors(mesh!.vertices, mesh!.vertexCount, values, stride, colorType, webgl!);
+            interpolatedColors = GlbExporter.getInterpolatedColors(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType });
+        }
+
+        let interpolatedOverpaint: Uint8Array | undefined;
+        if (overpaintType === 'volumeInstance') {
+            const stride = isGeoTexture ? 4 : 3;
+            interpolatedOverpaint = GlbExporter.getInterpolatedOverpaint(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType: overpaintType });
+        }
+
+        let interpolatedTransparency: Uint8Array | undefined;
+        if (transparencyType === 'volumeInstance') {
+            const stride = isGeoTexture ? 4 : 3;
+            interpolatedTransparency = GlbExporter.getInterpolatedTransparency(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType: transparencyType });
         }
 
         // instancing
@@ -201,7 +215,7 @@ export class GlbExporter extends MeshExporter<GlbData> {
 
                 // create a color buffer if needed
                 if (instanceIndex === 0 || !sameColorBuffer) {
-                    colorAccessorIndex = this.addColorBuffer(values, groups, vertexCount, instanceIndex, isGeoTexture, interpolatedColors);
+                    colorAccessorIndex = this.addColorBuffer({ values, groups, vertexCount, instanceIndex, isGeoTexture }, interpolatedColors, interpolatedOverpaint, interpolatedTransparency);
                 }
 
                 // glTF mesh
@@ -279,13 +293,13 @@ export class GlbExporter extends MeshExporter<GlbData> {
         const jsonBuffer = new Uint8Array(jsonString.length);
         asciiWrite(jsonBuffer, jsonString);
 
-        const [jsonChunk, jsonChunkLength] = createChunk(0x4E4F534A, [jsonBuffer.buffer], jsonBuffer.length, 0x20);
-        const [binaryChunk, binaryChunkLength] = createChunk(0x004E4942, this.binaryBuffer, binaryBufferLength, 0x00);
+        const [jsonChunk, jsonChunkLength] = createChunk(JSON_CHUNK_TYPE, [jsonBuffer.buffer], jsonBuffer.length, JSON_PAD_CHAR);
+        const [binaryChunk, binaryChunkLength] = createChunk(BIN_CHUNK_TYPE, this.binaryBuffer, binaryBufferLength, BIN_PAD_CHAR);
 
         const glbBufferLength = 12 + jsonChunkLength + binaryChunkLength;
         const header = new ArrayBuffer(12);
         const headerDataView = new DataView(header);
-        headerDataView.setUint32(0, 0x46546C67, true); // magic number "glTF"
+        headerDataView.setUint32(0, GLTF_MAGIC_BYTE, true); // magic number "glTF"
         headerDataView.setUint32(4, 2, true); // version
         headerDataView.setUint32(8, glbBufferLength, true); // length
         const glbBuffer = [header, ...jsonChunk, ...binaryChunk];

+ 101 - 7
src/extensions/geo-export/mesh-exporter.ts

@@ -2,6 +2,7 @@
  * Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
 import { sort, arraySwap } from '../../mol-data/util';
@@ -26,7 +27,7 @@ import { RuntimeContext } from '../../mol-task';
 import { Color } from '../../mol-util/color/color';
 import { decodeFloatRGB } from '../../mol-util/float-packing';
 import { RenderObjectExporter, RenderObjectExportData } from './render-object-exporter';
-import { readTexture } from '../../mol-gl/compute/util';
+import { readAlphaTexture, readTexture } from '../../mol-gl/compute/util';
 
 const GeoExportName = 'geo-export';
 
@@ -49,6 +50,14 @@ export interface AddMeshInput {
     ctx: RuntimeContext
 }
 
+export type MeshGeoData = {
+    values: BaseValues,
+    groups: Float32Array | Uint8Array,
+    vertexCount: number,
+    instanceIndex: number,
+    isGeoTexture: boolean
+}
+
 export abstract class MeshExporter<D extends RenderObjectExportData> implements RenderObjectExporter<D> {
     abstract readonly fileExtension: string;
 
@@ -91,7 +100,8 @@ export abstract class MeshExporter<D extends RenderObjectExportData> implements
         return decodeFloatRGB(r, g, b);
     }
 
-    protected static getInterpolatedColors(vertices: Float32Array, vertexCount: number, values: BaseValues, stride: number, colorType: 'volume' | 'volumeInstance', webgl: WebGLContext) {
+    protected static getInterpolatedColors(webgl: WebGLContext, input: { vertices: Float32Array, vertexCount: number, values: BaseValues, stride: 3 | 4, colorType: 'volume' | 'volumeInstance' }) {
+        const { values, vertexCount, vertices, colorType, stride } = input;
         const colorGridTransform = values.uColorGridTransform.ref.value;
         const colorGridDim = values.uColorGridDim.ref.value;
         const colorTexDim = values.uColorTexDim.ref.value;
@@ -99,7 +109,34 @@ export abstract class MeshExporter<D extends RenderObjectExportData> implements
         const instanceCount = values.uInstanceCount.ref.value;
 
         const colorGrid = readTexture(webgl, values.tColorGrid.ref.value).array;
-        const interpolated = getTrilinearlyInterpolated({ vertexCount, instanceCount, transformBuffer: aTransform, positionBuffer: vertices, colorType, grid: colorGrid, gridDim: colorGridDim, gridTexDim: colorTexDim, gridTransform: colorGridTransform, vertexStride: stride, colorStride: 4 });
+        const interpolated = getTrilinearlyInterpolated({ vertexCount, instanceCount, transformBuffer: aTransform, positionBuffer: vertices, colorType, grid: colorGrid, gridDim: colorGridDim, gridTexDim: colorTexDim, gridTransform: colorGridTransform, vertexStride: stride, colorStride: 4, outputStride: 3 });
+        return interpolated.array;
+    }
+
+    protected static getInterpolatedOverpaint(webgl: WebGLContext, input: { vertices: Float32Array, vertexCount: number, values: BaseValues, stride: 3 | 4, colorType: 'volumeInstance' }) {
+        const { values, vertexCount, vertices, colorType, stride } = input;
+        const overpaintGridTransform = values.uOverpaintGridTransform.ref.value;
+        const overpaintGridDim = values.uOverpaintGridDim.ref.value;
+        const overpaintTexDim = values.uOverpaintTexDim.ref.value;
+        const aTransform = values.aTransform.ref.value;
+        const instanceCount = values.uInstanceCount.ref.value;
+
+        const overpaintGrid = readTexture(webgl, values.tOverpaintGrid.ref.value).array;
+        const interpolated = getTrilinearlyInterpolated({ vertexCount, instanceCount, transformBuffer: aTransform, positionBuffer: vertices, colorType, grid: overpaintGrid, gridDim: overpaintGridDim, gridTexDim: overpaintTexDim, gridTransform: overpaintGridTransform, vertexStride: stride, colorStride: 4, outputStride: 4 });
+        return interpolated.array;
+    }
+
+    protected static getInterpolatedTransparency(webgl: WebGLContext, input: { vertices: Float32Array, vertexCount: number, values: BaseValues, stride: 3 | 4, colorType: 'volumeInstance' }) {
+        const { values, vertexCount, vertices, colorType, stride } = input;
+        const transparencyGridTransform = values.uTransparencyGridTransform.ref.value;
+        const transparencyGridDim = values.uTransparencyGridDim.ref.value;
+        const transparencyTexDim = values.uTransparencyTexDim.ref.value;
+        const aTransform = values.aTransform.ref.value;
+        const instanceCount = values.uInstanceCount.ref.value;
+
+        const transparencyGrid = readAlphaTexture(webgl, values.tTransparencyGrid.ref.value).array;
+        const interpolated = getTrilinearlyInterpolated({ vertexCount, instanceCount, transformBuffer: aTransform, positionBuffer: vertices, colorType, grid: transparencyGrid, gridDim: transparencyGridDim, gridTexDim: transparencyTexDim, gridTransform: transparencyGridTransform, vertexStride: stride, colorStride: 4, outputStride: 1, itemOffset: 3 });
+
         return interpolated.array;
     }
 
@@ -184,11 +221,13 @@ export abstract class MeshExporter<D extends RenderObjectExportData> implements
         }
     }
 
-    protected static getColor(values: BaseValues, groups: Float32Array | Uint8Array, vertexCount: number, instanceIndex: number, isGeoTexture: boolean, interpolatedColors: Uint8Array | undefined, vertexIndex: number): Color {
+    protected static getColor(vertexIndex: number, geoData: MeshGeoData, interpolatedColors?: Uint8Array, interpolatedOverpaint?: Uint8Array): Color {
+        const { values, instanceIndex, isGeoTexture, groups, vertexCount } = geoData;
         const groupCount = values.uGroupCount.ref.value;
         const colorType = values.dColorType.ref.value;
         const uColor = values.uColor.ref.value;
         const tColor = values.tColor.ref.value.array;
+        const overpaintType = values.dOverpaintType.ref.value;
         const dOverpaint = values.dOverpaint.ref.value;
         const tOverpaint = values.tOverpaint.ref.value.array;
 
@@ -226,15 +265,70 @@ export abstract class MeshExporter<D extends RenderObjectExportData> implements
         }
 
         if (dOverpaint) {
-            const group = isGeoTexture ? MeshExporter.getGroup(groups, vertexIndex) : groups[vertexIndex];
-            const overpaintColor = Color.fromArray(tOverpaint, (instanceIndex * groupCount + group) * 4);
-            const overpaintAlpha = tOverpaint[(instanceIndex * groupCount + group) * 4 + 3] / 255;
+            let overpaintColor: Color;
+            let overpaintAlpha: number;
+            switch (overpaintType) {
+                case 'groupInstance': {
+                    const group = isGeoTexture ? MeshExporter.getGroup(groups, vertexIndex) : groups[vertexIndex];
+                    const idx = (instanceIndex * groupCount + group) * 4;
+                    overpaintColor = Color.fromArray(tOverpaint, idx);
+                    overpaintAlpha = tOverpaint[idx + 3] / 255;
+                    break;
+                }
+                case 'vertexInstance': {
+                    const idx = (instanceIndex * vertexCount + vertexIndex) * 4;
+                    overpaintColor = Color.fromArray(tOverpaint, idx);
+                    overpaintAlpha = tOverpaint[idx + 3] / 255;
+                    break;
+                }
+                case 'volumeInstance': {
+                    const idx = (instanceIndex * vertexCount + vertexIndex) * 4;
+                    overpaintColor = Color.fromArray(interpolatedOverpaint!, idx);
+                    overpaintAlpha = interpolatedOverpaint![idx + 3] / 255;
+                    break;
+                }
+                default: throw new Error('Unsupported overpaint type.');
+            }
+            // interpolate twice to avoid darkening due to empty overpaint
+            overpaintColor = Color.interpolate(color, overpaintColor, overpaintAlpha);
             color = Color.interpolate(color, overpaintColor, overpaintAlpha);
         }
 
         return color;
     }
 
+    protected static getTransparency(vertexIndex: number, geoData: MeshGeoData, interpolatedTransparency?: Uint8Array): number {
+        const { values, instanceIndex, isGeoTexture, groups, vertexCount } = geoData;
+        const groupCount = values.uGroupCount.ref.value;
+        const dTransparency = values.dTransparency.ref.value;
+        const tTransparency = values.tTransparency.ref.value.array;
+        const transparencyType = values.dTransparencyType.ref.value;
+
+        let transparency: number = 0;
+        if (dTransparency) {
+            switch (transparencyType) {
+                case 'groupInstance': {
+                    const group = isGeoTexture ? MeshExporter.getGroup(groups, vertexIndex) : groups[vertexIndex];
+                    const idx = (instanceIndex * groupCount + group);
+                    transparency = tTransparency[idx] / 255;
+                    break;
+                }
+                case 'vertexInstance': {
+                    const idx = (instanceIndex * vertexCount + vertexIndex);
+                    transparency = tTransparency[idx] / 255;
+                    break;
+                }
+                case 'volumeInstance': {
+                    const idx = (instanceIndex * vertexCount + vertexIndex);
+                    transparency = interpolatedTransparency![idx] / 255;
+                    break;
+                }
+                default: throw new Error('Unsupported transparency type.');
+            }
+        }
+        return transparency;
+    }
+
     protected abstract addMeshWithColors(input: AddMeshInput): void;
 
     private async addMesh(values: MeshValues, webgl: WebGLContext, ctx: RuntimeContext) {

+ 31 - 15
src/extensions/geo-export/obj-exporter.ts

@@ -2,6 +2,7 @@
  * Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
 import { asciiWrite } from '../../mol-io/common/ascii';
@@ -47,7 +48,7 @@ export class ObjExporter extends MeshExporter<ObjData> {
         StringBuilder.newline(this.obj);
         if (!this.materialSet.has(material)) {
             this.materialSet.add(material);
-            const [r, g, b] = Color.toRgbNormalized(color);
+            const [r, g, b] = Color.toRgbNormalized(color).map(v => Math.round(v * 1000) / 1000);
             const mtl = this.mtl;
             StringBuilder.writeSafe(mtl, `newmtl ${material}\n`);
             StringBuilder.writeSafe(mtl, 'illum 2\n'); // illumination model
@@ -77,18 +78,27 @@ export class ObjExporter extends MeshExporter<ObjData> {
         const tmpV = Vec3();
         const stride = isGeoTexture ? 4 : 3;
 
-        const groupCount = values.uGroupCount.ref.value;
         const colorType = values.dColorType.ref.value;
+        const overpaintType = values.dOverpaintType.ref.value;
+        const transparencyType = values.dTransparencyType.ref.value;
         const uAlpha = values.uAlpha.ref.value;
-        const dTransparency = values.dTransparency.ref.value;
-        const tTransparency = values.tTransparency.ref.value;
         const aTransform = values.aTransform.ref.value;
         const instanceCount = values.uInstanceCount.ref.value;
 
         let interpolatedColors: Uint8Array | undefined;
         if (colorType === 'volume' || colorType === 'volumeInstance') {
-            interpolatedColors = ObjExporter.getInterpolatedColors(mesh!.vertices, mesh!.vertexCount, values, stride, colorType, webgl!);
-            ObjExporter.quantizeColors(interpolatedColors, mesh!.vertexCount);
+            interpolatedColors = ObjExporter.getInterpolatedColors(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType });
+        }
+
+        let interpolatedOverpaint: Uint8Array | undefined;
+        if (overpaintType === 'volumeInstance') {
+            interpolatedOverpaint = ObjExporter.getInterpolatedOverpaint(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType: overpaintType });
+        }
+
+        let interpolatedTransparency: Uint8Array | undefined;
+        if (transparencyType === 'volumeInstance') {
+            const stride = isGeoTexture ? 4 : 3;
+            interpolatedTransparency = ObjExporter.getInterpolatedTransparency(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType: transparencyType });
         }
 
         await ctx.update({ isIndeterminate: false, current: 0, max: instanceCount });
@@ -126,17 +136,23 @@ export class ObjExporter extends MeshExporter<ObjData> {
                 StringBuilder.newline(obj);
             }
 
-            // face
+            const geoData = { values, groups, vertexCount, instanceIndex, isGeoTexture };
+
+            // color
+            const quantizedColors = new Uint8Array(drawCount * 3);
             for (let i = 0; i < drawCount; i += 3) {
                 const v = isGeoTexture ? i : indices![i];
-                const color = ObjExporter.getColor(values, groups, vertexCount, instanceIndex, isGeoTexture, interpolatedColors, v);
-
-                let alpha = uAlpha;
-                if (dTransparency) {
-                    const group = isGeoTexture ? ObjExporter.getGroup(groups, i) : groups[indices![i]];
-                    const transparency = tTransparency.array[instanceIndex * groupCount + group] / 255;
-                    alpha *= 1 - transparency;
-                }
+                const color = ObjExporter.getColor(v, geoData, interpolatedColors, interpolatedOverpaint);
+                Color.toArray(color, quantizedColors, i);
+            }
+            ObjExporter.quantizeColors(quantizedColors, mesh!.vertexCount);
+
+            // face
+            for (let i = 0; i < drawCount; i += 3) {
+                const color = Color.fromArray(quantizedColors, i);
+
+                const transparency = ObjExporter.getTransparency(i, geoData, interpolatedTransparency);
+                const alpha = Math.round(uAlpha * (1 - transparency) * 10) / 10; // quantized
 
                 this.updateMaterial(color, alpha);
 

+ 31 - 14
src/extensions/geo-export/usdz-exporter.ts

@@ -2,6 +2,7 @@
  * Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Sukolsak Sakshuwong <sukolsak@stanford.edu>
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
 import { Style } from '../../mol-gl/renderer';
@@ -42,7 +43,7 @@ export class UsdzExporter extends MeshExporter<UsdzData> {
         const materialKey = UsdzExporter.getMaterialKey(color, alpha);
         if (this.materialSet.has(materialKey)) return;
         this.materialSet.add(materialKey);
-        const [r, g, b] = Color.toRgbNormalized(color);
+        const [r, g, b] = Color.toRgbNormalized(color).map(v => Math.round(v * 1000) / 1000);
         this.materials.push(`
 def Material "material${materialKey}"
 {
@@ -68,18 +69,28 @@ def Material "material${materialKey}"
         const tmpV = Vec3();
         const stride = isGeoTexture ? 4 : 3;
 
-        const groupCount = values.uGroupCount.ref.value;
         const colorType = values.dColorType.ref.value;
+        const overpaintType = values.dOverpaintType.ref.value;
+        const transparencyType = values.dTransparencyType.ref.value;
         const uAlpha = values.uAlpha.ref.value;
-        const dTransparency = values.dTransparency.ref.value;
-        const tTransparency = values.tTransparency.ref.value;
         const aTransform = values.aTransform.ref.value;
         const instanceCount = values.uInstanceCount.ref.value;
 
         let interpolatedColors: Uint8Array | undefined;
         if (colorType === 'volume' || colorType === 'volumeInstance') {
-            interpolatedColors = UsdzExporter.getInterpolatedColors(mesh!.vertices, mesh!.vertexCount, values, stride, colorType, webgl!);
-            UsdzExporter.quantizeColors(interpolatedColors, mesh!.vertexCount);
+            interpolatedColors = UsdzExporter.getInterpolatedColors(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType });
+        }
+
+        let interpolatedOverpaint: Uint8Array | undefined;
+        if (overpaintType === 'volumeInstance') {
+            const stride = isGeoTexture ? 4 : 3;
+            interpolatedOverpaint = UsdzExporter.getInterpolatedOverpaint(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType: overpaintType });
+        }
+
+        let interpolatedTransparency: Uint8Array | undefined;
+        if (transparencyType === 'volumeInstance') {
+            const stride = isGeoTexture ? 4 : 3;
+            interpolatedTransparency = UsdzExporter.getInterpolatedTransparency(webgl!, { vertices: mesh!.vertices, vertexCount: mesh!.vertexCount, values, stride, colorType: transparencyType });
         }
 
         await ctx.update({ isIndeterminate: false, current: 0, max: instanceCount });
@@ -121,6 +132,8 @@ def Material "material${materialKey}"
                 StringBuilder.writeSafe(normalBuilder, ')');
             }
 
+            const geoData = { values, groups, vertexCount, instanceIndex, isGeoTexture };
+
             // face
             for (let i = 0; i < drawCount; ++i) {
                 const v = isGeoTexture ? i : indices![i];
@@ -129,17 +142,21 @@ def Material "material${materialKey}"
             }
 
             // color
-            const faceIndicesByMaterial = new Map<number, number[]>();
+            const quantizedColors = new Uint8Array(drawCount * 3);
             for (let i = 0; i < drawCount; i += 3) {
                 const v = isGeoTexture ? i : indices![i];
-                const color = UsdzExporter.getColor(values, groups, vertexCount, instanceIndex, isGeoTexture, interpolatedColors, v);
+                const color = UsdzExporter.getColor(v, geoData, interpolatedColors, interpolatedOverpaint);
+                Color.toArray(color, quantizedColors, i);
+            }
+            UsdzExporter.quantizeColors(quantizedColors, mesh!.vertexCount);
 
-                let alpha = uAlpha;
-                if (dTransparency) {
-                    const group = isGeoTexture ? UsdzExporter.getGroup(groups, i) : groups[indices![i]];
-                    const transparency = tTransparency.array[instanceIndex * groupCount + group] / 255;
-                    alpha *= 1 - transparency;
-                }
+            // material
+            const faceIndicesByMaterial = new Map<number, number[]>();
+            for (let i = 0; i < drawCount; i += 3) {
+                const color = Color.fromArray(quantizedColors, i);
+
+                const transparency = UsdzExporter.getTransparency(i, geoData, interpolatedTransparency);
+                const alpha = Math.round(uAlpha * (1 - transparency) * 10) / 10; // quantized
 
                 this.addMaterial(color, alpha);
 

+ 36 - 1
src/mol-geo/geometry/base.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -15,6 +15,7 @@ import { ColorNames } from '../../mol-util/color/names';
 import { NullLocation } from '../../mol-model/location';
 import { UniformColorTheme } from '../../mol-theme/color/uniform';
 import { UniformSizeTheme } from '../../mol-theme/size/uniform';
+import { smoothstep } from '../../mol-math/interpolate';
 
 export const VisualQualityInfo = {
     'custom': {},
@@ -33,6 +34,40 @@ export const VisualQualityOptions = PD.arrayToOptions(VisualQualityNames);
 
 //
 
+export const ColorSmoothingParams = {
+    smoothColors: PD.MappedStatic('auto', {
+        auto: PD.Group({}),
+        on: PD.Group({
+            resolutionFactor: PD.Numeric(2, { min: 0.5, max: 6, step: 0.1 }),
+            sampleStride: PD.Numeric(3, { min: 1, max: 12, step: 1 }),
+        }),
+        off: PD.Group({})
+    }),
+};
+export type ColorSmoothingParams = typeof ColorSmoothingParams
+
+export function hasColorSmoothingProp(props: PD.Values<any>): props is PD.Values<ColorSmoothingParams> {
+    return !!props.smoothColors;
+}
+
+export function getColorSmoothingProps(smoothColors: PD.Values<ColorSmoothingParams>['smoothColors'], preferSmoothing?: boolean, resolution?: number) {
+    if ((smoothColors.name === 'on' || (smoothColors.name === 'auto' && preferSmoothing)) && resolution && resolution < 3) {
+        let stride = 3;
+        if (smoothColors.name === 'on') {
+            resolution *= smoothColors.params.resolutionFactor;
+            stride = smoothColors.params.sampleStride;
+        } else {
+            // https://graphtoy.com/?f1(x,t)=(2-smoothstep(0,1.1,x))*x&coords=0.7,0.6,1.8
+            resolution *= 2 - smoothstep(0, 1.1, resolution);
+            resolution = Math.max(0.5, resolution);
+            if (resolution > 1.2) stride = 2;
+        }
+        return { resolution, stride };
+    };
+}
+
+//
+
 export namespace BaseGeometry {
     export const Params = {
         alpha: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }, { label: 'Opacity', isEssential: true, description: 'How opaque/transparent the representation is rendered.' }),

+ 166 - 37
src/mol-geo/geometry/mesh/color-smoothing.ts

@@ -4,13 +4,15 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
+import { MeshValues } from '../../../mol-gl/renderable/mesh';
 import { createTextureImage, TextureImage } from '../../../mol-gl/renderable/util';
 import { WebGLContext } from '../../../mol-gl/webgl/context';
 import { Texture } from '../../../mol-gl/webgl/texture';
 import { Box3D, Sphere3D } from '../../../mol-math/geometry';
+import { lerp } from '../../../mol-math/interpolate';
 import { Vec2, Vec3, Vec4 } from '../../../mol-math/linear-algebra';
 import { getVolumeTexture2dLayout } from '../../../mol-repr/volume/util';
-import { Color } from '../../../mol-util/color';
+import { ValueCell } from '../../../mol-util';
 
 interface ColorSmoothingInput {
     vertexCount: number
@@ -24,10 +26,11 @@ interface ColorSmoothingInput {
     colorType: 'group' | 'groupInstance'
     boundingSphere: Sphere3D
     invariantBoundingSphere: Sphere3D
+    itemSize: 4 | 3 | 1
 }
 
 export function calcMeshColorSmoothing(input: ColorSmoothingInput, resolution: number, stride: number, webgl?: WebGLContext, texture?: Texture) {
-    const { colorType, vertexCount, groupCount, positionBuffer, transformBuffer, groupBuffer } = input;
+    const { colorType, vertexCount, groupCount, positionBuffer, transformBuffer, groupBuffer, itemSize } = input;
 
     const isInstanceType = colorType.endsWith('Instance');
     const box = Box3D.fromSphere3D(Box3D(), isInstanceType ? input.boundingSphere : input.invariantBoundingSphere);
@@ -43,7 +46,6 @@ export function calcMeshColorSmoothing(input: ColorSmoothingInput, resolution: n
     const { width, height } = getVolumeTexture2dLayout(gridDim);
     // console.log({ width, height, dim });
 
-    const itemSize = 3;
     const data = new Float32Array(width * height * itemSize);
     const count = new Float32Array(width * height);
 
@@ -78,10 +80,7 @@ export function calcMeshColorSmoothing(input: ColorSmoothingInput, resolution: n
             const z = Math.floor(vz);
 
             // group colors
-            const ci = i * groupCount + groupBuffer[j];
-            const r = colors[ci * 3];
-            const g = colors[ci * 3 + 1];
-            const b = colors[ci * 3 + 2];
+            const ci = (i * groupCount + groupBuffer[j]) * itemSize;
 
             // Extents of grid to consider for this atom
             const begX = Math.max(0, x - p);
@@ -106,10 +105,10 @@ export function calcMeshColorSmoothing(input: ColorSmoothingInput, resolution: n
 
                         const s = p - d;
                         const index = getIndex(xi, yi, zi);
-                        data[index] += r * s;
-                        data[index + 1] += g * s;
-                        data[index + 2] += b * s;
-                        count[index / 3] += s;
+                        for (let k = 0; k < itemSize; ++k) {
+                            data[index + k] += colors[ci + k] * s;
+                        }
+                        count[index / itemSize] += s;
                     }
                 }
             }
@@ -117,11 +116,11 @@ export function calcMeshColorSmoothing(input: ColorSmoothingInput, resolution: n
     }
 
     for (let i = 0, il = count.length; i < il; ++i) {
-        const i3 = i * 3;
+        const is = i * itemSize;
         const c = count[i];
-        grid[i3] = Math.round(data[i3] / c);
-        grid[i3 + 1] = Math.round(data[i3 + 1] / c);
-        grid[i3 + 2] = Math.round(data[i3 + 2] / c);
+        for (let k = 0; k < itemSize; ++k) {
+            grid[is + k] = Math.round(data[is + k] / c);
+        }
     }
 
     const gridTexDim = Vec2.create(width, height);
@@ -129,12 +128,16 @@ export function calcMeshColorSmoothing(input: ColorSmoothingInput, resolution: n
     const type = isInstanceType ? 'volumeInstance' as const : 'volume' as const;
 
     if (webgl) {
-        if (!texture) texture = webgl.resources.texture('image-uint8', 'rgb', 'ubyte', 'linear');
+        if (!texture) {
+            const format = itemSize === 4 ? 'rgba' :
+                itemSize === 3 ? 'rgb' : 'alpha';
+            texture = webgl.resources.texture('image-uint8', format, 'ubyte', 'linear');
+        }
         texture.load(textureImage);
 
         return { kind: 'volume' as const, texture, gridTexDim, gridDim, gridTransform, type };
     } else {
-        const interpolated = getTrilinearlyInterpolated({ vertexCount, instanceCount, transformBuffer, positionBuffer, colorType: type, grid, gridDim, gridTexDim, gridTransform, vertexStride: 3, colorStride: 3 });
+        const interpolated = getTrilinearlyInterpolated({ vertexCount, instanceCount, transformBuffer, positionBuffer, colorType: type, grid, gridDim, gridTexDim, gridTransform, vertexStride: 3, colorStride: itemSize, outputStride: itemSize });
 
         return {
             kind: 'vertex' as const,
@@ -157,16 +160,24 @@ interface ColorInterpolationInput {
     gridTexDim: Vec2
     gridDim: Vec3
     gridTransform: Vec4
-    vertexStride: number
-    colorStride: number
+    vertexStride: 3 | 4
+    colorStride: 1 | 3 | 4
+    outputStride: 1 | 3 | 4
+    itemOffset?: 0 | 1 | 2 | 3
 }
 
 export function getTrilinearlyInterpolated(input: ColorInterpolationInput): TextureImage<Uint8Array> {
     const { vertexCount, positionBuffer, transformBuffer, grid, gridDim, gridTexDim, gridTransform, vertexStride, colorStride } = input;
 
+    const itemOffset = input.itemOffset || 0;
+    const outputStride = input.outputStride;
+    if (outputStride + itemOffset > colorStride) {
+        throw new Error('outputStride + itemOffset must NOT be larger than colorStride');
+    }
+
     const isInstanceType = input.colorType.endsWith('Instance');
     const instanceCount = isInstanceType ? input.instanceCount : 1;
-    const image = createTextureImage(Math.max(1, instanceCount * vertexCount), 3, Uint8Array);
+    const image = createTextureImage(Math.max(1, instanceCount * vertexCount), outputStride, Uint8Array);
     const { array } = image;
 
     const [xn, yn] = gridDim;
@@ -204,26 +215,144 @@ export function getTrilinearlyInterpolated(input: ColorInterpolationInput): Text
             const [x1, y1, z1] = v1;
             const [xd, yd, zd] = vd;
 
-            const s000 = Color.fromArray(grid, getIndex(x0, y0, z0));
-            const s100 = Color.fromArray(grid, getIndex(x1, y0, z0));
-            const s001 = Color.fromArray(grid, getIndex(x0, y0, z1));
-            const s101 = Color.fromArray(grid, getIndex(x1, y0, z1));
-            const s010 = Color.fromArray(grid, getIndex(x0, y1, z0));
-            const s110 = Color.fromArray(grid, getIndex(x1, y1, z0));
-            const s011 = Color.fromArray(grid, getIndex(x0, y1, z1));
-            const s111 = Color.fromArray(grid, getIndex(x1, y1, z1));
+            const i000 = getIndex(x0, y0, z0) + itemOffset;
+            const i100 = getIndex(x1, y0, z0) + itemOffset;
+            const i001 = getIndex(x0, y0, z1) + itemOffset;
+            const i101 = getIndex(x1, y0, z1) + itemOffset;
+            const i010 = getIndex(x0, y1, z0) + itemOffset;
+            const i110 = getIndex(x1, y1, z0) + itemOffset;
+            const i011 = getIndex(x0, y1, z1) + itemOffset;
+            const i111 = getIndex(x1, y1, z1) + itemOffset;
+
+            const o = (i * vertexCount + j) * outputStride;
+
+            for (let k = 0; k < outputStride; ++k) {
+                const s000 = grid[i000 + k];
+                const s100 = grid[i100 + k];
+                const s001 = grid[i001 + k];
+                const s101 = grid[i101 + k];
+                const s010 = grid[i010 + k];
+                const s110 = grid[i110 + k];
+                const s011 = grid[i011 + k];
+                const s111 = grid[i111 + k];
+
+                const s00 = lerp(s000, s100, xd);
+                const s01 = lerp(s001, s101, xd);
+                const s10 = lerp(s010, s110, xd);
+                const s11 = lerp(s011, s111, xd);
+
+                const s0 = lerp(s00, s10, yd);
+                const s1 = lerp(s01, s11, yd);
+
+                array[o + k] = lerp(s0, s1, zd);
+            }
+        }
+    }
 
-            const s00 = Color.interpolate(s000, s100, xd);
-            const s01 = Color.interpolate(s001, s101, xd);
-            const s10 = Color.interpolate(s010, s110, xd);
-            const s11 = Color.interpolate(s011, s111, xd);
+    return image;
+}
 
-            const s0 = Color.interpolate(s00, s10, yd);
-            const s1 = Color.interpolate(s01, s11, yd);
+//
 
-            Color.toArray(Color.interpolate(s0, s1, zd), array, (i * vertexCount + j) * 3);
-        }
+function isSupportedColorType(x: string): x is 'group' | 'groupInstance' {
+    return x === 'group' || x === 'groupInstance';
+}
+
+export function applyMeshColorSmoothing(values: MeshValues, resolution: number, stride: number, webgl?: WebGLContext, colorTexture?: Texture) {
+    if (!isSupportedColorType(values.dColorType.ref.value)) return;
+
+    const smoothingData = calcMeshColorSmoothing({
+        vertexCount: values.uVertexCount.ref.value,
+        instanceCount: values.uInstanceCount.ref.value,
+        groupCount: values.uGroupCount.ref.value,
+        transformBuffer: values.aTransform.ref.value,
+        instanceBuffer: values.aInstance.ref.value,
+        positionBuffer: values.aPosition.ref.value,
+        groupBuffer: values.aGroup.ref.value,
+        colorData: values.tColor.ref.value,
+        colorType: values.dColorType.ref.value,
+        boundingSphere: values.boundingSphere.ref.value,
+        invariantBoundingSphere: values.invariantBoundingSphere.ref.value,
+        itemSize: 3
+    }, resolution, stride, webgl, colorTexture);
+
+    if (smoothingData.kind === 'volume') {
+        ValueCell.updateIfChanged(values.dColorType, smoothingData.type);
+        ValueCell.update(values.tColorGrid, smoothingData.texture);
+        ValueCell.update(values.uColorTexDim, smoothingData.gridTexDim);
+        ValueCell.update(values.uColorGridDim, smoothingData.gridDim);
+        ValueCell.update(values.uColorGridTransform, smoothingData.gridTransform);
+    } else if (smoothingData.kind === 'vertex') {
+        ValueCell.updateIfChanged(values.dColorType, smoothingData.type);
+        ValueCell.update(values.tColor, smoothingData.texture);
+        ValueCell.update(values.uColorTexDim, smoothingData.texDim);
     }
+}
 
-    return image;
+function isSupportedOverpaintType(x: string): x is 'groupInstance' {
+    return x === 'groupInstance';
+}
+
+export function applyMeshOverpaintSmoothing(values: MeshValues, resolution: number, stride: number, webgl?: WebGLContext, colorTexture?: Texture) {
+    if (!isSupportedOverpaintType(values.dOverpaintType.ref.value)) return;
+
+    const smoothingData = calcMeshColorSmoothing({
+        vertexCount: values.uVertexCount.ref.value,
+        instanceCount: values.uInstanceCount.ref.value,
+        groupCount: values.uGroupCount.ref.value,
+        transformBuffer: values.aTransform.ref.value,
+        instanceBuffer: values.aInstance.ref.value,
+        positionBuffer: values.aPosition.ref.value,
+        groupBuffer: values.aGroup.ref.value,
+        colorData: values.tOverpaint.ref.value,
+        colorType: values.dOverpaintType.ref.value,
+        boundingSphere: values.boundingSphere.ref.value,
+        invariantBoundingSphere: values.invariantBoundingSphere.ref.value,
+        itemSize: 4
+    }, resolution, stride, webgl, colorTexture);
+    if (smoothingData.kind === 'volume') {
+        ValueCell.updateIfChanged(values.dOverpaintType, smoothingData.type);
+        ValueCell.update(values.tOverpaintGrid, smoothingData.texture);
+        ValueCell.update(values.uOverpaintTexDim, smoothingData.gridTexDim);
+        ValueCell.update(values.uOverpaintGridDim, smoothingData.gridDim);
+        ValueCell.update(values.uOverpaintGridTransform, smoothingData.gridTransform);
+    } else if (smoothingData.kind === 'vertex') {
+        ValueCell.updateIfChanged(values.dOverpaintType, smoothingData.type);
+        ValueCell.update(values.tOverpaint, smoothingData.texture);
+        ValueCell.update(values.uOverpaintTexDim, smoothingData.texDim);
+    }
+}
+
+function isSupportedTransparencyType(x: string): x is 'groupInstance' {
+    return x === 'groupInstance';
+}
+
+export function applyMeshTransparencySmoothing(values: MeshValues, resolution: number, stride: number, webgl?: WebGLContext, colorTexture?: Texture) {
+    if (!isSupportedTransparencyType(values.dTransparencyType.ref.value)) return;
+
+    const smoothingData = calcMeshColorSmoothing({
+        vertexCount: values.uVertexCount.ref.value,
+        instanceCount: values.uInstanceCount.ref.value,
+        groupCount: values.uGroupCount.ref.value,
+        transformBuffer: values.aTransform.ref.value,
+        instanceBuffer: values.aInstance.ref.value,
+        positionBuffer: values.aPosition.ref.value,
+        groupBuffer: values.aGroup.ref.value,
+        colorData: values.tTransparency.ref.value,
+        colorType: values.dTransparencyType.ref.value,
+        boundingSphere: values.boundingSphere.ref.value,
+        invariantBoundingSphere: values.invariantBoundingSphere.ref.value,
+        itemSize: 1
+    }, resolution, stride, webgl, colorTexture);
+    if (smoothingData.kind === 'volume') {
+        ValueCell.updateIfChanged(values.dTransparencyType, smoothingData.type);
+        ValueCell.update(values.tTransparencyGrid, smoothingData.texture);
+        ValueCell.update(values.uTransparencyTexDim, smoothingData.gridTexDim);
+        ValueCell.update(values.uTransparencyGridDim, smoothingData.gridDim);
+        ValueCell.update(values.uTransparencyGridTransform, smoothingData.gridTransform);
+    } else if (smoothingData.kind === 'vertex') {
+        ValueCell.updateIfChanged(values.dTransparencyType, smoothingData.type);
+        ValueCell.update(values.tTransparency, smoothingData.texture);
+        ValueCell.update(values.uTransparencyTexDim, smoothingData.texDim);
+    }
 }

+ 18 - 2
src/mol-geo/geometry/overpaint-data.ts

@@ -1,18 +1,24 @@
 /**
- * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
 import { ValueCell } from '../../mol-util/value-cell';
-import { Vec2 } from '../../mol-math/linear-algebra';
+import { Vec2, Vec3, Vec4 } from '../../mol-math/linear-algebra';
 import { TextureImage, createTextureImage } from '../../mol-gl/renderable/util';
 import { Color } from '../../mol-util/color';
+import { createNullTexture, Texture } from '../../mol-gl/webgl/texture';
 
 export type OverpaintData = {
     tOverpaint: ValueCell<TextureImage<Uint8Array>>
     uOverpaintTexDim: ValueCell<Vec2>
     dOverpaint: ValueCell<boolean>,
+
+    tOverpaintGrid: ValueCell<Texture>,
+    uOverpaintGridDim: ValueCell<Vec3>,
+    uOverpaintGridTransform: ValueCell<Vec4>,
+    dOverpaintType: ValueCell<string>,
 }
 
 export function applyOverpaintColor(array: Uint8Array, start: number, end: number, color: Color) {
@@ -40,6 +46,11 @@ export function createOverpaint(count: number, overpaintData?: OverpaintData): O
             tOverpaint: ValueCell.create(overpaint),
             uOverpaintTexDim: ValueCell.create(Vec2.create(overpaint.width, overpaint.height)),
             dOverpaint: ValueCell.create(count > 0),
+
+            tOverpaintGrid: ValueCell.create(createNullTexture()),
+            uOverpaintGridDim: ValueCell.create(Vec3.create(1, 1, 1)),
+            uOverpaintGridTransform: ValueCell.create(Vec4.create(0, 0, 0, 1)),
+            dOverpaintType: ValueCell.create('groupInstance'),
         };
     }
 }
@@ -55,6 +66,11 @@ export function createEmptyOverpaint(overpaintData?: OverpaintData): OverpaintDa
             tOverpaint: ValueCell.create(emptyOverpaintTexture),
             uOverpaintTexDim: ValueCell.create(Vec2.create(1, 1)),
             dOverpaint: ValueCell.create(false),
+
+            tOverpaintGrid: ValueCell.create(createNullTexture()),
+            uOverpaintGridDim: ValueCell.create(Vec3.create(1, 1, 1)),
+            uOverpaintGridTransform: ValueCell.create(Vec4.create(0, 0, 0, 1)),
+            dOverpaintType: ValueCell.create('groupInstance'),
         };
     }
 }

+ 185 - 27
src/mol-geo/geometry/texture-mesh/color-smoothing.ts

@@ -18,7 +18,8 @@ import { Vec2, Vec3, Vec4 } from '../../../mol-math/linear-algebra';
 import { Box3D, Sphere3D } from '../../../mol-math/geometry';
 import { accumulate_frag } from '../../../mol-gl/shader/compute/color-smoothing/accumulate.frag';
 import { accumulate_vert } from '../../../mol-gl/shader/compute/color-smoothing/accumulate.vert';
-import { TextureImage } from '../../../mol-gl/renderable/util';
+import { isWebGL2 } from '../../../mol-gl/webgl/compat';
+import { TextureMeshValues } from '../../../mol-gl/renderable/texture-mesh';
 
 export const ColorAccumulateSchema = {
     drawCount: ValueSpec('number'),
@@ -38,7 +39,7 @@ export const ColorAccumulateSchema = {
     tGroup: TextureSpec('texture', 'rgba', 'float', 'nearest'),
 
     uColorTexDim: UniformSpec('v2'),
-    tColor: TextureSpec('image-uint8', 'rgb', 'ubyte', 'nearest'),
+    tColor: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
     dColorType: DefineSpec('string', ['group', 'groupInstance', 'vertex', 'vertexInstance']),
 
     uCurrentSlice: UniformSpec('f'),
@@ -50,6 +51,7 @@ export const ColorAccumulateSchema = {
 };
 type ColorAccumulateValues = Values<typeof ColorAccumulateSchema>
 const ColorAccumulateName = 'color-accumulate';
+const ColorCountName = 'color-count';
 
 interface AccumulateInput {
     vertexCount: number
@@ -59,7 +61,7 @@ interface AccumulateInput {
     instanceBuffer: Float32Array
     positionTexture: Texture
     groupTexture: Texture
-    colorData: TextureImage<Uint8Array>
+    colorData: Texture
     colorType: 'group' | 'groupInstance'
 }
 
@@ -96,7 +98,7 @@ function getAccumulateRenderable(ctx: WebGLContext, input: AccumulateInput, box:
         ValueCell.update(v.tPosition, input.positionTexture);
         ValueCell.update(v.tGroup, input.groupTexture);
 
-        ValueCell.update(v.uColorTexDim, Vec2.set(v.uColorTexDim.ref.value, input.colorData.width, input.colorData.height));
+        ValueCell.update(v.uColorTexDim, Vec2.set(v.uColorTexDim.ref.value, input.colorData.getWidth(), input.colorData.getHeight()));
         ValueCell.update(v.tColor, input.colorData);
         ValueCell.updateIfChanged(v.dColorType, input.colorType);
 
@@ -135,7 +137,7 @@ function createAccumulateRenderable(ctx: WebGLContext, input: AccumulateInput, b
         tPosition: ValueCell.create(input.positionTexture),
         tGroup: ValueCell.create(input.groupTexture),
 
-        uColorTexDim: ValueCell.create(Vec2.create(input.colorData.width, input.colorData.height)),
+        uColorTexDim: ValueCell.create(Vec2.create(input.colorData.getWidth(), input.colorData.getHeight())),
         tColor: ValueCell.create(input.colorData),
         dColorType: ValueCell.create(input.colorType),
 
@@ -148,7 +150,7 @@ function createAccumulateRenderable(ctx: WebGLContext, input: AccumulateInput, b
     };
 
     const schema = { ...ColorAccumulateSchema };
-    const shaderCode = ShaderCode('accumulate', accumulate_vert, accumulate_frag);
+    const shaderCode = ShaderCode('accumulate', accumulate_vert, accumulate_frag, { drawBuffers: 'required' });
     const renderItem = createComputeRenderItem(ctx, 'points', shaderCode, schema, values);
 
     return createComputeRenderable(renderItem, values);
@@ -172,30 +174,33 @@ export const ColorNormalizeSchema = {
     ...QuadSchema,
 
     tColor: TextureSpec('texture', 'rgba', 'float', 'nearest'),
+    tCount: TextureSpec('texture', 'alpha', 'float', 'nearest'),
     uTexSize: UniformSpec('v2'),
 
 };
 type ColorNormalizeValues = Values<typeof ColorNormalizeSchema>
 const ColorNormalizeName = 'color-normalize';
 
-function getNormalizeRenderable(ctx: WebGLContext, color: Texture): ComputeRenderable<ColorNormalizeValues> {
+function getNormalizeRenderable(ctx: WebGLContext, color: Texture, count: Texture): ComputeRenderable<ColorNormalizeValues> {
     if (ctx.namedComputeRenderables[ColorNormalizeName]) {
         const v = ctx.namedComputeRenderables[ColorNormalizeName].values as ColorNormalizeValues;
 
         ValueCell.update(v.tColor, color);
+        ValueCell.update(v.tCount, count);
         ValueCell.update(v.uTexSize, Vec2.set(v.uTexSize.ref.value, color.getWidth(), color.getHeight()));
 
         ctx.namedComputeRenderables[ColorNormalizeName].update();
     } else {
-        ctx.namedComputeRenderables[ColorNormalizeName] = createColorNormalizeRenderable(ctx, color);
+        ctx.namedComputeRenderables[ColorNormalizeName] = createColorNormalizeRenderable(ctx, color, count);
     }
     return ctx.namedComputeRenderables[ColorNormalizeName];
 }
 
-function createColorNormalizeRenderable(ctx: WebGLContext, color: Texture) {
+function createColorNormalizeRenderable(ctx: WebGLContext, color: Texture, count: Texture) {
     const values: ColorNormalizeValues = {
         ...QuadValues,
         tColor: ValueCell.create(color),
+        tCount: ValueCell.create(count),
         uTexSize: ValueCell.create(Vec2.create(color.getWidth(), color.getHeight())),
     };
 
@@ -247,6 +252,9 @@ interface ColorSmoothingInput extends AccumulateInput {
 }
 
 export function calcTextureMeshColorSmoothing(input: ColorSmoothingInput, resolution: number, stride: number, webgl: WebGLContext, texture?: Texture) {
+    const { drawBuffers } = webgl.extensions;
+    if (!drawBuffers) throw new Error('need WebGL draw buffers');
+
     const { gl, resources, state, extensions: { colorBufferHalfFloat, textureHalfFloat } } = webgl;
 
     const isInstanceType = input.colorType.endsWith('Instance');
@@ -263,29 +271,55 @@ export function calcTextureMeshColorSmoothing(input: ColorSmoothingInput, resolu
     const { texDimX: width, texDimY: height, texCols } = getTexture2dSize(gridDim);
     // console.log({ width, height, texCols, dim, resolution });
 
-    if (!webgl.namedTextures[ColorAccumulateName]) {
-        webgl.namedTextures[ColorAccumulateName] = colorBufferHalfFloat && textureHalfFloat
-            ? resources.texture('image-float16', 'rgba', 'fp16', 'nearest')
-            : resources.texture('image-float32', 'rgba', 'float', 'nearest');
+    if (!webgl.namedFramebuffers[ColorAccumulateName]) {
+        webgl.namedFramebuffers[ColorAccumulateName] = webgl.resources.framebuffer();
+    }
+    const framebuffer = webgl.namedFramebuffers[ColorAccumulateName];
+
+    if (isWebGL2(gl)) {
+        if (!webgl.namedTextures[ColorAccumulateName]) {
+            webgl.namedTextures[ColorAccumulateName] = colorBufferHalfFloat && textureHalfFloat
+                ? resources.texture('image-float16', 'rgba', 'fp16', 'nearest')
+                : resources.texture('image-float32', 'rgba', 'float', 'nearest');
+        }
+
+        if (!webgl.namedTextures[ColorCountName]) {
+            webgl.namedTextures[ColorCountName] = resources.texture('image-float32', 'alpha', 'float', 'nearest');
+        }
+    } else {
+        // in webgl1 drawbuffers must be in the same format for some reason
+        // this is quite wasteful but good enough for medium size meshes
+
+        if (!webgl.namedTextures[ColorAccumulateName]) {
+            webgl.namedTextures[ColorAccumulateName] = resources.texture('image-float32', 'rgba', 'float', 'nearest');
+        }
+
+        if (!webgl.namedTextures[ColorCountName]) {
+            webgl.namedTextures[ColorCountName] = resources.texture('image-float32', 'rgba', 'float', 'nearest');
+        }
     }
+
     const accumulateTexture = webgl.namedTextures[ColorAccumulateName];
+    const countTexture = webgl.namedTextures[ColorCountName];
+
     accumulateTexture.define(width, height);
+    countTexture.define(width, height);
+
+    accumulateTexture.attachFramebuffer(framebuffer, 0);
+    countTexture.attachFramebuffer(framebuffer, 1);
 
     const accumulateRenderable = getAccumulateRenderable(webgl, input, box, resolution, stride);
+    state.currentRenderItemId = -1;
 
-    //
+    framebuffer.bind();
+    drawBuffers.drawBuffers([
+        drawBuffers.COLOR_ATTACHMENT0,
+        drawBuffers.COLOR_ATTACHMENT1,
+    ]);
 
     const { uCurrentSlice, uCurrentX, uCurrentY } = accumulateRenderable.values;
 
-    if (!webgl.namedFramebuffers[ColorAccumulateName]) {
-        webgl.namedFramebuffers[ColorAccumulateName] = webgl.resources.framebuffer();
-    }
-    const framebuffer = webgl.namedFramebuffers[ColorAccumulateName];
-    framebuffer.bind();
-
     setAccumulateDefaults(webgl);
-    state.currentRenderItemId = -1;
-    accumulateTexture.attachFramebuffer(framebuffer, 0);
     gl.viewport(0, 0, width, height);
     gl.scissor(0, 0, width, height);
     gl.clear(gl.COLOR_BUFFER_BIT);
@@ -310,21 +344,31 @@ export function calcTextureMeshColorSmoothing(input: ColorSmoothingInput, resolu
         currX += dx;
     }
 
+    accumulateTexture.detachFramebuffer(framebuffer, 0);
+    countTexture.detachFramebuffer(framebuffer, 1);
+    drawBuffers.drawBuffers([gl.COLOR_ATTACHMENT0, gl.NONE]);
+
     // const accImage = new Float32Array(width * height * 4);
     // accumulateTexture.attachFramebuffer(framebuffer, 0);
     // webgl.readPixels(0, 0, width, height, accImage);
     // console.log(accImage);
-    // printTextureImage({ array: accImage, width, height }, 1 / 4);
+    // printTextureImage({ array: accImage, width, height }, { scale: 1 });
+
+    // const cntImage = new Float32Array(width * height * 4);
+    // countTexture.attachFramebuffer(framebuffer, 0);
+    // webgl.readPixels(0, 0, width, height, cntImage);
+    // console.log(cntImage);
+    // printTextureImage({ array: cntImage, width, height }, { scale: 1 });
 
     // normalize
 
-    if (!texture) texture = resources.texture('image-uint8', 'rgb', 'ubyte', 'linear');
+    if (!texture) texture = resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
     texture.define(width, height);
 
-    const normalizeRenderable = getNormalizeRenderable(webgl, accumulateTexture);
+    const normalizeRenderable = getNormalizeRenderable(webgl, accumulateTexture, countTexture);
+    state.currentRenderItemId = -1;
 
     setNormalizeDefaults(webgl);
-    state.currentRenderItemId = -1;
     texture.attachFramebuffer(framebuffer, 0);
     gl.viewport(0, 0, width, height);
     gl.scissor(0, 0, width, height);
@@ -335,10 +379,124 @@ export function calcTextureMeshColorSmoothing(input: ColorSmoothingInput, resolu
     // texture.attachFramebuffer(framebuffer, 0);
     // webgl.readPixels(0, 0, width, height, normImage);
     // console.log(normImage);
-    // printTextureImage({ array: normImage, width, height }, 1 / 4);
+    // printTextureImage({ array: normImage, width, height }, { scale: 1 });
 
     const gridTransform = Vec4.create(min[0], min[1], min[2], scaleFactor);
     const type = isInstanceType ? 'volumeInstance' : 'volume';
 
     return { texture, gridDim, gridTexDim: Vec2.create(width, height), gridTransform, type };
 }
+
+//
+
+const ColorSmoothingRgbName = 'color-smoothing-rgb';
+const ColorSmoothingRgbaName = 'color-smoothing-rgba';
+const ColorSmoothingAlphaName = 'color-smoothing-alpha';
+
+function isSupportedColorType(x: string): x is 'group' | 'groupInstance' {
+    return x === 'group' || x === 'groupInstance';
+}
+
+export function applyTextureMeshColorSmoothing(values: TextureMeshValues, resolution: number, stride: number, webgl: WebGLContext, colorTexture?: Texture) {
+    if (!isSupportedColorType(values.dColorType.ref.value)) return;
+
+    stride *= 3; // triple because TextureMesh is never indexed (no elements buffer)
+
+    if (!webgl.namedTextures[ColorSmoothingRgbName]) {
+        webgl.namedTextures[ColorSmoothingRgbName] = webgl.resources.texture('image-uint8', 'rgb', 'ubyte', 'nearest');
+    }
+    const colorData = webgl.namedTextures[ColorSmoothingRgbName];
+    colorData.load(values.tColor.ref.value);
+
+    const smoothingData = calcTextureMeshColorSmoothing({
+        vertexCount: values.uVertexCount.ref.value,
+        instanceCount: values.uInstanceCount.ref.value,
+        groupCount: values.uGroupCount.ref.value,
+        transformBuffer: values.aTransform.ref.value,
+        instanceBuffer: values.aInstance.ref.value,
+        positionTexture: values.tPosition.ref.value,
+        groupTexture: values.tGroup.ref.value,
+        colorData,
+        colorType: values.dColorType.ref.value,
+        boundingSphere: values.boundingSphere.ref.value,
+        invariantBoundingSphere: values.invariantBoundingSphere.ref.value,
+    }, resolution, stride, webgl, colorTexture);
+
+    ValueCell.updateIfChanged(values.dColorType, smoothingData.type);
+    ValueCell.update(values.tColorGrid, smoothingData.texture);
+    ValueCell.update(values.uColorTexDim, smoothingData.gridTexDim);
+    ValueCell.update(values.uColorGridDim, smoothingData.gridDim);
+    ValueCell.update(values.uColorGridTransform, smoothingData.gridTransform);
+}
+
+function isSupportedOverpaintType(x: string): x is 'groupInstance' {
+    return x === 'groupInstance';
+}
+
+export function applyTextureMeshOverpaintSmoothing(values: TextureMeshValues, resolution: number, stride: number, webgl: WebGLContext, colorTexture?: Texture) {
+    if (!isSupportedOverpaintType(values.dOverpaintType.ref.value)) return;
+
+    stride *= 3; // triple because TextureMesh is never indexed (no elements buffer)
+
+    if (!webgl.namedTextures[ColorSmoothingRgbaName]) {
+        webgl.namedTextures[ColorSmoothingRgbaName] = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
+    }
+    const colorData = webgl.namedTextures[ColorSmoothingRgbaName];
+    colorData.load(values.tOverpaint.ref.value);
+
+    const smoothingData = calcTextureMeshColorSmoothing({
+        vertexCount: values.uVertexCount.ref.value,
+        instanceCount: values.uInstanceCount.ref.value,
+        groupCount: values.uGroupCount.ref.value,
+        transformBuffer: values.aTransform.ref.value,
+        instanceBuffer: values.aInstance.ref.value,
+        positionTexture: values.tPosition.ref.value,
+        groupTexture: values.tGroup.ref.value,
+        colorData,
+        colorType: values.dOverpaintType.ref.value,
+        boundingSphere: values.boundingSphere.ref.value,
+        invariantBoundingSphere: values.invariantBoundingSphere.ref.value,
+    }, resolution, stride, webgl, colorTexture);
+
+    ValueCell.updateIfChanged(values.dOverpaintType, smoothingData.type);
+    ValueCell.update(values.tOverpaintGrid, smoothingData.texture);
+    ValueCell.update(values.uOverpaintTexDim, smoothingData.gridTexDim);
+    ValueCell.update(values.uOverpaintGridDim, smoothingData.gridDim);
+    ValueCell.update(values.uOverpaintGridTransform, smoothingData.gridTransform);
+}
+
+function isSupportedTransparencyType(x: string): x is 'groupInstance' {
+    return x === 'groupInstance';
+}
+
+export function applyTextureMeshTransparencySmoothing(values: TextureMeshValues, resolution: number, stride: number, webgl: WebGLContext, colorTexture?: Texture) {
+    if (!isSupportedTransparencyType(values.dTransparencyType.ref.value)) return;
+
+    stride *= 3; // triple because TextureMesh is never indexed (no elements buffer)
+
+    if (!webgl.namedTextures[ColorSmoothingAlphaName]) {
+        webgl.namedTextures[ColorSmoothingAlphaName] = webgl.resources.texture('image-uint8', 'alpha', 'ubyte', 'nearest');
+    }
+    const colorData = webgl.namedTextures[ColorSmoothingAlphaName];
+    colorData.load(values.tTransparency.ref.value);
+
+    const smoothingData = calcTextureMeshColorSmoothing({
+        vertexCount: values.uVertexCount.ref.value,
+        instanceCount: values.uInstanceCount.ref.value,
+        groupCount: values.uGroupCount.ref.value,
+        transformBuffer: values.aTransform.ref.value,
+        instanceBuffer: values.aInstance.ref.value,
+        positionTexture: values.tPosition.ref.value,
+        groupTexture: values.tGroup.ref.value,
+        colorData,
+        colorType: values.dTransparencyType.ref.value,
+        boundingSphere: values.boundingSphere.ref.value,
+        invariantBoundingSphere: values.invariantBoundingSphere.ref.value,
+    }, resolution, stride, webgl, colorTexture);
+
+    ValueCell.updateIfChanged(values.dTransparencyType, smoothingData.type);
+    ValueCell.update(values.tTransparencyGrid, smoothingData.texture);
+    ValueCell.update(values.uTransparencyTexDim, smoothingData.gridTexDim);
+    ValueCell.update(values.uTransparencyGridDim, smoothingData.gridDim);
+    ValueCell.update(values.uTransparencyGridTransform, smoothingData.gridTransform);
+}

+ 18 - 2
src/mol-geo/geometry/transparency-data.ts

@@ -1,18 +1,24 @@
 /**
- * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
 import { ValueCell } from '../../mol-util/value-cell';
-import { Vec2 } from '../../mol-math/linear-algebra';
+import { Vec2, Vec3, Vec4 } from '../../mol-math/linear-algebra';
 import { TextureImage, createTextureImage } from '../../mol-gl/renderable/util';
+import { createNullTexture, Texture } from '../../mol-gl/webgl/texture';
 
 export type TransparencyData = {
     tTransparency: ValueCell<TextureImage<Uint8Array>>
     uTransparencyTexDim: ValueCell<Vec2>
     dTransparency: ValueCell<boolean>,
     transparencyAverage: ValueCell<number>,
+
+    tTransparencyGrid: ValueCell<Texture>,
+    uTransparencyGridDim: ValueCell<Vec3>,
+    uTransparencyGridTransform: ValueCell<Vec4>,
+    dTransparencyType: ValueCell<string>,
 }
 
 export function applyTransparencyValue(array: Uint8Array, start: number, end: number, value: number) {
@@ -48,6 +54,11 @@ export function createTransparency(count: number, transparencyData?: Transparenc
             uTransparencyTexDim: ValueCell.create(Vec2.create(transparency.width, transparency.height)),
             dTransparency: ValueCell.create(count > 0),
             transparencyAverage: ValueCell.create(0),
+
+            tTransparencyGrid: ValueCell.create(createNullTexture()),
+            uTransparencyGridDim: ValueCell.create(Vec3.create(1, 1, 1)),
+            uTransparencyGridTransform: ValueCell.create(Vec4.create(0, 0, 0, 1)),
+            dTransparencyType: ValueCell.create('groupInstance'),
         };
     }
 }
@@ -64,6 +75,11 @@ export function createEmptyTransparency(transparencyData?: TransparencyData): Tr
             uTransparencyTexDim: ValueCell.create(Vec2.create(1, 1)),
             dTransparency: ValueCell.create(false),
             transparencyAverage: ValueCell.create(0),
+
+            tTransparencyGrid: ValueCell.create(createNullTexture()),
+            uTransparencyGridDim: ValueCell.create(Vec3.create(1, 1, 1)),
+            uTransparencyGridTransform: ValueCell.create(Vec4.create(0, 0, 0, 1)),
+            dTransparencyType: ValueCell.create('groupInstance'),
         };
     }
 }

+ 10 - 0
src/mol-gl/renderable/schema.ts

@@ -224,6 +224,11 @@ export const OverpaintSchema = {
     uOverpaintTexDim: UniformSpec('v2'),
     tOverpaint: TextureSpec('image-uint8', 'rgba', 'ubyte', 'nearest'),
     dOverpaint: DefineSpec('boolean'),
+
+    uOverpaintGridDim: UniformSpec('v3'),
+    uOverpaintGridTransform: UniformSpec('v4'),
+    tOverpaintGrid: TextureSpec('texture', 'rgba', 'ubyte', 'linear'),
+    dOverpaintType: DefineSpec('string', ['groupInstance', 'volumeInstance']),
 } as const;
 export type OverpaintSchema = typeof OverpaintSchema
 export type OverpaintValues = Values<OverpaintSchema>
@@ -233,6 +238,11 @@ export const TransparencySchema = {
     tTransparency: TextureSpec('image-uint8', 'alpha', 'ubyte', 'nearest'),
     dTransparency: DefineSpec('boolean'),
     transparencyAverage: ValueSpec('number'),
+
+    uTransparencyGridDim: UniformSpec('v3'),
+    uTransparencyGridTransform: UniformSpec('v4'),
+    tTransparencyGrid: TextureSpec('texture', 'alpha', 'ubyte', 'linear'),
+    dTransparencyType: DefineSpec('string', ['groupInstance', 'volumeInstance']),
 } as const;
 export type TransparencySchema = typeof TransparencySchema
 export type TransparencyValues = Values<TransparencySchema>

+ 28 - 6
src/mol-gl/shader/chunks/assign-color-varying.glsl.ts

@@ -13,11 +13,11 @@ export const assign_color_varying = `
     #elif defined(dColorType_vertexInstance)
         vColor.rgb = readFromTexture(tColor, int(aInstance) * uVertexCount + VertexID, uColorTexDim).rgb;
     #elif defined(dColorType_volume)
-        vec3 gridPos = (uColorGridTransform.w * (position - uColorGridTransform.xyz)) / uColorGridDim;
-        vColor.rgb = texture3dFrom2dLinear(tColorGrid, gridPos, uColorGridDim, uColorTexDim).rgb;
+        vec3 cgridPos = (uColorGridTransform.w * (position - uColorGridTransform.xyz)) / uColorGridDim;
+        vColor.rgb = texture3dFrom2dLinear(tColorGrid, cgridPos, uColorGridDim, uColorTexDim).rgb;
     #elif defined(dColorType_volumeInstance)
-        vec3 gridPos = (uColorGridTransform.w * (vModelPosition - uColorGridTransform.xyz)) / uColorGridDim;
-        vColor.rgb = texture3dFrom2dLinear(tColorGrid, gridPos, uColorGridDim, uColorTexDim).rgb;
+        vec3 cgridPos = (uColorGridTransform.w * (vModelPosition - uColorGridTransform.xyz)) / uColorGridDim;
+        vColor.rgb = texture3dFrom2dLinear(tColorGrid, cgridPos, uColorGridDim, uColorTexDim).rgb;
     #endif
 
     #ifdef dUsePalette
@@ -25,7 +25,21 @@ export const assign_color_varying = `
     #endif
 
     #ifdef dOverpaint
-        vOverpaint = readFromTexture(tOverpaint, aInstance * float(uGroupCount) + group, uOverpaintTexDim);
+        #if defined(dOverpaintType_groupInstance)
+            vOverpaint = readFromTexture(tOverpaint, aInstance * float(uGroupCount) + group, uOverpaintTexDim);
+        #elif defined(dOverpaintType_vertexInstance)
+            vOverpaint = readFromTexture(tOverpaint, int(aInstance) * uVertexCount + VertexID, uOverpaintTexDim);
+        #elif defined(dOverpaintType_volumeInstance)
+            vec3 ogridPos = (uOverpaintGridTransform.w * (vModelPosition - uOverpaintGridTransform.xyz)) / uOverpaintGridDim;
+            vOverpaint = texture3dFrom2dLinear(tOverpaintGrid, ogridPos, uOverpaintGridDim, uOverpaintTexDim);
+        #endif
+
+        // pre-mix to avoid darkening due to empty overpaint
+        #ifdef dColorType_uniform
+            vOverpaint.rgb = mix(uColor.rgb, vOverpaint.rgb, vOverpaint.a);
+        #else
+            vOverpaint.rgb = mix(vColor.rgb, vOverpaint.rgb, vOverpaint.a);
+        #endif
     #endif
 #elif defined(dRenderVariant_pick)
     #if defined(dRenderVariant_pickObject)
@@ -39,6 +53,14 @@ export const assign_color_varying = `
 
 #ifdef dTransparency
     vGroup = group;
-    vTransparency = readFromTexture(tTransparency, aInstance * float(uGroupCount) + group, uTransparencyTexDim).a;
+
+    #if defined(dTransparencyType_groupInstance)
+        vTransparency = readFromTexture(tTransparency, aInstance * float(uGroupCount) + group, uTransparencyTexDim).a;
+    #elif defined(dTransparencyType_vertexInstance)
+        vTransparency = readFromTexture(tTransparency, int(aInstance) * uVertexCount + VertexID, uTransparencyTexDim).a;
+    #elif defined(dTransparencyType_volumeInstance)
+        vec3 tgridPos = (uTransparencyGridTransform.w * (vModelPosition - uTransparencyGridTransform.xyz)) / uTransparencyGridDim;
+        vTransparency = texture3dFrom2dLinear(tTransparencyGrid, tgridPos, uTransparencyGridDim, uTransparencyTexDim).a;
+    #endif
 #endif
 `;

+ 3 - 0
src/mol-gl/shader/chunks/assign-material-color.glsl.ts

@@ -54,6 +54,9 @@ export const assign_material_color = `
 // apply screendoor transparency
 #if defined(dTransparency)
     float ta = 1.0 - vTransparency;
+    #if defined(dRenderVariant_colorWboit)
+        if (vTransparency < 0.2) ta = 1.0; // hard cutoff looks better with wboit
+    #endif
 
     #if defined(dRenderVariant_pick)
         if (ta < uPickingAlphaThreshold)

+ 22 - 6
src/mol-gl/shader/chunks/color-vert-params.glsl.ts

@@ -18,9 +18,17 @@ export const color_vert_params = `
     #endif
 
     #ifdef dOverpaint
-        varying vec4 vOverpaint;
-        uniform vec2 uOverpaintTexDim;
-        uniform sampler2D tOverpaint;
+        #if defined(dOverpaintType_groupInstance) || defined(dOverpaintType_vertexInstance)
+            varying vec4 vOverpaint;
+            uniform vec2 uOverpaintTexDim;
+            uniform sampler2D tOverpaint;
+        #elif defined(dOverpaintType_volumeInstance)
+            varying vec4 vOverpaint;
+            uniform vec2 uOverpaintTexDim;
+            uniform vec3 uOverpaintGridDim;
+            uniform vec4 uOverpaintGridTransform;
+            uniform sampler2D tOverpaintGrid;
+        #endif
     #endif
 #elif defined(dRenderVariant_pick)
     #if __VERSION__ == 100
@@ -32,9 +40,17 @@ export const color_vert_params = `
 
 #ifdef dTransparency
     varying float vGroup;
-    varying float vTransparency;
-    uniform vec2 uTransparencyTexDim;
-    uniform sampler2D tTransparency;
+    #if defined(dTransparencyType_groupInstance) || defined(dTransparencyType_vertexInstance)
+        varying float vTransparency;
+        uniform vec2 uTransparencyTexDim;
+        uniform sampler2D tTransparency;
+    #elif defined(dTransparencyType_volumeInstance)
+        varying float vTransparency;
+        uniform vec2 uTransparencyTexDim;
+        uniform vec3 uTransparencyGridDim;
+        uniform vec4 uTransparencyGridTransform;
+        uniform sampler2D tTransparencyGrid;
+    #endif
 #endif
 
 #ifdef dUsePalette

+ 4 - 2
src/mol-gl/shader/compute/color-smoothing/accumulate.frag.ts

@@ -8,7 +8,7 @@ export const accumulate_frag = `
 precision highp float;
 
 varying vec3 vPosition;
-varying vec3 vColor;
+varying vec4 vColor;
 
 uniform float uCurrentSlice;
 uniform float uCurrentX;
@@ -23,6 +23,8 @@ void main() {
     float dist = distance(fragPos, vPosition);
     if (dist > p) discard;
 
-    gl_FragColor = vec4(vColor, 1.0) * (p - dist);
+    float f = p - dist;
+    gl_FragColor = vColor * f;
+    gl_FragData[1] = vec4(f);
 }
 `;

+ 3 - 3
src/mol-gl/shader/compute/color-smoothing/accumulate.vert.ts

@@ -27,7 +27,7 @@ uniform vec2 uColorTexDim;
 uniform sampler2D tColor;
 
 varying vec3 vPosition;
-varying vec3 vColor;
+varying vec4 vColor;
 
 uniform vec3 uBboxSize;
 uniform vec3 uBboxMin;
@@ -43,9 +43,9 @@ void main() {
     gl_Position = vec4(((position - uBboxMin) / uBboxSize) * 2.0 - 1.0, 1.0);
 
     #if defined(dColorType_group)
-        vColor = readFromTexture(tColor, group, uColorTexDim).rgb;
+        vColor = readFromTexture(tColor, group, uColorTexDim);
     #elif defined(dColorType_groupInstance)
-        vColor = readFromTexture(tColor, aInstance * float(uGroupCount) + group, uColorTexDim).rgb;
+        vColor = readFromTexture(tColor, aInstance * float(uGroupCount) + group, uColorTexDim);
     #endif
 }
 `;

+ 3 - 1
src/mol-gl/shader/compute/color-smoothing/normalize.frag.ts

@@ -9,12 +9,14 @@ precision highp float;
 precision highp sampler2D;
 
 uniform sampler2D tColor;
+uniform sampler2D tCount;
 uniform vec2 uTexSize;
 
 void main(void) {
     vec2 coords = gl_FragCoord.xy / uTexSize;
     vec4 color = texture2D(tColor, coords);
+    float count = texture2D(tCount, coords).r;
 
-    gl_FragColor.rgb = color.rgb / color.a;
+    gl_FragColor = color / count;
 }
 `;

+ 16 - 3
src/mol-gl/shader/direct-volume.frag.ts

@@ -110,9 +110,10 @@ uniform mat4 uCartnToUnit;
     #endif
 
     #ifdef dOverpaint
-        varying vec4 vOverpaint;
-        uniform vec2 uOverpaintTexDim;
-        uniform sampler2D tOverpaint;
+        #if defined(dOverpaintType_groupInstance) || defined(dOverpaintType_vertexInstance)
+            uniform vec2 uOverpaintTexDim;
+            uniform sampler2D tOverpaint;
+        #endif
     #endif
 #endif
 
@@ -192,6 +193,8 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 rayDir) {
     float nextValue;
 
     vec3 color = vec3(0.45, 0.55, 0.8);
+    vec4 overpaint = vec4(0.0);
+
     vec3 gradient = vec3(1.0);
     vec3 dx = vec3(gradOffset * scaleVol.x, 0.0, 0.0);
     vec3 dy = vec3(0.0, gradOffset * scaleVol.y, 0.0);
@@ -297,6 +300,16 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 rayDir) {
                         color = texture3dFrom1dTrilinear(tColor, isoPos, uGridDim, uColorTexDim, vInstance * float(uVertexCount)).rgb;
                     #endif
 
+                    #ifdef dOverpaint
+                        #if defined(dOverpaintType_groupInstance)
+                            overpaint = readFromTexture(tOverpaint, vInstance * float(uGroupCount) + group, uOverpaintTexDim);
+                        #elif defined(dOverpaintType_vertexInstance)
+                            overpaint = texture3dFrom1dTrilinear(tOverpaint, isoPos, uGridDim, uOverpaintTexDim, vInstance * float(uVertexCount)).rgb;
+                        #endif
+
+                        color = mix(color, overpaint.rgb, overpaint.a);
+                    #endif
+
                     // handle flipping and negative isosurfaces
                     #ifdef dFlipSided
                         bool flipped = value < uIsoValue.y; // flipped

+ 1 - 4
src/mol-gl/shader/mesh.vert.ts

@@ -14,10 +14,7 @@ precision highp sampler2D;
 #include common_vert_params
 #include color_vert_params
 #include common_clip
-
-#if defined(dColorType_grid)
-    #include texture3d_from_2d_linear
-#endif
+#include texture3d_from_2d_linear
 
 #ifdef dGeoTexture
     uniform vec2 uGeoTexDim;

+ 41 - 15
src/mol-plugin-state/transforms/representation.ts

@@ -24,7 +24,7 @@ import { unwindStructureAssembly, explodeStructure, spinStructure, SpinStructure
 import { Color } from '../../mol-util/color';
 import { Overpaint } from '../../mol-theme/overpaint';
 import { Transparency } from '../../mol-theme/transparency';
-import { BaseGeometry } from '../../mol-geo/geometry/base';
+import { BaseGeometry, hasColorSmoothingProp } from '../../mol-geo/geometry/base';
 import { Script } from '../../mol-script/script';
 import { UnitcellParams, UnitcellRepresentation, getUnitcellData } from '../../mol-repr/shape/model/unitcell';
 import { DistanceParams, DistanceRepresentation } from '../../mol-repr/shape/loci/distance';
@@ -328,25 +328,31 @@ const OverpaintStructureRepresentation3DFromScript = PluginStateTransform.BuiltI
     },
     apply({ a, params }) {
         const structure = a.data.sourceData;
+        const geometryVersion = a.data.repr.geometryVersion;
         const overpaint = Overpaint.ofScript(params.layers, structure);
 
         return new SO.Molecule.Structure.Representation3DState({
             state: { overpaint },
             initialState: { overpaint: Overpaint.Empty },
-            info: structure,
+            info: { structure, geometryVersion },
             repr: a.data.repr
         }, { label: `Overpaint (${overpaint.layers.length} Layers)` });
     },
     update({ a, b, newParams, oldParams }) {
-        const oldStructure = b.data.info as Structure;
+        const info = b.data.info as { structure: Structure, geometryVersion: number };
         const newStructure = a.data.sourceData;
-        if (newStructure !== oldStructure) return StateTransformer.UpdateResult.Recreate;
+        if (newStructure !== info.structure) return StateTransformer.UpdateResult.Recreate;
         if (a.data.repr !== b.data.repr) return StateTransformer.UpdateResult.Recreate;
 
+        const newGeometryVersion = a.data.repr.geometryVersion;
+        // smoothing needs to be re-calculated when geometry changes
+        if (newGeometryVersion !== info.geometryVersion && hasColorSmoothingProp(a.data.repr.props)) return StateTransformer.UpdateResult.Unchanged;
+
         const oldOverpaint = b.data.state.overpaint!;
         const newOverpaint = Overpaint.ofScript(newParams.layers, newStructure);
         if (Overpaint.areEqual(oldOverpaint, newOverpaint)) return StateTransformer.UpdateResult.Unchanged;
 
+        info.geometryVersion = newGeometryVersion;
         b.data.state.overpaint = newOverpaint;
         b.data.repr = a.data.repr;
         b.label = `Overpaint (${newOverpaint.layers.length} Layers)`;
@@ -380,25 +386,31 @@ const OverpaintStructureRepresentation3DFromBundle = PluginStateTransform.BuiltI
     },
     apply({ a, params }) {
         const structure = a.data.sourceData;
+        const geometryVersion = a.data.repr.geometryVersion;
         const overpaint = Overpaint.ofBundle(params.layers, structure);
 
         return new SO.Molecule.Structure.Representation3DState({
             state: { overpaint },
             initialState: { overpaint: Overpaint.Empty },
-            info: structure,
+            info: { structure, geometryVersion },
             repr: a.data.repr
         }, { label: `Overpaint (${overpaint.layers.length} Layers)` });
     },
     update({ a, b, newParams, oldParams }) {
-        const oldStructure = b.data.info as Structure;
+        const info = b.data.info as { structure: Structure, geometryVersion: number };
         const newStructure = a.data.sourceData;
-        if (newStructure !== oldStructure) return StateTransformer.UpdateResult.Recreate;
+        if (newStructure !== info.structure) return StateTransformer.UpdateResult.Recreate;
         if (a.data.repr !== b.data.repr) return StateTransformer.UpdateResult.Recreate;
 
+        const newGeometryVersion = a.data.repr.geometryVersion;
+        // smoothing needs to be re-calculated when geometry changes
+        if (newGeometryVersion !== info.geometryVersion && hasColorSmoothingProp(a.data.repr.props)) return StateTransformer.UpdateResult.Unchanged;
+
         const oldOverpaint = b.data.state.overpaint!;
         const newOverpaint = Overpaint.ofBundle(newParams.layers, newStructure);
         if (Overpaint.areEqual(oldOverpaint, newOverpaint)) return StateTransformer.UpdateResult.Unchanged;
 
+        info.geometryVersion = newGeometryVersion;
         b.data.state.overpaint = newOverpaint;
         b.data.repr = a.data.repr;
         b.label = `Overpaint (${newOverpaint.layers.length} Layers)`;
@@ -429,24 +441,31 @@ const TransparencyStructureRepresentation3DFromScript = PluginStateTransform.Bui
     },
     apply({ a, params }) {
         const structure = a.data.sourceData;
+        const geometryVersion = a.data.repr.geometryVersion;
         const transparency = Transparency.ofScript(params.layers, structure);
 
         return new SO.Molecule.Structure.Representation3DState({
             state: { transparency },
             initialState: { transparency: Transparency.Empty },
-            info: structure,
+            info: { structure, geometryVersion },
             repr: a.data.repr
         }, { label: `Transparency (${transparency.layers.length} Layers)` });
     },
     update({ a, b, newParams, oldParams }) {
-        const structure = b.data.info as Structure;
-        if (a.data.sourceData !== structure) return StateTransformer.UpdateResult.Recreate;
+        const info = b.data.info as { structure: Structure, geometryVersion: number };
+        const newStructure = a.data.sourceData;
+        if (newStructure !== info.structure) return StateTransformer.UpdateResult.Recreate;
         if (a.data.repr !== b.data.repr) return StateTransformer.UpdateResult.Recreate;
 
+        const newGeometryVersion = a.data.repr.geometryVersion;
+        // smoothing needs to be re-calculated when geometry changes
+        if (newGeometryVersion !== info.geometryVersion && hasColorSmoothingProp(a.data.repr.props)) return StateTransformer.UpdateResult.Unchanged;
+
         const oldTransparency = b.data.state.transparency!;
-        const newTransparency = Transparency.ofScript(newParams.layers, structure);
+        const newTransparency = Transparency.ofScript(newParams.layers, newStructure);
         if (Transparency.areEqual(oldTransparency, newTransparency)) return StateTransformer.UpdateResult.Unchanged;
 
+        info.geometryVersion = newGeometryVersion;
         b.data.state.transparency = newTransparency;
         b.data.repr = a.data.repr;
         b.label = `Transparency (${newTransparency.layers.length} Layers)`;
@@ -478,24 +497,31 @@ const TransparencyStructureRepresentation3DFromBundle = PluginStateTransform.Bui
     },
     apply({ a, params }) {
         const structure = a.data.sourceData;
+        const geometryVersion = a.data.repr.geometryVersion;
         const transparency = Transparency.ofBundle(params.layers, structure);
 
         return new SO.Molecule.Structure.Representation3DState({
             state: { transparency },
             initialState: { transparency: Transparency.Empty },
-            info: structure,
+            info: { structure, geometryVersion },
             repr: a.data.repr
         }, { label: `Transparency (${transparency.layers.length} Layers)` });
     },
     update({ a, b, newParams, oldParams }) {
-        const structure = b.data.info as Structure;
-        if (a.data.sourceData !== structure) return StateTransformer.UpdateResult.Recreate;
+        const info = b.data.info as { structure: Structure, geometryVersion: number };
+        const newStructure = a.data.sourceData;
+        if (newStructure !== info.structure) return StateTransformer.UpdateResult.Recreate;
         if (a.data.repr !== b.data.repr) return StateTransformer.UpdateResult.Recreate;
 
+        const newGeometryVersion = a.data.repr.geometryVersion;
+        // smoothing needs to be re-calculated when geometry changes
+        if (newGeometryVersion !== info.geometryVersion && hasColorSmoothingProp(a.data.repr.props)) return StateTransformer.UpdateResult.Unchanged;
+
         const oldTransparency = b.data.state.transparency!;
-        const newTransparency = Transparency.ofBundle(newParams.layers, structure);
+        const newTransparency = Transparency.ofBundle(newParams.layers, newStructure);
         if (Transparency.areEqual(oldTransparency, newTransparency)) return StateTransformer.UpdateResult.Unchanged;
 
+        info.geometryVersion = newGeometryVersion;
         b.data.state.transparency = newTransparency;
         b.data.repr = a.data.repr;
         b.label = `Transparency (${newTransparency.layers.length} Layers)`;

+ 2 - 2
src/mol-repr/structure/complex-representation.ts

@@ -106,12 +106,12 @@ export function ComplexRepresentation<P extends StructureParams>(label: string,
         if (state.overpaint !== undefined && visual) {
             // Remap loci from equivalent structure to the current structure
             const remappedOverpaint = Overpaint.remap(state.overpaint, _structure);
-            visual.setOverpaint(remappedOverpaint);
+            visual.setOverpaint(remappedOverpaint, webgl);
         }
         if (state.transparency !== undefined && visual) {
             // Remap loci from equivalent structure to the current structure
             const remappedTransparency = Transparency.remap(state.transparency, _structure);
-            visual.setTransparency(remappedTransparency);
+            visual.setTransparency(remappedTransparency, webgl);
         }
         if (state.clipping !== undefined && visual) {
             // Remap loci from equivalent structure to the current structure

+ 6 - 4
src/mol-repr/structure/complex-visual.ts

@@ -258,11 +258,13 @@ export function ComplexVisual<G extends Geometry, P extends StructureParams & Ge
         setTransform(matrix?: Mat4, instanceMatrices?: Float32Array | null) {
             Visual.setTransform(renderObject, matrix, instanceMatrices);
         },
-        setOverpaint(overpaint: Overpaint) {
-            Visual.setOverpaint(renderObject, overpaint, lociApply, true);
+        setOverpaint(overpaint: Overpaint, webgl?: WebGLContext) {
+            const smoothing = { geometry, props: currentProps, webgl };
+            Visual.setOverpaint(renderObject, overpaint, lociApply, true, smoothing);
         },
-        setTransparency(transparency: Transparency) {
-            Visual.setTransparency(renderObject, transparency, lociApply, true);
+        setTransparency(transparency: Transparency, webgl?: WebGLContext) {
+            const smoothing = { geometry, props: currentProps, webgl };
+            Visual.setTransparency(renderObject, transparency, lociApply, true, smoothing);
         },
         setClipping(clipping: Clipping) {
             Visual.setClipping(renderObject, clipping, lociApply, true);

+ 2 - 2
src/mol-repr/structure/units-representation.ts

@@ -223,8 +223,8 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct
         if (visible !== undefined) visual.setVisibility(visible);
         if (alphaFactor !== undefined) visual.setAlphaFactor(alphaFactor);
         if (pickable !== undefined) visual.setPickable(pickable);
-        if (overpaint !== undefined) visual.setOverpaint(overpaint);
-        if (transparency !== undefined) visual.setTransparency(transparency);
+        if (overpaint !== undefined) visual.setOverpaint(overpaint, webgl);
+        if (transparency !== undefined) visual.setTransparency(transparency, webgl);
         if (clipping !== undefined) visual.setClipping(clipping);
         if (transform !== undefined) visual.setTransform(transform);
         if (unitTransforms !== undefined) {

+ 6 - 4
src/mol-repr/structure/units-visual.ts

@@ -323,11 +323,13 @@ export function UnitsVisual<G extends Geometry, P extends StructureParams & Geom
         setTransform(matrix?: Mat4, instanceMatrices?: Float32Array | null) {
             Visual.setTransform(renderObject, matrix, instanceMatrices);
         },
-        setOverpaint(overpaint: Overpaint) {
-            Visual.setOverpaint(renderObject, overpaint, lociApply, true);
+        setOverpaint(overpaint: Overpaint, webgl?: WebGLContext) {
+            const smoothing = { geometry, props: currentProps, webgl };
+            Visual.setOverpaint(renderObject, overpaint, lociApply, true, smoothing);
         },
-        setTransparency(transparency: Transparency) {
-            Visual.setTransparency(renderObject, transparency, lociApply, true);
+        setTransparency(transparency: Transparency, webgl?: WebGLContext) {
+            const smoothing = { geometry, props: currentProps, webgl };
+            Visual.setTransparency(renderObject, transparency, lociApply, true, smoothing);
         },
         setClipping(clipping: Clipping) {
             Visual.setClipping(renderObject, clipping, lociApply, true);

+ 7 - 5
src/mol-repr/structure/visual/gaussian-surface-mesh.ts

@@ -23,7 +23,9 @@ import { WebGLContext } from '../../../mol-gl/webgl/context';
 import { MeshValues } from '../../../mol-gl/renderable/mesh';
 import { TextureMeshValues } from '../../../mol-gl/renderable/texture-mesh';
 import { Texture } from '../../../mol-gl/webgl/texture';
-import { applyMeshColorSmoothing, applyTextureMeshColorSmoothing, ColorSmoothingParams, getColorSmoothingProps } from './util/color';
+import { applyMeshColorSmoothing } from '../../../mol-geo/geometry/mesh/color-smoothing';
+import { applyTextureMeshColorSmoothing } from '../../../mol-geo/geometry/texture-mesh/color-smoothing';
+import { ColorSmoothingParams, getColorSmoothingProps } from '../../../mol-geo/geometry/base';
 
 const SharedParams = {
     ...GaussianDensityParams,
@@ -131,7 +133,7 @@ export function GaussianSurfaceMeshVisual(materialId: number): UnitsVisual<Gauss
         },
         processValues: (values: MeshValues, geometry: Mesh, props: PD.Values<GaussianSurfaceMeshParams>, theme: Theme, webgl?: WebGLContext) => {
             const { resolution, colorTexture } = geometry.meta as GaussianSurfaceMeta;
-            const csp = getColorSmoothingProps(props, theme, resolution);
+            const csp = getColorSmoothingProps(props.smoothColors, theme.color.preferSmoothing, resolution);
             if (csp) {
                 applyMeshColorSmoothing(values, csp.resolution, csp.stride, webgl, colorTexture);
                 (geometry.meta.colorTexture as GaussianSurfaceMeta['colorTexture']) = values.tColorGrid.ref.value;
@@ -191,7 +193,7 @@ export function StructureGaussianSurfaceMeshVisual(materialId: number): ComplexV
         },
         processValues: (values: MeshValues, geometry: Mesh, props: PD.Values<GaussianSurfaceMeshParams>, theme: Theme, webgl?: WebGLContext) => {
             const { resolution, colorTexture } = geometry.meta as GaussianSurfaceMeta;
-            const csp = getColorSmoothingProps(props, theme, resolution);
+            const csp = getColorSmoothingProps(props.smoothColors, theme.color.preferSmoothing, resolution);
             if (csp) {
                 applyMeshColorSmoothing(values, csp.resolution, csp.stride, webgl, colorTexture);
                 (geometry.meta.colorTexture as GaussianSurfaceMeta['colorTexture']) = values.tColorGrid.ref.value;
@@ -264,7 +266,7 @@ export function GaussianSurfaceTextureMeshVisual(materialId: number): UnitsVisua
         },
         processValues: (values: TextureMeshValues, geometry: TextureMesh, props: PD.Values<GaussianSurfaceMeshParams>, theme: Theme, webgl?: WebGLContext) => {
             const { resolution, colorTexture } = geometry.meta as GaussianSurfaceMeta;
-            const csp = getColorSmoothingProps(props, theme, resolution);
+            const csp = getColorSmoothingProps(props.smoothColors, theme.color.preferSmoothing, resolution);
             if (csp && webgl) {
                 applyTextureMeshColorSmoothing(values, csp.resolution, csp.stride, webgl, colorTexture);
                 (geometry.meta as GaussianSurfaceMeta).colorTexture = values.tColorGrid.ref.value;
@@ -340,7 +342,7 @@ export function StructureGaussianSurfaceTextureMeshVisual(materialId: number): C
         },
         processValues: (values: TextureMeshValues, geometry: TextureMesh, props: PD.Values<GaussianSurfaceMeshParams>, theme: Theme, webgl?: WebGLContext) => {
             const { resolution, colorTexture } = geometry.meta as GaussianSurfaceMeta;
-            const csp = getColorSmoothingProps(props, theme, resolution);
+            const csp = getColorSmoothingProps(props.smoothColors, theme.color.preferSmoothing, resolution);
             if (csp && webgl) {
                 applyTextureMeshColorSmoothing(values, csp.resolution, csp.stride, webgl, colorTexture);
                 (geometry.meta as GaussianSurfaceMeta).colorTexture = values.tColorGrid.ref.value;

+ 3 - 2
src/mol-repr/structure/visual/molecular-surface-mesh.ts

@@ -20,7 +20,8 @@ import { Sphere3D } from '../../../mol-math/geometry';
 import { MeshValues } from '../../../mol-gl/renderable/mesh';
 import { Texture } from '../../../mol-gl/webgl/texture';
 import { WebGLContext } from '../../../mol-gl/webgl/context';
-import { applyMeshColorSmoothing, ColorSmoothingParams, getColorSmoothingProps } from './util/color';
+import { applyMeshColorSmoothing } from '../../../mol-geo/geometry/mesh/color-smoothing';
+import { ColorSmoothingParams, getColorSmoothingProps } from '../../../mol-geo/geometry/base';
 
 export const MolecularSurfaceMeshParams = {
     ...UnitsMeshParams,
@@ -87,7 +88,7 @@ export function MolecularSurfaceMeshVisual(materialId: number): UnitsVisual<Mole
         },
         processValues: (values: MeshValues, geometry: Mesh, props: PD.Values<MolecularSurfaceMeshParams>, theme: Theme, webgl?: WebGLContext) => {
             const { resolution, colorTexture } = geometry.meta as MolecularSurfaceMeta;
-            const csp = getColorSmoothingProps(props, theme, resolution);
+            const csp = getColorSmoothingProps(props.smoothColors, theme.color.preferSmoothing, resolution);
             if (csp) {
                 applyMeshColorSmoothing(values, csp.resolution, csp.stride, webgl, colorTexture);
                 (geometry.meta as MolecularSurfaceMeta).colorTexture = values.tColorGrid.ref.value;

+ 0 - 104
src/mol-repr/structure/visual/util/color.ts

@@ -1,104 +0,0 @@
-/**
- * Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author Alexander Rose <alexander.rose@weirdbyte.de>
- */
-
-import { calcMeshColorSmoothing } from '../../../../mol-geo/geometry/mesh/color-smoothing';
-import { calcTextureMeshColorSmoothing } from '../../../../mol-geo/geometry/texture-mesh/color-smoothing';
-import { MeshValues } from '../../../../mol-gl/renderable/mesh';
-import { TextureMeshValues } from '../../../../mol-gl/renderable/texture-mesh';
-import { WebGLContext } from '../../../../mol-gl/webgl/context';
-import { Texture } from '../../../../mol-gl/webgl/texture';
-import { smoothstep } from '../../../../mol-math/interpolate';
-import { Theme } from '../../../../mol-theme/theme';
-import { ValueCell } from '../../../../mol-util';
-import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
-
-export const ColorSmoothingParams = {
-    smoothColors: PD.MappedStatic('auto', {
-        auto: PD.Group({}),
-        on: PD.Group({
-            resolutionFactor: PD.Numeric(2, { min: 0.5, max: 6, step: 0.1 }),
-            sampleStride: PD.Numeric(3, { min: 1, max: 12, step: 1 }),
-        }),
-        off: PD.Group({})
-    }),
-};
-export type ColorSmoothingParams = typeof ColorSmoothingParams
-
-export function getColorSmoothingProps(props: PD.Values<ColorSmoothingParams>, theme: Theme, resolution?: number) {
-    if ((props.smoothColors.name === 'on' || (props.smoothColors.name === 'auto' && theme.color.preferSmoothing)) && resolution && resolution < 3) {
-        let stride = 3;
-        if (props.smoothColors.name === 'on') {
-            resolution *= props.smoothColors.params.resolutionFactor;
-            stride = props.smoothColors.params.sampleStride;
-        } else {
-            // https://graphtoy.com/?f1(x,t)=(2-smoothstep(0,1.1,x))*x&coords=0.7,0.6,1.8
-            resolution *= 2 - smoothstep(0, 1.1, resolution);
-            resolution = Math.max(0.5, resolution);
-            if (resolution > 1.2) stride = 2;
-        }
-        return { resolution, stride };
-    };
-}
-
-function isSupportedColorType(x: string): x is 'group' | 'groupInstance' {
-    return x === 'group' || x === 'groupInstance';
-}
-
-export function applyMeshColorSmoothing(values: MeshValues, resolution: number, stride: number, webgl?: WebGLContext, colorTexture?: Texture) {
-    if (!isSupportedColorType(values.dColorType.ref.value)) return;
-
-    const smoothingData = calcMeshColorSmoothing({
-        vertexCount: values.uVertexCount.ref.value,
-        instanceCount: values.uInstanceCount.ref.value,
-        groupCount: values.uGroupCount.ref.value,
-        transformBuffer: values.aTransform.ref.value,
-        instanceBuffer: values.aInstance.ref.value,
-        positionBuffer: values.aPosition.ref.value,
-        groupBuffer: values.aGroup.ref.value,
-        colorData: values.tColor.ref.value,
-        colorType: values.dColorType.ref.value,
-        boundingSphere: values.boundingSphere.ref.value,
-        invariantBoundingSphere: values.invariantBoundingSphere.ref.value,
-    }, resolution, stride, webgl, colorTexture);
-
-    if (smoothingData.kind === 'volume') {
-        ValueCell.updateIfChanged(values.dColorType, smoothingData.type);
-        ValueCell.update(values.tColorGrid, smoothingData.texture);
-        ValueCell.update(values.uColorTexDim, smoothingData.gridTexDim);
-        ValueCell.update(values.uColorGridDim, smoothingData.gridDim);
-        ValueCell.update(values.uColorGridTransform, smoothingData.gridTransform);
-    } else if (smoothingData.kind === 'vertex') {
-        ValueCell.updateIfChanged(values.dColorType, smoothingData.type);
-        ValueCell.update(values.tColor, smoothingData.texture);
-        ValueCell.update(values.uColorTexDim, smoothingData.texDim);
-    }
-}
-
-export function applyTextureMeshColorSmoothing(values: TextureMeshValues, resolution: number, stride: number, webgl: WebGLContext, colorTexture?: Texture) {
-    if (!isSupportedColorType(values.dColorType.ref.value)) return;
-
-    stride *= 3; // triple because TextureMesh is never indexed (no elements buffer)
-
-    const smoothingData = calcTextureMeshColorSmoothing({
-        vertexCount: values.uVertexCount.ref.value,
-        instanceCount: values.uInstanceCount.ref.value,
-        groupCount: values.uGroupCount.ref.value,
-        transformBuffer: values.aTransform.ref.value,
-        instanceBuffer: values.aInstance.ref.value,
-        positionTexture: values.tPosition.ref.value,
-        groupTexture: values.tGroup.ref.value,
-        colorData: values.tColor.ref.value,
-        colorType: values.dColorType.ref.value,
-        boundingSphere: values.boundingSphere.ref.value,
-        invariantBoundingSphere: values.invariantBoundingSphere.ref.value,
-    }, resolution, stride, webgl, colorTexture);
-
-    ValueCell.updateIfChanged(values.dColorType, smoothingData.type);
-    ValueCell.update(values.tColorGrid, smoothingData.texture);
-    ValueCell.update(values.uColorTexDim, smoothingData.gridTexDim);
-    ValueCell.update(values.uColorGridDim, smoothingData.gridDim);
-    ValueCell.update(values.uColorGridTransform, smoothingData.gridTransform);
-}

+ 67 - 6
src/mol-repr/visual.ts

@@ -24,6 +24,11 @@ import { createTransparency, clearTransparency, applyTransparencyValue, getTrans
 import { Clipping } from '../mol-theme/clipping';
 import { createClipping, applyClippingGroups, clearClipping } from '../mol-geo/geometry/clipping-data';
 import { getMarkersAverage } from '../mol-geo/geometry/marker-data';
+import { Texture } from '../mol-gl/webgl/texture';
+import { Geometry } from '../mol-geo/geometry/geometry';
+import { getColorSmoothingProps, hasColorSmoothingProp } from '../mol-geo/geometry/base';
+import { applyMeshOverpaintSmoothing, applyMeshTransparencySmoothing } from '../mol-geo/geometry/mesh/color-smoothing';
+import { applyTextureMeshOverpaintSmoothing, applyTextureMeshTransparencySmoothing } from '../mol-geo/geometry/texture-mesh/color-smoothing';
 
 export interface VisualContext {
     readonly runtime: RuntimeContext
@@ -44,8 +49,8 @@ interface Visual<D, P extends PD.Params> {
     setPickable: (pickable: boolean) => void
     setColorOnly: (colorOnly: boolean) => void
     setTransform: (matrix?: Mat4, instanceMatrices?: Float32Array | null) => void
-    setOverpaint: (overpaint: Overpaint) => void
-    setTransparency: (transparency: Transparency) => void
+    setOverpaint: (overpaint: Overpaint, webgl?: WebGLContext) => void
+    setTransparency: (transparency: Transparency, webgl?: WebGLContext) => void
     setClipping: (clipping: Clipping) => void
     destroy: () => void
     mustRecreate?: (data: D, props: PD.Values<P>, webgl?: WebGLContext) => boolean
@@ -134,10 +139,22 @@ namespace Visual {
         return changed;
     }
 
-    export function setOverpaint(renderObject: GraphicsRenderObject | undefined, overpaint: Overpaint, lociApply: LociApply, clear: boolean) {
+    type SurfaceMeta = {
+        resolution?: number
+        overpaintTexture?: Texture
+        transparencyTexture?: Texture
+    }
+
+    type SmoothingContext = {
+        geometry: Geometry,
+        props: PD.Values<any>,
+        webgl?: WebGLContext
+    }
+
+    export function setOverpaint(renderObject: GraphicsRenderObject | undefined, overpaint: Overpaint, lociApply: LociApply, clear: boolean, smoothing?: SmoothingContext) {
         if (!renderObject) return;
 
-        const { tOverpaint, uGroupCount, instanceCount } = renderObject.values;
+        const { tOverpaint, dOverpaintType, uGroupCount, instanceCount } = renderObject.values;
         const count = uGroupCount.ref.value * instanceCount.ref.value;
 
         // ensure texture has right size
@@ -159,12 +176,34 @@ namespace Visual {
             lociApply(loci, apply, false);
         }
         ValueCell.update(tOverpaint, tOverpaint.ref.value);
+        ValueCell.updateIfChanged(dOverpaintType, 'groupInstance');
+
+        if (overpaint.layers.length === 0) return;
+
+        if (smoothing && hasColorSmoothingProp(smoothing.props)) {
+            const { geometry, props, webgl } = smoothing;
+            if (geometry.kind === 'mesh') {
+                const { resolution, overpaintTexture } = geometry.meta as SurfaceMeta;
+                const csp = getColorSmoothingProps(props.smoothColors, true, resolution);
+                if (csp) {
+                    applyMeshOverpaintSmoothing(renderObject.values as any, csp.resolution, csp.stride, webgl, overpaintTexture);
+                    (geometry.meta as SurfaceMeta).overpaintTexture = renderObject.values.tOverpaintGrid.ref.value;
+                }
+            } else if (webgl && geometry.kind === 'texture-mesh') {
+                const { resolution, overpaintTexture } = geometry.meta as SurfaceMeta;
+                const csp = getColorSmoothingProps(props.smoothColors, true, resolution);
+                if (csp) {
+                    applyTextureMeshOverpaintSmoothing(renderObject.values as any, csp.resolution, csp.stride, webgl, overpaintTexture);
+                    (geometry.meta as SurfaceMeta).overpaintTexture = renderObject.values.tOverpaintGrid.ref.value;
+                }
+            }
+        }
     }
 
-    export function setTransparency(renderObject: GraphicsRenderObject | undefined, transparency: Transparency, lociApply: LociApply, clear: boolean) {
+    export function setTransparency(renderObject: GraphicsRenderObject | undefined, transparency: Transparency, lociApply: LociApply, clear: boolean, smoothing?: SmoothingContext) {
         if (!renderObject) return;
 
-        const { tTransparency, transparencyAverage, uGroupCount, instanceCount } = renderObject.values;
+        const { tTransparency, dTransparencyType, transparencyAverage, uGroupCount, instanceCount } = renderObject.values;
         const count = uGroupCount.ref.value * instanceCount.ref.value;
 
         // ensure texture has right size and variant
@@ -185,6 +224,28 @@ namespace Visual {
         }
         ValueCell.update(tTransparency, tTransparency.ref.value);
         ValueCell.updateIfChanged(transparencyAverage, getTransparencyAverage(array, count));
+        ValueCell.updateIfChanged(dTransparencyType, 'groupInstance');
+
+        if (transparency.layers.length === 0) return;
+
+        if (smoothing && hasColorSmoothingProp(smoothing.props)) {
+            const { geometry, props, webgl } = smoothing;
+            if (geometry.kind === 'mesh') {
+                const { resolution, transparencyTexture } = geometry.meta as SurfaceMeta;
+                const csp = getColorSmoothingProps(props.smoothColors, true, resolution);
+                if (csp) {
+                    applyMeshTransparencySmoothing(renderObject.values as any, csp.resolution, csp.stride, webgl, transparencyTexture);
+                    (geometry.meta as SurfaceMeta).transparencyTexture = renderObject.values.tTransparencyGrid.ref.value;
+                }
+            } else if (webgl && geometry.kind === 'texture-mesh') {
+                const { resolution, transparencyTexture } = geometry.meta as SurfaceMeta;
+                const csp = getColorSmoothingProps(props.smoothColors, true, resolution);
+                if (csp) {
+                    applyTextureMeshTransparencySmoothing(renderObject.values as any, csp.resolution, csp.stride, webgl, transparencyTexture);
+                    (geometry.meta as SurfaceMeta).transparencyTexture = renderObject.values.tTransparencyGrid.ref.value;
+                }
+            }
+        }
     }
 
     export function setClipping(renderObject: GraphicsRenderObject | undefined, clipping: Clipping, lociApply: LociApply, clear: boolean) {