diff --git a/src/mol-canvas3d/canvas3d.ts b/src/mol-canvas3d/canvas3d.ts
index 7f525ab85908c6cf9779042aa3b6141bd67ae995..814503272e6f6c3da610e6a438bb32c977a343a6 100644
--- a/src/mol-canvas3d/canvas3d.ts
+++ b/src/mol-canvas3d/canvas3d.ts
@@ -21,13 +21,14 @@ import { Representation } from 'mol-repr/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, decodeIdRGB } from 'mol-geo/geometry/picking';
+import { PickingId } 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';
 import { Camera } from './camera';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
 import { BoundingSphereHelper, DebugHelperParams } from './helper/bounding-sphere-helper';
+import { decodeFloatRGB } from 'mol-util/float-packing';
 
 export const Canvas3DParams = {
     // TODO: FPS cap?
@@ -276,19 +277,19 @@ namespace Canvas3D {
             // TODO slow in Chrome, ok in FF; doesn't play well with gpu surface calc
             // await webgl.readPixelsAsync(xp, yp, 1, 1, buffer)
             webgl.readPixels(xp, yp, 1, 1, buffer)
-            const objectId = decodeIdRGB(buffer[0], buffer[1], buffer[2])
+            const objectId = decodeFloatRGB(buffer[0], buffer[1], buffer[2])
             if (objectId === -1) { isIdentifying = false; return; }
 
             instancePickTarget.bind()
             // await webgl.readPixelsAsync(xp, yp, 1, 1, buffer)
             webgl.readPixels(xp, yp, 1, 1, buffer)
-            const instanceId = decodeIdRGB(buffer[0], buffer[1], buffer[2])
+            const instanceId = decodeFloatRGB(buffer[0], buffer[1], buffer[2])
             if (instanceId === -1) { isIdentifying = false; return; }
 
             groupPickTarget.bind()
             // await webgl.readPixelsAsync(xp, yp, 1, 1, buffer)
             webgl.readPixels(xp, yp, 1, 1, buffer)
-            const groupId = decodeIdRGB(buffer[0], buffer[1], buffer[2])
+            const groupId = decodeFloatRGB(buffer[0], buffer[1], buffer[2])
             if (groupId === -1) { isIdentifying = false; return; }
 
             isIdentifying = false
diff --git a/src/mol-geo/geometry/picking.ts b/src/mol-geo/geometry/picking.ts
index a42ae76ee567993c03282a46a6fd7ed4237edd04..8ae9b751027a9b55470721ca58a0d665ff67bb26 100644
--- a/src/mol-geo/geometry/picking.ts
+++ b/src/mol-geo/geometry/picking.ts
@@ -4,17 +4,6 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-function decodeFloatRGBA(r: number, g: number, b: number) {
-    r = Math.floor(r)
-    g = Math.floor(g)
-    b = Math.floor(b)
-    return r * 256 * 256 + g * 256 + b
-}
-
-export function decodeIdRGB(r: number, g: number, b: number) {
-    return decodeFloatRGBA(r, g, b) - 1
-}
-
 export interface PickingId {
     objectId: number
     instanceId: number
diff --git a/src/mol-gl/shader/chunks/assign-color-varying.glsl b/src/mol-gl/shader/chunks/assign-color-varying.glsl
index ad039eff1f54327427a3b9267b8b092ac095ba9a..e2e7e150af8b051086c3a1827e717e7a074588b4 100644
--- a/src/mol-gl/shader/chunks/assign-color-varying.glsl
+++ b/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 = vec4(encodeIdRGB(float(uObjectId)), 1.0);
+    vColor = vec4(encodeFloatRGB(float(uObjectId)), 1.0);
 #elif defined(dColorType_instancePicking)
-    vColor = vec4(encodeIdRGB(aInstance), 1.0);
+    vColor = vec4(encodeFloatRGB(aInstance), 1.0);
 #elif defined(dColorType_groupPicking)
-    vColor = vec4(encodeIdRGB(aGroup), 1.0);
+    vColor = vec4(encodeFloatRGB(aGroup), 1.0);
 #endif
\ No newline at end of file
diff --git a/src/mol-gl/shader/chunks/color-vert-params.glsl b/src/mol-gl/shader/chunks/color-vert-params.glsl
index f34e13df4cd569f7de08fcb7daff7350bf97d8ee..abc149ebdcf64187d4989f048a7b3651639846e7 100644
--- a/src/mol-gl/shader/chunks/color-vert-params.glsl
+++ b/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: encodeIdRGB = require(../utils/encode-id-rgb.glsl)
+    #pragma glslify: encodeFloatRGB = require(../utils/encode-float-rgb.glsl)
 #endif
\ No newline at end of file
diff --git a/src/mol-gl/shader/direct-volume.frag b/src/mol-gl/shader/direct-volume.frag
index de8f74643629d2ba0c7a66e900580f5d9ae9789e..5728c6c41b3e1a0239a305f38024d3034775e604 100644
--- a/src/mol-gl/shader/direct-volume.frag
+++ b/src/mol-gl/shader/direct-volume.frag
@@ -47,8 +47,8 @@ uniform int uPickable;
 
 #pragma glslify: import('./chunks/common.glsl')
 #pragma glslify: readFromTexture = require(./utils/read-from-texture.glsl, intMod=intMod, intDiv=intDiv, foo=foo) // foo=foo is a workaround for a bug in glslify
-#pragma glslify: encodeIdRGB = require(./utils/encode-id-rgb.glsl)
-#pragma glslify: decodeIdRGB = require(./utils/decode-id-rgb.glsl)
+#pragma glslify: encodeFloatRGB = require(./utils/encode-float-rgb.glsl)
+#pragma glslify: decodeFloatRGB = require(./utils/decode-float-rgb.glsl)
 #pragma glslify: texture3dFrom2dNearest = require(./utils/texture3d-from-2d-nearest.glsl, intMod=intMod, intDiv=intDiv, foo=foo) // foo=foo is a workaround for a bug in glslify
 #pragma glslify: texture3dFrom2dLinear = require(./utils/texture3d-from-2d-linear.glsl, intMod=intMod, intDiv=intDiv, foo=foo) // foo=foo is a workaround for a bug in glslify
 
@@ -135,12 +135,12 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 viewDir) {
                 #endif
 
                 #if defined(dColorType_objectPicking)
-                    return vec4(encodeIdRGB(float(uObjectId)), 1.0);
+                    return vec4(encodeFloatRGB(float(uObjectId)), 1.0);
                 #elif defined(dColorType_instancePicking)
-                    return vec4(encodeIdRGB(instance), 1.0);
+                    return vec4(encodeFloatRGB(instance), 1.0);
                 #elif defined(dColorType_groupPicking)
-                    float group = floor(decodeIdRGB(textureGroup(isoPos).rgb) + 0.5);
-                    return vec4(encodeIdRGB(group), 1.0);
+                    float group = floor(decodeFloatRGB(textureGroup(isoPos).rgb) + 0.5);
+                    return vec4(encodeFloatRGB(group), 1.0);
                 #else
                     // compute gradient by central differences
                     gradient.x = textureVal(isoPos - dx).a - textureVal(isoPos + dx).a;
@@ -150,7 +150,7 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 viewDir) {
                     float d = float(dot(gradient, viewDir) > 0.0);
                     gradient = (2.0 * d - 1.0) * gradient;
 
-                    float group = floor(decodeIdRGB(textureGroup(isoPos).rgb) + 0.5);
+                    float group = floor(decodeFloatRGB(textureGroup(isoPos).rgb) + 0.5);
 
                     #if defined(dColorType_instance)
                         color = readFromTexture(tColor, instance, uColorTexDim).rgb;
diff --git a/src/mol-gl/shader/gaussian-density.frag b/src/mol-gl/shader/gaussian-density.frag
index 137c2189100d4c1ba4c070e189cc10de0f0a5dbe..652e87891b45aa5e4a4bbafb8a5d654926acfe57 100644
--- a/src/mol-gl/shader/gaussian-density.frag
+++ b/src/mol-gl/shader/gaussian-density.frag
@@ -22,7 +22,9 @@ varying float vRadius;
 #endif
 
 #pragma glslify: import('./chunks/common.glsl')
-#pragma glslify: encodeIdRGB = require(./utils/encode-id-rgb.glsl)
+#pragma glslify: encodeFloatLog = require(./utils/encode-float-log.glsl)
+#pragma glslify: decodeFloatLog = require(./utils/decode-float-log.glsl)
+#pragma glslify: encodeFloatRGB = require(./utils/encode-float-rgb.glsl)
 #pragma glslify: texture3dFrom2dNearest = require(./utils/texture3d-from-2d-nearest.glsl, intMod=intMod, intDiv=intDiv, foo=foo) // foo=foo is a workaround for a bug in glslify
 
 uniform vec3 uBboxSize;
@@ -46,12 +48,6 @@ uniform float uAlpha;
     #endif
 #endif
 
-// encode distance logarithmically with given maxDistance
-const float maxDistance = 10000.0;
-const float distLogFactor = log(maxDistance + 1.0);
-float encodeDistLog(float dist) { return log(dist + 1.0) / distLogFactor; }
-float decodeDistLog(float logDist) { return exp(logDist * distLogFactor) - 1.0; }
-
 void main() {
     vec2 v = gl_FragCoord.xy - vec2(uCurrentX, uCurrentY) - 0.5;
     vec3 fragPos = vec3(v.x, v.y, uCurrentSlice) / uGridDim;
@@ -62,13 +58,13 @@ void main() {
         float density = exp(-uAlpha * ((dist * dist) / radiusSq));
         gl_FragColor = vec4(density);
     #elif defined(dCalcType_minDistance)
-        gl_FragColor.a = 1.0 - encodeDistLog(dist);
+        gl_FragColor.a = 1.0 - encodeFloatLog(dist);
     #elif defined(dCalcType_groupId)
-        float minDistance = decodeDistLog(1.0 - textureMinDist(fragPos).a);
+        float minDistance = decodeFloatLog(1.0 - textureMinDist(fragPos).a);
         // TODO verify `length(uBboxSize / uGridDim) * 2.0`
         //      on some machines `* 2.0` is needed while on others `* 0.5` works
         if (dist > minDistance + length(uBboxSize / uGridDim) * 0.5)
             discard;
-        gl_FragColor.rgb = encodeIdRGB(vGroup);
+        gl_FragColor.rgb = encodeFloatRGB(vGroup);
     #endif
 }
\ No newline at end of file
diff --git a/src/mol-gl/shader/utils/decode-float-log.glsl b/src/mol-gl/shader/utils/decode-float-log.glsl
new file mode 100644
index 0000000000000000000000000000000000000000..eee87721cf8c76d953394ec02e9bfc340c1bc841
--- /dev/null
+++ b/src/mol-gl/shader/utils/decode-float-log.glsl
@@ -0,0 +1,11 @@
+/**
+ * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+const float maxFloat = 10000.0; // NOTE constant also set in in encodeFloatLog and in TypeScript
+const float floatLogFactor = log(maxFloat + 1.0);
+float decodeFloatLog(in float value) { return exp(value * floatLogFactor) - 1.0; }
+
+#pragma glslify: export(decodeFloatLog)
\ No newline at end of file
diff --git a/src/mol-gl/shader/utils/decode-float-rgb.glsl b/src/mol-gl/shader/utils/decode-float-rgb.glsl
index 60a586e2865b11b21dd2857d5ddc3936975f2c3a..a0367ea32cc5ff4164b1e0282ad69978bafa0015 100644
--- a/src/mol-gl/shader/utils/decode-float-rgb.glsl
+++ b/src/mol-gl/shader/utils/decode-float-rgb.glsl
@@ -5,7 +5,7 @@
  */
 
 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;
+    return (rgb.r * 256.0 * 256.0 * 255.0 + rgb.g * 256.0 * 255.0 + rgb.b * 255.0) - 1.0;
 }
 
 #pragma glslify: export(decodeFloatRGB)
\ No newline at end of file
diff --git a/src/mol-gl/shader/utils/decode-id-rgb.glsl b/src/mol-gl/shader/utils/decode-id-rgb.glsl
deleted file mode 100644
index 1a4789e496505cf27c94d0086f1f928f6a58d1fd..0000000000000000000000000000000000000000
--- a/src/mol-gl/shader/utils/decode-id-rgb.glsl
+++ /dev/null
@@ -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: decodeFloatRGB = require(../utils/decode-float-rgb.glsl)
-
-float decodeIdRGB(const in vec3 v) {
-	return decodeFloatRGB(v) - 1.0;
-}
-
-#pragma glslify: export(decodeIdRGB)
\ No newline at end of file
diff --git a/src/mol-gl/shader/utils/encode-float-log.glsl b/src/mol-gl/shader/utils/encode-float-log.glsl
new file mode 100644
index 0000000000000000000000000000000000000000..908efb7fbe3d536ed43b4f0aa0c584d04cc475bf
--- /dev/null
+++ b/src/mol-gl/shader/utils/encode-float-log.glsl
@@ -0,0 +1,11 @@
+/**
+ * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+const float maxFloat = 10000.0; // NOTE constant also set in in decodeFloatLog and in TypeScript
+const float floatLogFactor = log(maxFloat + 1.0);
+float encodeFloatLog(in float value) { return log(value + 1.0) / floatLogFactor; }
+
+#pragma glslify: export(encodeFloatLog)
\ No newline at end of file
diff --git a/src/mol-gl/shader/utils/encode-float-rgb.glsl b/src/mol-gl/shader/utils/encode-float-rgb.glsl
index 07183663150d825630e63e886432bb0d9f2993c1..69d500fe25216e69cb5d9551ce071cc020504253 100644
--- a/src/mol-gl/shader/utils/encode-float-rgb.glsl
+++ b/src/mol-gl/shader/utils/encode-float-rgb.glsl
@@ -5,7 +5,7 @@
  */
 
 vec3 encodeFloatRGB(in float value) {
-    value = clamp(value, 0.0, 16777216.0);
+    value = clamp(value, 0.0, 16777216.0 - 1.0) + 1.0;
     vec3 c = vec3(0.0);
     c.b = mod(value, 256.0);
     value = floor(value / 256.0);
diff --git a/src/mol-gl/shader/utils/encode-id-rgb.glsl b/src/mol-gl/shader/utils/encode-id-rgb.glsl
deleted file mode 100644
index 5dd156c096e8ebf1506c8764564a61d015ee74e2..0000000000000000000000000000000000000000
--- a/src/mol-gl/shader/utils/encode-id-rgb.glsl
+++ /dev/null
@@ -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: encodeFloatRGB = require(../utils/encode-float-rgb.glsl)
-
-vec3 encodeIdRGB(const in float v) {
-	return encodeFloatRGB(v + 1.0);
-}
-
-#pragma glslify: export(encodeIdRGB)
\ No newline at end of file
diff --git a/src/mol-math/geometry/gaussian-density/gpu.ts b/src/mol-math/geometry/gaussian-density/gpu.ts
index 0e2056c7a1bff3474d589199fb22caf3043b5458..fd087282e22a55608965bab8c3160d0914802728 100644
--- a/src/mol-math/geometry/gaussian-density/gpu.ts
+++ b/src/mol-math/geometry/gaussian-density/gpu.ts
@@ -18,7 +18,7 @@ import { createRenderable, createGaussianDensityRenderObject } from 'mol-gl/rend
 import { WebGLContext } from 'mol-gl/webgl/context';
 import { createTexture, Texture } from 'mol-gl/webgl/texture';
 import { GLRenderingContext } from 'mol-gl/webgl/compat';
-import { decodeIdRGB } from 'mol-geo/geometry/picking';
+import { decodeFloatRGB } from 'mol-util/float-packing';
 
 /** name for shared framebuffer used for gpu gaussian surface operations */
 const FramebufferName = 'gaussian-density-gpu'
@@ -329,7 +329,7 @@ async function fieldFromTexture2d(ctx: WebGLContext, 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] = decodeIdRGB(image[idx], image[idx + 1], image[idx + 2])
+                idData[j] = decodeFloatRGB(image[idx], image[idx + 1], image[idx + 2])
                 j++
             }
         }
diff --git a/src/mol-util/float-packing.ts b/src/mol-util/float-packing.ts
new file mode 100644
index 0000000000000000000000000000000000000000..42087b10738d71adb4e1913c187cf5fd460b38cf
--- /dev/null
+++ b/src/mol-util/float-packing.ts
@@ -0,0 +1,32 @@
+/**
+ * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { clamp } from 'mol-math/interpolate';
+
+const maxFloat = 10000.0; // NOTE same constant is set in shaders
+const floatLogFactor = Math.log(maxFloat + 1.0);
+
+/** encode float logarithmically */
+export function encodeFloatLog(value: number) { return Math.log(value + 1.0) / floatLogFactor }
+
+/** decode logarithmically encoded float */
+export function decodeFloatLog(value: number) { return Math.exp(value * floatLogFactor) - 1.0 }
+
+/** encode float as rgb triplet */
+export function encodeFloatRGB(value: number) {
+    value = clamp(value, 0.0, 16777216.0 - 1.0) + 1.0
+    const b = (value % 256) / 255.0
+    value = Math.floor(value / 256.0)
+    const g = (value % 256) / 255.0
+    value = Math.floor(value / 256.0)
+    const r = (value % 256) / 255.0
+    return [r, g, b]
+}
+
+/** decode float encoded as rgb triplet */
+export function decodeFloatRGB(r: number, g: number, b: number) {
+    return (Math.floor(r) * 256 * 256 + Math.floor(g) * 256 + Math.floor(b)) - 1
+}
\ No newline at end of file