Bladeren bron

improved lighting

Alexander Rose 6 jaren geleden
bovenliggende
commit
629b8da956

+ 2 - 1
src/apps/basic-wrapper/index.ts

@@ -98,7 +98,8 @@ class BasicWrapper {
     }
 
     setBackground(color: number) {
-        PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: { backgroundColor: Color(color) } });
+        const renderer = this.plugin.canvas3d.props.renderer;
+        PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: { renderer: { ...renderer,  backgroundColor: Color(color) } } });
     }
 
     toggleSpin() {

+ 2 - 1
src/examples/proteopedia-wrapper/index.ts

@@ -224,7 +224,8 @@ class MolStarProteopediaWrapper {
     }
 
     setBackground(color: number) {
-        PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: { backgroundColor: Color(color) } });
+        const renderer = this.plugin.canvas3d.props.renderer;
+        PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: { renderer: { ...renderer,  backgroundColor: Color(color) } } });
     }
 
     toggleSpin() {

+ 20 - 5
src/mol-canvas3d/camera/util.ts

@@ -1,19 +1,28 @@
 /**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
 import { Mat4, Vec3, Vec4, EPSILON } from 'mol-math/linear-algebra'
 
-export type Viewport = {
+export { Viewport }
+
+type Viewport = {
     x: number
     y: number
     width: number
     height: number
 }
 
-export namespace Viewport {
+function Viewport() {
+    return Viewport.zero()
+}
+
+namespace Viewport {
+    export function zero(): Viewport {
+        return { x: 0, y: 0, width: 0, height: 0 }
+    }
     export function create(x: number, y: number, width: number, height: number): Viewport {
         return { x, y, width, height }
     }
@@ -38,9 +47,15 @@ export namespace Viewport {
         v4[3] = viewport.height
         return v4
     }
+
+    export function equals(a: Viewport, b: Viewport) {
+        return a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height
+    }
 }
 
-const tmpVec3 = Vec3.zero()
+//
+
+const tmpVec3 = Vec3()
 
 /** Modifies the direction & up vectors in place, both are normalized */
 export function cameraLookAt(position: Vec3, up: Vec3, direction: Vec3, target: Vec3) {
@@ -68,7 +83,7 @@ export function cameraLookAt(position: Vec3, up: Vec3, direction: Vec3, target:
 const NEAR_RANGE = 0
 const FAR_RANGE = 1
 
-const tmpVec4 = Vec4.zero()
+const tmpVec4 = Vec4()
 
 /** Transform point into 2D window coordinates. */
 export function cameraProject (out: Vec4, point: Vec3, viewport: Viewport, projectionView: Mat4) {

+ 6 - 15
src/mol-canvas3d/canvas3d.ts

@@ -9,7 +9,7 @@ import { now } from 'mol-util/now';
 
 import { Vec3 } from 'mol-math/linear-algebra'
 import InputObserver, { ModifiersKeys, ButtonsType } from 'mol-util/input/input-observer'
-import Renderer, { RendererStats } from 'mol-gl/renderer'
+import Renderer, { RendererStats, RendererParams } from 'mol-gl/renderer'
 import { GraphicsRenderObject } from 'mol-gl/render-object'
 
 import { TrackballControls, TrackballControlsParams } from './controls/trackball'
@@ -23,7 +23,6 @@ import { GraphicsRenderVariant } from 'mol-gl/webgl/render-item';
 import { PickingId } from 'mol-geo/geometry/picking';
 import { MarkerAction } from 'mol-geo/geometry/marker-data';
 import { Loci, EmptyLoci, isEmptyLoci } from 'mol-model/loci';
-import { Color } from 'mol-util/color';
 import { Camera } from './camera';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
 import { BoundingSphereHelper, DebugHelperParams } from './helper/bounding-sphere-helper';
@@ -35,11 +34,10 @@ export const Canvas3DParams = {
     // TODO: FPS cap?
     // maxFps: PD.Numeric(30),
     cameraMode: PD.Select('perspective', [['perspective', 'Perspective'], ['orthographic', 'Orthographic']]),
-    backgroundColor: PD.Color(Color(0x000000)),
     cameraClipDistance: PD.Numeric(0, { min: 0.0, max: 50.0, step: 0.1 }, { description: 'The distance between camera and scene at which to clip regardless of near clipping plane.' }),
     clip: PD.Interval([1, 100], { min: 1, max: 100, step: 1 }),
     fog: PD.Interval([50, 100], { min: 1, max: 100, step: 1 }),
-    pickingAlphaThreshold: PD.Numeric(0.5, { min: 0.0, max: 1.0, step: 0.01 }, { description: 'The minimum opacity value needed for an object to be pickable.' }),
+    renderer: PD.Group(RendererParams),
     trackball: PD.Group(TrackballControlsParams),
     debug: PD.Group(DebugHelperParams)
 }
@@ -74,7 +72,7 @@ interface Canvas3D {
     setProps: (props: Partial<Canvas3DProps>) => void
 
     /** Returns a copy of the current Canvas3D instance props */
-    readonly props: Canvas3DProps
+    readonly props: Readonly<Canvas3DProps>
     readonly input: InputObserver
     readonly stats: RendererStats
     readonly interaction: Canvas3dInteractionHelper['events']
@@ -117,7 +115,7 @@ namespace Canvas3D {
 
         const scene = Scene.create(webgl)
         const controls = TrackballControls.create(input, camera, p.trackball)
-        const renderer = Renderer.create(webgl, camera, { clearColor: p.backgroundColor })
+        const renderer = Renderer.create(webgl, camera, p.renderer)
 
         let pickScale = 0.25 / webgl.pixelRatio
         let pickWidth = Math.round(canvas.width * pickScale)
@@ -401,17 +399,11 @@ namespace Canvas3D {
                 if (props.cameraMode !== undefined && props.cameraMode !== camera.state.mode) {
                     camera.setState({ mode: props.cameraMode })
                 }
-                if (props.backgroundColor !== undefined && props.backgroundColor !== renderer.props.clearColor) {
-                    renderer.setClearColor(props.backgroundColor)
-                }
-
                 if (props.cameraClipDistance !== undefined) p.cameraClipDistance = props.cameraClipDistance
                 if (props.clip !== undefined) p.clip = [props.clip[0], props.clip[1]]
                 if (props.fog !== undefined) p.fog = [props.fog[0], props.fog[1]]
 
-                if (props.pickingAlphaThreshold !== undefined && props.pickingAlphaThreshold !== renderer.props.pickingAlphaThreshold) {
-                    renderer.setPickingAlphaThreshold(props.pickingAlphaThreshold)
-                }
+                if (props.renderer) renderer.setProps(props.renderer)
                 if (props.trackball) controls.setProps(props.trackball)
                 if (props.debug) debugHelper.setProps(props.debug)
                 requestDraw(true)
@@ -420,11 +412,10 @@ namespace Canvas3D {
             get props() {
                 return {
                     cameraMode: camera.state.mode,
-                    backgroundColor: renderer.props.clearColor,
                     cameraClipDistance: p.cameraClipDistance,
                     clip: p.clip,
                     fog: p.fog,
-                    pickingAlphaThreshold: renderer.props.pickingAlphaThreshold,
+                    renderer: { ...renderer.props },
                     trackball: { ...controls.props },
                     debug: { ...debugHelper.props }
                 }

+ 2 - 2
src/mol-geo/geometry/color-data.ts

@@ -33,14 +33,14 @@ export function createColors(locationIt: LocationIterator, colorTheme: ColorThem
 
 export function createValueColor(value: Color, colorData?: ColorData): ColorData {
     if (colorData) {
-        ValueCell.update(colorData.uColor, Color.toRgbNormalized(value) as Vec3)
+        ValueCell.update(colorData.uColor, Color.toVec3Normalized(colorData.uColor.ref.value, value))
         if (colorData.dColorType.ref.value !== 'uniform') {
             ValueCell.update(colorData.dColorType, 'uniform')
         }
         return colorData
     } else {
         return {
-            uColor: ValueCell.create(Color.toRgbNormalized(value) as Vec3),
+            uColor: ValueCell.create(Color.toVec3Normalized(Vec3(), value)),
             tColor: ValueCell.create({ array: new Uint8Array(3), width: 1, height: 1 }),
             uColorTexDim: ValueCell.create(Vec2.create(1, 1)),
             dColorType: ValueCell.create('uniform'),

+ 1 - 1
src/mol-gl/renderable/direct-volume.ts

@@ -67,7 +67,7 @@ export const DirectVolumeSchema = {
 
     dGridTexType: DefineSpec('string', ['2d', '3d']),
     uGridTexDim: UniformSpec('v3'),
-    tGridTex: TextureSpec('texture', 'rgba', 'ubyte', 'linear'),
+    tGridTex: TextureSpec('texture', 'rgba', 'float', 'nearest'),
 }
 export type DirectVolumeSchema = typeof DirectVolumeSchema
 export type DirectVolumeValues = Values<DirectVolumeSchema>

+ 8 - 4
src/mol-gl/renderable/schema.ts

@@ -146,9 +146,13 @@ export const GlobalUniformSchema = {
     uInvProjection: UniformSpec('m4'),
     uModelViewProjection: UniformSpec('m4'),
     uInvModelViewProjection: UniformSpec('m4'),
-    // uLightPosition: Uniform('v3'),
-    uLightColor: UniformSpec('v3'),
-    uLightAmbient: UniformSpec('v3'),
+
+    uLightIntensity: UniformSpec('f'),
+    uAmbientIntensity: UniformSpec('f'),
+
+    uMetalness: UniformSpec('f'),
+    uRoughness: UniformSpec('f'),
+    uReflectivity: UniformSpec('f'),
 
     uPixelRatio: UniformSpec('f'),
     uViewportHeight: UniformSpec('f'),
@@ -162,7 +166,7 @@ export const GlobalUniformSchema = {
     uPickingAlphaThreshold: UniformSpec('f'),
 }
 export type GlobalUniformSchema = typeof GlobalUniformSchema
-export type GlobalUniformValues = { [k in keyof GlobalUniformSchema]: ValueCell<any> }
+export type GlobalUniformValues = Values<GlobalUniformSchema> // { [k in keyof GlobalUniformSchema]: ValueCell<any> }
 
 export const InternalSchema = {
     uObjectId: UniformSpec('i'),

+ 80 - 45
src/mol-gl/renderer.ts

@@ -15,6 +15,8 @@ import { Color } from 'mol-util/color';
 import { ValueCell } from 'mol-util';
 import { RenderableValues, GlobalUniformValues, BaseValues } from './renderable/schema';
 import { GraphicsRenderVariant } from './webgl/render-item';
+import { ParamDefinition as PD } from 'mol-util/param-definition';
+import { deepClone } from 'mol-util/object';
 
 export interface RendererStats {
     programCount: number
@@ -33,44 +35,35 @@ export interface RendererStats {
 
 interface Renderer {
     readonly stats: RendererStats
-    readonly props: RendererProps
+    readonly props: Readonly<RendererProps>
 
     clear: () => void
     render: (scene: Scene, variant: GraphicsRenderVariant) => void
+    setProps: (props: Partial<RendererProps>) => void
     setViewport: (x: number, y: number, width: number, height: number) => void
-    setClearColor: (color: Color) => void
-    setPickingAlphaThreshold: (value: number) => void
     getImageData: () => ImageData
     dispose: () => void
 }
 
-export const DefaultRendererProps = {
-    clearColor: Color(0x000000),
-    viewport: Viewport.create(0, 0, 0, 0),
-    pickingAlphaThreshold: 0.5,
+export const RendererParams = {
+    backgroundColor: PD.Color(Color(0x000000)),
+    pickingAlphaThreshold: PD.Numeric(0.5, { min: 0.0, max: 1.0, step: 0.01 }, { description: 'The minimum opacity value needed for an object to be pickable.' }),
+
+    lightIntensity: PD.Numeric(0.8, { min: 0.0, max: 1.0, step: 0.01 }),
+    ambientIntensity: PD.Numeric(0.4, { min: 0.0, max: 1.0, step: 0.01 }),
+
+    metalness: PD.Numeric(0.0, { min: 0.0, max: 1.0, step: 0.01 }),
+    roughness: PD.Numeric(0.4, { min: 0.0, max: 1.0, step: 0.01 }),
+    reflectivity: PD.Numeric(0.5, { min: 0.0, max: 1.0, step: 0.01 }),
 }
-export type RendererProps = typeof DefaultRendererProps
+export type RendererProps = PD.Values<typeof RendererParams>
 
 namespace Renderer {
     export function create(ctx: WebGLContext, camera: Camera, props: Partial<RendererProps> = {}): Renderer {
         const { gl, state, stats } = ctx
-        let { clearColor, viewport: _viewport, pickingAlphaThreshold } = { ...DefaultRendererProps, ...props }
-
-        const viewport = Viewport.clone(_viewport)
-        const viewportVec4 = Viewport.toVec4(Vec4.zero(), viewport)
+        const p = deepClone({ ...PD.getDefaultValues(RendererParams), ...props })
 
-        // const lightPosition = Vec3.create(0, 0, -100)
-        const lightColor = Vec3.create(1.0, 1.0, 1.0)
-        const lightAmbient = Vec3.create(0.5, 0.5, 0.5)
-        const fogColor = Vec3.create(0.0, 0.0, 0.0)
-
-        function setClearColor(color: Color) {
-            clearColor = color
-            const [ r, g, b ] = Color.toRgbNormalized(color)
-            gl.clearColor(r, g, b, 1.0)
-            Vec3.set(fogColor, r, g, b)
-        }
-        setClearColor(clearColor)
+        const viewport = Viewport()
 
         const view = Mat4.clone(camera.view)
         const invView = Mat4.invert(Mat4.identity(), view)
@@ -93,20 +86,34 @@ namespace Renderer {
 
             uPixelRatio: ValueCell.create(ctx.pixelRatio),
             uViewportHeight: ValueCell.create(viewport.height),
-            uViewport: ValueCell.create(viewportVec4),
+            uViewport: ValueCell.create(Viewport.toVec4(Vec4(), viewport)),
 
-            uLightColor: ValueCell.create(lightColor),
-            uLightAmbient: ValueCell.create(lightAmbient),
+            uLightIntensity: ValueCell.create(p.lightIntensity),
+            uAmbientIntensity: ValueCell.create(p.ambientIntensity),
+
+            uMetalness: ValueCell.create(p.metalness),
+            uRoughness: ValueCell.create(p.roughness),
+            uReflectivity: ValueCell.create(p.reflectivity),
 
             uCameraPosition: ValueCell.create(Vec3.clone(camera.state.position)),
             uFogNear: ValueCell.create(camera.state.fogNear),
             uFogFar: ValueCell.create(camera.state.fogFar),
-            uFogColor: ValueCell.create(fogColor),
+            uFogColor: ValueCell.create(Color.toVec3Normalized(Vec3(), p.backgroundColor)),
 
-            uPickingAlphaThreshold: ValueCell.create(pickingAlphaThreshold),
+            uPickingAlphaThreshold: ValueCell.create(p.pickingAlphaThreshold),
         }
         const globalUniformList = Object.entries(globalUniforms)
 
+        const [ bgRed, bgGreen, bgBlue ] = Color.toRgbNormalized(p.backgroundColor)
+        gl.clearColor(bgRed, bgGreen, bgBlue, 1.0)
+
+        if (props.backgroundColor !== undefined && props.backgroundColor !== p.backgroundColor) {
+            p.backgroundColor = props.backgroundColor
+            const [ r, g, b ] = Color.toRgbNormalized(p.backgroundColor)
+            gl.clearColor(r, g, b, 1.0)
+            ValueCell.update(globalUniforms.uFogColor, Vec3.set(globalUniforms.uFogColor.ref.value, r, g, b))
+        }
+
         let globalUniformsNeedUpdate = true
         const renderObject = (r: Renderable<RenderableValues & BaseValues>, variant: GraphicsRenderVariant) => {
             const program = r.getProgram(variant)
@@ -207,31 +214,59 @@ namespace Renderer {
             },
             render,
 
-            setClearColor,
-            setPickingAlphaThreshold: (value: number) => {
-                pickingAlphaThreshold = value
-                ValueCell.update(globalUniforms.uPickingAlphaThreshold, pickingAlphaThreshold)
+            setProps: (props: Partial<RendererProps>) => {
+                if (props.pickingAlphaThreshold !== undefined && props.pickingAlphaThreshold !== p.pickingAlphaThreshold) {
+                    p.pickingAlphaThreshold = props.pickingAlphaThreshold
+                    ValueCell.update(globalUniforms.uPickingAlphaThreshold, p.pickingAlphaThreshold)
+                }
+                if (props.backgroundColor !== undefined && props.backgroundColor !== p.backgroundColor) {
+                    p.backgroundColor = props.backgroundColor
+                    const [ r, g, b ] = Color.toRgbNormalized(p.backgroundColor)
+                    gl.clearColor(r, g, b, 1.0)
+                    ValueCell.update(globalUniforms.uFogColor, Vec3.set(globalUniforms.uFogColor.ref.value, r, g, b))
+                }
+                if (props.lightIntensity !== undefined && props.lightIntensity !== p.lightIntensity) {
+                    p.lightIntensity = props.lightIntensity
+                    ValueCell.update(globalUniforms.uLightIntensity, p.lightIntensity)
+                }
+                if (props.ambientIntensity !== undefined && props.ambientIntensity !== p.ambientIntensity) {
+                    p.ambientIntensity = props.ambientIntensity
+                    ValueCell.update(globalUniforms.uAmbientIntensity, p.ambientIntensity)
+                }
+
+                if (props.metalness !== undefined && props.metalness !== p.metalness) {
+                    p.metalness = props.metalness
+                    ValueCell.update(globalUniforms.uMetalness, p.metalness)
+                }
+                if (props.roughness !== undefined && props.roughness !== p.roughness) {
+                    p.roughness = props.roughness
+                    ValueCell.update(globalUniforms.uRoughness, p.roughness)
+                }
+                if (props.reflectivity !== undefined && props.reflectivity !== p.reflectivity) {
+                    p.reflectivity = props.reflectivity
+                    ValueCell.update(globalUniforms.uReflectivity, p.reflectivity)
+                }
             },
             setViewport: (x: number, y: number, width: number, height: number) => {
-                Viewport.set(viewport, x, y, width, height)
                 gl.viewport(x, y, width, height)
-                ValueCell.update(globalUniforms.uViewportHeight, height)
-                ValueCell.update(globalUniforms.uViewport, Vec4.set(viewportVec4, x, y, width, height))
+                if (x !== viewport.x || y !== viewport.y || width !== viewport.width || height !== viewport.height) {
+                    Viewport.set(viewport, x, y, width, height)
+                    ValueCell.update(globalUniforms.uViewportHeight, height)
+                    ValueCell.update(globalUniforms.uViewport, Vec4.set(globalUniforms.uViewport.ref.value, x, y, width, height))
+                }
             },
             getImageData: () => {
-                const { width, height } = viewport
-                const buffer = new Uint8Array(width * height * 4)
+                const { x, y, width, height } = viewport
+                const dw = width - x
+                const dh = height - y
+                const buffer = new Uint8Array(dw * dh * 4)
                 ctx.unbindFramebuffer()
-                ctx.readPixels(0, 0, width, height, buffer)
-                return createImageData(buffer, width, height)
+                ctx.readPixels(x, y, width, height, buffer)
+                return createImageData(buffer, dw, dh)
             },
 
             get props() {
-                return {
-                    clearColor,
-                    pickingAlphaThreshold,
-                    viewport
-                }
+                return p
             },
             get stats(): RendererStats {
                 return {

+ 48 - 0
src/mol-gl/shader/chunks/apply-light-color.glsl

@@ -0,0 +1,48 @@
+/**
+ * Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ *
+ * adapted from three.js (https://github.com/mrdoob/three.js/)
+ * which under the MIT License, Copyright © 2010-2019 three.js authors
+ */
+
+// inputs
+// - vec4 material
+// - vec3 vViewPosition
+// - vec3 normal
+// - float uMetalness
+// - float uRoughness
+// - float uReflectivity
+// - float uLightIntensity
+// - float uAmbientIntensity
+
+// outputs
+// - sets gl_FragColor
+
+vec4 color = material;
+
+ReflectedLight reflectedLight = ReflectedLight(vec3(0.0), vec3(0.0), vec3(0.0));
+
+PhysicalMaterial physicalMaterial;
+physicalMaterial.diffuseColor = color.rgb * (1.0 - uMetalness);
+physicalMaterial.specularRoughness = clamp(uRoughness, 0.04, 1.0);
+physicalMaterial.specularColor = mix(vec3(0.16 * pow2(uReflectivity)), color.rgb, uMetalness);
+
+GeometricContext geometry;
+geometry.position = -vViewPosition;
+geometry.normal = normal;
+geometry.viewDir = normalize(vViewPosition);
+
+IncidentLight directLight;
+directLight.direction = geometry.viewDir;
+directLight.color = vec3(uLightIntensity);
+
+RE_Direct_Physical(directLight, geometry, physicalMaterial, reflectedLight);
+
+vec3 irradiance = vec3(uAmbientIntensity) * PI;
+RE_IndirectDiffuse_Physical(irradiance, geometry, physicalMaterial, reflectedLight);
+
+vec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular;
+
+gl_FragColor = vec4(outgoingLight, color.a);

+ 18 - 0
src/mol-gl/shader/chunks/assign-normal.glsl

@@ -0,0 +1,18 @@
+// inputs
+// - vViewPosition (if dFlatShaded)
+// - vNormal (if NOT dFlatShaded)
+
+// outputs
+// - normal
+
+// surface normal
+#if defined(dFlatShaded) && defined(enabledStandardDerivatives)
+    vec3 fdx = dFdx(vViewPosition);
+    vec3 fdy = dFdy(vViewPosition);
+    vec3 normal = -normalize(cross(fdx, fdy));
+#else
+    vec3 normal = -normalize(vNormal);
+    #ifdef dDoubleSided
+        normal = normal * (float(gl_FrontFacing) * 2.0 - 1.0);
+    #endif
+#endif

+ 10 - 2
src/mol-gl/shader/chunks/common.glsl

@@ -1,5 +1,13 @@
-float intDiv(float a, float b) { return float(int(a) / int(b)); }
-float intMod(float a, float b) { return a - b * float(int(a) / int(b)); }
+#define PI 3.14159265
+#define RECIPROCAL_PI 0.31830988618
+#define EPSILON 1e-6
+
+#define saturate(a) clamp(a, 0.0, 1.0)
+
+float intDiv(const in float a, const in float b) { return float(int(a) / int(b)); }
+float intMod(const in float a, const in float b) { return a - b * float(int(a) / int(b)); }
+
+float pow2(const in float x) { return x*x; }
 
 #if __VERSION__ != 300
     // transpose

+ 109 - 0
src/mol-gl/shader/chunks/light-frag-params.glsl

@@ -0,0 +1,109 @@
+/**
+ * Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ *
+ * adapted from three.js (https://github.com/mrdoob/three.js/)
+ * which under the MIT License, Copyright © 2010-2019 three.js authors
+ */
+
+uniform float uLightIntensity;
+uniform float uAmbientIntensity;
+uniform float uReflectivity;
+uniform float uMetalness;
+uniform float uRoughness;
+
+struct PhysicalMaterial {
+	vec3 diffuseColor;
+	float specularRoughness;
+	vec3 specularColor;
+};
+
+struct IncidentLight {
+	vec3 color;
+	vec3 direction;
+};
+
+struct ReflectedLight {
+	vec3 directDiffuse;
+	vec3 directSpecular;
+	vec3 indirectDiffuse;
+};
+
+struct GeometricContext {
+	vec3 position;
+	vec3 normal;
+	vec3 viewDir;
+};
+
+vec3 F_Schlick(const in vec3 specularColor, const in float dotLH) {
+	// Original approximation by Christophe Schlick '94
+	// float fresnel = pow( 1.0 - dotLH, 5.0 );
+	// Optimized variant (presented by Epic at SIGGRAPH '13)
+	// https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf
+	float fresnel = exp2((-5.55473 * dotLH - 6.98316) * dotLH);
+	return (1.0 - specularColor) * fresnel + specularColor;
+}
+
+// Moving Frostbite to Physically Based Rendering 3.0 - page 12, listing 2
+// https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf
+float G_GGX_SmithCorrelated(const in float alpha, const in float dotNL, const in float dotNV) {
+	float a2 = pow2(alpha);
+	// dotNL and dotNV are explicitly swapped. This is not a mistake.
+	float gv = dotNL * sqrt(a2 + (1.0 - a2) * pow2(dotNV));
+	float gl = dotNV * sqrt(a2 + (1.0 - a2) * pow2(dotNL));
+	return 0.5 / max(gv + gl, EPSILON);
+}
+
+// Microfacet Models for Refraction through Rough Surfaces - equation (33)
+// http://graphicrants.blogspot.com/2013/08/specular-brdf-reference.html
+// alpha is "roughness squared" in Disney’s reparameterization
+float D_GGX(const in float alpha, const in float dotNH) {
+	float a2 = pow2(alpha);
+	float denom = pow2(dotNH) * (a2 - 1.0) + 1.0; // avoid alpha = 0 with dotNH = 1
+	return RECIPROCAL_PI * a2 / pow2(denom);
+}
+
+vec3 BRDF_Diffuse_Lambert(const in vec3 diffuseColor) {
+	return RECIPROCAL_PI * diffuseColor;
+}
+
+// GGX Distribution, Schlick Fresnel, GGX-Smith Visibility
+vec3 BRDF_Specular_GGX(const in IncidentLight incidentLight, const in GeometricContext geometry, const in vec3 specularColor, const in float roughness) {
+	float alpha = pow2(roughness); // UE4's roughness
+	vec3 halfDir = normalize(incidentLight.direction + geometry.viewDir);
+
+	float dotNL = saturate(dot(geometry.normal, incidentLight.direction));
+	float dotNV = saturate(dot(geometry.normal, geometry.viewDir));
+	float dotNH = saturate(dot(geometry.normal, halfDir));
+	float dotLH = saturate(dot(incidentLight.direction, halfDir));
+
+	vec3 F = F_Schlick(specularColor, dotLH);
+	float G = G_GGX_SmithCorrelated(alpha, dotNL, dotNV);
+	float D = D_GGX(alpha, dotNH);
+	return F * (G * D);
+}
+
+// ref: https://www.unrealengine.com/blog/physically-based-shading-on-mobile - environmentBRDF for GGX on mobile
+vec3 BRDF_Specular_GGX_Environment(const in GeometricContext geometry, const in vec3 specularColor, const in float roughness) {
+	float dotNV = saturate(dot(geometry.normal, geometry.viewDir));
+	const vec4 c0 = vec4(-1, -0.0275, -0.572, 0.022);
+	const vec4 c1 = vec4(1, 0.0425, 1.04, -0.04);
+	vec4 r = roughness * c0 + c1;
+	float a004 = min(r.x * r.x, exp2(-9.28 * dotNV)) * r.x + r.y;
+	vec2 AB = vec2(-1.04, 1.04) * a004 + r.zw;
+	return specularColor * AB.x + AB.y;
+}
+
+void RE_Direct_Physical(const in IncidentLight directLight, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight) {
+	float dotNL = saturate(dot(geometry.normal, directLight.direction));
+    vec3 irradiance = dotNL * directLight.color;
+	irradiance *= PI; // punctual light
+
+	reflectedLight.directSpecular += irradiance * BRDF_Specular_GGX(directLight, geometry, material.specularColor, material.specularRoughness);
+	reflectedLight.directDiffuse += irradiance * BRDF_Diffuse_Lambert(material.diffuseColor);
+}
+
+void RE_IndirectDiffuse_Physical(const in vec3 irradiance, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight) {
+	reflectedLight.indirectDiffuse += irradiance * BRDF_Diffuse_Lambert(material.diffuseColor);
+}

+ 3 - 0
src/mol-gl/shader/chunks/normal-frag-params.glsl

@@ -0,0 +1,3 @@
+#if !defined(dFlatShaded) || !defined(enabledStandardDerivatives)
+    varying vec3 vNormal;
+#endif

+ 13 - 38
src/mol-gl/shader/direct-volume.frag

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2017-2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2017-2019 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>
@@ -30,11 +30,11 @@ uniform float uPickingAlphaThreshold;
 uniform int uPickable;
 
 #if defined(dGridTexType_2d)
-    precision mediump sampler2D;
+    precision highp sampler2D;
     uniform sampler2D tGridTex;
     uniform vec3 uGridTexDim;
 #elif defined(dGridTexType_3d)
-    precision mediump sampler3D;
+    precision highp sampler3D;
     uniform sampler3D tGridTex;
 #endif
 
@@ -46,26 +46,14 @@ uniform int uPickable;
 #endif
 
 #pragma glslify: import('./chunks/common.glsl')
+#pragma glslify: import('./chunks/light-frag-params.glsl')
+
 #pragma glslify: readFromTexture = require(./utils/read-from-texture.glsl, intMod=intMod, intDiv=intDiv, foo=foo) // foo=foo is a workaround for a bug in glslify
 #pragma glslify: encodeFloatRGB = require(./utils/encode-float-rgb.glsl)
 #pragma glslify: decodeFloatRGB = require(./utils/decode-float-rgb.glsl)
 #pragma glslify: texture3dFrom2dNearest = require(./utils/texture3d-from-2d-nearest.glsl, intMod=intMod, intDiv=intDiv, foo=foo) // foo=foo is a workaround for a bug in glslify
 #pragma glslify: texture3dFrom2dLinear = require(./utils/texture3d-from-2d-linear.glsl, intMod=intMod, intDiv=intDiv, foo=foo) // foo=foo is a workaround for a bug in glslify
 
-// uniform vec3 uLightPosition;
-uniform vec3 uLightColor;
-uniform vec3 uLightAmbient;
-uniform mat4 uView;
-
-#pragma glslify: attenuation = require(./utils/attenuation.glsl)
-#pragma glslify: calculateSpecular = require(./utils/phong-specular.glsl)
-#pragma glslify: calculateDiffuse = require(./utils/oren-nayar-diffuse.glsl)
-
-const float specularScale = 0.15;
-const float shininess = 200.0;
-const float roughness = 100.0;
-const float albedo = 0.95;
-
 #if defined(dGridTexType_2d)
     vec4 textureVal(vec3 pos) {
         return texture3dFrom2dLinear(tGridTex, pos, uGridDim, uGridTexDim.xy);
@@ -160,29 +148,16 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 viewDir) {
                         color = readFromTexture(tColor, instance * float(uGroupCount) + group, uColorTexDim).rgb;
                     #endif
 
-                    vec3 L = normalize(viewDir); // light direction
-                    vec3 V = normalize(viewDir); // eye direction
-                    vec3 N = normalize(gradient); // surface normal
+                    vec3 normal = normalize(gradient);
+                    vec3 vViewPosition = normalize(viewDir);
+                    vec4 material = vec4(color, uAlpha);
+                    #pragma glslify: import('./chunks/apply-light-color.glsl')
 
-                    // compute our diffuse & specular terms
-                    float specular = calculateSpecular(L, V, N, shininess) * specularScale;
-                    vec3 diffuse = uLightColor * calculateDiffuse(L, V, N, roughness, albedo);
-                    vec3 ambient = uLightAmbient;
+                    float vMarker = readFromTexture(tMarker, instance * float(uGroupCount) + group, uMarkerTexDim).a;
+                    #pragma glslify: import('./chunks/apply-marker-color.glsl')
 
-                    // add the lighting
-                    vec3 finalColor = color.rgb * (diffuse + ambient) + specular;
-
-                    src.rgb = finalColor;
-                    src.a = uAlpha;
-
-                    float marker = readFromTexture(tMarker, instance * float(uGroupCount) + group, uMarkerTexDim).a * 255.0;
-                    if (marker > 0.1) {
-                        if (mod(marker, 2.0) > 0.1) {
-                            src.rgb = mix(uHighlightColor, src.rgb, 0.3);
-                        } else {
-                            src.rgb = mix(uSelectColor, src.rgb, 0.3);
-                        }
-                    }
+                    src.rgb = gl_FragColor.rgb;
+                    src.a = gl_FragColor.a;
 
                     // draw interior darker
                     if( (prevValue - uIsoValue) > 0.0 ) {

+ 5 - 53
src/mol-gl/shader/mesh.frag

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -9,24 +9,8 @@ precision highp int;
 
 #pragma glslify: import('./chunks/common-frag-params.glsl')
 #pragma glslify: import('./chunks/color-frag-params.glsl')
-
-// uniform vec3 uLightPosition;
-uniform vec3 uLightColor;
-uniform vec3 uLightAmbient;
-uniform mat4 uView;
-
-#if !defined(dFlatShaded) || !defined(enabledStandardDerivatives)
-    varying vec3 vNormal;
-#endif
-
-#pragma glslify: attenuation = require(./utils/attenuation.glsl)
-#pragma glslify: calculateSpecular = require(./utils/phong-specular.glsl)
-#pragma glslify: calculateDiffuse = require(./utils/oren-nayar-diffuse.glsl)
-
-const float specularScale = 0.15;
-const float shininess = 200.0;
-const float roughness = 100.0;
-const float albedo = 0.95;
+#pragma glslify: import('./chunks/light-frag-params.glsl')
+#pragma glslify: import('./chunks/normal-frag-params.glsl')
 
 void main() {
     // material color
@@ -37,40 +21,8 @@ void main() {
             discard; // ignore so the element below can be picked
         gl_FragColor = material;
     #else
-        // determine surface to light direction
-        // vec4 viewLightPosition = view * vec4(lightPosition, 1.0);
-        // vec3 lightVector = viewLightPosition.xyz - vViewPosition;
-        vec3 lightVector = vViewPosition;
-
-        vec3 L = normalize(lightVector); // light direction
-        vec3 V = normalize(vViewPosition); // eye direction
-
-        // surface normal
-        #if defined(dFlatShaded) && defined(enabledStandardDerivatives)
-            vec3 fdx = dFdx(vViewPosition);
-            vec3 fdy = dFdy(vViewPosition);
-            vec3 N = -normalize(cross(fdx, fdy));
-        #else
-            vec3 N = -normalize(vNormal);
-            #ifdef dDoubleSided
-                N = N * (float(gl_FrontFacing) * 2.0 - 1.0);
-            #endif
-        #endif
-
-        // compute our diffuse & specular terms
-        float specular = calculateSpecular(L, V, N, shininess) * specularScale;
-        vec3 diffuse = uLightColor * calculateDiffuse(L, V, N, roughness, albedo);
-        vec3 ambient = uLightAmbient;
-
-        // add the lighting
-        vec3 finalColor = material.rgb * (diffuse + ambient) + specular;
-
-        // gl_FragColor.rgb = N;
-        // gl_FragColor.a = 1.0;
-        // gl_FragColor.rgb = vec3(1.0, 0.0, 0.0);
-        gl_FragColor.rgb = finalColor;
-        gl_FragColor.a = material.a;
-
+        #pragma glslify: import('./chunks/assign-normal.glsl')
+        #pragma glslify: import('./chunks/apply-light-color.glsl')
         #pragma glslify: import('./chunks/apply-marker-color.glsl')
         #pragma glslify: import('./chunks/apply-fog.glsl')
     #endif

+ 5 - 45
src/mol-gl/shader/spheres.frag

@@ -9,11 +9,7 @@ precision highp int;
 
 #pragma glslify: import('./chunks/common-frag-params.glsl')
 #pragma glslify: import('./chunks/color-frag-params.glsl')
-
-// uniform vec3 uLightPosition;
-uniform vec3 uLightColor;
-uniform vec3 uLightAmbient;
-uniform mat4 uView;
+#pragma glslify: import('./chunks/light-frag-params.glsl')
 
 uniform mat4 uProjection;
 // uniform vec3 interiorColor;
@@ -30,27 +26,18 @@ varying float vRadiusSq;
 varying vec3 vPoint;
 varying vec3 vPointViewPosition;
 
-#pragma glslify: attenuation = require(./utils/attenuation.glsl)
-#pragma glslify: calculateSpecular = require(./utils/phong-specular.glsl)
-#pragma glslify: calculateDiffuse = require(./utils/oren-nayar-diffuse.glsl)
-
-const float specularScale = 0.15;
-const float shininess = 200.0;
-const float roughness = 100.0;
-const float albedo = 0.95;
-
 bool flag2 = false;
 bool interior = false;
 vec3 cameraPos;
 vec3 cameraNormal;
 
 // Calculate depth based on the given camera position.
-float calcDepth(in vec3 cameraPos){
+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;
 }
 
-float calcClip(in vec3 cameraPos) {
+float calcClip(const in vec3 cameraPos) {
     return dot(vec4(cameraPos, 1.0), vec4(0.0, 0.0, 1.0, clipNear - 0.5));
 }
 
@@ -143,36 +130,9 @@ void main(void){
             discard; // ignore so the element below can be picked
         gl_FragColor = material;
     #else
-
-        vec3 vNormal = cameraNormal;
+        vec3 normal = cameraNormal;
         vec3 vViewPosition = -cameraPos;
-
-        // determine surface to light direction
-        // vec4 viewLightPosition = view * vec4(lightPosition, 1.0);
-        // vec3 lightVector = viewLightPosition.xyz - vViewPosition;
-        vec3 lightVector = vViewPosition;
-
-        vec3 L = normalize(lightVector); // light direction
-        vec3 V = normalize(vViewPosition); // eye direction
-
-        vec3 N = normalize(vNormal);
-        #ifdef dDoubleSided
-            N = N * (float(gl_FrontFacing) * 2.0 - 1.0);
-        #endif
-
-        // compute our diffuse & specular terms
-        float specular = calculateSpecular(L, V, N, shininess) * specularScale;
-        vec3 diffuse = uLightColor * calculateDiffuse(L, V, N, roughness, albedo);
-        vec3 ambient = uLightAmbient;
-
-        // add the lighting
-        vec3 finalColor = material.rgb * (diffuse + ambient) + specular;
-
-        // gl_FragColor.rgb = N;
-        // gl_FragColor.a = 1.0;
-        // gl_FragColor.rgb = vec3(1.0, 0.0, 0.0);
-        gl_FragColor.rgb = finalColor;
-        gl_FragColor.a = material.a;
+        #pragma glslify: import('./chunks/apply-light-color.glsl')
 
         if(interior){
             #ifdef USE_INTERIOR_COLOR

+ 0 - 14
src/mol-gl/shader/utils/attenuation.glsl

@@ -1,14 +0,0 @@
-// by Tom Madams
-// Simple:
-// https://imdoingitwrong.wordpress.com/2011/01/31/light-attenuation/
-//
-// Improved
-// https://imdoingitwrong.wordpress.com/2011/02/10/improved-light-attenuation/
-float attenuation(const in float r, const in float f, const in float d) {
-    float denom = d / r + 1.0;
-    float attenuation = 1.0 / (denom*denom);
-    float t = (attenuation - f) / (1.0 - f);
-    return max(t, 0.0);
-}
-
-#pragma glslify: export(attenuation)

+ 0 - 21
src/mol-gl/shader/utils/oren-nayar-diffuse.glsl

@@ -1,21 +0,0 @@
-// (c) 2014 Mikola Lysenko. MIT License
-// https://github.com/glslify/glsl-diffuse-oren-nayar
-
-#define PI 3.14159265
-
-float orenNayarDiffuse(const in vec3 lightDirection, const in vec3 viewDirection, const in vec3 surfaceNormal, const in float roughness, const in float albedo) {
-    float LdotV = dot(lightDirection, viewDirection);
-    float NdotL = dot(lightDirection, surfaceNormal);
-    float NdotV = dot(surfaceNormal, viewDirection);
-
-    float s = LdotV - NdotL * NdotV;
-    float t = mix(1.0, max(NdotL, NdotV), step(0.0, s));
-
-    float sigma2 = roughness * roughness;
-    float A = 1.0 + sigma2 * (albedo / (sigma2 + 0.13) + 0.5 / (sigma2 + 0.33));
-    float B = 0.45 * sigma2 / (sigma2 + 0.09);
-
-    return albedo * max(0.0, NdotL) * (A + B * s / t) / PI;
-}
-
-#pragma glslify: export(orenNayarDiffuse)

+ 0 - 10
src/mol-gl/shader/utils/phong-specular.glsl

@@ -1,10 +0,0 @@
-// (c) 2014 Mikola Lysenko. MIT License
-// https://github.com/glslify/glsl-specular-phong
-
-float phongSpecular(const in vec3 lightDirection, const in vec3 viewDirection, const in vec3 surfaceNormal, const in float shininess) {
-    //Calculate Phong power
-    vec3 R = -reflect(lightDirection, surfaceNormal);
-    return pow(max(0.0, dot(viewDirection, R)), shininess);
-}
-
-#pragma glslify: export(phongSpecular)

+ 2 - 1
src/mol-plugin/context.ts

@@ -112,7 +112,8 @@ export class PluginContext {
             this.layout.setRoot(container);
             if (this.spec.layout && this.spec.layout.initial) this.layout.setProps(this.spec.layout.initial);
             (this.canvas3d as Canvas3D) = Canvas3D.create(canvas, container);
-            PluginCommands.Canvas3D.SetSettings.dispatch(this, { settings: { backgroundColor: Color(0xFCFBF9) } });
+            const renderer = this.canvas3d.props.renderer;
+            PluginCommands.Canvas3D.SetSettings.dispatch(this, { settings: { renderer: { ...renderer, backgroundColor: Color(0xFCFBF9) } } });
             this.canvas3d.animate();
             return true;
         } catch (e) {

+ 19 - 2
src/mol-util/color/color.ts

@@ -5,6 +5,7 @@
  */
 
 import { NumberArray } from 'mol-util/type-helpers';
+import { Vec3 } from 'mol-math/linear-algebra';
 
 /** RGB color triplet expressed as a single number */
 export type Color = { readonly '@type': 'color' } & number
@@ -24,11 +25,11 @@ export namespace Color {
         return `RGB: ${Color.toRgb(hexColor).join(', ')}`
     }
 
-    export function toRgb(hexColor: Color) {
+    export function toRgb(hexColor: Color): [number, number, number] {
         return [ hexColor >> 16 & 255, hexColor >> 8 & 255, hexColor & 255 ]
     }
 
-    export function toRgbNormalized(hexColor: Color) {
+    export function toRgbNormalized(hexColor: Color): [number, number, number] {
         return [ (hexColor >> 16 & 255) / 255, (hexColor >> 8 & 255) / 255, (hexColor & 255) / 255 ]
     }
 
@@ -64,6 +65,22 @@ export namespace Color {
         return array
     }
 
+    /** Copies hex color to rgb vec3 */
+    export function toVec3(out: Vec3, hexColor: Color) {
+        out[0] = (hexColor >> 16 & 255)
+        out[1] = (hexColor >> 8 & 255)
+        out[2] = (hexColor & 255)
+        return out
+    }
+
+    /** Copies normalized (0 to 1) hex color to rgb vec3 */
+    export function toVec3Normalized(out: Vec3, hexColor: Color) {
+        out[0] = (hexColor >> 16 & 255) / 255
+        out[1] = (hexColor >> 8 & 255) / 255
+        out[2] = (hexColor & 255) / 255
+        return out
+    }
+
     /** Linear interpolation between two colors */
     export function interpolate(c1: Color, c2: Color, t: number): Color {
         const r1 = c1 >> 16 & 255

+ 3 - 1
src/tests/browser/marching-cubes.ts

@@ -20,6 +20,8 @@ import { TextureMesh } from 'mol-geo/geometry/texture-mesh/texture-mesh';
 import { calcActiveVoxels } from 'mol-gl/compute/marching-cubes/active-voxels';
 import { createHistogramPyramid } from 'mol-gl/compute/histogram-pyramid/reduction';
 import { createIsosurfaceBuffers } from 'mol-gl/compute/marching-cubes/isosurface';
+import { RendererParams } from 'mol-gl/renderer';
+import { ParamDefinition as PD } from 'mol-util/param-definition';
 
 const parent = document.getElementById('app')!
 parent.style.width = '100%'
@@ -31,7 +33,7 @@ canvas.style.height = '100%'
 parent.appendChild(canvas)
 
 const canvas3d = Canvas3D.create(canvas, parent, {
-    backgroundColor: ColorNames.white,
+    renderer: { ...PD.getDefaultValues(RendererParams), backgroundColor: ColorNames.white },
     cameraMode: 'orthographic'
 })
 canvas3d.animate()