Browse Source

wip, color support for direct volume isosurface

Alexander Rose 6 years ago
parent
commit
15ebfd611e

+ 24 - 19
src/mol-geo/geometry/direct-volume/direct-volume.ts

@@ -7,12 +7,17 @@
 import { RuntimeContext } from 'mol-task'
 import { ValueCell } from 'mol-util'
 import { Sphere3D, Box3D } from 'mol-math/geometry'
-import { paramDefaultValues, RangeParam, BooleanParam, SelectParam, TextParam } from 'mol-view/parameter';
+import { paramDefaultValues, RangeParam, SelectParam, TextParam } from 'mol-view/parameter';
 import { DirectVolume2dValues, DirectVolumeBaseValues, DirectVolume3dValues } from 'mol-gl/renderable/direct-volume';
 import { Vec3, Vec2, Mat4 } from 'mol-math/linear-algebra';
 import { Box } from '../../primitive/box';
 import { getControlPointsFromString, createTransferFunctionTexture } from './transfer-function';
 import { Texture } from 'mol-gl/webgl/texture';
+import { LocationIterator } from 'mol-geo/util/location-iterator';
+import { TransformData } from '../transform-data';
+import { createColors } from '../color-data';
+import { createMarkers } from '../marker-data';
+import { Geometry } from '../geometry';
 
 const VolumeBox = Box()
 const RenderModeOptions = [['isosurface', 'Isosurface'], ['volume', 'Volume']] as [string, string][]
