Browse Source

Merge pull request #299 from molstar/bump

procedural bump mapping
Alexander Rose 3 years ago
parent
commit
e2966241e8
36 changed files with 166 additions and 47 deletions
  1. 2 0
      CHANGELOG.md
  2. 2 0
      src/mol-geo/geometry/base.ts
  3. 3 0
      src/mol-geo/geometry/cylinders/cylinders.ts
  4. 1 1
      src/mol-geo/geometry/mesh/color-smoothing.ts
  5. 3 0
      src/mol-geo/geometry/mesh/mesh.ts
  6. 3 0
      src/mol-geo/geometry/spheres/spheres.ts
  7. 3 3
      src/mol-geo/geometry/substance-data.ts
  8. 3 3
      src/mol-geo/geometry/texture-mesh/color-smoothing.ts
  9. 3 0
      src/mol-geo/geometry/texture-mesh/texture-mesh.ts
  10. 3 2
      src/mol-gl/renderable/cylinders.ts
  11. 3 2
      src/mol-gl/renderable/mesh.ts
  12. 3 2
      src/mol-gl/renderable/schema.ts
  13. 3 2
      src/mol-gl/renderable/spheres.ts
  14. 1 0
      src/mol-gl/renderable/texture-mesh.ts
  15. 17 12
      src/mol-gl/shader/chunks/apply-light-color.glsl.ts
  16. 4 4
      src/mol-gl/shader/chunks/assign-color-varying.glsl.ts
  17. 4 2
      src/mol-gl/shader/chunks/assign-material-color.glsl.ts
  18. 5 1
      src/mol-gl/shader/chunks/color-frag-params.glsl.ts
  19. 3 2
      src/mol-gl/shader/chunks/color-vert-params.glsl.ts
  20. 45 0
      src/mol-gl/shader/chunks/common-frag-params.glsl.ts
  21. 6 0
      src/mol-gl/shader/chunks/common.glsl.ts
  22. 5 1
      src/mol-gl/shader/cylinders.frag.ts
  23. 2 0
      src/mol-gl/shader/mesh.frag.ts
  24. 4 1
      src/mol-gl/shader/spheres.frag.ts
  25. 3 1
      src/mol-repr/structure/representation/backbone.ts
  26. 3 1
      src/mol-repr/structure/representation/ball-and-stick.ts
  27. 2 0
      src/mol-repr/structure/representation/carbohydrate.ts
  28. 2 0
      src/mol-repr/structure/representation/cartoon.ts
  29. 2 0
      src/mol-repr/structure/representation/ellipsoid.ts
  30. 2 0
      src/mol-repr/structure/representation/gaussian-surface.ts
  31. 2 0
      src/mol-repr/structure/representation/molecular-surface.ts
  32. 2 0
      src/mol-repr/structure/representation/orientation.ts
  33. 2 0
      src/mol-repr/structure/representation/putty.ts
  34. 2 0
      src/mol-repr/structure/representation/spacefill.ts
  35. 2 0
      src/mol-repr/volume/isosurface.ts
  36. 11 7
      src/mol-util/material.ts

+ 2 - 0
CHANGELOG.md

@@ -6,6 +6,8 @@ Note that since we don't clearly distinguish between a public and private interf
 
 ## [Unreleased]
 
+- Add ``bumpiness`` (per-object and per-group) and ``bumpFrequency`` (per-object) render parameters (#299)
+
 ## [v3.0.0-dev.3] - 2021-12-4
 
 - Fix OBJ and USDZ export

+ 2 - 0
src/mol-geo/geometry/base.ts

@@ -105,6 +105,7 @@ export namespace BaseGeometry {
             drawCount: ValueCell.create(counts.drawCount),
             uMetalness: ValueCell.create(props.material.metalness),
             uRoughness: ValueCell.create(props.material.roughness),
+            uBumpiness: ValueCell.create(props.material.bumpiness),
             dLightCount: ValueCell.create(1),
         };
     }
@@ -113,6 +114,7 @@ export namespace BaseGeometry {
         ValueCell.updateIfChanged(values.alpha, props.alpha); // `uAlpha` is set in renderable.render
         ValueCell.updateIfChanged(values.uMetalness, props.material.metalness);
         ValueCell.updateIfChanged(values.uRoughness, props.material.roughness);
+        ValueCell.updateIfChanged(values.uBumpiness, props.material.bumpiness);
     }
 
     export function createRenderableState(props: Partial<PD.Values<Params>> = {}): RenderableState {

+ 3 - 0
src/mol-geo/geometry/cylinders/cylinders.ts

@@ -157,6 +157,7 @@ export namespace Cylinders {
         doubleSided: PD.Boolean(false, BaseGeometry.CustomQualityParamInfo),
         ignoreLight: PD.Boolean(false, BaseGeometry.ShadingCategory),
         xrayShaded: PD.Boolean(false, BaseGeometry.ShadingCategory),
+        bumpFrequency: PD.Numeric(0, { min: 0, max: 10, step: 0.1 }, BaseGeometry.ShadingCategory),
     };
     export type Params = typeof Params
 
@@ -237,6 +238,7 @@ export namespace Cylinders {
             dDoubleSided: ValueCell.create(props.doubleSided),
             dIgnoreLight: ValueCell.create(props.ignoreLight),
             dXrayShaded: ValueCell.create(props.xrayShaded),
+            uBumpFrequency: ValueCell.create(props.bumpFrequency),
         };
     }
 
