diff --git a/package-lock.json b/package-lock.json index 1a944196c1c6e3f06e68667429b103248fe14d46..ced5f348fdac4b2587a95d97acdd7f7c672f166f 100644 Binary files a/package-lock.json and b/package-lock.json differ diff --git a/package.json b/package.json index 8e9186ad2159a76e30a5a46a972d345ec4c32edc..7283779d1b43c0d1cc42699743ce8a5f66206e8f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "molstar", - "version": "1.2.4", + "version": "1.2.6", "description": "A comprehensive macromolecular library.", "homepage": "https://github.com/molstar/molstar#readme", "repository": { @@ -86,64 +86,64 @@ ], "license": "MIT", "devDependencies": { - "@graphql-codegen/add": "^1.17.7", - "@graphql-codegen/cli": "^1.17.8", - "@graphql-codegen/time": "^1.17.10", - "@graphql-codegen/typescript": "^1.17.9", - "@graphql-codegen/typescript-graphql-files-modules": "^1.17.8", - "@graphql-codegen/typescript-graphql-request": "^1.17.7", - "@graphql-codegen/typescript-operations": "^1.17.8", - "@types/cors": "^2.8.7", - "@typescript-eslint/eslint-plugin": "^3.10.1", - "@typescript-eslint/parser": "^3.10.1", + "@graphql-codegen/add": "^2.0.2", + "@graphql-codegen/cli": "^1.19.4", + "@graphql-codegen/time": "^2.0.2", + "@graphql-codegen/typescript": "^1.19.0", + "@graphql-codegen/typescript-graphql-files-modules": "^1.18.1", + "@graphql-codegen/typescript-graphql-request": "^2.0.3", + "@graphql-codegen/typescript-operations": "^1.17.12", + "@types/cors": "^2.8.8", + "@typescript-eslint/eslint-plugin": "^4.9.1", + "@typescript-eslint/parser": "^4.9.1", "benchmark": "^2.1.4", "concurrently": "^5.3.0", - "cpx2": "^2.0.0", - "css-loader": "^3.6.0", - "eslint": "^7.8.1", + "cpx2": "^3.0.0", + "css-loader": "^5.0.1", + "eslint": "^7.15.0", "extra-watch-webpack-plugin": "^1.0.3", - "file-loader": "^6.1.0", + "file-loader": "^6.2.0", "fs-extra": "^9.0.1", - "graphql": "^15.3.0", + "graphql": "^15.4.0", "http-server": "^0.12.3", - "jest": "^26.4.2", - "mini-css-extract-plugin": "^0.9.0", - "node-sass": "^4.14.1", - "raw-loader": "^4.0.1", - "sass-loader": "^8.0.2", - "simple-git": "^2.20.1", - "style-loader": "^1.2.1", - "ts-jest": "^26.3.0", - "typescript": "^4.0.2", + "jest": "^26.6.3", + "mini-css-extract-plugin": "^1.3.2", + "node-sass": "^5.0.0", + "raw-loader": "^4.0.2", + "sass-loader": "^10.1.0", + "simple-git": "^2.25.0", + "style-loader": "^2.0.0", + "ts-jest": "^26.4.4", + "typescript": "^4.1.2", "webpack": "^4.44.1", "webpack-cli": "^3.3.12", "webpack-version-file-plugin": "^0.4.0" }, "dependencies": { "@types/argparse": "^1.0.38", - "@types/benchmark": "^1.0.33", + "@types/benchmark": "^2.1.0", "@types/compression": "1.7.0", - "@types/express": "^4.17.8", - "@types/jest": "^25.2.3", - "@types/node": "^14.10.1", + "@types/express": "^4.17.9", + "@types/jest": "^26.0.18", + "@types/node": "^14.14.11", "@types/node-fetch": "^2.5.7", - "@types/react": "^16.9.49", - "@types/react-dom": "^16.9.8", - "@types/swagger-ui-dist": "3.0.5", + "@types/react": "^17.0.0", + "@types/react-dom": "^17.0.0", + "@types/swagger-ui-dist": "3.30.0", "argparse": "^1.0.10", "body-parser": "^1.19.0", "compression": "^1.7.4", "cors": "^2.8.5", "express": "^4.17.1", "h264-mp4-encoder": "^1.0.12", - "immer": "^7.0.9", + "immer": "^8.0.0", "immutable": "^3.8.2", - "node-fetch": "^2.6.0", - "react": "^16.13.1", - "react-dom": "^16.13.1", + "node-fetch": "^2.6.1", + "react": "^17.0.1", + "react-dom": "^17.0.1", "rxjs": "^6.6.3", - "swagger-ui-dist": "^3.33.0", - "tslib": "^2.0.1", + "swagger-ui-dist": "^3.37.2", + "tslib": "^2.0.3", "util.promisify": "^1.0.1", "xhr2": "^0.2.0" } diff --git a/src/apps/docking-viewer/viewport.tsx b/src/apps/docking-viewer/viewport.tsx index ed41fd7542c9cfae40414619c591060151ad052b..5ead151ff5ff4f28ee12d2e0d4033706704d7877 100644 --- a/src/apps/docking-viewer/viewport.tsx +++ b/src/apps/docking-viewer/viewport.tsx @@ -46,13 +46,14 @@ function occlusionStyle(plugin: PluginContext) { postprocessing: { ...plugin.canvas3d!.props.postprocessing, occlusion: { name: 'on', params: { - kernelSize: 8, - bias: 0.8, - radius: 64 + samples: 64, + radius: 8, + bias: 1.0, + blurKernelSize: 13 } }, outline: { name: 'on', params: { scale: 1.0, - threshold: 0.8 + threshold: 0.33 } } } } }); diff --git a/src/examples/lighting/index.ts b/src/examples/lighting/index.ts index f8abba5482a52eafbf57226ef2008cc802860223..95757a6215baf0d3737948d2cdf3065bb7f6a1f6 100644 --- a/src/examples/lighting/index.ts +++ b/src/examples/lighting/index.ts @@ -24,8 +24,8 @@ const Canvas3DPresets = { mode: 'temporal' as Canvas3DProps['multiSample']['mode'] }, postprocessing: { - occlusion: { name: 'on', params: { bias: 0.8, kernelSize: 6, radius: 64 } }, - outline: { name: 'on', params: { scale: 1, threshold: 0.8 } } + occlusion: { name: 'on', params: { samples: 64, radius: 8, bias: 1.0, blurKernelSize: 13 } }, + outline: { name: 'on', params: { scale: 1, threshold: 0.33 } } }, renderer: { ambientIntensity: 1, @@ -37,7 +37,7 @@ const Canvas3DPresets = { mode: 'temporal' as Canvas3DProps['multiSample']['mode'] }, postprocessing: { - occlusion: { name: 'on', params: { bias: 0.8, kernelSize: 6, radius: 64 } }, + occlusion: { name: 'on', params: { samples: 64, radius: 8, bias: 1.0, blurKernelSize: 13 } }, outline: { name: 'off', params: { } } }, renderer: { diff --git a/src/extensions/anvil/representation.ts b/src/extensions/anvil/representation.ts index b4bd6b112c33907a56b3e6fa96ff46525f1ab7fc..157de0a84eb5cc0fd837dc1a981c329c0e7f29cf 100644 --- a/src/extensions/anvil/representation.ts +++ b/src/extensions/anvil/representation.ts @@ -10,7 +10,6 @@ import { Vec3, Mat4 } from '../../mol-math/linear-algebra'; import { Representation, RepresentationContext, RepresentationParamsGetter } from '../../mol-repr/representation'; import { Structure } from '../../mol-model/structure'; import { Spheres } from '../../mol-geo/geometry/spheres/spheres'; -import { SpheresBuilder } from '../../mol-geo/geometry/spheres/spheres-builder'; import { StructureRepresentationProvider, StructureRepresentation, StructureRepresentationStateBuilder } from '../../mol-repr/structure/representation'; import { MembraneOrientation } from './prop'; import { ThemeRegistryContext } from '../../mol-theme/theme'; @@ -27,6 +26,7 @@ import { MembraneOrientationProvider } from './prop'; import { MarkerActions } from '../../mol-util/marker-action'; import { lociLabel } from '../../mol-theme/label'; import { ColorNames } from '../../mol-util/color/names'; +import { CustomProperty } from '../../mol-model-props/common/custom-property'; const SharedParams = { color: PD.Color(ColorNames.lightgrey), @@ -61,7 +61,6 @@ export type BilayerRimsParams = typeof BilayerRimsParams export type BilayerRimsProps = PD.Values<BilayerRimsParams> const MembraneOrientationVisuals = { - 'bilayer-spheres': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<MembraneOrientation, BilayerSpheresParams>) => ShapeRepresentation(getBilayerSpheres, Spheres.Utils, { modifyState: s => ({ ...s, markerActions: MarkerActions.Highlighting }) }), 'bilayer-planes': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<MembraneOrientation, BilayerPlanesParams>) => ShapeRepresentation(getBilayerPlanes, Mesh.Utils, { modifyState: s => ({ ...s, markerActions: MarkerActions.Highlighting }), modifyProps: p => ({ ...p, alpha: p.sectorOpacity, ignoreLight: true, doubleSided: false }) }), 'bilayer-rims': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<MembraneOrientation, BilayerRimsParams>) => ShapeRepresentation(getBilayerRims, Lines.Utils, { modifyState: s => ({ ...s, markerActions: MarkerActions.Highlighting }) }) }; @@ -91,9 +90,13 @@ export const MembraneOrientationRepresentationProvider = StructureRepresentation factory: MembraneOrientationRepresentation, getParams: getMembraneOrientationParams, defaultValues: PD.getDefaultValues(MembraneOrientationParams), - defaultColorTheme: { name: 'uniform' }, - defaultSizeTheme: { name: 'uniform' }, - isApplicable: (structure: Structure) => structure.elementCount > 0 + defaultColorTheme: { name: 'shape-group' }, + defaultSizeTheme: { name: 'shape-group' }, + isApplicable: (structure: Structure) => structure.elementCount > 0, + ensureCustomProperties: { + attach: (ctx: CustomProperty.Context, structure: Structure) => MembraneOrientationProvider.attach(ctx, structure, void 0, true), + detach: (data) => MembraneOrientationProvider.ref(data, false) + } }); function membraneLabel(data: Structure) { @@ -151,28 +154,3 @@ function getLayerPlane(state: MeshBuilder.State, p: Vec3, centroid: Vec3, normal MeshBuilder.addPrimitive(state, Mat4.id, circle); MeshBuilder.addPrimitiveFlipped(state, Mat4.id, circle); } - -function getBilayerSpheres(ctx: RuntimeContext, data: Structure, props: BilayerSpheresProps, shape?: Shape<Spheres>): Shape<Spheres> { - const { density } = props; - const { radius, planePoint1, planePoint2, normalVector } = MembraneOrientationProvider.get(data).value!; - const scaledRadius = (props.radiusFactor * radius) * (props.radiusFactor * radius); - - const spheresBuilder = SpheresBuilder.create(256, 128, shape?.geometry); - getLayerSpheres(spheresBuilder, planePoint1, normalVector, density, scaledRadius); - getLayerSpheres(spheresBuilder, planePoint2, normalVector, density, scaledRadius); - return Shape.create('Bilayer spheres', data, spheresBuilder.getSpheres(), () => props.color, () => props.sphereSize, () => membraneLabel(data)); -} - -function getLayerSpheres(spheresBuilder: SpheresBuilder, point: Vec3, normalVector: Vec3, density: number, sqRadius: number) { - Vec3.normalize(normalVector, normalVector); - const d = -Vec3.dot(normalVector, point); - const rep = Vec3(); - for (let i = -1000, il = 1000; i < il; i += density) { - for (let j = -1000, jl = 1000; j < jl; j += density) { - Vec3.set(rep, i, j, normalVector[2] === 0 ? 0 : -(d + i * normalVector[0] + j * normalVector[1]) / normalVector[2]); - if (Vec3.squaredDistance(rep, point) < sqRadius) { - spheresBuilder.add(rep[0], rep[1], rep[2], 0); - } - } - } -} \ No newline at end of file diff --git a/src/mol-canvas3d/canvas3d.ts b/src/mol-canvas3d/canvas3d.ts index cb6992f2ddc6182bf968784a940cbc7a58c42001..b9860ebc942af1c99668a39b3c80ca1c4519e620 100644 --- a/src/mol-canvas3d/canvas3d.ts +++ b/src/mol-canvas3d/canvas3d.ts @@ -24,7 +24,7 @@ import { ParamDefinition as PD } from '../mol-util/param-definition'; import { DebugHelperParams } from './helper/bounding-sphere-helper'; import { SetUtils } from '../mol-util/set'; import { Canvas3dInteractionHelper } from './helper/interaction-events'; -import { PostprocessingParams, PostprocessingPass } from './passes/postprocessing'; +import { PostprocessingParams } from './passes/postprocessing'; import { MultiSampleHelper, MultiSampleParams, MultiSamplePass } from './passes/multi-sample'; import { PickData } from './passes/pick'; import { PickHelper } from './passes/pick'; @@ -310,9 +310,7 @@ namespace Canvas3D { if (MultiSamplePass.isEnabled(p.multiSample)) { multiSampleHelper.render(renderer, cam, scene, helper, true, p.transparentBackground, p); } else { - const toDrawingBuffer = !PostprocessingPass.isEnabled(p.postprocessing) && scene.volumes.renderables.length === 0 && !passes.draw.wboitEnabled; - passes.draw.render(renderer, cam, scene, helper, toDrawingBuffer, p.transparentBackground); - if (!toDrawingBuffer) passes.postprocessing.render(cam, true, p.postprocessing); + passes.draw.render(renderer, cam, scene, helper, true, p.renderer.backgroundColor, p.transparentBackground, p.postprocessing); } pickHelper.dirty = true; didRender = true; diff --git a/src/mol-canvas3d/passes/draw.ts b/src/mol-canvas3d/passes/draw.ts index d6c821168b7ccbf57322c2432c505c81b5aa7bad..4224ea482f425f2631bc1bd4fd8942c74522fd51 100644 --- a/src/mol-canvas3d/passes/draw.ts +++ b/src/mol-canvas3d/passes/draw.ts @@ -22,8 +22,11 @@ import { Helper } from '../helper/helper'; import quad_vert from '../../mol-gl/shader/quad.vert'; import depthMerge_frag from '../../mol-gl/shader/depth-merge.frag'; +import copyFbo_frag from '../../mol-gl/shader/copy-fbo.frag'; import { StereoCamera } from '../camera/stereo'; import { WboitPass } from './wboit'; +import { AntialiasingPass, PostprocessingPass, PostprocessingProps } from './postprocessing'; +import { Color } from '../../mol-util/color'; const DepthMergeSchema = { ...QuadSchema, @@ -50,6 +53,29 @@ function getDepthMergeRenderable(ctx: WebGLContext, depthTexturePrimitives: Text return createComputeRenderable(renderItem, values); } +const CopyFboSchema = { + ...QuadSchema, + tColor: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'), + tDepth: TextureSpec('texture', 'depth', 'ushort', 'nearest'), + uTexSize: UniformSpec('v2'), +}; +const CopyFboShaderCode = ShaderCode('copy-fbo', quad_vert, copyFbo_frag); +type CopyFboRenderable = ComputeRenderable<Values<typeof CopyFboSchema>> + +function getCopyFboRenderable(ctx: WebGLContext, colorTexture: Texture, depthTexture: Texture): CopyFboRenderable { + const values: Values<typeof CopyFboSchema> = { + ...QuadValues, + tColor: ValueCell.create(colorTexture), + tDepth: ValueCell.create(depthTexture), + uTexSize: ValueCell.create(Vec2.create(colorTexture.getWidth(), colorTexture.getHeight())), + }; + + const schema = { ...CopyFboSchema }; + const renderItem = createComputeRenderItem(ctx, 'triangles', CopyFboShaderCode, schema, values); + + return createComputeRenderable(renderItem, values); +} + export class DrawPass { private readonly drawTarget: RenderTarget @@ -57,14 +83,20 @@ export class DrawPass { readonly depthTexture: Texture readonly depthTexturePrimitives: Texture - private readonly packedDepth: boolean + readonly packedDepth: boolean + private depthTarget: RenderTarget private depthTargetPrimitives: RenderTarget | null private depthTargetVolumes: RenderTarget | null private depthTextureVolumes: Texture private depthMerge: DepthMergeRenderable + private copyFboTarget: CopyFboRenderable + private copyFboPostprocessing: CopyFboRenderable + private wboit: WboitPass | undefined + readonly postprocessing: PostprocessingPass + private readonly fxaa: AntialiasingPass get wboitEnabled() { return !!this.wboit?.supported; @@ -93,6 +125,11 @@ export class DrawPass { this.depthMerge = getDepthMergeRenderable(webgl, this.depthTexturePrimitives, this.depthTextureVolumes, this.packedDepth); this.wboit = enableWboit ? new WboitPass(webgl, width, height) : undefined; + this.postprocessing = new PostprocessingPass(webgl, this); + this.fxaa = new AntialiasingPass(webgl, this); + + this.copyFboTarget = getCopyFboRenderable(webgl, this.colorTarget.texture, this.depthTarget.texture); + this.copyFboPostprocessing = getCopyFboRenderable(webgl, this.postprocessing.target.texture, this.depthTarget.texture); } setSize(width: number, height: number) { @@ -117,9 +154,15 @@ export class DrawPass { ValueCell.update(this.depthMerge.values.uTexSize, Vec2.set(this.depthMerge.values.uTexSize.ref.value, width, height)); + ValueCell.update(this.copyFboTarget.values.uTexSize, Vec2.set(this.copyFboTarget.values.uTexSize.ref.value, width, height)); + ValueCell.update(this.copyFboPostprocessing.values.uTexSize, Vec2.set(this.copyFboPostprocessing.values.uTexSize.ref.value, width, height)); + if (this.wboit?.supported) { this.wboit.setSize(width, height); } + + this.postprocessing.setSize(width, height); + this.fxaa.setSize(width, height); } } @@ -137,41 +180,50 @@ export class DrawPass { this.depthMerge.render(); } - private _renderWboit(renderer: Renderer, camera: ICamera, scene: Scene, toDrawingBuffer: boolean) { - if (!this.wboit?.supported) throw new Error('expected wboit to be enabled'); + private _renderWboit(renderer: Renderer, camera: ICamera, scene: Scene, backgroundColor: Color, postprocessingProps: PostprocessingProps) { + if (!this.wboit?.supported) throw new Error('expected wboit to be supported'); - const renderTarget = toDrawingBuffer ? this.drawTarget : this.colorTarget; - renderTarget.bind(); + this.colorTarget.bind(); renderer.clear(true); // render opaque primitives - this.depthTexturePrimitives.attachFramebuffer(renderTarget.framebuffer, 'depth'); - renderTarget.bind(); + this.depthTexturePrimitives.attachFramebuffer(this.colorTarget.framebuffer, 'depth'); + this.colorTarget.bind(); + renderer.clearDepth(); renderer.renderWboitOpaque(scene.primitives, camera, null); // render opaque volumes - this.depthTextureVolumes.attachFramebuffer(renderTarget.framebuffer, 'depth'); - renderTarget.bind(); + this.depthTextureVolumes.attachFramebuffer(this.colorTarget.framebuffer, 'depth'); + this.colorTarget.bind(); renderer.clearDepth(); renderer.renderWboitOpaque(scene.volumes, camera, this.depthTexturePrimitives); // merge depth of opaque primitives and volumes this._depthMerge(); + if (PostprocessingPass.isEnabled(postprocessingProps)) { + this.postprocessing.render(camera, false, backgroundColor, postprocessingProps); + } + // render transparent primitives and volumes this.wboit.bind(); renderer.renderWboitTransparent(scene.primitives, camera, this.depthTexture); renderer.renderWboitTransparent(scene.volumes, camera, this.depthTexture); // evaluate wboit - this.depthTexturePrimitives.attachFramebuffer(renderTarget.framebuffer, 'depth'); - renderTarget.bind(); + if (PostprocessingPass.isEnabled(postprocessingProps)) { + this.depthTexturePrimitives.attachFramebuffer(this.postprocessing.target.framebuffer, 'depth'); + this.postprocessing.target.bind(); + } else { + this.depthTexturePrimitives.attachFramebuffer(this.colorTarget.framebuffer, 'depth'); + this.colorTarget.bind(); + } this.wboit.render(); } - private _renderBlended(renderer: Renderer, camera: ICamera, scene: Scene, toDrawingBuffer: boolean) { + private _renderBlended(renderer: Renderer, camera: ICamera, scene: Scene, backgroundColor: Color, toDrawingBuffer: boolean, postprocessingProps: PostprocessingProps) { if (toDrawingBuffer) { - this.webgl.unbindFramebuffer(); + this.drawTarget.bind(); } else { this.colorTarget.bind(); if (!this.packedDepth) { @@ -182,22 +234,22 @@ export class DrawPass { renderer.clear(true); renderer.renderBlendedOpaque(scene.primitives, camera, null); - // do a depth pass if not rendering to drawing buffer and - // extensions.depthTexture is unsupported (i.e. depthTarget is set) - if (!toDrawingBuffer && this.depthTargetPrimitives) { - this.depthTargetPrimitives.bind(); - renderer.clear(false); - renderer.renderDepth(scene.primitives, camera, null); - this.colorTarget.bind(); - } - - // do direct-volume rendering if (!toDrawingBuffer) { + // do a depth pass if not rendering to drawing buffer and + // extensions.depthTexture is unsupported (i.e. depthTarget is set) + if (this.depthTargetPrimitives) { + this.depthTargetPrimitives.bind(); + renderer.clear(false); + renderer.renderDepth(scene.primitives, camera, null); + this.colorTarget.bind(); + } + + // do direct-volume rendering if (!this.packedDepth) { this.depthTextureVolumes.attachFramebuffer(this.colorTarget.framebuffer, 'depth'); renderer.clearDepth(); // from previous frame } - renderer.renderBlendedVolume(scene.volumes, camera, this.depthTexturePrimitives); + renderer.renderBlendedVolumeOpaque(scene.volumes, camera, this.depthTexturePrimitives); // do volume depth pass if extensions.depthTexture is unsupported (i.e. depthTarget is set) if (this.depthTargetVolumes) { @@ -207,29 +259,52 @@ export class DrawPass { this.colorTarget.bind(); } - if (!this.packedDepth) { - this.depthTexturePrimitives.attachFramebuffer(this.colorTarget.framebuffer, 'depth'); + // merge depths from primitive and volume rendering + this._depthMerge(); + this.colorTarget.bind(); + + if (PostprocessingPass.isEnabled(postprocessingProps)) { + this.postprocessing.render(camera, false, backgroundColor, postprocessingProps); + } + renderer.renderBlendedVolumeTransparent(scene.volumes, camera, this.depthTexturePrimitives); + + if (PostprocessingPass.isEnabled(postprocessingProps)) { + if (!this.packedDepth) { + this.depthTexturePrimitives.attachFramebuffer(this.postprocessing.target.framebuffer, 'depth'); + } + this.postprocessing.target.bind(); + } else { + if (!this.packedDepth) { + this.depthTexturePrimitives.attachFramebuffer(this.colorTarget.framebuffer, 'depth'); + } + this.colorTarget.bind(); } } renderer.renderBlendedTransparent(scene.primitives, camera, null); - - // merge depths from primitive and volume rendering - if (!toDrawingBuffer) { - this._depthMerge(); - this.colorTarget.bind(); - } } - private _render(renderer: Renderer, camera: ICamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean) { + private _render(renderer: Renderer, camera: ICamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, backgroundColor: Color, postprocessingProps: PostprocessingProps) { + const volumeRendering = scene.volumes.renderables.length > 0; + const postprocessingEnabled = PostprocessingPass.isEnabled(postprocessingProps); + const antialiasingEnabled = AntialiasingPass.isEnabled(postprocessingProps); + const { x, y, width, height } = camera.viewport; renderer.setViewport(x, y, width, height); renderer.update(camera); if (this.wboitEnabled) { - this._renderWboit(renderer, camera, scene, toDrawingBuffer); + this._renderWboit(renderer, camera, scene, backgroundColor, postprocessingProps); + } else { + this._renderBlended(renderer, camera, scene, backgroundColor, !volumeRendering && !postprocessingEnabled && !antialiasingEnabled && toDrawingBuffer, postprocessingProps); + } + + if (PostprocessingPass.isEnabled(postprocessingProps)) { + this.postprocessing.target.bind(); + } else if (!toDrawingBuffer || volumeRendering || this.wboitEnabled) { + this.colorTarget.bind(); } else { - this._renderBlended(renderer, camera, scene, toDrawingBuffer); + this.drawTarget.bind(); } if (helper.debug.isEnabled) { @@ -245,18 +320,40 @@ export class DrawPass { renderer.renderBlended(helper.camera.scene, helper.camera.camera, null); } + if (antialiasingEnabled) { + this.fxaa.render(camera, toDrawingBuffer, postprocessingProps); + } else if (toDrawingBuffer) { + this.drawTarget.bind(); + + this.webgl.state.disable(this.webgl.gl.DEPTH_TEST); + if (PostprocessingPass.isEnabled(postprocessingProps)) { + this.copyFboPostprocessing.render(); + } else if (volumeRendering || this.wboitEnabled) { + this.copyFboTarget.render(); + } + } + this.webgl.gl.flush(); } - render(renderer: Renderer, camera: Camera | StereoCamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean) { + render(renderer: Renderer, camera: Camera | StereoCamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, backgroundColor: Color, transparentBackground: boolean, postprocessingProps: PostprocessingProps) { renderer.setTransparentBackground(transparentBackground); renderer.setDrawingBufferSize(this.colorTarget.getWidth(), this.colorTarget.getHeight()); if (StereoCamera.is(camera)) { - this._render(renderer, camera.left, scene, helper, toDrawingBuffer); - this._render(renderer, camera.right, scene, helper, toDrawingBuffer); + this._render(renderer, camera.left, scene, helper, toDrawingBuffer, backgroundColor, postprocessingProps); + this._render(renderer, camera.right, scene, helper, toDrawingBuffer, backgroundColor, postprocessingProps); } else { - this._render(renderer, camera, scene, helper, toDrawingBuffer); + this._render(renderer, camera, scene, helper, toDrawingBuffer, backgroundColor, postprocessingProps); + } + } + + getColorTarget(postprocessingProps: PostprocessingProps): RenderTarget { + if (AntialiasingPass.isEnabled(postprocessingProps)) { + return this.fxaa.target; + } else if (PostprocessingPass.isEnabled(postprocessingProps)) { + return this.postprocessing.target; } + return this.colorTarget; } } \ No newline at end of file diff --git a/src/mol-canvas3d/passes/image.ts b/src/mol-canvas3d/passes/image.ts index 8c96084efc3f5400aef9a0d4ce152f7dcbb18068..89e0aa1ef0f70967d88e66937a9fd4de28d79783 100644 --- a/src/mol-canvas3d/passes/image.ts +++ b/src/mol-canvas3d/passes/image.ts @@ -10,13 +10,14 @@ import Renderer from '../../mol-gl/renderer'; import Scene from '../../mol-gl/scene'; import { ParamDefinition as PD } from '../../mol-util/param-definition'; import { DrawPass } from './draw'; -import { PostprocessingPass, PostprocessingParams } from './postprocessing'; +import { PostprocessingParams } from './postprocessing'; import { MultiSamplePass, MultiSampleParams, MultiSampleHelper } from './multi-sample'; import { Camera } from '../camera'; import { Viewport } from '../camera/util'; import { PixelData } from '../../mol-util/image'; import { Helper } from '../helper/helper'; import { CameraHelper, CameraHelperParams } from '../helper/camera-helper'; +import { Color } from '../../mol-util/color'; export const ImageParams = { transparentBackground: PD.Boolean(false), @@ -38,7 +39,6 @@ export class ImagePass { get colorTarget() { return this._colorTarget; } private readonly drawPass: DrawPass - private readonly postprocessingPass: PostprocessingPass private readonly multiSamplePass: MultiSamplePass private readonly multiSampleHelper: MultiSampleHelper private readonly helper: Helper @@ -50,8 +50,7 @@ export class ImagePass { this.props = { ...PD.getDefaultValues(ImageParams), ...props }; this.drawPass = new DrawPass(webgl, 128, 128, enableWboit); - this.postprocessingPass = new PostprocessingPass(webgl, this.drawPass); - this.multiSamplePass = new MultiSamplePass(webgl, this.drawPass, this.postprocessingPass); + this.multiSamplePass = new MultiSamplePass(webgl, this.drawPass); this.multiSampleHelper = new MultiSampleHelper(this.multiSamplePass); this.helper = { @@ -70,7 +69,6 @@ export class ImagePass { this._height = height; this.drawPass.setSize(width, height); - this.postprocessingPass.syncSize(); this.multiSamplePass.syncSize(); } @@ -88,13 +86,8 @@ export class ImagePass { this.multiSampleHelper.render(this.renderer, this._camera, this.scene, this.helper, false, this.props.transparentBackground, this.props); this._colorTarget = this.multiSamplePass.colorTarget; } else { - this.drawPass.render(this.renderer, this._camera, this.scene, this.helper, false, this.props.transparentBackground); - if (PostprocessingPass.isEnabled(this.props.postprocessing)) { - this.postprocessingPass.render(this._camera, false, this.props.postprocessing); - this._colorTarget = this.postprocessingPass.target; - } else { - this._colorTarget = this.drawPass.colorTarget; - } + this.drawPass.render(this.renderer, this._camera, this.scene, this.helper, false, Color(0xffffff), this.props.transparentBackground, this.props.postprocessing); + this._colorTarget = this.drawPass.getColorTarget(this.props.postprocessing); } } diff --git a/src/mol-canvas3d/passes/multi-sample.ts b/src/mol-canvas3d/passes/multi-sample.ts index 6db628cd96cc1cec14066676dabc18eea85913d6..c3b96eb642806191e58a253ecc4fde3028994d24 100644 --- a/src/mol-canvas3d/passes/multi-sample.ts +++ b/src/mol-canvas3d/passes/multi-sample.ts @@ -16,7 +16,7 @@ import { createComputeRenderable, ComputeRenderable } from '../../mol-gl/rendera import { ParamDefinition as PD } from '../../mol-util/param-definition'; import { RenderTarget } from '../../mol-gl/webgl/render-target'; import { Camera } from '../../mol-canvas3d/camera'; -import { PostprocessingPass, PostprocessingProps } from './postprocessing'; +import { PostprocessingProps } from './postprocessing'; import { DrawPass } from './draw'; import Renderer from '../../mol-gl/renderer'; import Scene from '../../mol-gl/scene'; @@ -25,6 +25,7 @@ import { StereoCamera } from '../camera/stereo'; import quad_vert from '../../mol-gl/shader/quad.vert'; import compose_frag from '../../mol-gl/shader/compose.frag'; +import { Color } from '../../mol-util/color'; const ComposeSchema = { ...QuadSchema, @@ -68,7 +69,7 @@ export class MultiSamplePass { private holdTarget: RenderTarget private compose: ComposeRenderable - constructor(private webgl: WebGLContext, private drawPass: DrawPass, private postprocessing: PostprocessingPass) { + constructor(private webgl: WebGLContext, private drawPass: DrawPass) { const { colorBufferFloat, textureFloat } = webgl.extensions; const width = drawPass.colorTarget.getWidth(); const height = drawPass.colorTarget.getHeight(); @@ -109,7 +110,7 @@ export class MultiSamplePass { } private renderMultiSample(renderer: Renderer, camera: Camera | StereoCamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean, props: Props) { - const { compose, composeTarget, drawPass, postprocessing, webgl } = this; + const { compose, composeTarget, drawPass, webgl } = this; const { gl, state } = webgl; // based on the Multisample Anti-Aliasing Render Pass @@ -123,10 +124,8 @@ export class MultiSamplePass { const baseSampleWeight = 1.0 / offsetList.length; const roundingRange = 1 / 32; - const postprocessingEnabled = PostprocessingPass.isEnabled(props.postprocessing); - camera.viewOffset.enabled = true; - ValueCell.update(compose.values.tColor, postprocessingEnabled ? postprocessing.target.texture : drawPass.colorTarget.texture); + ValueCell.update(compose.values.tColor, drawPass.getColorTarget(props.postprocessing).texture); compose.update(); // render the scene multiple times, each slightly jitter offset @@ -143,9 +142,8 @@ export class MultiSamplePass { const sampleWeight = baseSampleWeight + roundingRange * uniformCenteredDistribution; ValueCell.update(compose.values.uWeight, sampleWeight); - // render scene and optionally postprocess - drawPass.render(renderer, camera, scene, helper, false, transparentBackground); - if (postprocessingEnabled) postprocessing.render(camera, false, props.postprocessing); + // render scene + drawPass.render(renderer, camera, scene, helper, false, Color(0xffffff), transparentBackground, props.postprocessing); // compose rendered scene with compose target composeTarget.bind(); @@ -179,7 +177,7 @@ export class MultiSamplePass { } private renderTemporalMultiSample(sampleIndex: number, renderer: Renderer, camera: Camera | StereoCamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean, props: Props) { - const { compose, composeTarget, holdTarget, postprocessing, drawPass, webgl } = this; + const { compose, composeTarget, holdTarget, drawPass, webgl } = this; const { gl, state } = webgl; // based on the Multisample Anti-Aliasing Render Pass @@ -193,13 +191,11 @@ export class MultiSamplePass { const { x, y, width, height } = camera.viewport; const sampleWeight = 1.0 / offsetList.length; - const postprocessingEnabled = PostprocessingPass.isEnabled(props.postprocessing); if (sampleIndex === -1) { - drawPass.render(renderer, camera, scene, helper, false, transparentBackground); - if (postprocessingEnabled) postprocessing.render(camera, false, props.postprocessing); + drawPass.render(renderer, camera, scene, helper, false, Color(0xffffff), transparentBackground, props.postprocessing); ValueCell.update(compose.values.uWeight, 1.0); - ValueCell.update(compose.values.tColor, postprocessingEnabled ? postprocessing.target.texture : drawPass.colorTarget.texture); + ValueCell.update(compose.values.tColor, drawPass.getColorTarget(props.postprocessing).texture); compose.update(); holdTarget.bind(); @@ -212,7 +208,7 @@ export class MultiSamplePass { sampleIndex += 1; } else { camera.viewOffset.enabled = true; - ValueCell.update(compose.values.tColor, postprocessingEnabled ? postprocessing.target.texture : drawPass.colorTarget.texture); + ValueCell.update(compose.values.tColor, drawPass.getColorTarget(props.postprocessing).texture); ValueCell.update(compose.values.uWeight, sampleWeight); compose.update(); @@ -224,9 +220,8 @@ export class MultiSamplePass { Camera.setViewOffset(camera.viewOffset, width, height, offset[0], offset[1], width, height); camera.update(); - // render scene and optionally postprocess - drawPass.render(renderer, camera, scene, helper, false, transparentBackground); - if (postprocessingEnabled) postprocessing.render(camera, false, props.postprocessing); + // render scene + drawPass.render(renderer, camera, scene, helper, false, Color(0xffffff), transparentBackground, props.postprocessing); // compose rendered scene with compose target composeTarget.bind(); diff --git a/src/mol-canvas3d/passes/passes.ts b/src/mol-canvas3d/passes/passes.ts index 8e06be24334680d4d5f97a4d5115355e2e746d1b..cc61b18b7048192fa04948787356dbad44ee6b08 100644 --- a/src/mol-canvas3d/passes/passes.ts +++ b/src/mol-canvas3d/passes/passes.ts @@ -6,29 +6,25 @@ import { DrawPass } from './draw'; import { PickPass } from './pick'; -import { PostprocessingPass } from './postprocessing'; import { MultiSamplePass } from './multi-sample'; import { WebGLContext } from '../../mol-gl/webgl/context'; export class Passes { readonly draw: DrawPass readonly pick: PickPass - readonly postprocessing: PostprocessingPass readonly multiSample: MultiSamplePass constructor(private webgl: WebGLContext, attribs: Partial<{ pickScale: number, enableWboit: boolean }> = {}) { const { gl } = webgl; this.draw = new DrawPass(webgl, gl.drawingBufferWidth, gl.drawingBufferHeight, attribs.enableWboit || false); this.pick = new PickPass(webgl, this.draw, attribs.pickScale || 0.25); - this.postprocessing = new PostprocessingPass(webgl, this.draw); - this.multiSample = new MultiSamplePass(webgl, this.draw, this.postprocessing); + this.multiSample = new MultiSamplePass(webgl, this.draw); } updateSize() { const { gl } = this.webgl; this.draw.setSize(gl.drawingBufferWidth, gl.drawingBufferHeight); this.pick.syncSize(); - this.postprocessing.syncSize(); this.multiSample.syncSize(); } } \ No newline at end of file diff --git a/src/mol-canvas3d/passes/postprocessing.ts b/src/mol-canvas3d/passes/postprocessing.ts index 068a8b0a8a4dbf93ebd4fc1ba6433ce7ecea7275..5747c8d294d5f8ab7095f5ecaa7204ad74d008e4 100644 --- a/src/mol-canvas3d/passes/postprocessing.ts +++ b/src/mol-canvas3d/passes/postprocessing.ts @@ -2,6 +2,7 @@ * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> + * @author Áron Samuel Kovács <aron.kovacs@mail.muni.cz> */ import { QuadSchema, QuadValues } from '../../mol-gl/compute/util'; @@ -12,71 +13,227 @@ import { Texture } from '../../mol-gl/webgl/texture'; import { ValueCell } from '../../mol-util'; import { createComputeRenderItem } from '../../mol-gl/webgl/render-item'; import { createComputeRenderable, ComputeRenderable } from '../../mol-gl/renderable'; -import { Vec2, Vec3 } from '../../mol-math/linear-algebra'; +import { Mat4, Vec2, Vec3 } from '../../mol-math/linear-algebra'; import { ParamDefinition as PD } from '../../mol-util/param-definition'; import { RenderTarget } from '../../mol-gl/webgl/render-target'; import { DrawPass } from './draw'; -import { Camera, ICamera } from '../../mol-canvas3d/camera'; +import { ICamera } from '../../mol-canvas3d/camera'; import quad_vert from '../../mol-gl/shader/quad.vert'; +import outlines_frag from '../../mol-gl/shader/outlines.frag'; +import ssao_frag from '../../mol-gl/shader/ssao.frag'; +import ssao_blur_frag from '../../mol-gl/shader/ssao-blur.frag'; import postprocessing_frag from '../../mol-gl/shader/postprocessing.frag'; -import { StereoCamera } from '../camera/stereo'; +import { Framebuffer } from '../../mol-gl/webgl/framebuffer'; +import { Color } from '../../mol-util/color'; import { FxaaParams, FxaaPass } from './fxaa'; import { SmaaParams, SmaaPass } from './smaa'; +const OutlinesSchema = { + ...QuadSchema, + tDepth: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'), + uTexSize: UniformSpec('v2'), + + dOrthographic: DefineSpec('number'), + uNear: UniformSpec('f'), + uFar: UniformSpec('f'), + + uMaxPossibleViewZDiff: UniformSpec('f'), +}; +type OutlinesRenderable = ComputeRenderable<Values<typeof OutlinesSchema>> + +function getOutlinesRenderable(ctx: WebGLContext, depthTexture: Texture): OutlinesRenderable { + const values: Values<typeof OutlinesSchema> = { + ...QuadValues, + tDepth: ValueCell.create(depthTexture), + uTexSize: ValueCell.create(Vec2.create(depthTexture.getWidth(), depthTexture.getHeight())), + + dOrthographic: ValueCell.create(0), + uNear: ValueCell.create(1), + uFar: ValueCell.create(10000), + + uMaxPossibleViewZDiff: ValueCell.create(0.5), + }; + + const schema = { ...OutlinesSchema }; + const shaderCode = ShaderCode('outlines', quad_vert, outlines_frag); + const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values); + + return createComputeRenderable(renderItem, values); +} + +const SsaoSchema = { + ...QuadSchema, + tDepth: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'), + + uSamples: UniformSpec('v3[]'), + dNSamples: DefineSpec('number'), + + uProjection: UniformSpec('m4'), + uInvProjection: UniformSpec('m4'), + + uTexSize: UniformSpec('v2'), + + uRadius: UniformSpec('f'), + uBias: UniformSpec('f'), +}; + +type SsaoRenderable = ComputeRenderable<Values<typeof SsaoSchema>> + +function getSsaoRenderable(ctx: WebGLContext, depthTexture: Texture): SsaoRenderable { + const values: Values<typeof SsaoSchema> = { + ...QuadValues, + tDepth: ValueCell.create(depthTexture), + + uSamples: ValueCell.create([0.0, 0.0, 1.0]), + dNSamples: ValueCell.create(1), + + uProjection: ValueCell.create(Mat4.identity()), + uInvProjection: ValueCell.create(Mat4.identity()), + + uTexSize: ValueCell.create(Vec2.create(ctx.gl.drawingBufferWidth, ctx.gl.drawingBufferHeight)), + + uRadius: ValueCell.create(8.0), + uBias: ValueCell.create(0.025), + }; + + const schema = { ...SsaoSchema }; + const shaderCode = ShaderCode('ssao', quad_vert, ssao_frag); + const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values); + + return createComputeRenderable(renderItem, values); +} + +const SsaoBlurSchema = { + ...QuadSchema, + tSsaoDepth: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'), + uTexSize: UniformSpec('v2'), + + uKernel: UniformSpec('f[]'), + dOcclusionKernelSize: DefineSpec('number'), + + uBlurDirectionX: UniformSpec('f'), + uBlurDirectionY: UniformSpec('f'), + + uMaxPossibleViewZDiff: UniformSpec('f'), + + uNear: UniformSpec('f'), + uFar: UniformSpec('f'), + dOrthographic: DefineSpec('number'), +}; + +type SsaoBlurRenderable = ComputeRenderable<Values<typeof SsaoBlurSchema>> + +function getSsaoBlurRenderable(ctx: WebGLContext, ssaoDepthTexture: Texture, direction: 'horizontal' | 'vertical'): SsaoBlurRenderable { + const values: Values<typeof SsaoBlurSchema> = { + ...QuadValues, + tSsaoDepth: ValueCell.create(ssaoDepthTexture), + uTexSize: ValueCell.create(Vec2.create(ssaoDepthTexture.getWidth(), ssaoDepthTexture.getHeight())), + + uKernel: ValueCell.create([0.0]), + dOcclusionKernelSize: ValueCell.create(1), + + uBlurDirectionX: ValueCell.create(direction === 'horizontal' ? 1 : 0), + uBlurDirectionY: ValueCell.create(direction === 'vertical' ? 1 : 0), + + uMaxPossibleViewZDiff: ValueCell.create(0.5), + + uNear: ValueCell.create(0.0), + uFar: ValueCell.create(10000.0), + dOrthographic: ValueCell.create(0), + }; + + const schema = { ...SsaoBlurSchema }; + const shaderCode = ShaderCode('ssao_blur', quad_vert, ssao_blur_frag); + const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values); + + return createComputeRenderable(renderItem, values); +} + +function getBlurKernel(kernelSize: number): number[] { + let sigma = kernelSize / 3.0; + let halfKernelSize = Math.floor((kernelSize + 1) / 2); + + let kernel = []; + for (let x = 0; x < halfKernelSize; x++) { + kernel.push((1.0 / ((Math.sqrt(2 * Math.PI)) * sigma)) * Math.exp(-x * x / (2 * sigma * sigma))); + } + + return kernel; +} + +function getSamples(vectorSamples: Vec3[], nSamples: number): number[] { + let samples = []; + for (let i = 0; i < nSamples; i++) { + let scale = (i * i + 2.0 * i + 1) / (nSamples * nSamples); + scale = 0.1 + scale * (1.0 - 0.1); + + samples.push(vectorSamples[i][0] * scale); + samples.push(vectorSamples[i][1] * scale); + samples.push(vectorSamples[i][2] * scale); + } + + return samples; +} + const PostprocessingSchema = { ...QuadSchema, + tSsaoDepth: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'), tColor: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'), - tPackedDepth: TextureSpec('texture', 'depth', 'ushort', 'nearest'), + tDepth: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'), + tOutlines: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'), uTexSize: UniformSpec('v2'), dOrthographic: DefineSpec('number'), + uInvProjection: UniformSpec('m4'), uNear: UniformSpec('f'), uFar: UniformSpec('f'), uFogNear: UniformSpec('f'), uFogFar: UniformSpec('f'), uFogColor: UniformSpec('v3'), + uMaxPossibleViewZDiff: UniformSpec('f'), + dOcclusionEnable: DefineSpec('boolean'), - dOcclusionKernelSize: DefineSpec('number'), - uOcclusionBias: UniformSpec('f'), - uOcclusionRadius: UniformSpec('f'), dOutlineEnable: DefineSpec('boolean'), uOutlineScale: UniformSpec('f'), uOutlineThreshold: UniformSpec('f'), + + dPackedDepth: DefineSpec('boolean'), }; -const PostprocessingShaderCode = ShaderCode('postprocessing', quad_vert, postprocessing_frag); type PostprocessingRenderable = ComputeRenderable<Values<typeof PostprocessingSchema>> -function getPostprocessingRenderable(ctx: WebGLContext, colorTexture: Texture, depthTexture: Texture): PostprocessingRenderable { - const width = colorTexture.getWidth(); - const height = colorTexture.getHeight(); - +function getPostprocessingRenderable(ctx: WebGLContext, colorTexture: Texture, depthTexture: Texture, packedDepth: boolean, outlinesTexture: Texture, ssaoDepthTexture: Texture): PostprocessingRenderable { const values: Values<typeof PostprocessingSchema> = { ...QuadValues, + tSsaoDepth: ValueCell.create(ssaoDepthTexture), tColor: ValueCell.create(colorTexture), - tPackedDepth: ValueCell.create(depthTexture), - uTexSize: ValueCell.create(Vec2.create(width, height)), + tDepth: ValueCell.create(depthTexture), + tOutlines: ValueCell.create(outlinesTexture), + uTexSize: ValueCell.create(Vec2.create(colorTexture.getWidth(), colorTexture.getHeight())), dOrthographic: ValueCell.create(0), + uInvProjection: ValueCell.create(Mat4.identity()), uNear: ValueCell.create(1), uFar: ValueCell.create(10000), uFogNear: ValueCell.create(10000), uFogFar: ValueCell.create(10000), uFogColor: ValueCell.create(Vec3.create(1, 1, 1)), + uMaxPossibleViewZDiff: ValueCell.create(0.5), + dOcclusionEnable: ValueCell.create(false), - dOcclusionKernelSize: ValueCell.create(4), - uOcclusionBias: ValueCell.create(0.5), - uOcclusionRadius: ValueCell.create(64), dOutlineEnable: ValueCell.create(false), - uOutlineScale: ValueCell.create(1 * ctx.pixelRatio), + uOutlineScale: ValueCell.create(ctx.pixelRatio), uOutlineThreshold: ValueCell.create(0.8), + + dPackedDepth: ValueCell.create(packedDepth), }; const schema = { ...PostprocessingSchema }; - const renderItem = createComputeRenderItem(ctx, 'triangles', PostprocessingShaderCode, schema, values); + const shaderCode = ShaderCode('postprocessing', quad_vert, postprocessing_frag); + const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values); return createComputeRenderable(renderItem, values); } @@ -84,16 +241,17 @@ function getPostprocessingRenderable(ctx: WebGLContext, colorTexture: Texture, d export const PostprocessingParams = { occlusion: PD.MappedStatic('off', { on: PD.Group({ - kernelSize: PD.Numeric(4, { min: 1, max: 32, step: 1 }), - bias: PD.Numeric(0.5, { min: 0, max: 1, step: 0.01 }), - radius: PD.Numeric(64, { min: 0, max: 256, step: 1 }), + samples: PD.Numeric(64, {min: 1, max: 256, step: 1}), + radius: PD.Numeric(8.0, { min: 1, max: 64, step: 1 }), + bias: PD.Numeric(1.0, { min: 0, max: 1, step: 0.001 }), + blurKernelSize: PD.Numeric(13, { min: 1, max: 25, step: 2 }), }), off: PD.Group({}) }, { cycle: true, description: 'Darken occluded crevices with the ambient occlusion effect' }), outline: PD.MappedStatic('off', { on: PD.Group({ - scale: PD.Numeric(1, { min: 0, max: 10, step: 1 }), - threshold: PD.Numeric(0.8, { min: 0, max: 5, step: 0.01 }), + scale: PD.Numeric(1, { min: 1, max: 5, step: 1 }), + threshold: PD.Numeric(0.33, { min: 0.01, max: 1, step: 0.01 }), }), off: PD.Group({}) }, { cycle: true, description: 'Draw outline around 3D objects' }), @@ -107,44 +265,181 @@ export type PostprocessingProps = PD.Values<typeof PostprocessingParams> export class PostprocessingPass { static isEnabled(props: PostprocessingProps) { - return props.occlusion.name === 'on' || props.outline.name === 'on' || props.antialiasing.name !== 'off'; + return props.occlusion.name === 'on' || props.outline.name === 'on'; } readonly target: RenderTarget - private readonly tmpTarget: RenderTarget + private readonly outlinesTarget: RenderTarget + private readonly outlinesRenderable: OutlinesRenderable + + private readonly randomHemisphereVector: Vec3[] + private readonly ssaoFramebuffer: Framebuffer + private readonly ssaoBlurFirstPassFramebuffer: Framebuffer + private readonly ssaoBlurSecondPassFramebuffer: Framebuffer + + private readonly ssaoDepthTexture: Texture + private readonly ssaoDepthBlurProxyTexture: Texture + + private readonly ssaoRenderable: SsaoRenderable + private readonly ssaoBlurFirstPassRenderable: SsaoBlurRenderable + private readonly ssaoBlurSecondPassRenderable: SsaoBlurRenderable + + private nSamples: number + private blurKernelSize: number + private readonly renderable: PostprocessingRenderable - private readonly fxaa: FxaaPass - private readonly smaa: SmaaPass - constructor(private webgl: WebGLContext, private drawPass: DrawPass) { - const { colorTarget, depthTexture } = drawPass; + constructor(private webgl: WebGLContext, drawPass: DrawPass) { + const { colorTarget, depthTexture, packedDepth } = drawPass; const width = colorTarget.getWidth(); const height = colorTarget.getHeight(); - this.target = webgl.createRenderTarget(width, height, false); - this.tmpTarget = webgl.createRenderTarget(width, height, false, 'uint8', 'linear'); - this.renderable = getPostprocessingRenderable(webgl, colorTarget.texture, depthTexture); - this.fxaa = new FxaaPass(webgl, this.tmpTarget.texture); - this.smaa = new SmaaPass(webgl, this.tmpTarget.texture); - } + this.nSamples = 1; + this.blurKernelSize = 1; + + this.target = webgl.createRenderTarget(width, height, false, 'uint8', 'linear'); + + this.outlinesTarget = webgl.createRenderTarget(width, height, false); + this.outlinesRenderable = getOutlinesRenderable(webgl, depthTexture); + + this.randomHemisphereVector = []; + for (let i = 0; i < 256; i++) { + let v = Vec3(); + v[0] = Math.random() * 2.0 - 1.0; + v[1] = Math.random() * 2.0 - 1.0; + v[2] = Math.random(); + Vec3.normalize(v, v); + Vec3.scale(v, v, Math.random()); + this.randomHemisphereVector.push(v); + } + this.ssaoFramebuffer = webgl.resources.framebuffer(); + this.ssaoBlurFirstPassFramebuffer = webgl.resources.framebuffer(); + this.ssaoBlurSecondPassFramebuffer = webgl.resources.framebuffer(); - syncSize() { - const width = this.drawPass.colorTarget.getWidth(); - const height = this.drawPass.colorTarget.getHeight(); + this.ssaoDepthTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest'); + this.ssaoDepthTexture.define(width, height); + this.ssaoDepthTexture.attachFramebuffer(this.ssaoFramebuffer, 'color0'); + this.ssaoDepthBlurProxyTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest'); + this.ssaoDepthBlurProxyTexture.define(width, height); + this.ssaoDepthBlurProxyTexture.attachFramebuffer(this.ssaoBlurFirstPassFramebuffer, 'color0'); + + this.ssaoDepthTexture.attachFramebuffer(this.ssaoBlurSecondPassFramebuffer, 'color0'); + + this.ssaoRenderable = getSsaoRenderable(webgl, depthTexture); + this.ssaoBlurFirstPassRenderable = getSsaoBlurRenderable(webgl, this.ssaoDepthTexture, 'horizontal'); + this.ssaoBlurSecondPassRenderable = getSsaoBlurRenderable(webgl, this.ssaoDepthBlurProxyTexture, 'vertical'); + this.renderable = getPostprocessingRenderable(webgl, colorTarget.texture, depthTexture, packedDepth, this.outlinesTarget.texture, this.ssaoDepthTexture); + } + + setSize(width: number, height: number) { const [w, h] = this.renderable.values.uTexSize.ref.value; if (width !== w || height !== h) { this.target.setSize(width, height); - this.tmpTarget.setSize(width, height); - ValueCell.update(this.renderable.values.uTexSize, Vec2.set(this.renderable.values.uTexSize.ref.value, width, height)); - this.fxaa.setSize(width, height); + this.outlinesTarget.setSize(width, height); + this.ssaoDepthTexture.define(width, height); + this.ssaoDepthBlurProxyTexture.define(width, height); - if (this.smaa.supported) this.smaa.setSize(width, height); + ValueCell.update(this.renderable.values.uTexSize, Vec2.set(this.renderable.values.uTexSize.ref.value, width, height)); + ValueCell.update(this.outlinesRenderable.values.uTexSize, Vec2.set(this.outlinesRenderable.values.uTexSize.ref.value, width, height)); + ValueCell.update(this.ssaoRenderable.values.uTexSize, Vec2.set(this.ssaoRenderable.values.uTexSize.ref.value, width, height)); + ValueCell.update(this.ssaoBlurFirstPassRenderable.values.uTexSize, Vec2.set(this.ssaoRenderable.values.uTexSize.ref.value, width, height)); + ValueCell.update(this.ssaoBlurSecondPassRenderable.values.uTexSize, Vec2.set(this.ssaoRenderable.values.uTexSize.ref.value, width, height)); } } - private updateState(camera: ICamera) { + private updateState(camera: ICamera, backgroundColor: Color, props: PostprocessingProps) { + let needsUpdateMain = false; + let needsUpdateSsao = false; + let needsUpdateSsaoBlur = false; + + let orthographic = camera.state.mode === 'orthographic' ? 1 : 0; + let outlinesEnabled = props.outline.name === 'on'; + let occlusionEnabled = props.occlusion.name === 'on'; + + let invProjection = Mat4.identity(); + Mat4.invert(invProjection, camera.projection); + + if (props.occlusion.name === 'on') { + ValueCell.updateIfChanged(this.ssaoRenderable.values.uProjection, camera.projection); + ValueCell.updateIfChanged(this.ssaoRenderable.values.uInvProjection, invProjection); + + ValueCell.updateIfChanged(this.ssaoBlurFirstPassRenderable.values.uNear, camera.near); + ValueCell.updateIfChanged(this.ssaoBlurSecondPassRenderable.values.uNear, camera.near); + + ValueCell.updateIfChanged(this.ssaoBlurFirstPassRenderable.values.uFar, camera.far); + ValueCell.updateIfChanged(this.ssaoBlurSecondPassRenderable.values.uFar, camera.far); + + if (this.ssaoBlurFirstPassRenderable.values.dOrthographic.ref.value !== orthographic) { needsUpdateSsaoBlur = true; } + ValueCell.updateIfChanged(this.ssaoBlurFirstPassRenderable.values.dOrthographic, orthographic); + ValueCell.updateIfChanged(this.ssaoBlurSecondPassRenderable.values.dOrthographic, orthographic); + + if (this.nSamples !== props.occlusion.params.samples) { + needsUpdateSsao = true; + + this.nSamples = props.occlusion.params.samples; + ValueCell.updateIfChanged(this.ssaoRenderable.values.uSamples, getSamples(this.randomHemisphereVector, this.nSamples)); + ValueCell.updateIfChanged(this.ssaoRenderable.values.dNSamples, this.nSamples); + } + ValueCell.updateIfChanged(this.ssaoRenderable.values.uRadius, props.occlusion.params.radius); + ValueCell.updateIfChanged(this.ssaoRenderable.values.uBias, props.occlusion.params.bias); + + if (this.blurKernelSize !== props.occlusion.params.blurKernelSize) { + needsUpdateSsaoBlur = true; + + this.blurKernelSize = props.occlusion.params.blurKernelSize; + let kernel = getBlurKernel(this.blurKernelSize); + + ValueCell.updateIfChanged(this.ssaoBlurFirstPassRenderable.values.uKernel, kernel); + ValueCell.updateIfChanged(this.ssaoBlurSecondPassRenderable.values.uKernel, kernel); + ValueCell.updateIfChanged(this.ssaoBlurFirstPassRenderable.values.dOcclusionKernelSize, this.blurKernelSize); + ValueCell.updateIfChanged(this.ssaoBlurSecondPassRenderable.values.dOcclusionKernelSize, this.blurKernelSize); + } + + } + + if (props.outline.name === 'on') { + let factor = Math.pow(1000, props.outline.params.threshold) / 1000; + let maxPossibleViewZDiff = factor * (camera.far - camera.near); + + ValueCell.updateIfChanged(this.outlinesRenderable.values.uNear, camera.near); + ValueCell.updateIfChanged(this.outlinesRenderable.values.uFar, camera.far); + ValueCell.updateIfChanged(this.outlinesRenderable.values.uMaxPossibleViewZDiff, maxPossibleViewZDiff); + + ValueCell.updateIfChanged(this.renderable.values.uInvProjection, invProjection); + ValueCell.updateIfChanged(this.renderable.values.uMaxPossibleViewZDiff, maxPossibleViewZDiff); + let fogColor = Vec3(); + Color.toVec3Normalized(fogColor, backgroundColor); + ValueCell.updateIfChanged(this.renderable.values.uFogColor, fogColor); + ValueCell.updateIfChanged(this.renderable.values.uOutlineScale, props.outline.params.scale - 1); + ValueCell.updateIfChanged(this.renderable.values.uOutlineThreshold, props.outline.params.threshold); + } + + ValueCell.updateIfChanged(this.renderable.values.uFar, camera.far); + ValueCell.updateIfChanged(this.renderable.values.uNear, camera.near); + ValueCell.updateIfChanged(this.renderable.values.uFogFar, camera.fogFar); + ValueCell.updateIfChanged(this.renderable.values.uFogNear, camera.fogNear); + if (this.renderable.values.dOrthographic.ref.value !== orthographic) { needsUpdateMain = true; } + ValueCell.updateIfChanged(this.renderable.values.dOrthographic, orthographic); + if (this.renderable.values.dOutlineEnable.ref.value !== outlinesEnabled) { needsUpdateMain = true; } + ValueCell.updateIfChanged(this.renderable.values.dOutlineEnable, outlinesEnabled); + if (this.renderable.values.dOcclusionEnable.ref.value !== occlusionEnabled) { needsUpdateMain = true; } + ValueCell.updateIfChanged(this.renderable.values.dOcclusionEnable, occlusionEnabled); + + if (needsUpdateSsao) { + this.ssaoRenderable.update(); + } + + if (needsUpdateSsaoBlur) { + this.ssaoBlurFirstPassRenderable.update(); + this.ssaoBlurSecondPassRenderable.update(); + } + + if (needsUpdateMain) { + this.renderable.update(); + } + const { gl, state } = this.webgl; state.enable(gl.SCISSOR_TEST); @@ -155,65 +450,91 @@ export class PostprocessingPass { const { x, y, width, height } = camera.viewport; gl.viewport(x, y, width, height); gl.scissor(x, y, width, height); - - state.clearColor(0, 0, 0, 1); - gl.clear(gl.COLOR_BUFFER_BIT); } - private _renderPostprocessing(camera: ICamera, toDrawingBuffer: boolean, props: PostprocessingProps) { - const { values } = this.renderable; + render(camera: ICamera, toDrawingBuffer: boolean, backgroundColor: Color, props: PostprocessingProps) { + this.updateState(camera, backgroundColor, props); - ValueCell.updateIfChanged(values.uFar, camera.far); - ValueCell.updateIfChanged(values.uNear, camera.near); - ValueCell.updateIfChanged(values.uFogFar, camera.fogFar); - ValueCell.updateIfChanged(values.uFogNear, camera.fogNear); - - let needsUpdate = false; - - const orthographic = camera.state.mode === 'orthographic' ? 1 : 0; - if (values.dOrthographic.ref.value !== orthographic) needsUpdate = true; - ValueCell.updateIfChanged(values.dOrthographic, orthographic); + if (props.outline.name === 'on') { + this.outlinesTarget.bind(); + this.outlinesRenderable.render(); + } - const occlusion = props.occlusion.name === 'on'; - if (values.dOcclusionEnable.ref.value !== occlusion) needsUpdate = true; - ValueCell.updateIfChanged(this.renderable.values.dOcclusionEnable, occlusion); if (props.occlusion.name === 'on') { - const { kernelSize } = props.occlusion.params; - if (values.dOcclusionKernelSize.ref.value !== kernelSize) needsUpdate = true; - ValueCell.updateIfChanged(values.dOcclusionKernelSize, kernelSize); - ValueCell.updateIfChanged(values.uOcclusionBias, props.occlusion.params.bias); - ValueCell.updateIfChanged(values.uOcclusionRadius, props.occlusion.params.radius); - } + this.ssaoFramebuffer.bind(); + this.ssaoRenderable.render(); - const outline = props.outline.name === 'on'; - if (values.dOutlineEnable.ref.value !== outline) needsUpdate = true; - ValueCell.updateIfChanged(values.dOutlineEnable, outline); - if (props.outline.name === 'on') { - ValueCell.updateIfChanged(values.uOutlineScale, props.outline.params.scale * this.webgl.pixelRatio); - ValueCell.updateIfChanged(values.uOutlineThreshold, props.outline.params.threshold); - } + this.ssaoBlurFirstPassFramebuffer.bind(); + this.ssaoBlurFirstPassRenderable.render(); - if (needsUpdate) { - this.renderable.update(); + this.ssaoBlurSecondPassFramebuffer.bind(); + this.ssaoBlurSecondPassRenderable.render(); } - if (props.antialiasing.name !== 'off') { - this.tmpTarget.bind(); - } else if (toDrawingBuffer) { + if (toDrawingBuffer) { this.webgl.unbindFramebuffer(); } else { this.target.bind(); } - this.updateState(camera); + const { gl, state } = this.webgl; + state.clearColor(0, 0, 0, 1); + gl.clear(gl.COLOR_BUFFER_BIT); + this.renderable.render(); } +} + +export class AntialiasingPass { + static isEnabled(props: PostprocessingProps) { + return props.antialiasing.name !== 'off'; + } + + readonly target: RenderTarget + private readonly fxaa: FxaaPass + private readonly smaa: SmaaPass + + constructor(private webgl: WebGLContext, private drawPass: DrawPass) { + const { colorTarget } = drawPass; + const width = colorTarget.getWidth(); + const height = colorTarget.getHeight(); + + this.target = webgl.createRenderTarget(width, height, false); + this.fxaa = new FxaaPass(webgl, this.target.texture); + this.smaa = new SmaaPass(webgl, this.target.texture); + } + + setSize(width: number, height: number) { + const [w, h] = [this.target.texture.getWidth(), this.target.texture.getHeight()]; + if (width !== w || height !== h) { + this.target.setSize(width, height); + this.fxaa.setSize(width, height); + if (this.smaa.supported) this.smaa.setSize(width, height); + } + } + + private updateState(camera: ICamera) { + const { gl, state } = this.webgl; + + state.enable(gl.SCISSOR_TEST); + state.disable(gl.BLEND); + state.disable(gl.DEPTH_TEST); + state.depthMask(false); + + const { x, y, width, height } = camera.viewport; + gl.viewport(x, y, width, height); + gl.scissor(x, y, width, height); + + state.clearColor(0, 0, 0, 1); + gl.clear(gl.COLOR_BUFFER_BIT); + } private _renderFxaa(camera: ICamera, toDrawingBuffer: boolean, props: PostprocessingProps) { if (props.antialiasing.name !== 'fxaa') return; - const input = (props.occlusion.name === 'on' || props.outline.name === 'on') - ? this.tmpTarget.texture : this.drawPass.colorTarget.texture; + const input = PostprocessingPass.isEnabled(props) + ? this.drawPass.postprocessing.target.texture + : this.drawPass.colorTarget.texture; this.fxaa.update(input, props.antialiasing.params); if (toDrawingBuffer) { @@ -229,17 +550,16 @@ export class PostprocessingPass { private _renderSmaa(camera: ICamera, toDrawingBuffer: boolean, props: PostprocessingProps) { if (props.antialiasing.name !== 'smaa') return; - const input = (props.occlusion.name === 'on' || props.outline.name === 'on') - ? this.tmpTarget.texture : this.drawPass.colorTarget.texture; + const input = PostprocessingPass.isEnabled(props) + ? this.drawPass.postprocessing.target.texture + : this.drawPass.colorTarget.texture; this.smaa.update(input, props.antialiasing.params); this.smaa.render(camera.viewport, toDrawingBuffer ? undefined : this.target); } - private _render(camera: ICamera, toDrawingBuffer: boolean, props: PostprocessingProps) { - if (props.occlusion.name === 'on' || props.outline.name === 'on' || props.antialiasing.name === 'off') { - this._renderPostprocessing(camera, toDrawingBuffer, props); - } + render(camera: ICamera, toDrawingBuffer: boolean, props: PostprocessingProps) { + if (props.antialiasing.name === 'off') return; if (props.antialiasing.name === 'fxaa') { this._renderFxaa(camera, toDrawingBuffer, props); @@ -250,14 +570,5 @@ export class PostprocessingPass { this._renderSmaa(camera, toDrawingBuffer, props); } } - - render(camera: Camera | StereoCamera, toDrawingBuffer: boolean, props: PostprocessingProps) { - if (StereoCamera.is(camera)) { - this._render(camera.left, toDrawingBuffer, props); - this._render(camera.right, toDrawingBuffer, props); - } else { - this._render(camera, toDrawingBuffer, props); - } - } } diff --git a/src/mol-gl/renderer.ts b/src/mol-gl/renderer.ts index d5fc2ef5297417682d8aee12d8a996d7c3669df8..88da0af16e68f83d3987883cb989b857c6631332 100644 --- a/src/mol-gl/renderer.ts +++ b/src/mol-gl/renderer.ts @@ -20,6 +20,7 @@ import { stringToWords } from '../mol-util/string'; import { degToRad } from '../mol-math/misc'; import { createNullTexture, Texture, Textures } from './webgl/texture'; import { arrayMapUpsert } from '../mol-util/array'; +import { clamp } from '../mol-math/interpolate'; export interface RendererStats { programCount: number @@ -50,7 +51,8 @@ interface Renderer { renderBlended: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void renderBlendedOpaque: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void renderBlendedTransparent: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void - renderBlendedVolume: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void + renderBlendedVolumeOpaque: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void + renderBlendedVolumeTransparent: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void renderWboitOpaque: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void renderWboitTransparent: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void @@ -451,7 +453,7 @@ namespace Renderer { } }; - const renderBlendedVolume = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => { + const renderBlendedVolumeOpaque = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => { state.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); state.enable(gl.BLEND); @@ -460,7 +462,32 @@ namespace Renderer { const { renderables } = group; for (let i = 0, il = renderables.length; i < il; ++i) { const r = renderables[i]; - renderObject(r, 'colorBlended'); + + // TODO: simplify, handle on renderable.state??? + // uAlpha is updated in "render" so we need to recompute it here + const alpha = clamp(r.values.alpha.ref.value * r.state.alphaFactor, 0, 1); + if (alpha === 1 && r.values.transparencyAverage.ref.value !== 1 && !r.values.dXrayShaded?.ref.value) { + renderObject(r, 'colorBlended'); + } + } + }; + + const renderBlendedVolumeTransparent = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => { + state.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); + state.enable(gl.BLEND); + + updateInternal(group, camera, depthTexture, false); + + const { renderables } = group; + for (let i = 0, il = renderables.length; i < il; ++i) { + const r = renderables[i]; + + // TODO: simplify, handle on renderable.state??? + // uAlpha is updated in "render" so we need to recompute it here + const alpha = clamp(r.values.alpha.ref.value * r.state.alphaFactor, 0, 1); + if (alpha < 1 || r.values.transparencyAverage.ref.value > 0 || r.values.dXrayShaded?.ref.value) { + renderObject(r, 'colorBlended'); + } } }; @@ -474,8 +501,11 @@ namespace Renderer { const { renderables } = group; for (let i = 0, il = renderables.length; i < il; ++i) { const r = renderables[i]; + // TODO: simplify, handle on renderable.state??? - if (r.values.uAlpha.ref.value === 1 && r.values.transparencyAverage.ref.value !== 1 && r.values.dRenderMode?.ref.value !== 'volume' && !r.values.dPointFilledCircle?.ref.value && !r.values.dXrayShaded?.ref.value) { + // uAlpha is updated in "render" so we need to recompute it here + const alpha = clamp(r.values.alpha.ref.value * r.state.alphaFactor, 0, 1); + if (alpha === 1 && r.values.transparencyAverage.ref.value !== 1 && r.values.dRenderMode?.ref.value !== 'volume' && !r.values.dPointFilledCircle?.ref.value && !r.values.dXrayShaded?.ref.value) { renderObject(r, 'colorWboit'); } } @@ -487,8 +517,11 @@ namespace Renderer { const { renderables } = group; for (let i = 0, il = renderables.length; i < il; ++i) { const r = renderables[i]; + // TODO: simplify, handle on renderable.state??? - if (r.values.uAlpha.ref.value < 1 || r.values.transparencyAverage.ref.value > 0 || r.values.dRenderMode?.ref.value === 'volume' || r.values.dPointFilledCircle?.ref.value || !!r.values.uBackgroundColor || r.values.dXrayShaded?.ref.value) { + // uAlpha is updated in "render" so we need to recompute it here + const alpha = clamp(r.values.alpha.ref.value * r.state.alphaFactor, 0, 1); + if (alpha < 1 || r.values.transparencyAverage.ref.value > 0 || r.values.dRenderMode?.ref.value === 'volume' || r.values.dPointFilledCircle?.ref.value || !!r.values.uBackgroundColor || r.values.dXrayShaded?.ref.value) { renderObject(r, 'colorWboit'); } } @@ -523,7 +556,8 @@ namespace Renderer { renderBlended, renderBlendedOpaque, renderBlendedTransparent, - renderBlendedVolume, + renderBlendedVolumeOpaque, + renderBlendedVolumeTransparent, renderWboitOpaque, renderWboitTransparent, diff --git a/src/mol-gl/shader/chunks/common.glsl.ts b/src/mol-gl/shader/chunks/common.glsl.ts index 710e8779fb0938206849f7be11191c8e067b31e6..077baa036bd8bf81b3b565c9a187a5b4f09e7580 100644 --- a/src/mol-gl/shader/chunks/common.glsl.ts +++ b/src/mol-gl/shader/chunks/common.glsl.ts @@ -49,6 +49,25 @@ float decodeFloatRGB(const in vec3 rgb) { return (rgb.r * 256.0 * 256.0 * 255.0 + rgb.g * 256.0 * 255.0 + rgb.b * 255.0) - 1.0; } +vec2 packUnitIntervalToRG(const in float v) { + vec2 enc; + enc.xy = vec2(fract(v * 256.0), v); + enc.y -= enc.x * (1.0 / 256.0); + enc.xy *= 256.0 / 255.0; + + return enc; +} + +float unpackRGToUnitInterval(const in vec2 enc) { + return dot(enc, vec2(255.0 / (256.0 * 256.0), 255.0 / 256.0)); +} + +vec3 screenSpaceToViewSpace(const in vec3 ssPos, const in mat4 invProjection) { + vec4 p = vec4(ssPos * 2.0 - 1.0, 1.0); + p = invProjection * p; + return p.xyz / p.w; +} + const float PackUpscale = 256.0 / 255.0; // fraction -> 0..1 (including 1) const float UnpackDownscale = 255.0 / 256.0; // 0..1 -> fraction (excluding 1) const vec3 PackFactors = vec3(256.0 * 256.0 * 256.0, 256.0 * 256.0, 256.0); diff --git a/src/mol-gl/shader/chunks/light-frag-params.glsl.ts b/src/mol-gl/shader/chunks/light-frag-params.glsl.ts index 7c0a731c744a75d766e7d717b1533b19febeb3fb..7f6f25493f26153be4515feba58d6cc22efab6b3 100644 --- a/src/mol-gl/shader/chunks/light-frag-params.glsl.ts +++ b/src/mol-gl/shader/chunks/light-frag-params.glsl.ts @@ -15,97 +15,97 @@ uniform float uMetalness; uniform float uRoughness; struct PhysicalMaterial { - vec3 diffuseColor; - float specularRoughness; - vec3 specularColor; + vec3 diffuseColor; + float specularRoughness; + vec3 specularColor; }; struct IncidentLight { - vec3 color; - vec3 direction; + vec3 color; + vec3 direction; }; struct ReflectedLight { - vec3 directDiffuse; - vec3 directSpecular; - vec3 indirectDiffuse; + vec3 directDiffuse; + vec3 directSpecular; + vec3 indirectDiffuse; }; struct GeometricContext { - vec3 position; - vec3 normal; - vec3 viewDir; + 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; + // 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); + 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); + 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; + 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); + 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; + 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)); + float dotNL = saturate(dot(geometry.normal, directLight.direction)); vec3 irradiance = dotNL * directLight.color; - irradiance *= PI; // punctual light + irradiance *= PI; // punctual light - reflectedLight.directSpecular += irradiance * BRDF_Specular_GGX(directLight, geometry, material.specularColor, material.specularRoughness); - reflectedLight.directDiffuse += irradiance * BRDF_Diffuse_Lambert(material.diffuseColor); + 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); + reflectedLight.indirectDiffuse += irradiance * BRDF_Diffuse_Lambert(material.diffuseColor); } `; \ No newline at end of file diff --git a/src/mol-gl/shader/chunks/wboit-write.glsl.ts b/src/mol-gl/shader/chunks/wboit-write.glsl.ts index 644fd8665ab3514f539a6e323781d3176491d220..92c5d4ddffe13fccfebf58ff8e9d3cd1091c6090 100644 --- a/src/mol-gl/shader/chunks/wboit-write.glsl.ts +++ b/src/mol-gl/shader/chunks/wboit-write.glsl.ts @@ -1,3 +1,10 @@ +/** + * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + * @author Áron Samuel Kovács <aron.kovacs@mail.muni.cz> + */ + export default ` #if defined(dRenderVariant_colorWboit) if (!uRenderWboit) { diff --git a/src/mol-gl/shader/copy-fbo.frag.ts b/src/mol-gl/shader/copy-fbo.frag.ts new file mode 100644 index 0000000000000000000000000000000000000000..e0200bb182e8e17771baee888c83f5a644bc65ee --- /dev/null +++ b/src/mol-gl/shader/copy-fbo.frag.ts @@ -0,0 +1,20 @@ +export default ` +precision highp float; +precision highp sampler2D; + +uniform sampler2D tColor; +uniform sampler2D tDepth; +uniform vec2 uTexSize; + +#include common + +float getDepth(const in vec2 coords) { + return unpackRGBAToDepth(texture2D(tDepth, coords)); +} + +void main() { + vec2 coords = gl_FragCoord.xy / uTexSize; + gl_FragColor = texture2D(tColor, coords); + gl_FragDepthEXT = getDepth(coords); +} +`; \ No newline at end of file diff --git a/src/mol-gl/shader/outlines.frag.ts b/src/mol-gl/shader/outlines.frag.ts new file mode 100644 index 0000000000000000000000000000000000000000..19f91fc381dedd8c9da325303f81f43b1bc2d048 --- /dev/null +++ b/src/mol-gl/shader/outlines.frag.ts @@ -0,0 +1,73 @@ +/** + * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Áron Samuel Kovács <aron.kovacs@mail.muni.cz> + */ + +export default ` +precision highp float; +precision highp int; +precision highp sampler2D; + +uniform sampler2D tDepth; +uniform vec2 uTexSize; + +uniform float uNear; +uniform float uFar; + +uniform float uMaxPossibleViewZDiff; + +#include common + +float perspectiveDepthToViewZ(const in float invClipZ, const in float near, const in float far) { + return (near * far) / ((far - near) * invClipZ - far); +} + +float orthographicDepthToViewZ(const in float linearClipZ, const in float near, const in float far) { + return linearClipZ * (near - far) - near; +} + +float getViewZ(const in float depth) { + #if dOrthographic == 1 + return orthographicDepthToViewZ(depth, uNear, uFar); + #else + return perspectiveDepthToViewZ(depth, uNear, uFar); + #endif +} + +float getDepth(const in vec2 coords) { + return unpackRGBAToDepth(texture2D(tDepth, coords)); +} + +bool isBackground(const in float depth) { + return depth >= 0.99; +} + +void main(void) { + float backgroundViewZ = uFar + 3.0 * uMaxPossibleViewZDiff; + + vec2 coords = gl_FragCoord.xy / uTexSize; + vec2 invTexSize = 1.0 / uTexSize; + + float selfDepth = getDepth(coords); + float selfViewZ = isBackground(selfDepth) ? backgroundViewZ : getViewZ(getDepth(coords)); + + float outline = 1.0; + float bestDepth = 1.0; + + for (int y = -1; y <= 1; y++) { + for (int x = -1; x <= 1; x++) { + vec2 sampleCoords = coords + vec2(float(x), float(y)) * invTexSize; + float sampleDepth = getDepth(sampleCoords); + float sampleViewZ = isBackground(sampleDepth) ? backgroundViewZ : getViewZ(sampleDepth); + + if (abs(selfViewZ - sampleViewZ) > uMaxPossibleViewZDiff && selfDepth > sampleDepth && sampleDepth <= bestDepth) { + outline = 0.0; + bestDepth = sampleDepth; + } + } + } + + gl_FragColor = vec4(outline, packUnitIntervalToRG(bestDepth), 0.0); +} +`; \ No newline at end of file diff --git a/src/mol-gl/shader/postprocessing.frag.ts b/src/mol-gl/shader/postprocessing.frag.ts index 04d2864cfa23fb87280c970c2ca764a13db04064..ecc9e5153c782e992ac7b5f3406ec09f69b7db50 100644 --- a/src/mol-gl/shader/postprocessing.frag.ts +++ b/src/mol-gl/shader/postprocessing.frag.ts @@ -1,12 +1,22 @@ +/** + * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + * @author Áron Samuel Kovács <aron.kovacs@mail.muni.cz> + */ + export default ` precision highp float; precision highp int; precision highp sampler2D; +uniform sampler2D tSsaoDepth; uniform sampler2D tColor; -uniform sampler2D tPackedDepth; +uniform sampler2D tDepth; +uniform sampler2D tOutlines; uniform vec2 uTexSize; +uniform mat4 uInvProjection; uniform float uNear; uniform float uFar; uniform float uFogNear; @@ -19,21 +29,12 @@ uniform float uOcclusionRadius; uniform float uOutlineScale; uniform float uOutlineThreshold; -const float noiseAmount = 0.0002; +uniform float uMaxPossibleViewZDiff; + const vec4 occlusionColor = vec4(0.0, 0.0, 0.0, 1.0); #include common -float noise(const in vec2 coords) { - float a = 12.9898; - float b = 78.233; - float c = 43758.5453; - float dt = dot(coords, vec2(a,b)); - float sn = mod(dt, 3.14159); - - return fract(sin(sn) * c); -} - float perspectiveDepthToViewZ(const in float invClipZ, const in float near, const in float far) { return (near * far) / ((far - near) * invClipZ - far); } @@ -51,68 +52,80 @@ float getViewZ(const in float depth) { } float getDepth(const in vec2 coords) { - return unpackRGBAToDepth(texture2D(tPackedDepth, coords)); + return unpackRGBAToDepth(texture2D(tDepth, coords)); } -float calcSSAO(const in vec2 coords, const in float depth) { - float occlusionFactor = 0.0; +bool isBackground(const in float depth) { + return depth >= 0.99; +} + +float getOutline(const in vec2 coords, out float closestTexel) { + float backgroundViewZ = uFar + 3.0 * uMaxPossibleViewZDiff; + vec2 invTexSize = 1.0 / uTexSize; + + float selfDepth = getDepth(coords); + float selfViewZ = isBackground(selfDepth) ? backgroundViewZ : getViewZ(getDepth(coords)); - for (int i = -dOcclusionKernelSize; i <= dOcclusionKernelSize; i++) { - for (int j = -dOcclusionKernelSize; j <= dOcclusionKernelSize; j++) { - vec2 coordsDelta = coords + uOcclusionRadius / float(dOcclusionKernelSize) * vec2(float(i) / uTexSize.x, float(j) / uTexSize.y); - coordsDelta += noiseAmount * (noise(coordsDelta) - 0.5) / uTexSize; - coordsDelta = clamp(coordsDelta, 0.5 / uTexSize, 1.0 - 1.0 / uTexSize); - if (getDepth(coordsDelta) < depth) occlusionFactor += 1.0; + float outline = 1.0; + closestTexel = backgroundViewZ; + for (float y = -uOutlineScale; y <= uOutlineScale; y++) { + for (float x = -uOutlineScale; x <= uOutlineScale; x++) { + if (x * x + y * y > uOutlineScale * uOutlineScale) { + continue; + } + + vec2 sampleCoords = coords + vec2(x, y) * invTexSize; + + vec4 sampleOutlineCombined = texture2D(tOutlines, sampleCoords); + float sampleOutline = sampleOutlineCombined.r; + float sampleOutlineDepth = unpackRGToUnitInterval(sampleOutlineCombined.gb); + + float sampleOutlineViewDirLength = length(screenSpaceToViewSpace(vec3(sampleCoords, sampleOutlineDepth), uInvProjection)); + + if (sampleOutline == 0.0 && sampleOutlineViewDirLength < closestTexel && abs(selfViewZ - sampleOutlineDepth) > uMaxPossibleViewZDiff) { + outline = 0.0; + closestTexel = sampleOutlineDepth; + } } } - - return occlusionFactor / float((2 * dOcclusionKernelSize + 1) * (2 * dOcclusionKernelSize + 1)); + return outline; } -vec2 calcEdgeDepth(const in vec2 coords) { - vec2 invTexSize = 1.0 / uTexSize; - float halfScaleFloor = floor(uOutlineScale * 0.5); - float halfScaleCeil = ceil(uOutlineScale * 0.5); - - vec2 bottomLeftUV = coords - invTexSize * halfScaleFloor; - vec2 topRightUV = coords + invTexSize * halfScaleCeil; - vec2 bottomRightUV = coords + vec2(invTexSize.x * halfScaleCeil, -invTexSize.y * halfScaleFloor); - vec2 topLeftUV = coords + vec2(-invTexSize.x * halfScaleFloor, invTexSize.y * halfScaleCeil); - - float depth0 = getDepth(bottomLeftUV); - float depth1 = getDepth(topRightUV); - float depth2 = getDepth(bottomRightUV); - float depth3 = getDepth(topLeftUV); - - float depthFiniteDifference0 = depth1 - depth0; - float depthFiniteDifference1 = depth3 - depth2; - - return vec2( - sqrt(pow(depthFiniteDifference0, 2.0) + pow(depthFiniteDifference1, 2.0)) * 100.0, - min(depth0, min(depth1, min(depth2, depth3))) - ); +float getSsao(vec2 coords) { + float rawSsao = unpackRGToUnitInterval(texture(tSsaoDepth, coords).xy); + if (rawSsao > 0.999) { + return 1.0; + } else if (rawSsao > 0.001) { + return rawSsao; + } + return 0.0; } void main(void) { vec2 coords = gl_FragCoord.xy / uTexSize; - vec4 color = texture2D(tColor, coords); + vec4 color = texture(tColor, coords); #ifdef dOutlineEnable - vec2 edgeDepth = calcEdgeDepth(coords); - float edgeFlag = step(edgeDepth.x, uOutlineThreshold); - color.rgb *= edgeFlag; - - float viewDist = abs(getViewZ(edgeDepth.y)); - float fogFactor = smoothstep(uFogNear, uFogFar, viewDist) * (1.0 - edgeFlag); - color.rgb = mix(color.rgb, uFogColor, fogFactor); + float closestTexel; + float outline = getOutline(coords, closestTexel); + + if (outline == 0.0) { + color.rgb *= outline; + float viewDist = abs(getViewZ(closestTexel)); + float fogFactor = smoothstep(uFogNear, uFogFar, viewDist); + if (color.a != 1.0) { + color.a = 1.0 - fogFactor; + } + color.rgb = mix(color.rgb, uFogColor, fogFactor); + } #endif // occlusion needs to be handled after outline to darken them properly #ifdef dOcclusionEnable float depth = getDepth(coords); - if (depth <= 0.99) { - float occlusionFactor = calcSSAO(coords, depth); - color = mix(color, occlusionColor, uOcclusionBias * occlusionFactor); + if (!isBackground(depth)) { + float occlusionFactor = getSsao(coords); + color = mix(occlusionColor, color, occlusionFactor); } #endif diff --git a/src/mol-gl/shader/ssao-blur.frag.ts b/src/mol-gl/shader/ssao-blur.frag.ts new file mode 100644 index 0000000000000000000000000000000000000000..ace3593d5f54e0036c3b8735ed6fa63edbae18b7 --- /dev/null +++ b/src/mol-gl/shader/ssao-blur.frag.ts @@ -0,0 +1,92 @@ +/** + * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Áron Samuel Kovács <aron.kovacs@mail.muni.cz> + */ + +export default ` +precision highp float; +precision highp int; +precision highp sampler2D; + +uniform sampler2D tSsaoDepth; +uniform vec2 uTexSize; + +uniform float uKernel[dOcclusionKernelSize]; + +uniform float uBlurDirectionX; +uniform float uBlurDirectionY; + +uniform float uMaxPossibleViewZDiff; + +uniform float uNear; +uniform float uFar; + +#include common + +float perspectiveDepthToViewZ(const in float invClipZ, const in float near, const in float far) { + return (near * far) / ((far - near) * invClipZ - far); +} + +float orthographicDepthToViewZ(const in float linearClipZ, const in float near, const in float far) { + return linearClipZ * (near - far) - near; +} + +float getViewZ(const in float depth) { + #if dOrthographic == 1 + return orthographicDepthToViewZ(depth, uNear, uFar); + #else + return perspectiveDepthToViewZ(depth, uNear, uFar); + #endif +} + +bool isBackground(const in float depth) { + return depth >= 0.99; +} + +void main(void) { + vec2 coords = gl_FragCoord.xy / uTexSize; + + vec2 packedDepth = texture(tSsaoDepth, coords).zw; + + float selfDepth = unpackRGToUnitInterval(packedDepth); + // if background and if second pass + if (isBackground(selfDepth) && uBlurDirectionY != 0.0) { + gl_FragColor = vec4(packUnitIntervalToRG(1.0), packedDepth); + return; + } + + float selfViewZ = getViewZ(selfDepth); + + vec2 offset = vec2(uBlurDirectionX, uBlurDirectionY) / uTexSize; + + float sum = 0.0; + float kernelSum = 0.0; + // only if kernelSize is odd + for (int i = -dOcclusionKernelSize / 2; i <= dOcclusionKernelSize / 2; i++) { + vec2 sampleCoords = coords + float(i) * offset; + + vec4 sampleSsaoDepth = texture(tSsaoDepth, sampleCoords); + + float sampleDepth = unpackRGToUnitInterval(sampleSsaoDepth.zw); + if (isBackground(sampleDepth)) { + continue; + } + + if (abs(i) > 1) { + float sampleViewZ = getViewZ(sampleDepth); + if (abs(selfViewZ - sampleViewZ) > uMaxPossibleViewZDiff) { + continue; + } + } + + float kernel = uKernel[abs(i)]; + float sampleValue = unpackRGToUnitInterval(sampleSsaoDepth.xy); + + sum += kernel * sampleValue; + kernelSum += kernel; + } + + gl_FragColor = vec4(packUnitIntervalToRG(sum / kernelSum), packedDepth); +} +`; \ No newline at end of file diff --git a/src/mol-gl/shader/ssao.frag.ts b/src/mol-gl/shader/ssao.frag.ts new file mode 100644 index 0000000000000000000000000000000000000000..ef74f9d5d7c8b87fbc10a739a9eaaed488de4ac5 --- /dev/null +++ b/src/mol-gl/shader/ssao.frag.ts @@ -0,0 +1,110 @@ +/** + * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + * @author Áron Samuel Kovács <aron.kovacs@mail.muni.cz> + */ + +export default ` +precision highp float; +precision highp int; +precision highp sampler2D; + +#include common + +uniform sampler2D tDepth; + +uniform vec3 uSamples[dNSamples]; + +uniform mat4 uProjection; +uniform mat4 uInvProjection; + +uniform vec2 uTexSize; + +uniform float uRadius; +uniform float uBias; + +float smootherstep(float edge0, float edge1, float x) { + x = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0); + return x * x * x * (x * (x * 6.0 - 15.0) + 10.0); +} + +float noise(const in vec2 coords) { + float a = 12.9898; + float b = 78.233; + float c = 43758.5453; + float dt = dot(coords, vec2(a,b)); + float sn = mod(dt, PI); + return abs(fract(sin(sn) * c)); // is abs necessary? +} + +vec2 getNoiseVec2(const in vec2 coords) { + return vec2(noise(coords), noise(coords + vec2(PI, 2.71828))); +} + +bool isBackground(const in float depth) { + return depth >= 0.99; +} + +float getDepth(const in vec2 coords) { + return unpackRGBAToDepth(texture2D(tDepth, coords)); +} + +vec3 normalFromDepth(const in float depth, const in float depth1, const in float depth2, vec2 offset1, vec2 offset2) { + vec3 p1 = vec3(offset1, depth1 - depth); + vec3 p2 = vec3(offset2, depth2 - depth); + + vec3 normal = cross(p1, p2); + normal.z = -normal.z; + + return normalize(normal); +} + +// StarCraft II Ambient Occlusion by [Filion and McNaughton 2008] +void main(void) { + vec2 invTexSize = 1.0 / uTexSize; + vec2 selfCoords = gl_FragCoord.xy * invTexSize; + + float selfDepth = getDepth(selfCoords); + vec2 selfPackedDepth = packUnitIntervalToRG(selfDepth); + + if (isBackground(selfDepth)) { + gl_FragColor = vec4(packUnitIntervalToRG(0.0), selfPackedDepth); + return; + } + + vec2 offset1 = vec2(0.0, invTexSize.y); + vec2 offset2 = vec2(invTexSize.x, 0.0); + + float selfDepth1 = getDepth(selfCoords + offset1); + float selfDepth2 = getDepth(selfCoords + offset2); + + vec3 selfViewNormal = normalFromDepth(selfDepth, selfDepth1, selfDepth2, offset1, offset2); + vec3 selfViewPos = screenSpaceToViewSpace(vec3(selfCoords, selfDepth), uInvProjection); + + vec3 randomVec = normalize(vec3(getNoiseVec2(selfCoords) * 2.0 - 1.0, 0.0)); + + vec3 tangent = normalize(randomVec - selfViewNormal * dot(randomVec, selfViewNormal)); + vec3 bitangent = cross(selfViewNormal, tangent); + mat3 TBN = mat3(tangent, bitangent, selfViewNormal); + + float occlusion = 0.0; + for(int i = 0; i < dNSamples; i++){ + vec3 sampleViewPos = TBN * uSamples[i]; + sampleViewPos = selfViewPos + sampleViewPos * uRadius; + + vec4 offset = vec4(sampleViewPos, 1.0); + offset = uProjection * offset; + offset.xyz = (offset.xyz / offset.w) * 0.5 + 0.5; + + float sampleViewZ = screenSpaceToViewSpace(vec3(offset.xy, getDepth(offset.xy)), uInvProjection).z; + + occlusion += step(sampleViewPos.z + 0.025, sampleViewZ) * smootherstep(0.0, 1.0, uRadius / abs(selfViewPos.z - sampleViewZ)); + } + occlusion = 1.0 - (uBias * occlusion / float(dNSamples)); + + vec2 packedOcclusion = packUnitIntervalToRG(occlusion); + + gl_FragColor = vec4(packedOcclusion, selfPackedDepth); +} +`; \ No newline at end of file diff --git a/src/mol-io/reader/3dg/parser.ts b/src/mol-io/reader/3dg/parser.ts deleted file mode 100644 index 1fc7b6a36b2aa1eb428a38015fcab3f48ad723ce..0000000000000000000000000000000000000000 --- a/src/mol-io/reader/3dg/parser.ts +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author Alexander Rose <alexander.rose@weirdbyte.de> - */ - -import { ReaderResult as Result } from '../result'; -import { Task } from '../../../mol-task'; -import { parseCsv } from '../csv/parser'; -import { Column, Table } from '../../../mol-data/db'; -import { toTable } from '../cif/schema'; - -import Schema = Column.Schema -import { CsvTable } from '../csv/data-model'; - - -export const Schema3DG = { - /** Chromosome name */ - chromosome: Schema.str, - /** Base position */ - position: Schema.int, - /** X coordinate */ - x: Schema.float, - /** Y coordinate */ - y: Schema.float, - /** Z coordinate */ - z: Schema.float, -}; -export type Schema3DG = typeof Schema3DG - -export interface File3DG { - table: Table<Schema3DG> -} - -const FieldNames = [ 'chromosome', 'position', 'x', 'y', 'z' ]; - -function categoryFromTable(name: string, table: CsvTable) { - return { - name, - rowCount: table.rowCount, - fieldNames: FieldNames, - getField: (name: string) => { - return table.getColumn(FieldNames.indexOf(name).toString()); - } - }; -} - -export function parse3DG(data: string) { - return Task.create<Result<File3DG>>('Parse 3DG', async ctx => { - const opts = { quote: '', comment: '#', delimiter: '\t', noColumnNames: true }; - const csvFile = await parseCsv(data, opts).runInContext(ctx); - if (csvFile.isError) return Result.error(csvFile.message, csvFile.line); - const category = categoryFromTable('3dg', csvFile.result.table); - const table = toTable(Schema3DG, category); - return Result.success({ table }); - }); -} \ No newline at end of file diff --git a/src/mol-io/reader/_spec/3dg.spec.ts b/src/mol-io/reader/_spec/3dg.spec.ts deleted file mode 100644 index 25f4dd4d6bd72261d34f018bdf53c8de8ff9773a..0000000000000000000000000000000000000000 --- a/src/mol-io/reader/_spec/3dg.spec.ts +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author Alexander Rose <alexander.rose@weirdbyte.de> - */ - -import { parse3DG } from '../3dg/parser'; - -const basic3dgString = `1(mat) 1420000 0.791377837067 10.9947291355 -13.1882897693 -1(mat) 1440000 -0.268241283699 10.5200875887 -13.0896257278 -1(mat) 1460000 -1.3853075236 10.5513787498 -13.1440142173 -1(mat) 1480000 -1.55984101733 11.4340829129 -13.6026301209 -1(mat) 1500000 -0.770991778399 11.4758488546 -14.5881137222 -1(mat) 1520000 -0.0848245107875 12.2624690808 -14.354289628 -1(mat) 1540000 -0.458643807046 12.5985791771 -13.4701149287 -1(mat) 1560000 -0.810322906201 12.2461643989 -12.3172933413 -1(mat) 1580000 -2.08211172035 12.8886838656 -12.8742007778 -1(mat) 1600000 -3.52093948201 13.1850935438 -12.4118684428`; - -describe('3dg reader', () => { - it('basic', async () => { - const parsed = await parse3DG(basic3dgString).run(); - expect(parsed.isError).toBe(false); - - if (parsed.isError) return; - const { chromosome, position, x, y, z } = parsed.result.table; - expect(chromosome.value(0)).toBe('1(mat)'); - expect(position.value(1)).toBe(1440000); - expect(x.value(5)).toBe(-0.0848245107875); - expect(y.value(5)).toBe(12.2624690808); - expect(z.value(5)).toBe(-14.354289628); - }); -}); \ No newline at end of file diff --git a/src/mol-model-formats/structure/3dg.ts b/src/mol-model-formats/structure/3dg.ts deleted file mode 100644 index 18aeab4730f8c4897a90049795fc5136a81784f7..0000000000000000000000000000000000000000 --- a/src/mol-model-formats/structure/3dg.ts +++ /dev/null @@ -1,83 +0,0 @@ -/** - * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author Alexander Rose <alexander.rose@weirdbyte.de> - */ - -import { Task } from '../../mol-task'; -import { ModelFormat } from '../format'; -import { Column, Table } from '../../mol-data/db'; -import { EntityBuilder } from './common/entity'; -import { File3DG } from '../../mol-io/reader/3dg/parser'; -import { fillSerial } from '../../mol-util/array'; -import { MoleculeType } from '../../mol-model/structure/model/types'; -import { BasicSchema, createBasic } from './basic/schema'; -import { createModels } from './basic/parser'; -import { Trajectory } from '../../mol-model/structure'; - -function getBasic(table: File3DG['table']) { - const entityIds = new Array<string>(table._rowCount); - const entityBuilder = new EntityBuilder(); - - const seqIdStarts = table.position.toArray({ array: Uint32Array }); - const seqIdEnds = new Uint32Array(table._rowCount); - const stride = seqIdStarts[1] - seqIdStarts[0]; - - const objectRadius = stride / 3500; - - for (let i = 0, il = table._rowCount; i < il; ++i) { - const chr = table.chromosome.value(i); - const entityId = entityBuilder.getEntityId(chr, MoleculeType.DNA, chr); - entityIds[i] = entityId; - seqIdEnds[i] = seqIdStarts[i] + stride - 1; - } - - const ihm_sphere_obj_site = Table.ofPartialColumns(BasicSchema.ihm_sphere_obj_site, { - id: Column.ofIntArray(fillSerial(new Uint32Array(table._rowCount))), - entity_id: Column.ofStringArray(entityIds), - seq_id_begin: Column.ofIntArray(seqIdStarts), - seq_id_end: Column.ofIntArray(seqIdEnds), - asym_id: table.chromosome, - - Cartn_x: Column.ofFloatArray(Column.mapToArray(table.x, x => x * 10, Float32Array)), - Cartn_y: Column.ofFloatArray(Column.mapToArray(table.y, y => y * 10, Float32Array)), - Cartn_z: Column.ofFloatArray(Column.mapToArray(table.z, z => z * 10, Float32Array)), - - object_radius: Column.ofConst(objectRadius, table._rowCount, Column.Schema.float), - rmsf: Column.ofConst(0, table._rowCount, Column.Schema.float), - model_id: Column.ofConst(1, table._rowCount, Column.Schema.int), - }, table._rowCount); - - return createBasic({ - entity: entityBuilder.getEntityTable(), - ihm_model_list: Table.ofPartialColumns(BasicSchema.ihm_model_list, { - model_id: Column.ofIntArray([1]), - model_name: Column.ofStringArray(['3DG Model']), - }, 1), - ihm_sphere_obj_site - }); -} - -// - -export { Format3dg }; - -type Format3dg = ModelFormat<File3DG> - -namespace Format3dg { - export function is(x: ModelFormat): x is Format3dg { - return x.kind === '3dg'; - } - - export function from3dg(file3dg: File3DG): Format3dg { - return { kind: '3dg', name: '3DG', data: file3dg }; - } -} - -export function trajectoryFrom3DG(file3dg: File3DG): Task<Trajectory> { - return Task.create('Parse 3DG', async ctx => { - const format = Format3dg.from3dg(file3dg); - const basic = getBasic(file3dg.table); - return createModels(basic, format, ctx); - }); -} diff --git a/src/mol-model/structure/export/categories/atom_site.ts b/src/mol-model/structure/export/categories/atom_site.ts index 3f8354fb354388e6922eafa9cb432b2b93384708..98ab02958a7658f08c13d58f823cb96cbef72911 100644 --- a/src/mol-model/structure/export/categories/atom_site.ts +++ b/src/mol-model/structure/export/categories/atom_site.ts @@ -83,13 +83,16 @@ export const _atom_site: CifCategory<CifExportContext> = { } }; -function prepostfixed(prefix: string | undefined, postfix: string | undefined, name: string) { - if (prefix && postfix) return `${prefix}_${name}_${postfix}`; +function prepostfixed(prefix: string | undefined, name: string) { if (prefix) return `${prefix}_${name}`; - if (postfix) return `${name}_${postfix}`; return name; } +function prefixedInsCode(prefix: string | undefined) { + if (!prefix) return 'pdbx_PDB_ins_code'; + return `pdbx_${prefix}_PDB_ins_code`; +} + function mappedProp<K, D>(loc: (key: K, data: D) => StructureElement.Location, prop: (e: StructureElement.Location) => any) { return (k: K, d: D) => prop(loc(k, d)); } @@ -102,15 +105,14 @@ function addModelNum<K, D>(fields: CifWriter.Field.Builder<K, D>, getLocation: ( export interface IdFieldsOptions { prefix?: string, - postfix?: string, includeModelNum?: boolean } export function residueIdFields<K, D>(getLocation: (key: K, data: D) => StructureElement.Location, options?: IdFieldsOptions): CifField<K, D>[] { - const prefix = options && options.prefix, postfix = options && options.postfix; + const prefix = options && options.prefix; const ret = CifWriter.fields<K, D>() - .str(prepostfixed(prefix, postfix, `label_comp_id`), mappedProp(getLocation, P.atom.label_comp_id)) - .int(prepostfixed(prefix, postfix, `label_seq_id`), mappedProp(getLocation, P.residue.label_seq_id), { + .str(prepostfixed(prefix, `label_comp_id`), mappedProp(getLocation, P.atom.label_comp_id)) + .int(prepostfixed(prefix, `label_seq_id`), mappedProp(getLocation, P.residue.label_seq_id), { encoder: E.deltaRLE, valueKind: (k, d) => { const e = getLocation(k, d); @@ -118,45 +120,45 @@ export function residueIdFields<K, D>(getLocation: (key: K, data: D) => Structur return m.atomicHierarchy.residues.label_seq_id.valueKind(m.atomicHierarchy.residueAtomSegments.index[e.element]); } }) - .str(prepostfixed(prefix, postfix, `pdbx_PDB_ins_code`), mappedProp(getLocation, P.residue.pdbx_PDB_ins_code)) + .str(prefixedInsCode(prefix), mappedProp(getLocation, P.residue.pdbx_PDB_ins_code)) - .str(prepostfixed(prefix, postfix, `label_asym_id`), mappedProp(getLocation, P.chain.label_asym_id)) - .str(prepostfixed(prefix, postfix, `label_entity_id`), mappedProp(getLocation, P.chain.label_entity_id)) + .str(prepostfixed(prefix, `label_asym_id`), mappedProp(getLocation, P.chain.label_asym_id)) + .str(prepostfixed(prefix, `label_entity_id`), mappedProp(getLocation, P.chain.label_entity_id)) - .str(prepostfixed(prefix, postfix, `auth_comp_id`), mappedProp(getLocation, P.atom.auth_comp_id)) - .int(prepostfixed(prefix, postfix, `auth_seq_id`), mappedProp(getLocation, P.residue.auth_seq_id), { encoder: E.deltaRLE }) - .str(prepostfixed(prefix, postfix, `auth_asym_id`), mappedProp(getLocation, P.chain.auth_asym_id)); + .str(prepostfixed(prefix, `auth_comp_id`), mappedProp(getLocation, P.atom.auth_comp_id)) + .int(prepostfixed(prefix, `auth_seq_id`), mappedProp(getLocation, P.residue.auth_seq_id), { encoder: E.deltaRLE }) + .str(prepostfixed(prefix, `auth_asym_id`), mappedProp(getLocation, P.chain.auth_asym_id)); addModelNum(ret, getLocation, options); return ret.getFields(); } export function chainIdFields<K, D>(getLocation: (key: K, data: D) => StructureElement.Location, options?: IdFieldsOptions): CifField<K, D>[] { - const prefix = options && options.prefix, postfix = options && options.postfix; + const prefix = options && options.prefix; const ret = CifField.build<K, D>() - .str(prepostfixed(prefix, postfix, `label_asym_id`), mappedProp(getLocation, P.chain.label_asym_id)) - .str(prepostfixed(prefix, postfix, `label_entity_id`), mappedProp(getLocation, P.chain.label_entity_id)) - .str(prepostfixed(prefix, postfix, `auth_asym_id`), mappedProp(getLocation, P.chain.auth_asym_id)); + .str(prepostfixed(prefix, `label_asym_id`), mappedProp(getLocation, P.chain.label_asym_id)) + .str(prepostfixed(prefix, `label_entity_id`), mappedProp(getLocation, P.chain.label_entity_id)) + .str(prepostfixed(prefix, `auth_asym_id`), mappedProp(getLocation, P.chain.auth_asym_id)); addModelNum(ret, getLocation, options); return ret.getFields(); } export function entityIdFields<K, D>(getLocation: (key: K, data: D) => StructureElement.Location, options?: IdFieldsOptions): CifField<K, D>[] { - const prefix = options && options.prefix, postfix = options && options.postfix; + const prefix = options && options.prefix; const ret = CifField.build<K, D>() - .str(prepostfixed(prefix, postfix, `label_entity_id`), mappedProp(getLocation, P.chain.label_entity_id)); + .str(prepostfixed(prefix, `label_entity_id`), mappedProp(getLocation, P.chain.label_entity_id)); addModelNum(ret, getLocation, options); return ret.getFields(); } export function atomIdFields<K, D>(getLocation: (key: K, data: D) => StructureElement.Location, options?: IdFieldsOptions): CifField<K, D>[] { - const prefix = options && options.prefix, postfix = options && options.postfix; + const prefix = options && options.prefix; const ret = CifWriter.fields<K, D>() - .str(prepostfixed(prefix, postfix, `label_atom_id`), mappedProp(getLocation, P.atom.label_atom_id)) - .str(prepostfixed(prefix, postfix, `label_comp_id`), mappedProp(getLocation, P.atom.label_comp_id)) - .int(prepostfixed(prefix, postfix, `label_seq_id`), mappedProp(getLocation, P.residue.label_seq_id), { + .str(prepostfixed(prefix, `label_atom_id`), mappedProp(getLocation, P.atom.label_atom_id)) + .str(prepostfixed(prefix, `label_comp_id`), mappedProp(getLocation, P.atom.label_comp_id)) + .int(prepostfixed(prefix, `label_seq_id`), mappedProp(getLocation, P.residue.label_seq_id), { encoder: E.deltaRLE, valueKind: (k, d) => { const e = getLocation(k, d); @@ -164,16 +166,16 @@ export function atomIdFields<K, D>(getLocation: (key: K, data: D) => StructureEl return m.atomicHierarchy.residues.label_seq_id.valueKind(m.atomicHierarchy.residueAtomSegments.index[e.element]); } }) - .str(prepostfixed(prefix, postfix, `label_alt_id`), mappedProp(getLocation, P.atom.label_alt_id)) - .str(prepostfixed(prefix, postfix, `pdbx_PDB_ins_code`), mappedProp(getLocation, P.residue.pdbx_PDB_ins_code)) + .str(prepostfixed(prefix, `label_alt_id`), mappedProp(getLocation, P.atom.label_alt_id)) + .str(prefixedInsCode(prefix), mappedProp(getLocation, P.residue.pdbx_PDB_ins_code)) - .str(prepostfixed(prefix, postfix, `label_asym_id`), mappedProp(getLocation, P.chain.label_asym_id)) - .str(prepostfixed(prefix, postfix, `label_entity_id`), mappedProp(getLocation, P.chain.label_entity_id)) + .str(prepostfixed(prefix, `label_asym_id`), mappedProp(getLocation, P.chain.label_asym_id)) + .str(prepostfixed(prefix, `label_entity_id`), mappedProp(getLocation, P.chain.label_entity_id)) - .str(prepostfixed(prefix, postfix, `auth_atom_id`), mappedProp(getLocation, P.atom.auth_atom_id)) - .str(prepostfixed(prefix, postfix, `auth_comp_id`), mappedProp(getLocation, P.atom.auth_comp_id)) - .int(prepostfixed(prefix, postfix, `auth_seq_id`), mappedProp(getLocation, P.residue.auth_seq_id), { encoder: E.deltaRLE }) - .str(prepostfixed(prefix, postfix, `auth_asym_id`), mappedProp(getLocation, P.chain.auth_asym_id)); + .str(prepostfixed(prefix, `auth_atom_id`), mappedProp(getLocation, P.atom.auth_atom_id)) + .str(prepostfixed(prefix, `auth_comp_id`), mappedProp(getLocation, P.atom.auth_comp_id)) + .int(prepostfixed(prefix, `auth_seq_id`), mappedProp(getLocation, P.residue.auth_seq_id), { encoder: E.deltaRLE }) + .str(prepostfixed(prefix, `auth_asym_id`), mappedProp(getLocation, P.chain.auth_asym_id)); addModelNum(ret, getLocation, options); return ret.getFields(); diff --git a/src/mol-plugin-state/formats/trajectory.ts b/src/mol-plugin-state/formats/trajectory.ts index bfeddf182f650e060944be6eefb68a6c3e066716..b0494f01b5c7eeafd0c9c7620c4c58bbad4b332a 100644 --- a/src/mol-plugin-state/formats/trajectory.ts +++ b/src/mol-plugin-state/formats/trajectory.ts @@ -113,15 +113,6 @@ export const GroProvider: TrajectoryFormatProvider = { visuals: defaultVisuals }; -export const Provider3dg: TrajectoryFormatProvider = { - label: '3DG', - description: '3DG', - category: TrajectoryFormatCategory, - stringExtensions: ['3dg'], - parse: directTrajectory(StateTransforms.Model.TrajectoryFrom3DG), - visuals: defaultVisuals -}; - export const MolProvider: TrajectoryFormatProvider = { label: 'MOL/SDF', description: 'MOL/SDF', @@ -146,7 +137,6 @@ export const BuiltInTrajectoryFormats = [ ['pdb', PdbProvider] as const, ['pdbqt', PdbqtProvider] as const, ['gro', GroProvider] as const, - ['3dg', Provider3dg] as const, ['mol', MolProvider] as const, ['mol2', Mol2Provider] as const, ] as const; diff --git a/src/mol-plugin-state/objects.ts b/src/mol-plugin-state/objects.ts index 9c3fade63ea32e43f608e5554635389ba94f0f99..403fa9669755c33dd187aa44dc32de8f8f64e82d 100644 --- a/src/mol-plugin-state/objects.ts +++ b/src/mol-plugin-state/objects.ts @@ -5,7 +5,6 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { File3DG } from '../mol-io/reader/3dg/parser'; import { Ccp4File } from '../mol-io/reader/ccp4/schema'; import { CifFile } from '../mol-io/reader/cif'; import { DcdFile } from '../mol-io/reader/dcd/parser'; @@ -83,7 +82,6 @@ export namespace PluginStateObject { { kind: 'cif', data: CifFile } | { kind: 'pdb', data: CifFile } | { kind: 'gro', data: CifFile } | - { kind: '3dg', data: File3DG } | { kind: 'dcd', data: DcdFile } | { kind: 'ccp4', data: Ccp4File } | { kind: 'dsn6', data: Dsn6File } | diff --git a/src/mol-plugin-state/transforms/model.ts b/src/mol-plugin-state/transforms/model.ts index 31bcef917f3a7fa3d432a131323e0411669d4085..a40cd78e5f676953cf9169d2cf2abecb213d153b 100644 --- a/src/mol-plugin-state/transforms/model.ts +++ b/src/mol-plugin-state/transforms/model.ts @@ -5,13 +5,11 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { parse3DG } from '../../mol-io/reader/3dg/parser'; import { parseDcd } from '../../mol-io/reader/dcd/parser'; import { parseGRO } from '../../mol-io/reader/gro/parser'; import { parsePDB } from '../../mol-io/reader/pdb/parser'; import { Mat4, Vec3 } from '../../mol-math/linear-algebra'; import { shapeFromPly } from '../../mol-model-formats/shape/ply'; -import { trajectoryFrom3DG } from '../../mol-model-formats/structure/3dg'; import { coordinatesFromDcd } from '../../mol-model-formats/structure/dcd'; import { trajectoryFromGRO } from '../../mol-model-formats/structure/gro'; import { trajectoryFromMmCIF } from '../../mol-model-formats/structure/mmcif'; @@ -52,7 +50,6 @@ export { TrajectoryFromMOL }; export { TrajectoryFromMOL2 }; export { TrajectoryFromCube }; export { TrajectoryFromCifCore }; -export { TrajectoryFrom3DG }; export { ModelFromTrajectory }; export { StructureFromTrajectory }; export { StructureFromModel }; @@ -339,24 +336,6 @@ const TrajectoryFromCifCore = PluginStateTransform.BuiltIn({ } }); -type TrajectoryFrom3DG = typeof TrajectoryFrom3DG -const TrajectoryFrom3DG = PluginStateTransform.BuiltIn({ - name: 'trajectory-from-3dg', - display: { name: 'Parse 3DG', description: 'Parse 3DG string and create trajectory.' }, - from: [SO.Data.String], - to: SO.Molecule.Trajectory -})({ - apply({ a }) { - return Task.create('Parse 3DG', async ctx => { - const parsed = await parse3DG(a.data).runInContext(ctx); - if (parsed.isError) throw new Error(parsed.message); - const models = await trajectoryFrom3DG(parsed.result).runInContext(ctx); - const props = trajectoryProps(models); - return new SO.Molecule.Trajectory(models, props); - }); - } -}); - const plus1 = (v: number) => v + 1, minus1 = (v: number) => v - 1; type ModelFromTrajectory = typeof ModelFromTrajectory const ModelFromTrajectory = PluginStateTransform.BuiltIn({ diff --git a/src/mol-repr/shape/representation.ts b/src/mol-repr/shape/representation.ts index 3ee72b35acb74a0a582d94046d495a46d916e904..50b9b53f249680d1d3b96ff03272a5de4519e401 100644 --- a/src/mol-repr/shape/representation.ts +++ b/src/mol-repr/shape/representation.ts @@ -23,6 +23,7 @@ import { PickingId } from '../../mol-geo/geometry/picking'; import { Visual } from '../visual'; import { RuntimeContext, Task } from '../../mol-task'; import { ParamDefinition as PD } from '../../mol-util/param-definition'; +import { isDebugMode } from '../../mol-util/debug'; export interface ShapeRepresentation<D, G extends Geometry, P extends Geometry.Params<G>> extends Representation<D, P> { } @@ -216,7 +217,9 @@ export function ShapeRepresentation<D, G extends Geometry, P extends Geometry.Pa Representation.updateState(_state, state); }, setTheme(theme: Theme) { - console.warn('The `ShapeRepresentation` theme is fixed to `ShapeGroupColorTheme` and `ShapeGroupSizeTheme`. Colors are taken from `Shape.getColor` and sizes from `Shape.getSize`'); + if(isDebugMode) { + console.warn('The `ShapeRepresentation` theme is fixed to `ShapeGroupColorTheme` and `ShapeGroupSizeTheme`. Colors are taken from `Shape.getColor` and sizes from `Shape.getSize`'); + } }, destroy() { // TODO