@@ -29,10 +34,7 @@ interface DirectVolumeBase {
 }
 
 const BaseParams = {
-    alpha: RangeParam('Opacity', '', 1, 0, 1, 0.01),
-    visible: BooleanParam('Visible', '', true),
-    depthMask: BooleanParam('Depth Mask', '', true),
-    useFog: BooleanParam('Use Fog', '', false),
+    ...Geometry.Params,
     isoValueAbsolute: RangeParam('Iso Value Absolute', '', 0.22, -1, 1, 0.01),
     isoValueRelative: RangeParam('Iso Value Relative', '', 2, -10, 10, 0.1),
     renderMode: SelectParam('Render Mode', '', 'volume', RenderModeOptions),
@@ -41,31 +43,35 @@ const BaseParams = {
 const DefaultBaseProps = paramDefaultValues(BaseParams)
 type BaseProps = typeof DefaultBaseProps
 
-async function createBaseValues(ctx: RuntimeContext, directVolume: DirectVolumeBase, props: BaseProps): Promise<DirectVolumeBaseValues> {
-    const { bboxSize, bboxMin, bboxMax, gridDimension, transform } = directVolume
+async function createBaseValues(ctx: RuntimeContext, directVolume: DirectVolumeBase, transform: TransformData, locationIt: LocationIterator, props: BaseProps): Promise<DirectVolumeBaseValues> {
+    const { instanceCount, groupCount } = locationIt
+    const color = await createColors(ctx, locationIt, props)
+    const marker = createMarkers(instanceCount * groupCount)
+
+    const counts = { drawCount: VolumeBox.indices.length, groupCount, instanceCount }
+
+    const { bboxSize, bboxMin, bboxMax, gridDimension, transform: gridTransform } = directVolume
 
     const controlPoints = getControlPointsFromString(props.controlPoints)
     const transferTex = createTransferFunctionTexture(controlPoints)
 
     const maxSteps = Math.ceil(Vec3.magnitude(gridDimension.ref.value)) * 2
-    console.log('maxSteps', maxSteps)
 
     return {
-        drawCount: ValueCell.create(VolumeBox.indices.length),
-        instanceCount: ValueCell.create(1),
+        ...color,
+        ...marker,
+        ...transform,
+        ...Geometry.createValues(props, counts),
 
         aPosition: ValueCell.create(VolumeBox.vertices as Float32Array),
         elements: ValueCell.create(VolumeBox.indices as Uint32Array),
 
-        uAlpha: ValueCell.create(props.alpha),
-        dUseFog: ValueCell.create(props.useFog),
-
         uIsoValue: ValueCell.create(props.isoValueAbsolute),
         uBboxMin: bboxMin,
         uBboxMax: bboxMax,
         uBboxSize: bboxSize,
         dMaxSteps: ValueCell.create(maxSteps),
-        uTransform: transform,
+        uTransform: gridTransform,
         uGridDim: gridDimension,
         dRenderMode: ValueCell.create(props.renderMode),
         tTransferTex: transferTex,
@@ -73,7 +79,6 @@ async function createBaseValues(ctx: RuntimeContext, directVolume: DirectVolumeB
 }
 
 function updateBaseValues(values: DirectVolumeBaseValues, props: BaseProps) {
-    console.log('DirectVolumeBaseValues', props, values)
     ValueCell.updateIfChanged(values.uIsoValue, props.isoValueAbsolute)
     ValueCell.updateIfChanged(values.uAlpha, props.alpha)
     ValueCell.updateIfChanged(values.dUseFog, props.useFog)
@@ -123,11 +128,11 @@ export namespace DirectVolume2d {
     export const DefaultProps = paramDefaultValues(Params)
     export type Props = typeof DefaultProps
 
-    export async function createValues(ctx: RuntimeContext, directVolume: DirectVolume2d, props: Props): Promise<DirectVolume2dValues> {
+    export async function createValues(ctx: RuntimeContext, directVolume: DirectVolume2d, transform: TransformData, locationIt: LocationIterator, props: Props): Promise<DirectVolume2dValues> {
         const { gridTexture, gridTextureDim } = directVolume
 
         return {
-            ...await createBaseValues(ctx, directVolume, props),
+            ...await createBaseValues(ctx, directVolume, transform, locationIt, props),
             dGridTexType: ValueCell.create('2d'),
             uGridTexDim: gridTextureDim,
             tGridTex: gridTexture,
@@ -176,11 +181,11 @@ export namespace DirectVolume3d {
     export const DefaultProps = paramDefaultValues(Params)
     export type Props = typeof DefaultProps
 
-    export async function createValues(ctx: RuntimeContext, directVolume: DirectVolume3d, props: Props): Promise<DirectVolume3dValues> {
+    export async function createValues(ctx: RuntimeContext, directVolume: DirectVolume3d, transform: TransformData, locationIt: LocationIterator, props: Props): Promise<DirectVolume3dValues> {
         const { gridTexture } = directVolume
 
         return {
-            ...await createBaseValues(ctx, directVolume, props),
+            ...await createBaseValues(ctx, directVolume, transform, locationIt, props),
             dGridTexType: ValueCell.create('3d'),
             tGridTex: gridTexture,
         }

+ 1 - 1
src/mol-geo/geometry/picking.ts

@@ -11,7 +11,7 @@ function decodeFloatRGBA(r: number, g: number, b: number) {
     return r * 256 * 256 + g * 256 + b
 }
 
-export function decodeIdRGBA(r: number, g: number, b: number) {
+export function decodeIdRGB(r: number, g: number, b: number) {
     return decodeFloatRGBA(r, g, b) - 1
 }
 

+ 10 - 12
src/mol-geo/representation/structure/units-visual.ts

@@ -555,8 +555,6 @@ export function UnitsDirectVolumeVisual<P extends UnitsDirectVolumeProps>(builde
                 DirectVolume2d.createEmpty(directVolume as DirectVolume2d) :
                 DirectVolume3d.createEmpty(directVolume as DirectVolume3d))
 
-        console.log('directVolume', directVolume)
-
         // TODO create empty location iterator when not in unitKinds
         locationIt = createLocationIterator(group)
         renderObject = await createUnitsDirectVolumeRenderObject(ctx, group, directVolume, locationIt, currentProps)
@@ -589,13 +587,13 @@ export function UnitsDirectVolumeVisual<P extends UnitsDirectVolumeProps>(builde
 
         //
 
-        // if (updateState.updateTransform) {
-        //     locationIt = createLocationIterator(currentGroup)
-        //     const { instanceCount, groupCount } = locationIt
-        //     createUnitsTransform(currentGroup, renderObject.values)
-        //     createMarkers(instanceCount * groupCount, renderObject.values)
-        //     updateState.updateColor = true
-        // }
+        if (updateState.updateTransform) {
+            locationIt = createLocationIterator(currentGroup)
+            const { instanceCount, groupCount } = locationIt
+            createUnitsTransform(currentGroup, renderObject.values)
+            createMarkers(instanceCount * groupCount, renderObject.values)
+            updateState.updateColor = true
+        }
 
         if (updateState.createGeometry) {
             directVolume = includesUnitKind(newProps.unitKinds, unit)
@@ -606,9 +604,9 @@ export function UnitsDirectVolumeVisual<P extends UnitsDirectVolumeProps>(builde
             updateState.updateColor = true
         }
 
-        // if (updateState.updateColor) {
-        //     await createColors(ctx, locationIt, newProps, renderObject.values)
-        // }
+        if (updateState.updateColor) {
+            await createColors(ctx, locationIt, newProps, renderObject.values)
+        }
 
         if (renderObject.type === 'direct-volume-2d') {
             DirectVolume2d.updateValues(renderObject.values, newProps)

+ 2 - 44
src/mol-geo/representation/structure/visual/gaussian-surface-mesh.ts

@@ -4,7 +4,7 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { Unit, Structure, StructureElement, ElementIndex } from 'mol-model/structure';
+import { Unit, Structure } from 'mol-model/structure';
 import { UnitsVisual, VisualUpdateState } from '..';
 import { RuntimeContext } from 'mol-task'
 import { Mesh } from '../../../geometry/mesh/mesh';
@@ -13,11 +13,9 @@ import { StructureElementIterator, getElementLoci, markElement } from './util/el
 import { computeMarchingCubesMesh } from '../../../util/marching-cubes/algorithm';
 import { GaussianDensityProps, GaussianDensityParams } from 'mol-model/structure/structure/unit/gaussian-density';
 import { paramDefaultValues } from 'mol-view/parameter';
-import { SizeTheme } from 'mol-view/theme/size';
-import { OrderedSet } from 'mol-data/int';
 
 async function createGaussianSurfaceMesh(ctx: RuntimeContext, unit: Unit, structure: Structure, props: GaussianDensityProps, mesh?: Mesh): Promise<Mesh> {
-    const { smoothness, radiusOffset } = props
+    const { smoothness } = props
     const { transform, field, idField } = await unit.computeGaussianDensity(props, ctx)
 
     const params = {
@@ -28,46 +26,6 @@ async function createGaussianSurfaceMesh(ctx: RuntimeContext, unit: Unit, struct
     const surface = await computeMarchingCubesMesh(params, mesh).runAsChild(ctx)
 
     Mesh.transformImmediate(surface, transform)
-
-    // if (props.useGpu) {
-    //     console.time('find max element radius')
-    //     const { elements } = unit
-    //     const n = OrderedSet.size(elements)
-    //     const l = StructureElement.create(unit)
-    //     const sizeTheme = SizeTheme({ name: 'physical' })
-    //     const radius = (index: number) => {
-    //         l.element = index as ElementIndex
-    //         return sizeTheme.size(l)
-    //     }
-    //     let maxRadius = 0
-    //     for (let i = 0; i < n; ++i) {
-    //         const r = radius(OrderedSet.getAt(elements, i)) + radiusOffset
-    //         if (maxRadius < r) maxRadius = r
-    //     }
-    //     console.timeEnd('find max element radius')
-
-    //     console.time('find closest element for vertices')
-    //     const { lookup3d } = unit
-
-    //     const { vertexCount, vertexBuffer, groupBuffer } = surface
-    //     const vertices = vertexBuffer.ref.value
-    //     const groups = groupBuffer.ref.value
-    //     for (let i = 0; i < vertexCount; ++i) {
-    //         const r = lookup3d.find(vertices[i * 3], vertices[i * 3 + 1], vertices[i * 3 + 2], maxRadius * 2)
-    //         let minDsq = Infinity
-    //         let group = 0
-    //         for (let j = 0, jl = r.count; j < jl; ++j) {
-    //             const dSq = r.squaredDistances[j]
-    //             if (dSq < minDsq) {
-    //                 minDsq = dSq
-    //                 group = r.indices[j]
-    //             }
-    //         }
-    //         groups[i] = group
-    //     }
-    //     console.timeEnd('find closest element for vertices')
-    // }
-
     Mesh.computeNormalsImmediate(surface)
     Mesh.uniformTriangleGroup(surface)
 

+ 3 - 4
src/mol-geo/representation/structure/visual/util/common.ts

@@ -73,10 +73,9 @@ export async function createUnitsLinesRenderObject(ctx: RuntimeContext, group: U
 type StructureDirectVolumeProps = DirectVolume2d.Props & DirectVolume3d.Props & StructureProps
 
 export async function createUnitsDirectVolumeRenderObject(ctx: RuntimeContext, group: Unit.SymmetryGroup, directVolume: DirectVolume2d | DirectVolume3d, locationIt: LocationIterator, props: StructureDirectVolumeProps) {
-    // TODO transform support
-    // const transform = createUnitsTransform(group)
+    const transform = createUnitsTransform(group)
     const state = createRenderableState(props)
     return directVolume.kind === 'direct-volume-2d' ?
-        createDirectVolume2dRenderObject(await DirectVolume2d.createValues(ctx, directVolume, props), state) :
-        createDirectVolume3dRenderObject(await DirectVolume3d.createValues(ctx, directVolume, props), state)
+        createDirectVolume2dRenderObject(await DirectVolume2d.createValues(ctx, directVolume, transform, locationIt, props), state) :
+        createDirectVolume3dRenderObject(await DirectVolume3d.createValues(ctx, directVolume, transform, locationIt, props), state)
 }

+ 7 - 2
src/mol-geo/representation/volume/direct-volume.ts

@@ -19,6 +19,9 @@ import { Box3D } from 'mol-math/geometry';
 import { Context } from 'mol-gl/webgl/context';
 import { DirectVolume3dValues, DirectVolume2dValues } from 'mol-gl/renderable/direct-volume';
 import { createTexture } from 'mol-gl/webgl/texture';
+import { LocationIterator } from 'mol-geo/util/location-iterator';
+import { NullLocation } from 'mol-model/location';
+import { createIdentityTransform } from 'mol-geo/geometry/transform-data';
 
 function getBoundingBox(gridDimension: Vec3, transform: Mat4) {
     const bbox = Box3D.empty()
@@ -160,15 +163,17 @@ export function DirectVolumeVisual(): VolumeVisual<DirectVolumeProps> {
         }
 
         const state = createRenderableState(currentProps)
+        const locationIt = LocationIterator(1, 1, () => NullLocation)
+        const transform = createIdentityTransform()
 
         if (webgl.isWebGL2) {
             console.log('creating 3d volume')
             directVolume = await createDirectVolume3d(ctx, webgl, volume, directVolume as DirectVolume3d)
-            const values = await DirectVolume3d.createValues(ctx, directVolume as DirectVolume3d, currentProps)
+            const values = await DirectVolume3d.createValues(ctx, directVolume as DirectVolume3d, transform, locationIt, currentProps)
             renderObject = createDirectVolume3dRenderObject(values, state)
         } else {
             directVolume = await createDirectVolume2d(ctx, webgl, volume, directVolume as DirectVolume2d)
-            const values = await DirectVolume2d.createValues(ctx, directVolume as DirectVolume2d, currentProps)
+            const values = await DirectVolume2d.createValues(ctx, directVolume as DirectVolume2d, transform, locationIt, currentProps)
             renderObject = createDirectVolume2dRenderObject(values, state)
         }
     }

+ 15 - 0
src/mol-gl/renderable/direct-volume.ts

@@ -12,6 +12,21 @@ import { DirectVolumeShaderCode } from '../shader-code';
 import { ValueCell } from 'mol-util';
 
 export const DirectVolumeBaseSchema = {
+    aColor: AttributeSpec('float32', 3, 0), // TODO not used, just for type checking
+    uColor: UniformSpec('v3'),
+    uColorTexDim: UniformSpec('v2'),
+    tColor: TextureSpec('image-uint8', 'rgb', 'ubyte', 'nearest'),
+    dColorType: DefineSpec('string', ['uniform', 'instance', 'group', 'group_instance']),
+
+    uMarkerTexDim: UniformSpec('v2'),
+    tMarker: TextureSpec('image-uint8', 'alpha', 'ubyte', 'nearest'),
+
+    uInstanceCount: UniformSpec('i'),
+    uGroupCount: UniformSpec('i'),
+
+    aInstance: AttributeSpec('float32', 1, 1),
+    aTransform: AttributeSpec('float32', 16, 1),
+
     drawCount: ValueSpec('number'),
     instanceCount: ValueSpec('number'),
 

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

@@ -59,7 +59,7 @@ export function splitValues(schema: RenderableSchema, values: RenderableValues)
     const defineValues: DefineValues = {}
     const textureValues: TextureValues = {}
     const uniformValues: UniformValues = {}
-    Object.keys(values).forEach(k => {
+    Object.keys(schema).forEach(k => {
         if (schema[k].type === 'attribute') attributeValues[k] = values[k]
         if (schema[k].type === 'define') defineValues[k] = values[k]
         if (schema[k].type === 'texture') textureValues[k] = values[k]

+ 3 - 3
src/mol-gl/shader/chunks/assign-color-varying.glsl

@@ -7,9 +7,9 @@
 #elif defined(dColorType_groupInstance)
     vColor.rgb = readFromTexture(tColor, aInstance * float(uGroupCount) + aGroup, uColorTexDim).rgb;
 #elif defined(dColorType_objectPicking)
-    vColor = encodeIdRGBA(float(uObjectId));
+    vColor = vec4(encodeIdRGB(float(uObjectId)), 1.0);
 #elif defined(dColorType_instancePicking)
-    vColor = encodeIdRGBA(aInstance);
+    vColor = vec4(encodeIdRGB(aInstance), 1.0);
 #elif defined(dColorType_groupPicking)
-    vColor = encodeIdRGBA(aGroup);
+    vColor = vec4(encodeIdRGB(aGroup), 1.0);
 #endif

+ 1 - 1
src/mol-gl/shader/chunks/color-vert-params.glsl

@@ -9,5 +9,5 @@
     uniform sampler2D tColor;
 #elif defined(dColorType_objectPicking) || defined(dColorType_instancePicking) || defined(dColorType_groupPicking)
     varying vec4 vColor;
-    #pragma glslify: encodeIdRGBA = require(../utils/encode-id-rgba.glsl)
+    #pragma glslify: encodeIdRGB = require(../utils/encode-id-rgb.glsl)
 #endif

+ 73 - 45
src/mol-gl/shader/direct-volume.frag

@@ -9,6 +9,7 @@ precision highp float;
 
 varying vec3 unitCoord;
 varying vec3 origPos;
+varying float instance;
 
 uniform float uAlpha;
 uniform mat4 uInvView;
@@ -16,6 +17,15 @@ uniform float uIsoValue;
 uniform vec3 uGridDim;
 uniform sampler2D tTransferTex;
 
+uniform int uObjectId;
+uniform int uInstanceCount;
+uniform int uGroupCount;
+
+uniform vec3 uHighlightColor;
+uniform vec3 uSelectColor;
+uniform vec2 uMarkerTexDim;
+uniform sampler2D tMarker;
+
 #if defined(dGridTexType_2d)
     precision mediump sampler2D;
     uniform sampler2D tGridTex;
@@ -25,35 +35,34 @@ uniform sampler2D tTransferTex;
     uniform sampler3D tGridTex;
 #endif
 
-#if defined(dGridTexType_2d)
-    // TODO workaround due to some kind of GPU bug
-    float myMod(float a, float b) {
-        return a - b * float(int(a) / int(b));
-    }
-    float myDiv(float a, float b) {
-        return float(int(a) / int(b));
-    }
+#if defined(dColorType_uniform)
+    uniform vec3 uColor;
+#elif defined(dColorType_instance) || defined(dColorType_group) || defined(dColorType_groupInstance)
+    uniform vec2 uColorTexDim;
+    uniform sampler2D tColor;
+#endif
+
+#pragma glslify: readFromTexture = require(./utils/read-from-texture.glsl)
+#pragma glslify: encodeIdRGB = require(./utils/encode-id-rgb.glsl)
+#pragma glslify: decodeIdRGB = require(./utils/decode-id-rgb.glsl)
+#pragma glslify: texture3dFrom2dNearest = require(./utils/texture3d-from-2d-nearest.glsl)
+#pragma glslify: texture3dFrom2dLinear = require(./utils/texture3d-from-2d-linear.glsl)
 
+#if defined(dGridTexType_2d)
     vec4 textureVal(vec3 pos) {
-        float zSlice0 = floor(pos.z * uGridDim.z);
-        float column0 = myMod(zSlice0 * uGridDim.x, uGridTexDim.x) / uGridDim.x;
-        float row0 = floor(myDiv(zSlice0 * uGridDim.x, uGridTexDim.x));
-        vec2 coord0 = (vec2(column0 * uGridDim.x, row0 * uGridDim.y) + (pos.xy * uGridDim.xy)) / uGridTexDim;
-        vec4 color0 = texture2D(tGridTex, coord0);
-
-        float zSlice1 = zSlice0 + 1.0;
-        float column1 = myMod(zSlice1 * uGridDim.x, uGridTexDim.x) / uGridDim.x;
-        float row1 = floor(myDiv(zSlice1 * uGridDim.x, uGridTexDim.x));
-        vec2 coord1 = (vec2(column1 * uGridDim.x, row1 * uGridDim.y) + (pos.xy * uGridDim.xy)) / uGridTexDim;
-        vec4 color1 = texture2D(tGridTex, coord1);
-
-        float delta0 = abs((pos.z * uGridDim.z) - zSlice0);
-        return mix(color0, color1, delta0);
+        return texture3dFrom2dLinear(tGridTex, pos, uGridDim, uGridTexDim);
+    }
+    vec4 textureGroup(vec3 pos) {
+        vec3 nearestPos = floor(pos * uGridDim + 0.5) / uGridDim + 0.5 / uGridDim;
+        return texture3dFrom2dNearest(tGridTex, nearestPos, uGridDim, uGridTexDim);
     }
 #elif defined(dGridTexType_3d)
     vec4 textureVal(vec3 pos) {
         return texture(tGridTex, pos);
     }
+    vec4 textureGroup(vec3 pos) {
+        return texelFetch(tGridTex, ivec3(pos * uGridDim), 0);
+    }
 #endif
 
 vec4 transferFunction(float value) {
@@ -61,7 +70,6 @@ vec4 transferFunction(float value) {
 }
 
 const float gradOffset = 0.5;
-const vec3 color = vec3(0.45, 0.55, 0.8);
 
 vec4 raymarch(vec3 startLoc, vec3 step, vec3 viewDir) {
     vec3 scaleVol = vec3(1.0) / uGridDim;
@@ -75,6 +83,7 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 viewDir) {
         vec3 isoPos;
         float tmp;
 
+        vec3 color = vec3(0.45, 0.55, 0.8);
         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);
@@ -100,28 +109,47 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 viewDir) {
                 tmp = ((prevValue - uIsoValue) / ((prevValue - uIsoValue) - (value - uIsoValue)));
                 isoPos = mix(pos - step, pos, tmp);
 
-                // compute gradient by central differences
-                gradient.x = textureVal(isoPos - dx).a - textureVal(isoPos + dx).a;
-                gradient.y = textureVal(isoPos - dy).a - textureVal(isoPos + dy).a;
-                gradient.z = textureVal(isoPos - dz).a - textureVal(isoPos + dz).a;
-                gradient = normalize(gradient);
-
-                float d = float(dot(gradient, viewDir) > 0.0);
-                gradient = (2.0 * d - 1.0) * gradient;
-
-                src.rgb = color.rgb * abs(dot(gradient, viewDir));
-                src.a = uAlpha;
-
-                // draw interior darker
-                if( (prevValue - uIsoValue) > 0.0 ) {
-                    src.rgb *= 0.5;
-                }
-
-                src.rgb *= src.a;
-                dst = (1.0 - dst.a) * src + dst; // standard blending
-                if(dst.a >= 1.0) {
-                    break;
-                }
+                #if defined(dColorType_objectPicking)
+                    return vec4(encodeIdRGB(float(uObjectId)), 1.0);
+                #elif defined(dColorType_instancePicking)
+                    return vec4(encodeIdRGB(instance), 1.0);
+                #elif defined(dColorType_groupPicking)
+                    float group = floor(decodeIdRGB(textureGroup(isoPos).rgb) + 0.5);
+                    return vec4(encodeIdRGB(group), 1.0);
+                #else
+                    // compute gradient by central differences
+                    gradient.x = textureVal(isoPos - dx).a - textureVal(isoPos + dx).a;
+                    gradient.y = textureVal(isoPos - dy).a - textureVal(isoPos + dy).a;
+                    gradient.z = textureVal(isoPos - dz).a - textureVal(isoPos + dz).a;
+                    gradient = normalize(gradient);
+
+                    float d = float(dot(gradient, viewDir) > 0.0);
+                    gradient = (2.0 * d - 1.0) * gradient;
+
+                    #if defined(dColorType_instance)
+                        color = readFromTexture(tColor, instance, uColorTexDim).rgb;
+                    #elif defined(dColorType_group)
+                        float group = floor(decodeIdRGB(textureGroup(isoPos).rgb) + 0.5);
+                        color = readFromTexture(tColor, group, uColorTexDim).rgb;
+                    #elif defined(dColorType_groupInstance)
+                        float group = floor(decodeIdRGB(textureGroup(isoPos).rgb) + 0.5);
+                        color = readFromTexture(tColor, instance * float(uGroupCount) + group, uColorTexDim).rgb;
+                    #endif
+
+                    src.rgb = color * abs(dot(gradient, viewDir));
+                    src.a = uAlpha;
+
+                    // draw interior darker
+                    if( (prevValue - uIsoValue) > 0.0 ) {
+                        src.rgb *= 0.5;
+                    }
+
+                    src.rgb *= src.a;
+                    dst = (1.0 - dst.a) * src + dst; // standard blending
+                    if(dst.a >= 1.0) {
+                        break;
+                    }
+                #endif
             }
             prevValue = value;
         #endif

+ 4 - 0
src/mol-gl/shader/direct-volume.vert

@@ -8,9 +8,12 @@
 precision highp float;
 
 attribute vec3 aPosition;
+attribute mat4 aTransform;
+attribute float aInstance;
 
 varying vec3 unitCoord;
 varying vec3 origPos;
+varying float instance;
 
 uniform vec3 uBboxSize;
 uniform vec3 uBboxMin;
@@ -23,5 +26,6 @@ void main() {
     unitCoord = aPosition + vec3(0.5);
     vec4 mvPosition = uModelView * vec4(unitCoord * uBboxSize + uBboxMin, 1.0);
     origPos = unitCoord * uBboxSize + uBboxMin;
+    instance = aInstance;
     gl_Position = uProjection * mvPosition;
 }

+ 2 - 2
src/mol-gl/shader/gaussian-density.frag

@@ -21,7 +21,7 @@ varying float vRadius;
     varying float vGroup;
 #endif
 
-#pragma glslify: encodeIdRGBA = require(./utils/encode-id-rgba.glsl)
+#pragma glslify: encodeIdRGB = require(./utils/encode-id-rgb.glsl)
 #pragma glslify: texture3dFrom2dNearest = require(./utils/texture3d-from-2d-nearest.glsl)
 
 uniform vec3 uBboxSize;
@@ -66,6 +66,6 @@ void main() {
         float minDistance = decodeDistLog(1.0 - textureMinDist(fragPos).a);
         if (dist > minDistance + length(uBboxSize / uGridDim) / 1.5)
             discard;
-        gl_FragColor = encodeIdRGBA(vGroup);
+        gl_FragColor.rgb = encodeIdRGB(vGroup);
     #endif
 }

+ 11 - 0
src/mol-gl/shader/utils/decode-float-rgb.glsl

@@ -0,0 +1,11 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+float decodeFloatRGB(const in vec3 rgb) {
+    return rgb.r * 256.0 * 256.0 * 255.0 + rgb.g * 256.0 * 255.0 + rgb.b * 255.0;
+}
+
+#pragma glslify: export(decodeFloatRGB)

+ 0 - 5
src/mol-gl/shader/utils/decode-float-rgba.glsl

@@ -1,5 +0,0 @@
-float decodeFloatRGBA(const in vec4 rgba) {
-    return dot(rgba, vec4(1.0, 1.0/255.0, 1.0/65025.0, 1.0/16581375.0));
-}
-
-#pragma glslify: export(decodeFloatRGBA)

+ 13 - 0
src/mol-gl/shader/utils/decode-id-rgb.glsl

@@ -0,0 +1,13 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+#pragma glslify: decodeFloatRGB = require(../utils/decode-float-rgb.glsl)
+
+float decodeIdRGB(const in vec3 v) {
+	return decodeFloatRGB(v) - 1.0;
+}
+
+#pragma glslify: export(decodeIdRGB)

+ 20 - 0
src/mol-gl/shader/utils/encode-float-rgb.glsl

@@ -0,0 +1,20 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+// TODO use myMod and myDiv to fix issues with picking?
+
+vec3 encodeFloatRGB(in float value) {
+    value = clamp(value, 0.0, 16777216.0);
+    vec3 c = vec3(0.0);
+    c.b = mod(value, 256.0);
+    value = floor(value / 256.0);
+    c.g = mod(value, 256.0);
+    value = floor(value / 256.0);
+    c.r = mod(value, 256.0);
+    return c / 255.0;
+}
+
+#pragma glslify: export(encodeFloatRGB)

+ 0 - 12
src/mol-gl/shader/utils/encode-float-rgba.glsl

@@ -1,12 +0,0 @@
-vec4 encodeFloatRGBA(in float value) {
-    value = clamp(value, 0., 16777216.);
-    vec3 c = vec3(0.);
-    c.b = mod(value, 256.);
-    value = floor(value/256.);
-    c.g = mod(value, 256.);
-    value = floor(value/256.);
-    c.r = mod(value, 256.);
-    return vec4(c/255., 1.);
-}
-
-#pragma glslify: export(encodeFloatRGBA)

+ 13 - 0
src/mol-gl/shader/utils/encode-id-rgb.glsl

@@ -0,0 +1,13 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+#pragma glslify: encodeFloatRGB = require(../utils/encode-float-rgb.glsl)
+
+vec3 encodeIdRGB(const in float v) {
+	return encodeFloatRGB(v + 1.0);
+}
+
+#pragma glslify: export(encodeIdRGB)

+ 0 - 13
src/mol-gl/shader/utils/encode-id-rgba.glsl

@@ -1,13 +0,0 @@
-/**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author Alexander Rose <alexander.rose@weirdbyte.de>
- */
-
-#pragma glslify: encodeFloatRGBA = require(../utils/encode-float-rgba.glsl)
-
-vec4 encodeIdRGBA(const in float v) {
-	return encodeFloatRGBA(v + 1.0);
-}
-
-#pragma glslify: export(encodeIdRGBA)

+ 0 - 13
src/mol-gl/shader/utils/my-div.glsl

@@ -1,13 +0,0 @@
-/**
- * Copyright (c) 2017-2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author Alexander Rose <alexander.rose@weirdbyte.de>
- * @author Michael Krone <michael.krone@uni-tuebingen.de>
- */
-
-// TODO workaround due to some kind of GPU quirk
-float myDiv(float a, float b) {
-    return float(int(a) / int(b));
-}
-
-#pragma glslify: export(myDiv)

+ 0 - 13
src/mol-gl/shader/utils/my-mod.glsl

@@ -1,13 +0,0 @@
-/**
- * Copyright (c) 2017-2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author Alexander Rose <alexander.rose@weirdbyte.de>
- * @author Michael Krone <michael.krone@uni-tuebingen.de>
- */
-
-// TODO workaround due to some kind of GPU quirk
-float myMod(float a, float b) {
-    return a - b * float(int(a) / int(b));
-}
-
-#pragma glslify: export(myMod)

+ 3 - 2
src/mol-gl/shader/utils/texture3d-from-2d-linear.glsl

@@ -5,8 +5,9 @@
  * @author Michael Krone <michael.krone@uni-tuebingen.de>
  */
 
-#pragma glslify: myMod = require(./my-mod.glsl)
-#pragma glslify: myDiv = require(./my-div.glsl)
+// TODO workaround due to some kind of GPU quirk
+float myMod(float a, float b) { return a - b * float(int(a) / int(b)); }
+float myDiv(float a, float b) { return float(int(a) / int(b)); }
 
 vec4 texture3dFrom2dLinear(sampler2D tex, vec3 pos, vec3 gridDim, vec2 texDim) {
     float zSlice0 = floor(pos.z * gridDim.z);

+ 3 - 2
src/mol-gl/shader/utils/texture3d-from-2d-nearest.glsl

@@ -5,8 +5,9 @@
  * @author Michael Krone <michael.krone@uni-tuebingen.de>
  */
 
-#pragma glslify: myMod = require(./my-mod.glsl)
-#pragma glslify: myDiv = require(./my-div.glsl)
+// TODO workaround due to some kind of GPU quirk
+float myMod(float a, float b) { return a - b * float(int(a) / int(b)); }
+float myDiv(float a, float b) { return float(int(a) / int(b)); }
 
 vec4 texture3dFrom2dNearest(sampler2D tex, vec3 pos, vec3 gridDim, vec2 texDim) {
     float zSlice = floor(pos.z * gridDim.z + 0.5); // round to nearest z-slice

+ 3 - 3
src/mol-math/geometry/gaussian-density/gpu.ts

@@ -19,7 +19,7 @@ import { Context, createContext, getGLContext } from 'mol-gl/webgl/context';
 import { createFramebuffer } from 'mol-gl/webgl/framebuffer';
 import { createTexture, Texture } from 'mol-gl/webgl/texture';
 import { GLRenderingContext } from 'mol-gl/webgl/compat';
-import { decodeIdRGBA } from 'mol-geo/geometry/picking';
+import { decodeIdRGB } from 'mol-geo/geometry/picking';
 
 export async function GaussianDensityGPU(ctx: RuntimeContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityProps): Promise<DensityData> {
     const webgl = defaults(props.webgl, getWebGLContext())
@@ -344,7 +344,7 @@ function fieldFromTexture2d(ctx: Context, texture: Texture, dim: Vec3) {
             for (let ix = 0; ix < dx; ++ix) {
                 const idx = 4 * (tmpCol * dx + (iy + tmpRow) * width + ix)
                 data[j] = image[idx + 3] / 255
-                idData[j] = decodeIdRGBA(image[idx], image[idx + 1], image[idx + 2])
+                idData[j] = decodeIdRGB(image[idx], image[idx + 1], image[idx + 2])
                 j++
             }
         }
@@ -381,7 +381,7 @@ function fieldFromTexture2d(ctx: Context, texture: Texture, dim: Vec3) {
 //             for (let ix = 0; ix < width; ++ix) {
 //                 const idx = 4 * (iy * width + ix)
 //                 data[j] = slice[idx + 3] / 255
-//                 idData[j] = decodeIdRGBA(slice[idx], slice[idx + 1], slice[idx + 2])
+//                 idData[j] = decodeIdRGB(slice[idx], slice[idx + 1], slice[idx + 2])
 //                 ++j
 //             }
 //         }

+ 4 - 4
src/mol-view/viewer.ts

@@ -21,7 +21,7 @@ import { Representation } from 'mol-geo/representation';
 import { createRenderTarget } from 'mol-gl/webgl/render-target';
 import Scene from 'mol-gl/scene';
 import { RenderVariant } from 'mol-gl/webgl/render-item';
-import { PickingId, decodeIdRGBA } from 'mol-geo/geometry/picking';
+import { PickingId, decodeIdRGB } from 'mol-geo/geometry/picking';
 import { MarkerAction } from 'mol-geo/geometry/marker-data';
 import { Loci, EmptyLoci, isEmptyLoci } from 'mol-model/loci';
 import { Color } from 'mol-util/color';
@@ -231,15 +231,15 @@ namespace Viewer {
 
             objectPickTarget.bind()
             ctx.readPixels(xp, yp, 1, 1, buffer)
-            const objectId = decodeIdRGBA(buffer[0], buffer[1], buffer[2])
+            const objectId = decodeIdRGB(buffer[0], buffer[1], buffer[2])
 
             instancePickTarget.bind()
             ctx.readPixels(xp, yp, 1, 1, buffer)
-            const instanceId = decodeIdRGBA(buffer[0], buffer[1], buffer[2])
+            const instanceId = decodeIdRGB(buffer[0], buffer[1], buffer[2])
 
             groupPickTarget.bind()
             ctx.readPixels(xp, yp, 1, 1, buffer)
-            const groupId = decodeIdRGBA(buffer[0], buffer[1], buffer[2])
+            const groupId = decodeIdRGB(buffer[0], buffer[1], buffer[2])
 
             isPicking = false