Browse Source

wip, direct-volume rendering

- better near/far clipping plane support
- use quality props in volume representations
Alexander Rose 4 years ago
parent
commit
d141c27765

+ 1 - 0
src/mol-canvas3d/passes/draw.ts

@@ -154,6 +154,7 @@ export class DrawPass {
             // this.webgl.state.disable(this.webgl.gl.SCISSOR_TEST);
             this.webgl.state.disable(this.webgl.gl.BLEND);
             this.webgl.state.disable(this.webgl.gl.DEPTH_TEST);
+            this.webgl.state.disable(this.webgl.gl.CULL_FACE);
             this.webgl.state.depthMask(false);
             this.webgl.state.clearColor(1, 1, 1, 1);
             this.webgl.gl.viewport(x, y, width, height);

+ 35 - 20
src/mol-gl/renderer.ts

@@ -158,7 +158,7 @@ function getClip(props: RendererProps['clip'], clip?: Clip): Clip {
 
 namespace Renderer {
     export function create(ctx: WebGLContext, props: Partial<RendererProps> = {}): Renderer {
-        const { gl, state, stats } = ctx;
+        const { gl, state, stats, extensions: { fragDepth } } = ctx;
         const p = PD.merge(RendererParams, PD.getDefaultValues(RendererParams), props);
         const style = getStyle(p.style);
         const clip = getClip(p.clip);
@@ -267,34 +267,49 @@ namespace Renderer {
 
             if (depthTexture) program.bindTextures([['tDepth', depthTexture]]);
 
-            if (r.values.dDoubleSided) {
-                if ((r.values.dDoubleSided.ref.value || r.values.hasReflection.ref.value) &&
-                    !r.values.uStepFactor // indicates direct-volume, always cull
-                ) {
-                    state.disable(gl.CULL_FACE);
+            if (r.values.uStepFactor) { // indicates direct-volume
+                // always cull front
+                state.enable(gl.CULL_FACE);
+                state.frontFace(gl.CW);
+                state.cullFace(gl.BACK);
+
+                // depth test done manually in shader against `depthTexture`
+                // still need to enable when fragDepth can be used to write depth
+                // (unclear why depthMask is insufficient)
+                if (r.values.dRenderMode.ref.value === 'volume' || !fragDepth) {
+                    state.disable(gl.DEPTH_TEST);
+                    state.depthMask(false);
                 } else {
-                    state.enable(gl.CULL_FACE);
+                    state.enable(gl.DEPTH_TEST);
+                    state.depthMask(r.state.writeDepth);
                 }
             } else {
-                // webgl default
-                state.disable(gl.CULL_FACE);
-            }
+                state.enable(gl.DEPTH_TEST);
+                if (r.values.dDoubleSided) {
+                    if (r.values.dDoubleSided.ref.value || r.values.hasReflection.ref.value) {
+                        state.disable(gl.CULL_FACE);
+                    } else {
+                        state.enable(gl.CULL_FACE);
+                    }
+                } else {
+                    // webgl default
+                    state.disable(gl.CULL_FACE);
+                }
 
-            if (r.values.dFlipSided) {
-                if (r.values.dFlipSided.ref.value) {
-                    state.frontFace(gl.CW);
-                    state.cullFace(gl.FRONT);
+                if (r.values.dFlipSided) {
+                    if (r.values.dFlipSided.ref.value) {
+                        state.frontFace(gl.CW);
+                        state.cullFace(gl.FRONT);
+                    } else {
+                        state.frontFace(gl.CCW);
+                        state.cullFace(gl.BACK);
+                    }
                 } else {
+                    // webgl default
                     state.frontFace(gl.CCW);
                     state.cullFace(gl.BACK);
                 }
-            } else {
-                // webgl default
-                state.frontFace(gl.CCW);
-                state.cullFace(gl.BACK);
-            }
 
-            if (variant === 'color') {
                 state.depthMask(r.state.writeDepth);
             }
 

+ 15 - 14
src/mol-gl/shader-code.ts

@@ -15,11 +15,12 @@ export type DefineValues = { [k: string]: ValueCell<DefineType> }
 
 const shaderCodeId = idFactory();
 
+type ShaderExtensionsValue = 'required' | 'optional'
 export interface ShaderExtensions {
-    readonly standardDerivatives?: boolean
-    readonly fragDepth?: boolean
-    readonly drawBuffers?: boolean
-    readonly shaderTextureLod?: boolean
+    readonly standardDerivatives?: ShaderExtensionsValue
+    readonly fragDepth?: ShaderExtensionsValue
+    readonly drawBuffers?: ShaderExtensionsValue
+    readonly shaderTextureLod?: ShaderExtensionsValue
 }
 
 export interface ShaderCode {
@@ -118,11 +119,11 @@ export const PointsShaderCode = ShaderCode('points', points_vert, points_frag);
 
 import spheres_vert from './shader/spheres.vert';
 import spheres_frag from './shader/spheres.frag';
-export const SpheresShaderCode = ShaderCode('spheres', spheres_vert, spheres_frag, { fragDepth: true });
+export const SpheresShaderCode = ShaderCode('spheres', spheres_vert, spheres_frag, { fragDepth: 'required' });
 
 import text_vert from './shader/text.vert';
 import text_frag from './shader/text.frag';
-export const TextShaderCode = ShaderCode('text', text_vert, text_frag, { standardDerivatives: true });
+export const TextShaderCode = ShaderCode('text', text_vert, text_frag, { standardDerivatives: 'required' });
 
 import lines_vert from './shader/lines.vert';
 import lines_frag from './shader/lines.frag';
@@ -130,11 +131,11 @@ export const LinesShaderCode = ShaderCode('lines', lines_vert, lines_frag);
 
 import mesh_vert from './shader/mesh.vert';
 import mesh_frag from './shader/mesh.frag';
-export const MeshShaderCode = ShaderCode('mesh', mesh_vert, mesh_frag, { standardDerivatives: true });
+export const MeshShaderCode = ShaderCode('mesh', mesh_vert, mesh_frag, { standardDerivatives: 'optional' });
 
 import direct_volume_vert from './shader/direct-volume.vert';
 import direct_volume_frag from './shader/direct-volume.frag';
-export const DirectVolumeShaderCode = ShaderCode('direct-volume', direct_volume_vert, direct_volume_frag, { fragDepth: true });
+export const DirectVolumeShaderCode = ShaderCode('direct-volume', direct_volume_vert, direct_volume_frag, { fragDepth: 'optional' });
 
 import image_vert from './shader/image.vert';
 import image_frag from './shader/image.frag';
@@ -177,24 +178,24 @@ function getGlsl100FragPrefix(extensions: WebGLExtensions, shaderExtensions: Sha
         if (extensions.fragDepth) {
             prefix.push('#extension GL_EXT_frag_depth : enable');
             prefix.push('#define enabledFragDepth');
-        } else {
-            throw new Error(`requested 'GL_EXT_frag_depth' extension is unavailable`);
+        } else if (shaderExtensions.fragDepth === 'required') {
+            throw new Error(`required 'GL_EXT_frag_depth' extension not available`);
         }
     }
     if (shaderExtensions.drawBuffers) {
         if (extensions.drawBuffers) {
             prefix.push('#extension GL_EXT_draw_buffers : require');
             prefix.push('#define requiredDrawBuffers');
-        } else {
-            throw new Error(`requested 'GL_EXT_draw_buffers' extension is unavailable`);
+        } else if (shaderExtensions.drawBuffers === 'required') {
+            throw new Error(`required 'GL_EXT_draw_buffers' extension not available`);
         }
     }
     if (shaderExtensions.shaderTextureLod) {
         if (extensions.shaderTextureLod) {
             prefix.push('#extension GL_EXT_shader_texture_lod : enable');
             prefix.push('#define enabledShaderTextureLod');
-        } else {
-            throw new Error(`requested 'GL_EXT_shader_texture_lod' extension is unavailable`);
+        } else if (shaderExtensions.shaderTextureLod === 'required') {
+            throw new Error(`required 'GL_EXT_shader_texture_lod' extension not available`);
         }
     }
     return prefix.join('\n') + '\n';

+ 56 - 19
src/mol-gl/shader/direct-volume.frag.ts

@@ -12,12 +12,20 @@ precision highp int;
 #include common
 #include light_frag_params
 
+#if dClipObjectCount != 0
+    uniform int uClipObjectType[dClipObjectCount];
+    uniform vec3 uClipObjectPosition[dClipObjectCount];
+    uniform vec4 uClipObjectRotation[dClipObjectCount];
+    uniform vec3 uClipObjectScale[dClipObjectCount];
+#endif
+#include common_clip
+
 #include read_from_texture
 #include texture3d_from_1d_trilinear
 #include texture3d_from_2d_nearest
 #include texture3d_from_2d_linear
 
-uniform mat4 uProjection, uTransform, uModelView, uView;
+uniform mat4 uProjection, uTransform, uModelView, uModel, uView;
 uniform vec3 uCameraDir;
 
 uniform sampler2D tDepth;
@@ -25,13 +33,14 @@ uniform vec2 uDrawingBufferSize;
 uniform float uNear;
 uniform float uFar;
 
-varying vec3 unitCoord;
-varying vec3 origPos;
-varying float instance;
+varying vec3 vOrigPos;
+varying float vInstance;
+varying vec4 vBoundingSphere;
 
 uniform mat4 uInvView;
 uniform vec2 uIsoValue;
 uniform vec3 uGridDim;
+uniform vec3 uBboxSize;
 uniform sampler2D tTransferTex;
 uniform float uStepFactor;
 
@@ -178,6 +187,17 @@ vec4 raymarch(vec3 startLoc, vec3 step) {
                 isoPos = toUnit(mix(pos - step, pos, ((prevValue - uIsoValue.x) / ((prevValue - uIsoValue.x) - (value - uIsoValue.x)))));
 
                 vec4 mvPosition = uModelView * uTransform * vec4(isoPos * uGridDim, 1.0);
+
+                #if defined(dClipVariant_pixel) && dClipObjectCount != 0
+                    vec3 vModelPosition = (uModel * uTransform * vec4(isoPos * uGridDim, 1.0)).xyz;
+                    if (clipTest(vec4(vModelPosition, 0.0), 0)) {
+                        prevValue = value;
+                        prevCell = cell;
+                        pos += step;
+                        continue;
+                    }
+                #endif
+
                 float depth = calcDepth(mvPosition.xyz);
                 if (depth > getDepth(gl_FragCoord.xy / uDrawingBufferSize))
                     break;
@@ -192,7 +212,7 @@ vec4 raymarch(vec3 startLoc, vec3 step) {
                 #if defined(dRenderVariant_pickObject)
                     return vec4(encodeFloatRGB(float(uObjectId)), 1.0);
                 #elif defined(dRenderVariant_pickInstance)
-                    return vec4(encodeFloatRGB(instance), 1.0);
+                    return vec4(encodeFloatRGB(vInstance), 1.0);
                 #elif defined(dRenderVariant_pickGroup)
                     #ifdef dPackedGroup
                         return vec4(textureGroup(floor(isoPos * uGridDim + 0.5) / uGridDim).rgb, 1.0);
@@ -217,15 +237,15 @@ vec4 raymarch(vec3 startLoc, vec3 step) {
                     #if defined(dColorType_uniform)
                         color = uColor;
                     #elif defined(dColorType_instance)
-                        color = readFromTexture(tColor, instance, uColorTexDim).rgb;
+                        color = readFromTexture(tColor, vInstance, uColorTexDim).rgb;
                     #elif defined(dColorType_group)
                         color = readFromTexture(tColor, group, uColorTexDim).rgb;
                     #elif defined(dColorType_groupInstance)
-                        color = readFromTexture(tColor, instance * float(uGroupCount) + group, uColorTexDim).rgb;
+                        color = readFromTexture(tColor, vInstance * float(uGroupCount) + group, uColorTexDim).rgb;
                     #elif defined(dColorType_vertex)
                         color = texture3dFrom1dTrilinear(tColor, isoPos, uGridDim, uColorTexDim, 0.0).rgb;
                     #elif defined(dColorType_vertexInstance)
-                        color = texture3dFrom1dTrilinear(tColor, isoPos, uGridDim, uColorTexDim, instance * float(uVertexCount)).rgb;
+                        color = texture3dFrom1dTrilinear(tColor, isoPos, uGridDim, uColorTexDim, vInstance * float(uVertexCount)).rgb;
                     #endif
 
                     // handle flipping and negative isosurfaces
@@ -268,7 +288,7 @@ vec4 raymarch(vec3 startLoc, vec3 step) {
                         #include apply_light_color
                     #endif
 
-                    float vMarker = readFromTexture(tMarker, instance * float(uGroupCount) + group, uMarkerTexDim).a;
+                    float vMarker = readFromTexture(tMarker, vInstance * float(uGroupCount) + group, uMarkerTexDim).a;
                     #include apply_interior_color
                     #include apply_marker_color
                     #include apply_fog
@@ -290,6 +310,16 @@ vec4 raymarch(vec3 startLoc, vec3 step) {
             if (calcDepth(mvPosition.xyz) > getDepth(gl_FragCoord.xy / uDrawingBufferSize))
                 break;
 
+            #if defined(dClipVariant_pixel) && dClipObjectCount != 0
+                vec3 vModelPosition = (uModel * uTransform * vec4(isoPos * uGridDim, 1.0)).xyz;
+                if (clipTest(vec4(vModelPosition, 0.0), 0)) {
+                    prevValue = value;
+                    prevCell = cell;
+                    pos += step;
+                    continue;
+                }
+            #endif
+
             vec3 vViewPosition = mvPosition.xyz;
             vec4 material = transferFunction(value);
 
@@ -318,7 +348,7 @@ vec4 raymarch(vec3 startLoc, vec3 step) {
                 float group = g.z + g.y * uGridDim.z + g.x * uGridDim.z * uGridDim.y;
             #endif
 
-            float vMarker = readFromTexture(tMarker, instance * float(uGroupCount) + group, uMarkerTexDim).a;
+            float vMarker = readFromTexture(tMarker, vInstance * float(uGroupCount) + group, uMarkerTexDim).a;
             #include apply_marker_color
             #include apply_fog
 
@@ -344,15 +374,15 @@ vec4 raymarch(vec3 startLoc, vec3 step) {
     return dst;
 }
 
-// TODO calculate normalMatrix on CPU
-// TODO fix near/far clipping
-// TODO support clip objects
-// TODO support float texture for higher precision values???
+// TODO: calculate normalMatrix on CPU
+// TODO: support float texture for higher precision values???
+// TODO: support clipping exclusion texture support
+// TODO: support instance transforms
 
 void main () {
-    // TODO handle on CPU in renderloop?
-    #if defined(dRenderVariant_pick)
+    #if defined(dRenderVariant_pick) || defined(dRenderVariant_depth)
         #if defined(dRenderMode_volume)
+            // always ignore pick & depth for volume
             discard;
         #elif defined(dRenderMode_isosurface)
             if (uAlpha < uPickingAlphaThreshold)
@@ -360,13 +390,20 @@ void main () {
         #endif
     #endif
 
-    vec3 rayDir = mix(normalize(origPos - uCameraPosition), uCameraDir, uIsOrtho);;
+    vec3 rayDir = mix(normalize(vOrigPos - uCameraPosition), uCameraDir, uIsOrtho);;
 
     // TODO: set the scale as uniform?
     float stepScale = min(uCellDim.x, min(uCellDim.y, uCellDim.z)) * uStepFactor;
     vec3 step = rayDir * stepScale;
 
-    float d = uNear - distance(origPos, uCameraPosition);
-    gl_FragColor = raymarch(origPos + (d * rayDir), step);
+    float boundingSphereNear = distance(vBoundingSphere.xyz, uCameraPosition) - vBoundingSphere.w;
+    float d = max(uNear, boundingSphereNear) - distance(vOrigPos, uCameraPosition);
+    gl_FragColor = raymarch(vOrigPos + (d * rayDir), step);
+
+    #if defined(dRenderVariant_pick) || defined(dRenderVariant_depth)
+        // discard when nothing was hit
+        if (gl_FragColor == vec4(0.0))
+            discard;
+    #endif
 }
 `;

+ 15 - 15
src/mol-gl/shader/direct-volume.vert.ts

@@ -12,9 +12,13 @@ attribute vec3 aPosition;
 attribute mat4 aTransform;
 attribute float aInstance;
 
-varying vec3 unitCoord;
-varying vec3 origPos;
-varying float instance;
+uniform mat4 uModelView;
+uniform mat4 uProjection;
+uniform vec4 uInvariantBoundingSphere;
+
+varying vec3 vOrigPos;
+varying float vInstance;
+varying vec4 vBoundingSphere;
 
 uniform vec3 uBboxSize;
 uniform vec3 uBboxMin;
@@ -25,21 +29,17 @@ uniform mat4 uTransform;
 uniform mat4 uUnitToCartn;
 uniform mat4 uCartnToUnit;
 
-uniform mat4 uModelView;
-uniform mat4 uProjection;
-
 void main() {
-    unitCoord = aPosition + vec3(0.5);
+    vec3 unitCoord = aPosition + vec3(0.5);
     vec4 mvPosition = uModelView * uUnitToCartn * vec4(unitCoord, 1.0);
-    origPos = (uUnitToCartn * vec4(unitCoord, 1.0)).xyz;
-    instance = aInstance;
+
+    vOrigPos = (uUnitToCartn * vec4(unitCoord, 1.0)).xyz;
+    vInstance = aInstance;
+    vBoundingSphere = uInvariantBoundingSphere;
+
     gl_Position = uProjection * mvPosition;
 
-    // clamp z position to clip space
-    if(gl_Position.z > gl_Position.w) {
-        gl_Position.z = gl_Position.w - 0.0001;
-    } else if(gl_Position.z < -gl_Position.w) {
-        gl_Position.z = -gl_Position.w + 0.0001;
-    }
+    // move z position to near clip plane
+    gl_Position.z = gl_Position.w - 0.0001;
 }
 `;

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

@@ -49,9 +49,8 @@ export const GaussianDensitySchema = {
 type GaussianDensityValues = Values<typeof GaussianDensitySchema>
 type GaussianDensityRenderable = ComputeRenderable<GaussianDensityValues>
 
-export const GaussianDensityShaderCode = ShaderCode(
-    'gaussian-density', gaussian_density_vert, gaussian_density_frag,
-    { standardDerivatives: false, fragDepth: false }
+const GaussianDensityShaderCode = ShaderCode(
+    'gaussian-density', gaussian_density_vert, gaussian_density_frag
 );
 
 let _tmpTexture: Texture | undefined = undefined;
@@ -299,22 +298,22 @@ function getGaussianDensityRenderable(webgl: WebGLContext, drawCount: number, po
         ValueCell.updateIfChanged(v.drawCount, drawCount);
         ValueCell.updateIfChanged(v.instanceCount, 1);
 
-        ValueCell.updateIfChanged(v.aRadius, radii);
-        ValueCell.updateIfChanged(v.aPosition, positions);
-        ValueCell.updateIfChanged(v.aGroup, groups);
+        ValueCell.update(v.aRadius, radii);
+        ValueCell.update(v.aPosition, positions);
+        ValueCell.update(v.aGroup, groups);
 
         ValueCell.updateIfChanged(v.uCurrentSlice, 0);
         ValueCell.updateIfChanged(v.uCurrentX, 0);
         ValueCell.updateIfChanged(v.uCurrentY, 0);
-        ValueCell.updateIfChanged(v.uBboxMin, box.min);
-        ValueCell.updateIfChanged(v.uBboxSize, extent);
-        ValueCell.updateIfChanged(v.uGridDim, gridDim);
-        ValueCell.updateIfChanged(v.uGridTexDim, gridTexDim);
-        ValueCell.updateIfChanged(v.uGridTexScale, gridTexScale);
+        ValueCell.update(v.uBboxMin, box.min);
+        ValueCell.update(v.uBboxSize, extent);
+        ValueCell.update(v.uGridDim, gridDim);
+        ValueCell.update(v.uGridTexDim, gridTexDim);
+        ValueCell.update(v.uGridTexScale, gridTexScale);
         ValueCell.updateIfChanged(v.uAlpha, smoothness);
         ValueCell.updateIfChanged(v.uResolution, resolution);
         ValueCell.updateIfChanged(v.uRadiusFactor, radiusFactor);
-        ValueCell.updateIfChanged(v.tMinDistanceTex, minDistanceTexture);
+        ValueCell.update(v.tMinDistanceTex, minDistanceTexture);
 
         ValueCell.updateIfChanged(v.dGridTexType, minDistanceTexture.getDepth() > 0 ? '3d' : '2d');
         ValueCell.updateIfChanged(v.dCalcType, 'density');
@@ -355,8 +354,7 @@ function _getGaussianDensityRenderable(webgl: WebGLContext, drawCount: number, p
     };
 
     const schema = { ...GaussianDensitySchema };
-    const shaderCode = GaussianDensityShaderCode;
-    const renderItem =  createComputeRenderItem(webgl, 'points', shaderCode, schema, values);
+    const renderItem =  createComputeRenderItem(webgl, 'points', GaussianDensityShaderCode, schema, values);
 
     return createComputeRenderable(renderItem, values);
 }

+ 10 - 0
src/mol-model/volume/volume.ts

@@ -36,6 +36,16 @@ export interface Volume {
 }
 
 export namespace Volume {
+    export function is(x: any): x is Volume {
+        // TODO: improve
+        return (
+            x?.grid?.cells?.space?.dimensions?.length &&
+            x?.sourceData &&
+            x?.customProperties &&
+            x?._propertyData
+        );
+    }
+
     export type CellIndex = { readonly '@type': 'cell-index' } & number
 
     export type IsoValue = IsoValue.Absolute | IsoValue.Relative

+ 10 - 3
src/mol-repr/util.ts

@@ -9,6 +9,7 @@ import { Structure } from '../mol-model/structure';
 import { VisualQuality } from '../mol-geo/geometry/base';
 import { Box3D, SpacegroupCell } from '../mol-math/geometry';
 import { ModelSymmetry } from '../mol-model-formats/structure/property/symmetry';
+import { Volume } from '../mol-model/volume';
 
 export interface VisualUpdateState {
     updateTransform: boolean
@@ -111,9 +112,15 @@ export function getQualityProps(props: Partial<QualityProps>, data?: any) {
     let doubleSided = defaults(props.doubleSided, true);
 
     let volume = 0;
-    if (quality === 'auto' && data instanceof Structure) {
-        quality = getStructureQuality(data.root);
-        volume = getRootVolume(data);
+    if (quality === 'auto') {
+        if (data instanceof Structure) {
+            quality = getStructureQuality(data.root);
+            volume = getRootVolume(data);
+        } else if (Volume.is(data)) {
+            const [x, y, z] = data.grid.cells.space.dimensions;
+            volume = x * y * z;
+            quality = volume < 10_000_000 ? 'medium' : 'low';
+        }
     }
 
     switch (quality) {

+ 3 - 2
src/mol-repr/volume/representation.ts

@@ -15,7 +15,7 @@ import { createRenderObject, getNextMaterialId, GraphicsRenderObject } from '../
 import { PickingId } from '../../mol-geo/geometry/picking';
 import { Loci, isEveryLoci, EmptyLoci } from '../../mol-model/loci';
 import { Interval } from '../../mol-data/int';
-import { VisualUpdateState } from '../util';
+import { getQualityProps, VisualUpdateState } from '../util';
 import { ColorTheme } from '../../mol-theme/color';
 import { ValueCell } from '../../mol-util';
 import { createSizes } from '../../mol-geo/geometry/size-data';
@@ -243,7 +243,8 @@ export function VolumeRepresentation<P extends VolumeParams>(label: string, ctx:
             _volume = volume;
             if (!_props) _props = PD.getDefaultValues(_params);
         }
-        _props = Object.assign({}, _props, props);
+        const qualityProps = getQualityProps(Object.assign({}, _props, props), _volume);
+        Object.assign(_props, props, qualityProps);
 
         return Task.create('Creating or updating VolumeRepresentation', async runtime => {
             if (!visual) visual = visualCtor(materialId);