Sfoglia il codice sorgente

wip, direct-volume rendering

- re-enabled for volume data
- fix normal calc
- support negative isovalues
- flat-shaded support
- ignore-light support
- support cell picking & marking
- support depth calculation/write
- support fog
- support interior coloring
- maxSteps loop counter as uniform in webgl2
- fix shifted coordinates and boundary issues
- improved geo/repr params
Alexander Rose 4 anni fa
parent
commit
d5b7cd370b

+ 75 - 28
src/mol-geo/geometry/direct-volume/direct-volume.ts

@@ -26,16 +26,17 @@ import { TransformData } from '../transform-data';
 import { createEmptyTransparency } from '../transparency-data';
 import { createTransferFunctionTexture, getControlPointsFromVec2Array } from './transfer-function';
 import { createEmptyClipping } from '../clipping-data';
+import { Grid, Volume } from '../../../mol-model/volume';
 
 const VolumeBox = Box();
-const RenderModeOptions = [['isosurface', 'Isosurface'], ['volume', 'Volume']] as [string, string][];
 
 export interface DirectVolume {
     readonly kind: 'direct-volume',
 
-    readonly gridTexture: ValueCell<Texture>,
-    readonly gridTextureDim: ValueCell<Vec3>,
-    readonly gridDimension: ValueCell<Vec3>,
+    readonly gridTexture: ValueCell<Texture>
+    readonly gridTextureDim: ValueCell<Vec3>
+    readonly gridDimension: ValueCell<Vec3>
+    readonly gridStats: ValueCell<Vec4> // [min, max, mean, sigma]
     readonly bboxSize: ValueCell<Vec3>
     readonly bboxMin: ValueCell<Vec3>
     readonly bboxMax: ValueCell<Vec3>
@@ -46,20 +47,21 @@ export interface DirectVolume {
 }
 
 export namespace DirectVolume {
-    export function create(bbox: Box3D, gridDimension: Vec3, transform: Mat4, texture: Texture, directVolume?: DirectVolume): DirectVolume {
+    export function create(bbox: Box3D, gridDimension: Vec3, transform: Mat4, texture: Texture, stats: Grid['stats'], directVolume?: DirectVolume): DirectVolume {
         return directVolume ?
-            update(bbox, gridDimension, transform, texture, directVolume) :
-            fromData(bbox, gridDimension, transform, texture);
+            update(bbox, gridDimension, transform, texture, stats, directVolume) :
+            fromData(bbox, gridDimension, transform, texture, stats);
     }
 
     function hashCode(directVolume: DirectVolume) {
         return hashFnv32a([
             directVolume.bboxSize.ref.version, directVolume.gridDimension.ref.version,
             directVolume.gridTexture.ref.version, directVolume.transform.ref.version,
+            directVolume.gridStats.ref.version
         ]);
     }
 
-    function fromData(bbox: Box3D, gridDimension: Vec3, transform: Mat4, texture: Texture): DirectVolume {
+    function fromData(bbox: Box3D, gridDimension: Vec3, transform: Mat4, texture: Texture, stats: Grid['stats']): DirectVolume {
         const boundingSphere = Sphere3D();
         let currentHash = -1;
 
@@ -72,6 +74,7 @@ export namespace DirectVolume {
             gridDimension: ValueCell.create(gridDimension),
             gridTexture: ValueCell.create(texture),
             gridTextureDim: ValueCell.create(Vec3.create(width, height, depth)),
+            gridStats: ValueCell.create(Vec4.create(stats.min, stats.max, stats.mean, stats.sigma)),
             bboxMin: ValueCell.create(bbox.min),
             bboxMax: ValueCell.create(bbox.max),
             bboxSize: ValueCell.create(Vec3.sub(Vec3.zero(), bbox.max, bbox.min)),
@@ -89,7 +92,7 @@ export namespace DirectVolume {
         return directVolume;
     }
 
-    function update(bbox: Box3D, gridDimension: Vec3, transform: Mat4, texture: Texture, directVolume: DirectVolume): DirectVolume {
+    function update(bbox: Box3D, gridDimension: Vec3, transform: Mat4, texture: Texture, stats: Grid['stats'], directVolume: DirectVolume): DirectVolume {
         const width = texture.getWidth();
         const height = texture.getHeight();
         const depth = texture.getDepth();
@@ -97,6 +100,7 @@ export namespace DirectVolume {
         ValueCell.update(directVolume.gridDimension, gridDimension);
         ValueCell.update(directVolume.gridTexture, texture);
         ValueCell.update(directVolume.gridTextureDim, Vec3.set(directVolume.gridTextureDim.ref.value, width, height, depth));
+        ValueCell.update(directVolume.gridStats, Vec4.set(directVolume.gridStats.ref.value, stats.min, stats.max, stats.mean, stats.sigma));
         ValueCell.update(directVolume.bboxMin, bbox.min);
         ValueCell.update(directVolume.bboxMax, bbox.max);
         ValueCell.update(directVolume.bboxSize, Vec3.sub(directVolume.bboxSize.ref.value, bbox.max, bbox.min));
@@ -108,15 +112,32 @@ export namespace DirectVolume {
         return {} as DirectVolume; // TODO
     }
 
+    export function createRenderModeParam(volume?: Volume) {
+        const isoValueParam = volume
+            ? Volume.createIsoValueParam(Volume.IsoValue.relative(2), volume.grid.stats)
+            : Volume.IsoValueParam;
+
+        return PD.MappedStatic('volume', {
+            isosurface: PD.Group({
+                isoValue: isoValueParam,
+            }, { isFlat: true }),
+            volume: PD.Group({
+                controlPoints: PD.LineGraph([
+                    Vec2.create(0.19, 0.0), Vec2.create(0.2, 0.05), Vec2.create(0.25, 0.05), Vec2.create(0.26, 0.0),
+                    Vec2.create(0.79, 0.0), Vec2.create(0.8, 0.05), Vec2.create(0.85, 0.05), Vec2.create(0.86, 0.0),
+                ]),
+                list: PD.ColorList('red-yellow-blue'),
+            }, { isFlat: true })
+        }, { isEssential: true });
+    }
+
     export const Params = {
         ...BaseGeometry.Params,
-        isoValueNorm: PD.Numeric(0.22, { min: 0, max: 1, step: 0.01 }, { description: 'Normalized Isolevel Value' }),
-        renderMode: PD.Select('volume', RenderModeOptions),
-        controlPoints: PD.LineGraph([
-            Vec2.create(0.19, 0.0), Vec2.create(0.2, 0.05), Vec2.create(0.25, 0.05), Vec2.create(0.26, 0.0),
-            Vec2.create(0.79, 0.0), Vec2.create(0.8, 0.05), Vec2.create(0.85, 0.05), Vec2.create(0.86, 0.0),
-        ]),
-        list: PD.ColorList('red-yellow-blue'),
+        // doubleSided: PD.Boolean(false, BaseGeometry.CustomQualityParamInfo),
+        // flipSided: PD.Boolean(false, BaseGeometry.ShadingCategory),
+        flatShaded: PD.Boolean(false, BaseGeometry.ShadingCategory),
+        ignoreLight: PD.Boolean(false, BaseGeometry.ShadingCategory),
+        renderMode: createRenderModeParam(),
     };
     export type Params = typeof Params
 
@@ -131,8 +152,15 @@ export namespace DirectVolume {
         updateRenderableState
     };
 
+    function getNormalizedIsoValue(out: Vec2, isoValue: Volume.IsoValue, stats: Vec4) {
+        const [min, max, mean, sigma] = stats;
+        const value = Volume.IsoValue.toAbsolute(isoValue, { min, max, mean, sigma }).absoluteValue;
+        Vec2.set(out, (value - min) / (max - min), (0 - min) / (max - min));
+        return out;
+    }
+
     function createValues(directVolume: DirectVolume, transform: TransformData, locationIt: LocationIterator, theme: Theme, props: PD.Values<Params>): DirectVolumeValues {
-        const { gridTexture, gridTextureDim } = directVolume;
+        const { gridTexture, gridTextureDim, gridStats } = directVolume;
         const { bboxSize, bboxMin, bboxMax, gridDimension, transform: gridTransform } = directVolume;
 
         const { instanceCount, groupCount } = locationIt;
@@ -147,10 +175,14 @@ export namespace DirectVolume {
         const invariantBoundingSphere = Sphere3D.clone(directVolume.boundingSphere);
         const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount);
 
-        const controlPoints = getControlPointsFromVec2Array(props.controlPoints);
-        const transferTex = createTransferFunctionTexture(controlPoints, props.list.colors);
+        const controlPoints = props.renderMode.name === 'volume' ? getControlPointsFromVec2Array(props.renderMode.params.controlPoints) : [];
+        const transferTex = createTransferFunctionTexture(controlPoints, props.renderMode.name === 'volume' ? props.renderMode.params.list.colors : []);
 
-        const maxSteps = Math.ceil(Vec3.magnitude(gridDimension.ref.value)) * 2 * 5;
+        const isoValue = props.renderMode.name === 'isosurface'
+            ? props.renderMode.params.isoValue
+            : Volume.IsoValue.relative(2);
+
+        const maxSteps = Math.ceil(Vec3.magnitude(gridDimension.ref.value) * 5);
 
         return {
             ...color,
@@ -167,19 +199,25 @@ export namespace DirectVolume {
             invariantBoundingSphere: ValueCell.create(invariantBoundingSphere),
             uInvariantBoundingSphere: ValueCell.create(Vec4.ofSphere(invariantBoundingSphere)),
 
-            uIsoValue: ValueCell.create(props.isoValueNorm),
+            uIsoValue: ValueCell.create(getNormalizedIsoValue(Vec2(), isoValue, directVolume.gridStats.ref.value)),
             uBboxMin: bboxMin,
             uBboxMax: bboxMax,
             uBboxSize: bboxSize,
-            dMaxSteps: ValueCell.create(maxSteps),
+            uMaxSteps: ValueCell.create(maxSteps),
             uTransform: gridTransform,
             uGridDim: gridDimension,
-            dRenderMode: ValueCell.create(props.renderMode),
+            dRenderMode: ValueCell.create(props.renderMode.name),
             tTransferTex: transferTex,
 
             dGridTexType: ValueCell.create(gridTexture.ref.value.getDepth() > 0 ? '3d' : '2d'),
             uGridTexDim: gridTextureDim,
             tGridTex: gridTexture,
+            uGridStats: gridStats,
+
+            dDoubleSided: ValueCell.create(false),
+            dFlatShaded: ValueCell.create(props.flatShaded),
+            dFlipSided: ValueCell.create(true),
+            dIgnoreLight: ValueCell.create(props.ignoreLight),
         };
     }
 
@@ -190,12 +228,20 @@ export namespace DirectVolume {
     }
 
     function updateValues(values: DirectVolumeValues, props: PD.Values<Params>) {
-        ValueCell.updateIfChanged(values.uIsoValue, props.isoValueNorm);
+        ValueCell.updateIfChanged(values.alpha, props.alpha);
         ValueCell.updateIfChanged(values.uAlpha, props.alpha);
-        ValueCell.updateIfChanged(values.dRenderMode, props.renderMode);
-
-        const controlPoints = getControlPointsFromVec2Array(props.controlPoints);
-        createTransferFunctionTexture(controlPoints, props.list.colors, values.tTransferTex);
+        // ValueCell.updateIfChanged(values.dDoubleSided, props.doubleSided);
+        ValueCell.updateIfChanged(values.dFlatShaded, props.flatShaded);
+        // ValueCell.updateIfChanged(values.dFlipSided, props.flipSided);
+        ValueCell.updateIfChanged(values.dIgnoreLight, props.ignoreLight);
+        ValueCell.updateIfChanged(values.dRenderMode, props.renderMode.name);
+
+        if (props.renderMode.name === 'isosurface') {
+            ValueCell.updateIfChanged(values.uIsoValue, getNormalizedIsoValue(values.uIsoValue.ref.value, props.renderMode.params.isoValue, values.uGridStats.ref.value));
+        } else if (props.renderMode.name === 'volume') {
+            const controlPoints = getControlPointsFromVec2Array(props.renderMode.params.controlPoints);
+            createTransferFunctionTexture(controlPoints, props.renderMode.params.list.colors, values.tTransferTex);
+        }
     }
 
     function updateBoundingSphere(values: DirectVolumeValues, directVolume: DirectVolume) {
@@ -220,6 +266,7 @@ export namespace DirectVolume {
     function updateRenderableState(state: RenderableState, props: PD.Values<Params>) {
         BaseGeometry.updateRenderableState(state, props);
         state.opaque = false;
+        state.writeDepth = props.renderMode.name === 'isosurface';
     }
 }
 

+ 14 - 4
src/mol-gl/renderable/direct-volume.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -60,11 +60,11 @@ export const DirectVolumeSchema = {
 
     uAlpha: UniformSpec('f'),
 
-    uIsoValue: UniformSpec('f'),
+    uIsoValue: UniformSpec('v2'),
     uBboxMin: UniformSpec('v3'),
     uBboxMax: UniformSpec('v3'),
     uBboxSize: UniformSpec('v3'),
-    dMaxSteps: DefineSpec('number'),
+    uMaxSteps: UniformSpec('i'),
     uTransform: UniformSpec('m4'),
     uGridDim: UniformSpec('v3'),
     dRenderMode: DefineSpec('string', ['isosurface', 'volume']),
@@ -72,13 +72,23 @@ export const DirectVolumeSchema = {
 
     dGridTexType: DefineSpec('string', ['2d', '3d']),
     uGridTexDim: UniformSpec('v3'),
-    tGridTex: TextureSpec('texture', 'rgba', 'float', 'nearest'),
+    tGridTex: TextureSpec('texture', 'rgba', 'ubyte', 'linear'),
+    uGridStats: UniformSpec('v4'), // [min, max, mean, sigma]
+
+    dDoubleSided: DefineSpec('boolean'),
+    dFlipSided: DefineSpec('boolean'),
+    dFlatShaded: DefineSpec('boolean'),
+    dIgnoreLight: DefineSpec('boolean'),
 };
 export type DirectVolumeSchema = typeof DirectVolumeSchema
 export type DirectVolumeValues = Values<DirectVolumeSchema>
 
 export function DirectVolumeRenderable(ctx: WebGLContext, id: number, values: DirectVolumeValues, state: RenderableState, materialId: number): Renderable<DirectVolumeValues> {
     const schema = { ...GlobalUniformSchema, ...InternalSchema, ...DirectVolumeSchema };
+    if (!ctx.isWebGL2) {
+        // workaround for webgl1 limitation that loop counters need to be `const`
+        (schema.uMaxSteps as any) = DefineSpec('number');
+    }
     const internalValues: InternalValues = {
         uObjectId: ValueCell.create(id),
     };

+ 133 - 51
src/mol-gl/shader/direct-volume.frag.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2017-2020 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>
@@ -7,13 +7,23 @@
 
 export default `
 precision highp float;
+precision highp int;
+
+#include common
+#include light_frag_params
+
+#include read_from_texture
+#include texture3d_from_2d_nearest
+#include texture3d_from_2d_linear
+
+uniform mat4 uProjection, uTransform, uModelView, uView;
 
 varying vec3 unitCoord;
 varying vec3 origPos;
 varying float instance;
 
 uniform mat4 uInvView;
-uniform float uIsoValue;
+uniform vec2 uIsoValue;
 uniform vec3 uGridDim;
 uniform sampler2D tTransferTex;
 
@@ -26,8 +36,25 @@ uniform vec3 uSelectColor;
 uniform vec2 uMarkerTexDim;
 uniform sampler2D tMarker;
 
+uniform float uFogNear;
+uniform float uFogFar;
+uniform vec3 uFogColor;
+
 uniform float uAlpha;
 uniform float uPickingAlphaThreshold;
+uniform int uTransparentBackground;
+
+uniform float uInteriorDarkening;
+uniform int uInteriorColorFlag;
+uniform vec3 uInteriorColor;
+bool interior;
+
+uniform float uIsOrtho;
+
+#if __VERSION__ == 300
+    // for webgl1 this is given as a 'define'
+    uniform int uMaxSteps;
+#endif
 
 #if defined(dGridTexType_2d)
     precision highp sampler2D;
@@ -38,31 +65,32 @@ uniform float uPickingAlphaThreshold;
     uniform sampler3D tGridTex;
 #endif
 
-#if defined(dColorType_uniform)
-    uniform vec3 uColor;
-#elif defined(dColorType_varying)
-    uniform vec2 uColorTexDim;
-    uniform sampler2D tColor;
-#endif
-
-#include common
-#include light_frag_params
+#if defined(dRenderVariant_color)
+    #if defined(dColorType_uniform)
+        uniform vec3 uColor;
+    #elif defined(dColorType_texture)
+        uniform vec2 uColorTexDim;
+        uniform sampler2D tColor;
+    #endif
 
-#include read_from_texture
-#include texture3d_from_2d_nearest
-#include texture3d_from_2d_linear
+    #ifdef dOverpaint
+        varying vec4 vOverpaint;
+        uniform vec2 uOverpaintTexDim;
+        uniform sampler2D tOverpaint;
+    #endif
+#endif
 
 #if defined(dGridTexType_2d)
     vec4 textureVal(vec3 pos) {
-        return texture3dFrom2dLinear(tGridTex, pos, uGridDim, uGridTexDim.xy);
+        return texture3dFrom2dLinear(tGridTex, pos + (vec3(0.5, 0.5, 0.0) / uGridDim), uGridDim, uGridTexDim.xy);
     }
     vec4 textureGroup(vec3 pos) {
-        vec3 nearestPos = floor(pos * uGridDim + 0.5) / uGridDim + 0.5 / uGridDim;
-        return texture3dFrom2dNearest(tGridTex, nearestPos, uGridDim, uGridTexDim.xy);
+        vec3 nearestPos = floor(pos * uGridDim + 0.5) / uGridDim;
+        return texture3dFrom2dNearest(tGridTex, nearestPos + (vec3(0.5, 0.5, 0.0) / uGridDim), uGridDim, uGridTexDim.xy);
     }
 #elif defined(dGridTexType_3d)
     vec4 textureVal(vec3 pos) {
-        return texture(tGridTex, pos);
+        return texture(tGridTex, pos + (vec3(0.5) / uGridDim));
     }
     vec4 textureGroup(vec3 pos) {
         return texelFetch(tGridTex, ivec3(pos * uGridDim), 0);
@@ -73,15 +101,26 @@ vec4 transferFunction(float value) {
     return texture2D(tTransferTex, vec2(value, 0.0));
 }
 
+// Calculate depth based on the given camera position.
+float calcDepth(const in vec3 cameraPos){
+    vec2 clipZW = cameraPos.z * uProjection[2].zw + uProjection[3].zw;
+    return 0.5 + 0.5 * clipZW.x / clipZW.y;
+}
+
 const float gradOffset = 0.5;
 
-vec4 raymarch(vec3 startLoc, vec3 step, vec3 viewDir) {
+vec4 raymarch(vec3 startLoc, vec3 step, vec3 viewDir, vec3 rayDir) {
     vec3 scaleVol = vec3(1.0) / uGridDim;
     vec3 pos = startLoc;
     float prevValue = -1.0;
     float value = 0.0;
     vec4 src = vec4(0.0);
     vec4 dst = vec4(0.0);
+    bool hit = false;
+    // float count = 0.0;
+
+    vec3 posMin = vec3(0.0);
+    vec3 posMax = vec3(1.0) - vec3(1.0) / uGridDim;
 
     #if defined(dRenderMode_isosurface)
         vec3 isoPos;
@@ -94,10 +133,16 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 viewDir) {
         vec3 dz = vec3(0.0, 0.0, gradOffset * scaleVol.z);
     #endif
 
-    for(int i = 0; i < dMaxSteps; ++i){
+    for(int i = 0; i < uMaxSteps; ++i){
         value = textureVal(pos).a; // current voxel value
-        if(pos.x > 1.01 || pos.y > 1.01 || pos.z > 1.01 || pos.x < -0.01 || pos.y < -0.01 || pos.z < -0.01)
-            break;
+        // if(pos.x > 1.01 || pos.y > 1.01 || pos.z > 1.01 || pos.x < -0.01 || pos.y < -0.01 || pos.z < -0.01)
+        //     break;
+
+        if(pos.x > posMax.x || pos.y > posMax.y || pos.z > posMax.z || pos.x < posMin.x || pos.y < posMin.y || pos.z < posMin.z) {
+            prevValue = value;
+            pos += step;
+            continue;
+        }
 
         #if defined(dRenderMode_volume)
             src = transferFunction(value);
@@ -107,15 +152,18 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 viewDir) {
 
         #if defined(dRenderMode_isosurface)
             if(prevValue > 0.0 && ( // there was a prev Value
-                (prevValue < uIsoValue && value > uIsoValue) || // entering isosurface
-                (prevValue > uIsoValue && value < uIsoValue) // leaving isosurface
+                (prevValue < uIsoValue.x && value > uIsoValue.x) || // entering isosurface
+                (prevValue > uIsoValue.x && value < uIsoValue.x) // leaving isosurface
             )) {
-                tmp = ((prevValue - uIsoValue) / ((prevValue - uIsoValue) - (value - uIsoValue)));
+                tmp = ((prevValue - uIsoValue.x) / ((prevValue - uIsoValue.x) - (value - uIsoValue.x)));
                 isoPos = mix(pos - step, pos, tmp);
 
-                #if defined(dRenderVariant_pick)
-                    if (uAlpha < uPickingAlphaThreshold)
-                        discard; // ignore so the element below can be picked
+                vec4 mvPosition = uModelView * uTransform * vec4(isoPos * uGridDim, 1.0);
+                #ifdef enabledFragDepth
+                    if (!hit) {
+                        gl_FragDepthEXT = calcDepth(mvPosition.xyz);
+                        hit = true;
+                    }
                 #endif
 
                 #if defined(dRenderVariant_pickObject)
@@ -123,19 +171,14 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 viewDir) {
                 #elif defined(dRenderVariant_pickInstance)
                     return vec4(encodeFloatRGB(instance), 1.0);
                 #elif defined(dRenderVariant_pickGroup)
-                    float group = floor(decodeFloatRGB(textureGroup(isoPos).rgb) + 0.5);
-                    return vec4(encodeFloatRGB(group), 1.0);
+                    return vec4(textureGroup(isoPos).rgb, 1.0);
                 #elif defined(dRenderVariant_depth)
-                    return packDepthToRGBA(gl_FragCoord.z); // TODO calculate isosurface depth
+                    #ifdef enabledFragDepth
+                        return packDepthToRGBA(gl_FragDepthEXT);
+                    #else
+                        return packDepthToRGBA(gl_FragCoord.z);
+                    #endif
                 #elif defined(dRenderVariant_color)
-                    // 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;
-
                     float group = floor(decodeFloatRGB(textureGroup(isoPos).rgb) + 0.5);
 
                     #if defined(dColorType_instance)
@@ -144,27 +187,48 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 viewDir) {
                         color = readFromTexture(tColor, group, uColorTexDim).rgb;
                     #elif defined(dColorType_groupInstance)
                         color = readFromTexture(tColor, instance * float(uGroupCount) + group, uColorTexDim).rgb;
+                    #elif defined(dColorType_uniform)
+                        color = uColor;
                     #endif
 
-                    vec3 normal = normalize(gradient);
-                    vec3 vViewPosition = normalize(viewDir);
+                    bool flipped = value > uIsoValue.y; // negative isosurfaces
+                    interior = value < uIsoValue.x && flipped;
+                    vec3 vViewPosition = mvPosition.xyz;
                     vec4 material = vec4(color, uAlpha);
-                    #include apply_light_color
+
+                    #ifdef dIgnoreLight
+                        gl_FragColor = material;
+                    #else
+                        #if defined(dFlatShaded)
+                            // nearest grid point
+                            isoPos = floor(isoPos * uGridDim + 0.5) / uGridDim;
+                        #endif
+                        // 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;
+                        mat3 normalMatrix = transpose3(inverse3(mat3(uModelView)));
+                        vec3 normal = -normalize(normalMatrix * normalize(gradient));
+                        normal = normal * (float(flipped) * 2.0 - 1.0);
+                        normal = normal * -(float(interior) * 2.0 - 1.0);
+                        #include apply_light_color
+                    #endif
 
                     float vMarker = readFromTexture(tMarker, instance * float(uGroupCount) + group, uMarkerTexDim).a;
+                    #include apply_interior_color
                     #include apply_marker_color
+                    #include apply_fog
 
                     src.rgb = gl_FragColor.rgb;
-                    src.a = gl_FragColor.a;
-
-                    // draw interior darker
-                    if( (prevValue - uIsoValue) > 0.0 ) {
-                        src.rgb *= 0.5;
-                    }
+                    src.a =  gl_FragColor.a;
 
+                    // count += 1.0;
                     src.rgb *= src.a;
                     dst = (1.0 - dst.a) * src + dst; // standard blending
+                    // dst.rgb = vec3(1.0, 0.0, 0.0) * (count / 20.0);
+                    dst.a = min(1.0, dst.a);
                     if(dst.a >= 1.0) {
+                        // dst.rgb = vec3(1.0, 0.0, 0.0);
                         break;
                     }
                 #endif
@@ -177,14 +241,32 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 viewDir) {
     return dst;
 }
 
+// TODO calculate normalMatrix on CPU
+// TODO fix orthographic projection
+// TODO fix near/far clipping
+// TODO support clip objects
+// TODO check and combine with pre-rendererd opaque texture
+// TODO support float texture for higher precision values
+
 void main () {
+    // TODO handle on CPU in renderloop
+    #if defined(dRenderVariant_pick)
+        #if defined(dRenderMode_volume)
+            discard;
+        #elif defined(dRenderMode_isosurface)
+            if (uAlpha < uPickingAlphaThreshold)
+                discard; // ignore so the element below can be picked
+        #endif
+    #endif
+    // gl_FragColor = vec4(1.0, 0.0, 0.0, uAlpha);
+
     vec3 cameraPos = uInvView[3].xyz / uInvView[3].w;
 
     vec3 rayDir = normalize(origPos - cameraPos);
-    vec3 startLoc = unitCoord;
-    vec3 step = rayDir * (1.0 / uGridDim) * 0.1;
+    vec3 step = rayDir * (1.0 / uGridDim) * 0.2;
+    vec3 startLoc = unitCoord; // - step * float(uMaxSteps);
 
-    gl_FragColor = raymarch(startLoc, step, normalize(cameraPos));
+    gl_FragColor = raymarch(startLoc, step, normalize(cameraPos), rayDir);
     if (length(gl_FragColor.rgb) < 0.00001) discard;
     #if defined(dRenderMode_volume)
         gl_FragColor.a *= uAlpha;

+ 5 - 1
src/mol-plugin-state/transforms/representation.ts

@@ -563,7 +563,11 @@ export namespace VolumeRepresentation3DHelpers {
     }
 
     export function getDescription(props: any) {
-        return props.isoValue && Volume.IsoValue.toString(props.isoValue);
+        if (props.isoValue) {
+            return Volume.IsoValue.toString(props.isoValue);
+        } else if (props.renderMode?.params?.isoValue) {
+            return Volume.IsoValue.toString(props.renderMode?.params?.isoValue);
+        }
     }
 }
 type VolumeRepresentation3D = typeof VolumeRepresentation3D

+ 4 - 6
src/mol-repr/structure/visual/gaussian-density-volume.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -24,8 +24,9 @@ async function createGaussianDensityVolume(ctx: VisualContext, structure: Struct
     const oldTexture = directVolume ? directVolume.gridTexture.ref.value : undefined;
     const densityTextureData = await computeStructureGaussianDensityTexture(structure, p, webgl, oldTexture).runInContext(runtime);
     const { transform, texture, bbox, gridDim } = densityTextureData;
+    const stats = { min: 0, max: 1, mean: 0.5, sigma: 0.1 };
 
-    return DirectVolume.create(bbox, gridDim, transform, texture, directVolume);
+    return DirectVolume.create(bbox, gridDim, transform, texture, stats, directVolume);
 }
 
 export const GaussianDensityVolumeParams = {
@@ -45,10 +46,7 @@ export function GaussianDensityVolumeVisual(materialId: number): ComplexVisual<G
         setUpdateState: (state: VisualUpdateState, newProps: PD.Values<GaussianDensityVolumeParams>, currentProps: PD.Values<GaussianDensityVolumeParams>) => {
             if (newProps.resolution !== currentProps.resolution) state.createGeometry = true;
             if (newProps.radiusOffset !== currentProps.radiusOffset) state.createGeometry = true;
-            if (newProps.smoothness !== currentProps.smoothness) {
-                state.createGeometry = true;
-                newProps.isoValueNorm = Math.exp(-newProps.smoothness);
-            }
+            if (newProps.smoothness !== currentProps.smoothness) state.createGeometry = true;
             if (newProps.ignoreHydrogens !== currentProps.ignoreHydrogens) state.createGeometry = true;
             if (newProps.includeParent !== currentProps.includeParent) state.createGeometry = true;
         }

+ 51 - 36
src/mol-repr/volume/direct-volume.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -13,13 +13,16 @@ import { WebGLContext } from '../../mol-gl/webgl/context';
 import { DirectVolume } from '../../mol-geo/geometry/direct-volume/direct-volume';
 import { VisualContext } from '../visual';
 import { Theme, ThemeRegistryContext } from '../../mol-theme/theme';
-import { BaseGeometry } from '../../mol-geo/geometry/base';
 import { VolumeVisual, VolumeRepresentation, VolumeRepresentationProvider } from './representation';
 import { LocationIterator } from '../../mol-geo/util/location-iterator';
 import { NullLocation } from '../../mol-model/location';
-import { EmptyLoci } from '../../mol-model/loci';
 import { VisualUpdateState } from '../util';
 import { RepresentationContext, RepresentationParamsGetter } from '../representation';
+import { Interval } from '../../mol-data/int';
+import { Loci, EmptyLoci } from '../../mol-model/loci';
+import { PickingId } from '../../mol-geo/geometry/picking';
+import { eachVolumeLoci } from './util';
+import { encodeFloatRGBtoArray } from '../../mol-util/float-packing';
 
 function getBoundingBox(gridDimension: Vec3, transform: Mat4) {
     const bbox = Box3D.empty();
@@ -49,33 +52,29 @@ function getVolumeTexture2dLayout(dim: Vec3, maxTextureSize: number) {
 }
 
 function createVolumeTexture2d(volume: Volume, maxTextureSize: number) {
-    const { cells: { space, data }, stats } = volume.grid;
+    const { cells: { space, data }, stats: { max, min } } = volume.grid;
     const dim = space.dimensions as Vec3;
-    const { get } = space;
-    const { width, height, columns, rows } = getVolumeTexture2dLayout(dim, maxTextureSize);
+    const { dataOffset } = space;
+    const { width, height } = getVolumeTexture2dLayout(dim, maxTextureSize);
 
     const array = new Uint8Array(width * height * 4);
     const textureImage = { array, width, height };
 
+    const diff = max - min;
     const [ xl, yl, zl ] = dim;
     const xlp = xl + 1; // horizontal padding
     const ylp = yl + 1; // vertical padding
 
-    function setTex(value: number, x: number, y: number, z: number) {
-        const column = Math.floor(((z * xlp) % width) / xlp);
-        const row = Math.floor((z * xlp) / width);
-        const px = column * xlp + x;
-        const index = 4 * ((row * ylp * width) + (y * width) + px);
-        array[index + 3] = ((value - stats.min) / (stats.max - stats.min)) * 255;
-    }
-
-    console.log('dim', dim);
-    console.log('layout', { width, height, columns, rows });
-
     for (let z = 0; z < zl; ++z) {
         for (let y = 0; y < yl; ++y) {
             for (let x = 0; x < xl; ++x) {
-                setTex(get(data, x, y, z), x, y, z);
+                const column = Math.floor(((z * xlp) % width) / xlp);
+                const row = Math.floor((z * xlp) / width);
+                const px = column * xlp + x;
+                const index = 4 * ((row * ylp * width) + (y * width) + px);
+                const offset = dataOffset(x, y, z);
+                encodeFloatRGBtoArray(offset, array, index);
+                array[index + 3] = ((data[offset] - min) / diff) * 255;
             }
         }
     }
@@ -91,32 +90,32 @@ export function createDirectVolume2d(ctx: RuntimeContext, webgl: WebGLContext, v
     const bbox = getBoundingBox(gridDimension, transform);
     const dim = Vec3.create(gridDimension[0], gridDimension[1], gridDimension[2]);
     dim[0] += 1; // horizontal padding
-    dim[0] += 1; // vertical padding
+    dim[1] += 1; // vertical padding
 
-    const texture = directVolume ? directVolume.gridTexture.ref.value : webgl.resources.texture('volume-uint8', 'rgba', 'ubyte', 'linear');
+    const texture = directVolume ? directVolume.gridTexture.ref.value : webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
     texture.load(textureImage);
 
-    return DirectVolume.create(bbox, dim, transform, texture, directVolume);
+    return DirectVolume.create(bbox, dim, transform, texture, volume.grid.stats, directVolume);
 }
 
 // 3d volume texture
 
 function createVolumeTexture3d(volume: Volume) {
-    const { cells: { space, data }, stats } = volume.grid;
+    const { cells: { space, data }, stats: { max, min } } = volume.grid;
     const [ width, height, depth ] = space.dimensions as Vec3;
-    const { get } = space;
+    const { dataOffset } = space;
 
     const array = new Uint8Array(width * height * depth * 4);
     const textureVolume = { array, width, height, depth };
+    const diff = max - min;
 
     let i = 0;
     for (let z = 0; z < depth; ++z) {
         for (let y = 0; y < height; ++y) {
             for (let x = 0; x < width; ++x) {
-                if (i < 100) {
-                    console.log(get(data, x, y, z), ((get(data, x, y, z) - stats.min) / (stats.max - stats.min)) * 255);
-                }
-                array[i + 3] = ((get(data, x, y, z) - stats.min) / (stats.max - stats.min)) * 255;
+                const offset = dataOffset(x, y, z);
+                encodeFloatRGBtoArray(offset, array, i);
+                array[i + 3] = ((data[offset] - min) / diff) * 255;
                 i += 4;
             }
         }
@@ -129,13 +128,12 @@ export function createDirectVolume3d(ctx: RuntimeContext, webgl: WebGLContext, v
     const gridDimension = volume.grid.cells.space.dimensions as Vec3;
     const textureVolume = createVolumeTexture3d(volume);
     const transform = Grid.getGridToCartesianTransform(volume.grid);
-    // Mat4.invert(transform, transform)
     const bbox = getBoundingBox(gridDimension, transform);
 
     const texture = directVolume ? directVolume.gridTexture.ref.value : webgl.resources.texture('volume-uint8', 'rgba', 'ubyte', 'linear');
     texture.load(textureVolume);
 
-    return DirectVolume.create(bbox, gridDimension, transform, texture, directVolume);
+    return DirectVolume.create(bbox, gridDimension, transform, texture, volume.grid.stats, directVolume);
 }
 
 //
@@ -153,24 +151,41 @@ function getLoci(volume: Volume, props: PD.Values<DirectVolumeParams>) {
     return Volume.Loci(volume);
 }
 
+export function getDirectVolumeLoci(pickingId: PickingId, volume: Volume, props: DirectVolumeProps, id: number) {
+    const { objectId, groupId } = pickingId;
+    if (id === objectId) {
+        return Volume.Cell.Loci(volume, Interval.ofSingleton(groupId as Volume.CellIndex));
+    }
+    return EmptyLoci;
+}
+
+export function eachDirectVolume(loci: Loci, volume: Volume, props: DirectVolumeProps, apply: (interval: Interval) => boolean) {
+    return props.renderMode.name === 'isosurface'
+        ? eachVolumeLoci(loci, volume, props.renderMode.params.isoValue, apply)
+        : false;
+}
+
 //
 
 export const DirectVolumeParams = {
-    ...BaseGeometry.Params,
-    ...DirectVolume.Params
+    ...DirectVolume.Params,
+    quality: { ...DirectVolume.Params.quality, isEssential: false },
 };
 export type DirectVolumeParams = typeof DirectVolumeParams
 export function getDirectVolumeParams(ctx: ThemeRegistryContext, volume: Volume) {
-    return PD.clone(DirectVolumeParams);
+    const p = PD.clone(DirectVolumeParams);
+    p.renderMode = DirectVolume.createRenderModeParam(volume);
+    return p;
 }
+export type DirectVolumeProps = PD.Values<DirectVolumeParams>
 
 export function DirectVolumeVisual(materialId: number): VolumeVisual<DirectVolumeParams> {
     return VolumeVisual<DirectVolume, DirectVolumeParams>({
         defaultProps: PD.getDefaultValues(DirectVolumeParams),
         createGeometry: createDirectVolume,
-        createLocationIterator: (volume: Volume) => LocationIterator(1, 1, () => NullLocation),
-        getLoci: () => EmptyLoci,
-        eachLocation: () => false,
+        createLocationIterator: (volume: Volume) => LocationIterator(volume.grid.cells.data.length, 1, () => NullLocation),
+        getLoci: getDirectVolumeLoci,
+        eachLocation: eachDirectVolume,
         setUpdateState: (state: VisualUpdateState, volume: Volume, newProps: PD.Values<DirectVolumeParams>, currentProps: PD.Values<DirectVolumeParams>) => {
         },
         geometryUtils: DirectVolume.Utils
@@ -184,7 +199,7 @@ export function DirectVolumeRepresentation(ctx: RepresentationContext, getParams
 export const DirectVolumeRepresentationProvider = VolumeRepresentationProvider({
     name: 'direct-volume',
     label: 'Direct Volume',
-    description: 'Direct volume rendering of volumetric data.',
+    description: 'Direct rendering of volumetric data.',
     factory: DirectVolumeRepresentation,
     getParams: getDirectVolumeParams,
     defaultValues: PD.getDefaultValues(DirectVolumeParams),

+ 2 - 1
src/mol-repr/volume/registry.ts

@@ -9,6 +9,7 @@ import { Volume } from '../../mol-model/volume';
 import { IsosurfaceRepresentationProvider } from './isosurface';
 import { objectForEach } from '../../mol-util/object';
 import { SliceRepresentationProvider } from './slice';
+import { DirectVolumeRepresentationProvider } from './direct-volume';
 
 export class VolumeRepresentationRegistry extends RepresentationRegistry<Volume, Representation.State> {
     constructor() {
@@ -24,7 +25,7 @@ export namespace VolumeRepresentationRegistry {
     export const BuiltIn = {
         'isosurface': IsosurfaceRepresentationProvider,
         'slice': SliceRepresentationProvider,
-        // 'direct-volume': DirectVolumeRepresentationProvider, // TODO disabled for now, needs more work
+        'direct-volume': DirectVolumeRepresentationProvider,
     };
 
     type _BuiltIn = typeof BuiltIn