@@ -252,6 +254,7 @@ export namespace Cylinders {
         ValueCell.updateIfChanged(values.dDoubleSided, props.doubleSided);
         ValueCell.updateIfChanged(values.dIgnoreLight, props.ignoreLight);
         ValueCell.updateIfChanged(values.dXrayShaded, props.xrayShaded);
+        ValueCell.updateIfChanged(values.uBumpFrequency, props.bumpFrequency);
     }
 
     function updateBoundingSphere(values: CylindersValues, cylinders: Cylinders) {

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

@@ -376,7 +376,7 @@ export function applyMeshSubstanceSmoothing(values: MeshValues, resolution: numb
         colorType: values.dSubstanceType.ref.value,
         boundingSphere: values.boundingSphere.ref.value,
         invariantBoundingSphere: values.invariantBoundingSphere.ref.value,
-        itemSize: 3
+        itemSize: 4
     }, resolution, stride, webgl, colorTexture);
     if (smoothingData.kind === 'volume') {
         ValueCell.updateIfChanged(values.dSubstanceType, smoothingData.type);

+ 3 - 0
src/mol-geo/geometry/mesh/mesh.ts

@@ -625,6 +625,7 @@ export namespace Mesh {
         flatShaded: PD.Boolean(false, BaseGeometry.ShadingCategory),
         ignoreLight: PD.Boolean(false, BaseGeometry.ShadingCategory),
         xrayShaded: PD.Boolean(false, BaseGeometry.ShadingCategory),
+        bumpFrequency: PD.Numeric(0, { min: 0, max: 10, step: 0.1 }, BaseGeometry.ShadingCategory),
     };
     export type Params = typeof Params
 
@@ -696,6 +697,7 @@ export namespace Mesh {
             dFlipSided: ValueCell.create(props.flipSided),
             dIgnoreLight: ValueCell.create(props.ignoreLight),
             dXrayShaded: ValueCell.create(props.xrayShaded),
+            uBumpFrequency: ValueCell.create(props.bumpFrequency),
 
             meta: ValueCell.create(mesh.meta),
         };
@@ -714,6 +716,7 @@ export namespace Mesh {
         ValueCell.updateIfChanged(values.dFlipSided, props.flipSided);
         ValueCell.updateIfChanged(values.dIgnoreLight, props.ignoreLight);
         ValueCell.updateIfChanged(values.dXrayShaded, props.xrayShaded);
+        ValueCell.updateIfChanged(values.uBumpFrequency, props.bumpFrequency);
     }
 
     function updateBoundingSphere(values: MeshValues, mesh: Mesh) {

+ 3 - 0
src/mol-geo/geometry/spheres/spheres.ts

@@ -129,6 +129,7 @@ export namespace Spheres {
         doubleSided: PD.Boolean(false, BaseGeometry.CustomQualityParamInfo),
         ignoreLight: PD.Boolean(false, BaseGeometry.ShadingCategory),
         xrayShaded: PD.Boolean(false, BaseGeometry.ShadingCategory),
+        bumpFrequency: PD.Numeric(0, { min: 0, max: 10, step: 0.1 }, BaseGeometry.ShadingCategory),
     };
     export type Params = typeof Params
 
@@ -204,6 +205,7 @@ export namespace Spheres {
             dDoubleSided: ValueCell.create(props.doubleSided),
             dIgnoreLight: ValueCell.create(props.ignoreLight),
             dXrayShaded: ValueCell.create(props.xrayShaded),
+            uBumpFrequency: ValueCell.create(props.bumpFrequency),
         };
     }
 
@@ -219,6 +221,7 @@ export namespace Spheres {
         ValueCell.updateIfChanged(values.dDoubleSided, props.doubleSided);
         ValueCell.updateIfChanged(values.dIgnoreLight, props.ignoreLight);
         ValueCell.updateIfChanged(values.dXrayShaded, props.xrayShaded);
+        ValueCell.updateIfChanged(values.uBumpFrequency, props.bumpFrequency);
     }
 
     function updateBoundingSphere(values: SpheresValues, spheres: Spheres) {

+ 3 - 3
src/mol-geo/geometry/substance-data.ts

@@ -23,14 +23,14 @@ export type SubstanceData = {
 
 export function applySubstanceMaterial(array: Uint8Array, start: number, end: number, material: Material) {
     for (let i = start; i < end; ++i) {
-        Material.toArray(material, array, i * 3);
-        array[i * 3 + 2] = 255;
+        Material.toArray(material, array, i * 4);
+        array[i * 4 + 3] = 255;
     }
     return true;
 }
 
 export function clearSubstance(array: Uint8Array, start: number, end: number) {
-    array.fill(0, start * 3, end * 3);
+    array.fill(0, start * 4, end * 4);
     return true;
 }
 

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

@@ -510,10 +510,10 @@ export function applyTextureMeshSubstanceSmoothing(values: TextureMeshValues, re
 
     stride *= 3; // triple because TextureMesh is never indexed (no elements buffer)
 
-    if (!webgl.namedTextures[ColorSmoothingRgbName]) {
-        webgl.namedTextures[ColorSmoothingRgbName] = webgl.resources.texture('image-uint8', 'rgb', 'ubyte', 'nearest');
+    if (!webgl.namedTextures[ColorSmoothingRgbaName]) {
+        webgl.namedTextures[ColorSmoothingRgbaName] = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
     }
-    const colorData = webgl.namedTextures[ColorSmoothingRgbName];
+    const colorData = webgl.namedTextures[ColorSmoothingRgbaName];
     colorData.load(values.tSubstance.ref.value);
 
     const smoothingData = calcTextureMeshColorSmoothing({

+ 3 - 0
src/mol-geo/geometry/texture-mesh/texture-mesh.ts

@@ -113,6 +113,7 @@ export namespace TextureMesh {
         flatShaded: PD.Boolean(false, BaseGeometry.ShadingCategory),
         ignoreLight: PD.Boolean(false, BaseGeometry.ShadingCategory),
         xrayShaded: PD.Boolean(false, BaseGeometry.ShadingCategory),
+        bumpFrequency: PD.Numeric(0, { min: 0, max: 10, step: 0.1 }, BaseGeometry.ShadingCategory),
     };
     export type Params = typeof Params
 
@@ -168,6 +169,7 @@ export namespace TextureMesh {
             dFlipSided: ValueCell.create(props.flipSided),
             dIgnoreLight: ValueCell.create(props.ignoreLight),
             dXrayShaded: ValueCell.create(props.xrayShaded),
+            uBumpFrequency: ValueCell.create(props.bumpFrequency),
             dGeoTexture: ValueCell.create(true),
 
             meta: ValueCell.create(textureMesh.meta),
@@ -187,6 +189,7 @@ export namespace TextureMesh {
         ValueCell.updateIfChanged(values.dFlipSided, props.flipSided);
         ValueCell.updateIfChanged(values.dIgnoreLight, props.ignoreLight);
         ValueCell.updateIfChanged(values.dXrayShaded, props.xrayShaded);
+        ValueCell.updateIfChanged(values.uBumpFrequency, props.bumpFrequency);
     }
 
     function updateBoundingSphere(values: TextureMeshValues, textureMesh: TextureMesh) {

+ 3 - 2
src/mol-gl/renderable/cylinders.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -7,7 +7,7 @@
 import { Renderable, RenderableState, createRenderable } from '../renderable';
 import { WebGLContext } from '../webgl/context';
 import { createGraphicsRenderItem } from '../webgl/render-item';
-import { GlobalUniformSchema, BaseSchema, AttributeSpec, Values, InternalSchema, SizeSchema, InternalValues, ElementsSpec, ValueSpec, DefineSpec, GlobalTextureSchema } from './schema';
+import { GlobalUniformSchema, BaseSchema, AttributeSpec, Values, InternalSchema, SizeSchema, InternalValues, ElementsSpec, ValueSpec, DefineSpec, GlobalTextureSchema, UniformSpec } from './schema';
 import { CylindersShaderCode } from '../shader-code';
 import { ValueCell } from '../../mol-util';
 
@@ -26,6 +26,7 @@ export const CylindersSchema = {
     dDoubleSided: DefineSpec('boolean'),
     dIgnoreLight: DefineSpec('boolean'),
     dXrayShaded: DefineSpec('boolean'),
+    uBumpFrequency: UniformSpec('f', 'material'),
 };
 export type CylindersSchema = typeof CylindersSchema
 export type CylindersValues = Values<CylindersSchema>

+ 3 - 2
src/mol-gl/renderable/mesh.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -7,7 +7,7 @@
 import { Renderable, RenderableState, createRenderable } from '../renderable';
 import { WebGLContext } from '../webgl/context';
 import { createGraphicsRenderItem } from '../webgl/render-item';
-import { GlobalUniformSchema, BaseSchema, AttributeSpec, ElementsSpec, DefineSpec, Values, InternalSchema, InternalValues, GlobalTextureSchema, ValueSpec } from './schema';
+import { GlobalUniformSchema, BaseSchema, AttributeSpec, ElementsSpec, DefineSpec, Values, InternalSchema, InternalValues, GlobalTextureSchema, ValueSpec, UniformSpec } from './schema';
 import { MeshShaderCode } from '../shader-code';
 import { ValueCell } from '../../mol-util';
 
@@ -22,6 +22,7 @@ export const MeshSchema = {
     dFlipSided: DefineSpec('boolean'),
     dIgnoreLight: DefineSpec('boolean'),
     dXrayShaded: DefineSpec('boolean'),
+    uBumpFrequency: UniformSpec('f', 'material'),
     meta: ValueSpec('unknown')
 } as const;
 export type MeshSchema = typeof MeshSchema

+ 3 - 2
src/mol-gl/renderable/schema.ts

@@ -243,12 +243,12 @@ export type TransparencyValues = Values<TransparencySchema>
 
 export const SubstanceSchema = {
     uSubstanceTexDim: UniformSpec('v2'),
-    tSubstance: TextureSpec('image-uint8', 'rgb', 'ubyte', 'nearest'),
+    tSubstance: TextureSpec('image-uint8', 'rgba', 'ubyte', 'nearest'),
     dSubstance: DefineSpec('boolean'),
 
     uSubstanceGridDim: UniformSpec('v3'),
     uSubstanceGridTransform: UniformSpec('v4'),
-    tSubstanceGrid: TextureSpec('texture', 'rgb', 'ubyte', 'linear'),
+    tSubstanceGrid: TextureSpec('texture', 'rgba', 'ubyte', 'linear'),
     dSubstanceType: DefineSpec('string', ['groupInstance', 'volumeInstance']),
 } as const;
 export type SubstanceSchema = typeof SubstanceSchema
@@ -288,6 +288,7 @@ export const BaseSchema = {
     uAlpha: UniformSpec('f', 'material'),
     uMetalness: UniformSpec('f', 'material'),
     uRoughness: UniformSpec('f', 'material'),
+    uBumpiness: UniformSpec('f', 'material'),
 
     uVertexCount: UniformSpec('i'),
     uInstanceCount: UniformSpec('i'),

+ 3 - 2
src/mol-gl/renderable/spheres.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -7,7 +7,7 @@
 import { Renderable, RenderableState, createRenderable } from '../renderable';
 import { WebGLContext } from '../webgl/context';
 import { createGraphicsRenderItem } from '../webgl/render-item';
-import { GlobalUniformSchema, BaseSchema, AttributeSpec, Values, InternalSchema, SizeSchema, InternalValues, ElementsSpec, ValueSpec, DefineSpec, GlobalTextureSchema } from './schema';
+import { GlobalUniformSchema, BaseSchema, AttributeSpec, Values, InternalSchema, SizeSchema, InternalValues, ElementsSpec, ValueSpec, DefineSpec, GlobalTextureSchema, UniformSpec } from './schema';
 import { SpheresShaderCode } from '../shader-code';
 import { ValueCell } from '../../mol-util';
 
@@ -23,6 +23,7 @@ export const SpheresSchema = {
     dDoubleSided: DefineSpec('boolean'),
     dIgnoreLight: DefineSpec('boolean'),
     dXrayShaded: DefineSpec('boolean'),
+    uBumpFrequency: UniformSpec('f', 'material'),
 };
 export type SpheresSchema = typeof SpheresSchema
 export type SpheresValues = Values<SpheresSchema>

+ 1 - 0
src/mol-gl/renderable/texture-mesh.ts

@@ -24,6 +24,7 @@ export const TextureMeshSchema = {
     dIgnoreLight: DefineSpec('boolean'),
     dXrayShaded: DefineSpec('boolean'),
     dGeoTexture: DefineSpec('boolean'),
+    uBumpFrequency: UniformSpec('f', 'material'),
     meta: ValueSpec('unknown')
 };
 export type TextureMeshSchema = typeof TextureMeshSchema

+ 17 - 12
src/mol-gl/shader/chunks/apply-light-color.glsl.ts

@@ -8,17 +8,16 @@
  */
 
 export const apply_light_color = `
-// inputs
-// - vec4 material
-// - vec3 vViewPosition
-// - vec3 normal
-// - float uMetalness
-// - float uRoughness
-// - vec3 uLightColor
-// - vec3 uAmbientColor
-
-// outputs
-// - sets gl_FragColor
+#ifdef bumpEnabled
+    if (uBumpFrequency > 0.0) {
+        vec3 bumpNormal = perturbNormal(-vViewPosition, normal, fbm(vModelPosition * uBumpFrequency), bumpiness / uBumpFrequency);
+        #ifdef enabledFragDepth
+            if (!any(isNaN(bumpNormal))) normal = bumpNormal;
+        #else
+            normal = bumpNormal;
+        #endif
+    }
+#endif
 
 vec4 color = material;
 
@@ -26,7 +25,13 @@ ReflectedLight reflectedLight = ReflectedLight(vec3(0.0), vec3(0.0), vec3(0.0),
 
 PhysicalMaterial physicalMaterial;
 physicalMaterial.diffuseColor = color.rgb * (1.0 - metalness);
-physicalMaterial.roughness = max(roughness, 0.0525);
+#ifdef enabledFragDepth
+    physicalMaterial.roughness = min(max(roughness, 0.0525), 1.0);
+#else
+    vec3 dxy = max(abs(dFdx(normal)), abs(dFdy(normal)));
+    float geometryRoughness = max(max(dxy.x, dxy.y), dxy.z);
+    physicalMaterial.roughness = min(max(roughness, 0.0525) + geometryRoughness, 1.0);
+#endif
 physicalMaterial.specularColor = mix(vec3(0.04), color.rgb, metalness);
 physicalMaterial.specularF90 = 1.0;
 

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

@@ -44,16 +44,16 @@ export const assign_color_varying = `
 
     #ifdef dSubstance
         #if defined(dSubstanceType_groupInstance)
-            vSubstance = readFromTexture(tSubstance, aInstance * float(uGroupCount) + group, uSubstanceTexDim).rgb;
+            vSubstance = readFromTexture(tSubstance, aInstance * float(uGroupCount) + group, uSubstanceTexDim);
         #elif defined(dSubstanceType_vertexInstance)
-            vSubstance = readFromTexture(tSubstance, int(aInstance) * uVertexCount + VertexID, uSubstanceTexDim).rgb;
+            vSubstance = readFromTexture(tSubstance, int(aInstance) * uVertexCount + VertexID, uSubstanceTexDim);
         #elif defined(dSubstanceType_volumeInstance)
             vec3 sgridPos = (uSubstanceGridTransform.w * (vModelPosition - uSubstanceGridTransform.xyz)) / uSubstanceGridDim;
-            vSubstance = texture3dFrom2dLinear(tSubstanceGrid, sgridPos, uSubstanceGridDim, uSubstanceTexDim).rgb;
+            vSubstance = texture3dFrom2dLinear(tSubstanceGrid, sgridPos, uSubstanceGridDim, uSubstanceTexDim);
         #endif
 
         // pre-mix to avoid artifacts due to empty substance
-        vSubstance.rg = mix(vec2(uMetalness, uRoughness), vSubstance.rg, vSubstance.b);
+        vSubstance.rgb = mix(vec3(uMetalness, uRoughness, uBumpiness), vSubstance.rgb, vSubstance.a);
     #endif
 #elif defined(dRenderVariant_pick)
     #if defined(dRenderVariant_pickObject)

+ 4 - 2
src/mol-gl/shader/chunks/assign-material-color.glsl.ts

@@ -23,9 +23,11 @@ export const assign_material_color = `
 
     float metalness = uMetalness;
     float roughness = uRoughness;
+    float bumpiness = uBumpiness;
     #ifdef dSubstance
-        metalness = mix(metalness, vSubstance.r, vSubstance.b);
-        roughness = mix(roughness, vSubstance.g, vSubstance.b);
+        metalness = mix(metalness, vSubstance.r, vSubstance.a);
+        roughness = mix(roughness, vSubstance.g, vSubstance.a);
+        bumpiness = mix(bumpiness, vSubstance.b, vSubstance.a);
     #endif
 #elif defined(dRenderVariant_pick)
     vec4 material = vColor;

+ 5 - 1
src/mol-gl/shader/chunks/color-frag-params.glsl.ts

@@ -1,6 +1,10 @@
 export const color_frag_params = `
 uniform float uMetalness;
 uniform float uRoughness;
+uniform float uBumpiness;
+#ifdef bumpEnabled
+    uniform float uBumpFrequency;
+#endif
 
 #if defined(dRenderVariant_color)
     #if defined(dColorType_uniform)
@@ -14,7 +18,7 @@ uniform float uRoughness;
     #endif
 
     #ifdef dSubstance
-        varying vec3 vSubstance;
+        varying vec4 vSubstance;
     #endif
 #elif defined(dRenderVariant_pick)
     #if __VERSION__ == 100

+ 3 - 2
src/mol-gl/shader/chunks/color-vert-params.glsl.ts

@@ -1,6 +1,7 @@
 export const color_vert_params = `
 uniform float uMetalness;
 uniform float uRoughness;
+uniform float uBumpiness;
 
 #if defined(dRenderVariant_color)
     #if defined(dColorType_uniform)
@@ -36,11 +37,11 @@ uniform float uRoughness;
 
     #ifdef dSubstance
         #if defined(dSubstanceType_groupInstance) || defined(dSubstanceType_vertexInstance)
-            varying vec3 vSubstance;
+            varying vec4 vSubstance;
             uniform vec2 uSubstanceTexDim;
             uniform sampler2D tSubstance;
         #elif defined(dSubstanceType_volumeInstance)
-            varying vec3 vSubstance;
+            varying vec4 vSubstance;
             uniform vec2 uSubstanceTexDim;
             uniform vec3 uSubstanceGridDim;
             uniform vec4 uSubstanceGridTransform;

+ 45 - 0
src/mol-gl/shader/chunks/common-frag-params.glsl.ts

@@ -76,4 +76,49 @@ float calcDepth(const in vec3 pos) {
     vec2 clipZW = pos.z * uProjection[2].zw + uProjection[3].zw;
     return 0.5 + 0.5 * clipZW.x / clipZW.y;
 }
+
+// "Bump Mapping Unparametrized Surfaces on the GPU" Morten S. Mikkelsen
+// https://mmikk.github.io/papers3d/mm_sfgrad_bump.pdf
+vec3 perturbNormal(in vec3 position, in vec3 normal, in float height, in float scale) {
+    vec3 sigmaS = dFdx(position);
+    vec3 sigmaT = dFdy(position);
+
+    vec3 r1 = cross(sigmaT, normal);
+    vec3 r2 = cross(normal, sigmaS);
+    float det = dot(sigmaS, r1);
+
+    float bs = dFdx(height);
+    float bt = dFdy(height);
+
+    vec3 surfGrad = sign(det) * (bs * r1 + bt * r2);
+    return normalize(abs(det) * normal - scale * surfGrad);
+}
+
+float hash(in float h) {
+    return fract(sin(h) * 43758.5453123);
+}
+
+float noise(in vec3 x) {
+    vec3 p = floor(x);
+    vec3 f = fract(x);
+    f = f * f * (3.0 - 2.0 * f);
+
+    float n = p.x + p.y * 157.0 + 113.0 * p.z;
+    return mix(
+        mix(mix(hash(n + 0.0), hash(n + 1.0), f.x),
+            mix(hash(n + 157.0), hash(n + 158.0), f.x), f.y),
+        mix(mix(hash(n + 113.0), hash(n + 114.0), f.x),
+            mix(hash(n + 270.0), hash(n + 271.0), f.x), f.y), f.z);
+}
+
+float fbm(in vec3 p) {
+    float f = 0.0;
+    f += 0.5 * noise(p);
+    p *= 2.01;
+    f += 0.25 * noise(p);
+    p *= 2.02;
+    f += 0.125 * noise(p);
+
+    return f;
+}
 `;

+ 6 - 0
src/mol-gl/shader/chunks/common.glsl.ts

@@ -214,6 +214,9 @@ float depthToViewZ(const in float isOrtho, const in float linearClipZ, const in
             a31 * b01 - a30 * b03 - a32 * b00,
             a20 * b03 - a21 * b01 + a22 * b00) / det;
     }
+
+    #define isNaN(x) ( (x) != (x)    )
+    #define isInf(x) ( (x) == (x)+1. )
 #else
     #define transpose2(m) transpose(m)
     #define transpose3(m) transpose(m)
@@ -222,5 +225,8 @@ float depthToViewZ(const in float isOrtho, const in float linearClipZ, const in
     #define inverse2(m) inverse(m)
     #define inverse3(m) inverse(m)
     #define inverse4(m) inverse(m)
+
+    #define isNaN isnan
+    #define isInf isinf
 #endif
 `;

+ 5 - 1
src/mol-gl/shader/cylinders.frag.ts

@@ -8,6 +8,8 @@ export const cylinders_frag = `
 precision highp float;
 precision highp int;
 
+#define bumpEnabled
+
 uniform mat4 uView;
 
 varying mat4 vTransform;
@@ -18,6 +20,7 @@ varying float vCap;
 
 uniform vec3 uCameraDir;
 uniform vec3 uCameraPosition;
+uniform mat4 uInvView;
 
 #include common
 #include common_frag_params
@@ -108,7 +111,8 @@ void main() {
     vViewPosition = (uView * vec4(vViewPosition, 1.0)).xyz;
     gl_FragDepthEXT = calcDepth(vViewPosition);
 
-    // bugfix (mac only?)
+    vec3 vModelPosition = (uInvView * vec4(vViewPosition, 1.0)).xyz;
+
     if (gl_FragDepthEXT < 0.0) discard;
     if (gl_FragDepthEXT > 1.0) discard;
 

+ 2 - 0
src/mol-gl/shader/mesh.frag.ts

@@ -8,6 +8,8 @@ export const mesh_frag = `
 precision highp float;
 precision highp int;
 
+#define bumpEnabled
+
 #include common
 #include common_frag_params
 #include color_frag_params

+ 4 - 1
src/mol-gl/shader/spheres.frag.ts

@@ -14,6 +14,8 @@ precision highp int;
 #include light_frag_params
 #include common_clip
 
+uniform mat4 uInvView;
+
 varying float vRadius;
 varying float vRadiusSq;
 varying vec3 vPoint;
@@ -73,7 +75,8 @@ void main(void){
         gl_FragDepthEXT = 0.0 + (0.0000001 / vRadius);
     }
 
-    // bugfix (mac only?)
+    vec3 vModelPosition = (uInvView * vec4(vViewPosition, 1.0)).xyz;
+
     if (gl_FragDepthEXT < 0.0) discard;
     if (gl_FragDepthEXT > 1.0) discard;
 

+ 3 - 1
src/mol-repr/structure/representation/backbone.ts

@@ -13,6 +13,7 @@ import { ThemeRegistryContext } from '../../../mol-theme/theme';
 import { Structure } from '../../../mol-model/structure';
 import { PolymerBackboneSphereParams, PolymerBackboneSphereVisual } from '../visual/polymer-backbone-sphere';
 import { PolymerGapParams, PolymerGapVisual } from '../visual/polymer-gap-cylinder';
+import { BaseGeometry } from '../../../mol-geo/geometry/base';
 
 const BackboneVisuals = {
     'polymer-backbone-cylinder': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, PolymerBackboneCylinderParams>) => UnitsRepresentation('Polymer backbone cylinder', ctx, getParams, PolymerBackboneCylinderVisual),
@@ -25,7 +26,8 @@ export const BackboneParams = {
     ...PolymerBackboneCylinderParams,
     ...PolymerGapParams,
     sizeAspectRatio: PD.Numeric(1, { min: 0.1, max: 3, step: 0.1 }),
-    visuals: PD.MultiSelect(['polymer-backbone-cylinder', 'polymer-backbone-sphere', 'polymer-gap'], PD.objectToOptions(BackboneVisuals))
+    visuals: PD.MultiSelect(['polymer-backbone-cylinder', 'polymer-backbone-sphere', 'polymer-gap'], PD.objectToOptions(BackboneVisuals)),
+    bumpFrequency: PD.Numeric(0, { min: 0, max: 10, step: 0.1 }, BaseGeometry.ShadingCategory),
 };
 export type BackboneParams = typeof BackboneParams
 export function getBackboneParams(ctx: ThemeRegistryContext, structure: Structure) {

+ 3 - 1
src/mol-repr/structure/representation/ball-and-stick.ts

@@ -15,6 +15,7 @@ import { Representation, RepresentationParamsGetter, RepresentationContext } fro
 import { ThemeRegistryContext } from '../../../mol-theme/theme';
 import { Structure } from '../../../mol-model/structure';
 import { getUnitKindsParam } from '../params';
+import { BaseGeometry } from '../../../mol-geo/geometry/base';
 
 const BallAndStickVisuals = {
     'element-sphere': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, ElementSphereParams>) => UnitsRepresentation('Element sphere', ctx, getParams, ElementSphereVisual),
@@ -31,7 +32,8 @@ export const BallAndStickParams = {
     unitKinds: getUnitKindsParam(['atomic']),
     sizeFactor: PD.Numeric(0.15, { min: 0.01, max: 10, step: 0.01 }),
     sizeAspectRatio: PD.Numeric(2 / 3, { min: 0.01, max: 3, step: 0.01 }),
-    visuals: PD.MultiSelect(['element-sphere', 'intra-bond', 'inter-bond'], PD.objectToOptions(BallAndStickVisuals))
+    visuals: PD.MultiSelect(['element-sphere', 'intra-bond', 'inter-bond'], PD.objectToOptions(BallAndStickVisuals)),
+    bumpFrequency: PD.Numeric(0, { min: 0, max: 10, step: 0.1 }, BaseGeometry.ShadingCategory),
 };
 export type BallAndStickParams = typeof BallAndStickParams
 export function getBallAndStickParams(ctx: ThemeRegistryContext, structure: Structure) {

+ 2 - 0
src/mol-repr/structure/representation/carbohydrate.ts

@@ -4,6 +4,7 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
+import { BaseGeometry } from '../../../mol-geo/geometry/base';
 import { Structure, Model } from '../../../mol-model/structure';
 import { Representation, RepresentationContext, RepresentationParamsGetter } from '../../../mol-repr/representation';
 import { ThemeRegistryContext } from '../../../mol-theme/theme';
@@ -25,6 +26,7 @@ export const CarbohydrateParams = {
     ...CarbohydrateLinkParams,
     ...CarbohydrateTerminalLinkParams,
     visuals: PD.MultiSelect(['carbohydrate-symbol', 'carbohydrate-link', 'carbohydrate-terminal-link'], PD.objectToOptions(CarbohydrateVisuals)),
+    bumpFrequency: PD.Numeric(0, { min: 0, max: 10, step: 0.1 }, BaseGeometry.ShadingCategory),
 };
 export type CarbohydrateParams = typeof CarbohydrateParams
 export function getCarbohydrateParams(ctx: ThemeRegistryContext, structure: Structure) {

+ 2 - 0
src/mol-repr/structure/representation/cartoon.ts

@@ -18,6 +18,7 @@ import { PolymerTraceParams, PolymerTraceVisual } from '../visual/polymer-trace-
 import { SecondaryStructureProvider } from '../../../mol-model-props/computed/secondary-structure';
 import { CustomProperty } from '../../../mol-model-props/common/custom-property';
 import { HelixOrientationProvider } from '../../../mol-model-props/computed/helix-orientation';
+import { BaseGeometry } from '../../../mol-geo/geometry/base';
 
 const CartoonVisuals = {
     'polymer-trace': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, PolymerTraceParams>) => UnitsRepresentation('Polymer trace mesh', ctx, getParams, PolymerTraceVisual),
@@ -35,6 +36,7 @@ export const CartoonParams = {
     ...PolymerDirectionParams,
     sizeFactor: PD.Numeric(0.2, { min: 0, max: 10, step: 0.01 }),
     visuals: PD.MultiSelect(['polymer-trace', 'polymer-gap', 'nucleotide-block'], PD.objectToOptions(CartoonVisuals)),
+    bumpFrequency: PD.Numeric(2, { min: 0, max: 10, step: 0.1 }, BaseGeometry.ShadingCategory),
 };
 
 export type CartoonParams = typeof CartoonParams

+ 2 - 0
src/mol-repr/structure/representation/ellipsoid.ts

@@ -14,6 +14,7 @@ import { AtomSiteAnisotrop } from '../../../mol-model-formats/structure/property
 import { IntraUnitBondCylinderParams, IntraUnitBondCylinderVisual } from '../visual/bond-intra-unit-cylinder';
 import { InterUnitBondCylinderVisual, InterUnitBondCylinderParams } from '../visual/bond-inter-unit-cylinder';
 import { getUnitKindsParam } from '../params';
+import { BaseGeometry } from '../../../mol-geo/geometry/base';
 
 const EllipsoidVisuals = {
     'ellipsoid-mesh': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, EllipsoidMeshParams>) => UnitsRepresentation('Ellipsoid Mesh', ctx, getParams, EllipsoidMeshVisual),
@@ -32,6 +33,7 @@ export const EllipsoidParams = {
     sizeAspectRatio: PD.Numeric(0.1, { min: 0.01, max: 3, step: 0.01 }),
     linkCap: PD.Boolean(true),
     visuals: PD.MultiSelect(['ellipsoid-mesh', 'intra-bond', 'inter-bond'], PD.objectToOptions(EllipsoidVisuals)),
+    bumpFrequency: PD.Numeric(0, { min: 0, max: 10, step: 0.1 }, BaseGeometry.ShadingCategory),
 };
 export type EllipsoidParams = typeof EllipsoidParams
 export function getEllipsoidParams(ctx: ThemeRegistryContext, structure: Structure) {

+ 2 - 0
src/mol-repr/structure/representation/gaussian-surface.ts

@@ -12,6 +12,7 @@ import { StructureRepresentation, StructureRepresentationProvider, StructureRepr
 import { Representation, RepresentationParamsGetter, RepresentationContext } from '../../../mol-repr/representation';
 import { ThemeRegistryContext } from '../../../mol-theme/theme';
 import { Structure } from '../../../mol-model/structure';
+import { BaseGeometry } from '../../../mol-geo/geometry/base';
 
 const GaussianSurfaceVisuals = {
     'gaussian-surface-mesh': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, GaussianSurfaceMeshParams>) => UnitsRepresentation('Gaussian surface mesh', ctx, getParams, GaussianSurfaceVisual),
@@ -23,6 +24,7 @@ export const GaussianSurfaceParams = {
     ...GaussianSurfaceMeshParams,
     ...GaussianWireframeParams,
     visuals: PD.MultiSelect(['gaussian-surface-mesh'], PD.objectToOptions(GaussianSurfaceVisuals)),
+    bumpFrequency: PD.Numeric(1, { min: 0, max: 10, step: 0.1 }, BaseGeometry.ShadingCategory),
 };
 export type GaussianSurfaceParams = typeof GaussianSurfaceParams
 export function getGaussianSurfaceParams(ctx: ThemeRegistryContext, structure: Structure) {

+ 2 - 0
src/mol-repr/structure/representation/molecular-surface.ts

@@ -12,6 +12,7 @@ import { Representation, RepresentationParamsGetter, RepresentationContext } fro
 import { ThemeRegistryContext } from '../../../mol-theme/theme';
 import { Structure } from '../../../mol-model/structure';
 import { MolecularSurfaceWireframeParams, MolecularSurfaceWireframeVisual } from '../visual/molecular-surface-wireframe';
+import { BaseGeometry } from '../../../mol-geo/geometry/base';
 
 const MolecularSurfaceVisuals = {
     'molecular-surface-mesh': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, MolecularSurfaceMeshParams>) => UnitsRepresentation('Molecular surface mesh', ctx, getParams, MolecularSurfaceMeshVisual),
@@ -22,6 +23,7 @@ export const MolecularSurfaceParams = {
     ...MolecularSurfaceMeshParams,
     ...MolecularSurfaceWireframeParams,
     visuals: PD.MultiSelect(['molecular-surface-mesh'], PD.objectToOptions(MolecularSurfaceVisuals)),
+    bumpFrequency: PD.Numeric(1, { min: 0, max: 10, step: 0.1 }, BaseGeometry.ShadingCategory),
 };
 export type MolecularSurfaceParams = typeof MolecularSurfaceParams
 export function getMolecularSurfaceParams(ctx: ThemeRegistryContext, structure: Structure) {

+ 2 - 0
src/mol-repr/structure/representation/orientation.ts

@@ -11,6 +11,7 @@ import { Representation, RepresentationParamsGetter, RepresentationContext } fro
 import { ThemeRegistryContext } from '../../../mol-theme/theme';
 import { Structure } from '../../../mol-model/structure';
 import { OrientationEllipsoidMeshParams, OrientationEllipsoidMeshVisual } from '../visual/orientation-ellipsoid-mesh';
+import { BaseGeometry } from '../../../mol-geo/geometry/base';
 
 const OrientationVisuals = {
     'orientation-ellipsoid-mesh': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, OrientationEllipsoidMeshParams>) => UnitsRepresentation('Orientation ellipsoid mesh', ctx, getParams, OrientationEllipsoidMeshVisual),
@@ -19,6 +20,7 @@ const OrientationVisuals = {
 export const OrientationParams = {
     ...OrientationEllipsoidMeshParams,
     visuals: PD.MultiSelect(['orientation-ellipsoid-mesh'], PD.objectToOptions(OrientationVisuals)),
+    bumpFrequency: PD.Numeric(1, { min: 0, max: 10, step: 0.1 }, BaseGeometry.ShadingCategory),
 };
 export type OrientationParams = typeof OrientationParams
 export function getOrientationParams(ctx: ThemeRegistryContext, structure: Structure) {

+ 2 - 0
src/mol-repr/structure/representation/putty.ts

@@ -12,6 +12,7 @@ import { StructureRepresentation, StructureRepresentationProvider, StructureRepr
 import { Representation, RepresentationParamsGetter, RepresentationContext } from '../../../mol-repr/representation';
 import { Structure, Unit } from '../../../mol-model/structure';
 import { ThemeRegistryContext } from '../../../mol-theme/theme';
+import { BaseGeometry } from '../../../mol-geo/geometry/base';
 
 const PuttyVisuals = {
     'polymer-tube': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, PolymerTubeParams>) => UnitsRepresentation('Polymer tube mesh', ctx, getParams, PolymerTubeVisual),
@@ -23,6 +24,7 @@ export const PuttyParams = {
     ...PolymerGapParams,
     sizeFactor: PD.Numeric(0.2, { min: 0, max: 10, step: 0.01 }),
     visuals: PD.MultiSelect(['polymer-tube', 'polymer-gap'], PD.objectToOptions(PuttyVisuals)),
+    bumpFrequency: PD.Numeric(2, { min: 0, max: 10, step: 0.1 }, BaseGeometry.ShadingCategory),
 };
 export type PuttyParams = typeof PuttyParams
 export function getPuttyParams(ctx: ThemeRegistryContext, structure: Structure) {

+ 2 - 0
src/mol-repr/structure/representation/spacefill.ts

@@ -11,6 +11,7 @@ import { StructureRepresentation, StructureRepresentationProvider, StructureRepr
 import { RepresentationParamsGetter, RepresentationContext, Representation } from '../../../mol-repr/representation';
 import { ThemeRegistryContext } from '../../../mol-theme/theme';
 import { Structure } from '../../../mol-model/structure';
+import { BaseGeometry } from '../../../mol-geo/geometry/base';
 
 const SpacefillVisuals = {
     'element-sphere': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, ElementSphereParams>) => UnitsRepresentation('Sphere mesh', ctx, getParams, ElementSphereVisual),
@@ -18,6 +19,7 @@ const SpacefillVisuals = {
 
 export const SpacefillParams = {
     ...ElementSphereParams,
+    bumpFrequency: PD.Numeric(1, { min: 0, max: 10, step: 0.1 }, BaseGeometry.ShadingCategory),
 };
 export type SpacefillParams = typeof SpacefillParams
 export function getSpacefillParams(ctx: ThemeRegistryContext, structure: Structure) {

+ 2 - 0
src/mol-repr/volume/isosurface.ts

@@ -28,6 +28,7 @@ import { extractIsosurface } from '../../mol-gl/compute/marching-cubes/isosurfac
 import { WebGLContext } from '../../mol-gl/webgl/context';
 import { CustomPropertyDescriptor } from '../../mol-model/custom-property';
 import { Texture } from '../../mol-gl/webgl/texture';
+import { BaseGeometry } from '../../mol-geo/geometry/base';
 
 export const VolumeIsosurfaceParams = {
     isoValue: Volume.IsoValueParam
@@ -260,6 +261,7 @@ export const IsosurfaceParams = {
     ...IsosurfaceMeshParams,
     ...IsosurfaceWireframeParams,
     visuals: PD.MultiSelect(['solid'], PD.objectToOptions(IsosurfaceVisuals)),
+    bumpFrequency: PD.Numeric(1, { min: 0, max: 10, step: 0.1 }, BaseGeometry.ShadingCategory),
 };
 export type IsosurfaceParams = typeof IsosurfaceParams
 export function getIsosurfaceParams(ctx: ThemeRegistryContext, volume: Volume) {

+ 11 - 7
src/mol-util/material.ts

@@ -12,6 +12,8 @@ export interface Material {
     metalness: number,
     /** Normalized to [0, 1] range */
     roughness: number
+    /** Normalized to [0, 1] range */
+    bumpiness: number
 }
 
 export function Material(values?: Partial<Material>) {
@@ -19,29 +21,31 @@ export function Material(values?: Partial<Material>) {
 }
 
 export namespace Material {
-    export const Zero: Material = { metalness: 0, roughness: 0 };
+    export const Zero: Material = { metalness: 0, roughness: 0, bumpiness: 0 };
 
     export function toArray(material: Material, array: NumberArray, offset: number) {
         array[offset] = material.metalness * 255;
         array[offset + 1] = material.roughness * 255;
+        array[offset + 2] = material.bumpiness * 255;
         return array;
     }
 
-    export function toString({ metalness, roughness }: Material) {
-        return `M ${metalness.toFixed(2)} | R ${roughness.toFixed(2)}`;
+    export function toString({ metalness, roughness, bumpiness }: Material) {
+        return `M ${metalness.toFixed(2)} | R ${roughness.toFixed(2)} | B ${bumpiness.toFixed(2)}`;
     }
 
     export function getParam(info?: { isExpanded?: boolean, isFlat?: boolean }) {
         return PD.Group({
             metalness: PD.Numeric(0, { min: 0, max: 1, step: 0.01 }),
             roughness: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }),
+            bumpiness: PD.Numeric(0, { min: 0, max: 1, step: 0.01 }),
         }, {
             ...info,
             presets: [
-                [{ metalness: 0, roughness: 1 }, 'Matte'],
-                [{ metalness: 0, roughness: 0.2 }, 'Plastic'],
-                [{ metalness: 0, roughness: 0.6 }, 'Glossy'],
-                [{ metalness: 1.0, roughness: 0.6 }, 'Metallic'],
+                [{ metalness: 0, roughness: 1, bumpiness: 0 }, 'Matte'],
+                [{ metalness: 0, roughness: 0.2, bumpiness: 0 }, 'Plastic'],
+                [{ metalness: 0, roughness: 0.6, bumpiness: 0 }, 'Glossy'],
+                [{ metalness: 1.0, roughness: 0.6, bumpiness: 0 }, 'Metallic'],
             ]
         });
     }