diff --git a/src/apps/basic-wrapper/index.ts b/src/apps/basic-wrapper/index.ts index 90c0f76f469e2507da86a68dc2aef8088261773d..9ce98ddac0cc0b27808a39f163dc91f8df875cca 100644 --- a/src/apps/basic-wrapper/index.ts +++ b/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() { diff --git a/src/examples/proteopedia-wrapper/index.ts b/src/examples/proteopedia-wrapper/index.ts index ee540eef34939d060f3d3813a0d4826be364ad27..c72a1adbe4107474d221a2d29694de44a00b7fe6 100644 --- a/src/examples/proteopedia-wrapper/index.ts +++ b/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() { diff --git a/src/mol-canvas3d/camera/util.ts b/src/mol-canvas3d/camera/util.ts index 52c431833d5e93b2633d2eb08be71f6ab19735e9..488999370a4d42ab2ad196bafdbb0f9b1f11f12f 100644 --- a/src/mol-canvas3d/camera/util.ts +++ b/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) { diff --git a/src/mol-canvas3d/canvas3d.ts b/src/mol-canvas3d/canvas3d.ts index 09f1abea6af0831032ffebaacd9ddc3d2797fb3b..d32b4991e31a481b1e98fcf1f0338858506e1599 100644 --- a/src/mol-canvas3d/canvas3d.ts +++ b/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 } } diff --git a/src/mol-geo/geometry/color-data.ts b/src/mol-geo/geometry/color-data.ts index ca57e2f49accec4851001bb73129cd75ad1e71dc..f4991df31f0e78e79baeed21abf7b5c8c1d6b370 100644 --- a/src/mol-geo/geometry/color-data.ts +++ b/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'), diff --git a/src/mol-gl/renderable/direct-volume.ts b/src/mol-gl/renderable/direct-volume.ts index 89b77527892f2972e8599218d1ab4ffbc9bb10b2..8b8d2dfe47ab5c0366156b739bbfcba32debbb2c 100644 --- a/src/mol-gl/renderable/direct-volume.ts +++ b/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> diff --git a/src/mol-gl/renderable/schema.ts b/src/mol-gl/renderable/schema.ts index 233176dd2e3ef1d691cc24e5b2e2d5348ade57ab..4c9a9e453dcd536a60efccbc3c7ac8a06be8411c 100644 --- a/src/mol-gl/renderable/schema.ts +++ b/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'), diff --git a/src/mol-gl/renderer.ts b/src/mol-gl/renderer.ts index d3d46423529fedd2138d3a69b4529efa6b346f9f..5813109172599f1f08c791d5f7348a152ab14021 100644 --- a/src/mol-gl/renderer.ts +++ b/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 { diff --git a/src/mol-gl/shader/chunks/apply-light-color.glsl b/src/mol-gl/shader/chunks/apply-light-color.glsl new file mode 100644 index 0000000000000000000000000000000000000000..d71afe83dc4f4459587ae31ae52a04503a581819 --- /dev/null +++ b/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); \ No newline at end of file diff --git a/src/mol-gl/shader/chunks/assign-normal.glsl b/src/mol-gl/shader/chunks/assign-normal.glsl new file mode 100644 index 0000000000000000000000000000000000000000..56caada40a5324b4ecd8bb9b07bfd57683aa8a89 --- /dev/null +++ b/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 \ No newline at end of file diff --git a/src/mol-gl/shader/chunks/common.glsl b/src/mol-gl/shader/chunks/common.glsl index c26150ec86632fe48034fa8e6265a42d375ed4eb..25d0d9b9c52522b3c70f7909bf8958cbc1472767 100644 --- a/src/mol-gl/shader/chunks/common.glsl +++ b/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 diff --git a/src/mol-gl/shader/chunks/light-frag-params.glsl b/src/mol-gl/shader/chunks/light-frag-params.glsl new file mode 100644 index 0000000000000000000000000000000000000000..8a7eae37e202880a108212ca6d4b623babbd4c17 --- /dev/null +++ b/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); +} \ No newline at end of file diff --git a/src/mol-gl/shader/chunks/normal-frag-params.glsl b/src/mol-gl/shader/chunks/normal-frag-params.glsl new file mode 100644 index 0000000000000000000000000000000000000000..81ce20d5df0e2db35bbf769137bb73102bfcc17f --- /dev/null +++ b/src/mol-gl/shader/chunks/normal-frag-params.glsl @@ -0,0 +1,3 @@ +#if !defined(dFlatShaded) || !defined(enabledStandardDerivatives) + varying vec3 vNormal; +#endif \ No newline at end of file diff --git a/src/mol-gl/shader/direct-volume.frag b/src/mol-gl/shader/direct-volume.frag index 5728c6c41b3e1a0239a305f38024d3034775e604..42ce46af5477c52be865333100b75f29dd4a3d50 100644 --- a/src/mol-gl/shader/direct-volume.frag +++ b/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 ) { diff --git a/src/mol-gl/shader/mesh.frag b/src/mol-gl/shader/mesh.frag index 93858961510496217a7121ee8182aee085218313..5198d5e30ef434aacb170ab68b0dbff3e643425d 100644 --- a/src/mol-gl/shader/mesh.frag +++ b/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 diff --git a/src/mol-gl/shader/spheres.frag b/src/mol-gl/shader/spheres.frag index fc215b5ad16a0e2969feb038fefa47ad7963e507..0f0488b4f66fee13a14590d01dbaa28ce5bc7d6c 100644 --- a/src/mol-gl/shader/spheres.frag +++ b/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 diff --git a/src/mol-gl/shader/utils/attenuation.glsl b/src/mol-gl/shader/utils/attenuation.glsl deleted file mode 100644 index 833423b85b9fcfc3dfd334b1714bfccfec9d78ee..0000000000000000000000000000000000000000 --- a/src/mol-gl/shader/utils/attenuation.glsl +++ /dev/null @@ -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) \ No newline at end of file diff --git a/src/mol-gl/shader/utils/oren-nayar-diffuse.glsl b/src/mol-gl/shader/utils/oren-nayar-diffuse.glsl deleted file mode 100644 index 59a3078b831a51003f990693a0da9ff0ed06a743..0000000000000000000000000000000000000000 --- a/src/mol-gl/shader/utils/oren-nayar-diffuse.glsl +++ /dev/null @@ -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) \ No newline at end of file diff --git a/src/mol-gl/shader/utils/phong-specular.glsl b/src/mol-gl/shader/utils/phong-specular.glsl deleted file mode 100644 index 6d42305acf8e23d6204d7b0a13f91a69d15f9cf3..0000000000000000000000000000000000000000 --- a/src/mol-gl/shader/utils/phong-specular.glsl +++ /dev/null @@ -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) \ No newline at end of file diff --git a/src/mol-plugin/context.ts b/src/mol-plugin/context.ts index 245dcaad2e9e66467d674bc03d9786afe92c633e..85da6b7514b8073897fb16a3c4f7cdf1eb5237ea 100644 --- a/src/mol-plugin/context.ts +++ b/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) { diff --git a/src/mol-util/color/color.ts b/src/mol-util/color/color.ts index 150417a6820ebfb44ac7fe6ff46884b0c1e926fa..60260c745788673d11c3e6fcd8f588321b542584 100644 --- a/src/mol-util/color/color.ts +++ b/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 diff --git a/src/tests/browser/marching-cubes.ts b/src/tests/browser/marching-cubes.ts index 3f3a187d5c356300ca52670da442b1fa072160d7..a0ee2f5389f74bdaddb8dbaa8dbf44883c57ba8c 100644 --- a/src/tests/browser/marching-cubes.ts +++ b/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()