UNPKG

molstar

Version:

A comprehensive macromolecular library.

359 lines (358 loc) 18 kB
/** * Copyright (c) 2019-2025 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> * @author Ludovic Autin <ludovic.autin@gmail.com> * @author Gianluca Tomasello <giagitom@gmail.com> */ import { QuadSchema, QuadValues } from '../../mol-gl/compute/util'; import { TextureSpec, UniformSpec, DefineSpec } from '../../mol-gl/renderable/schema'; import { ShaderCode } from '../../mol-gl/shader-code'; import { ValueCell } from '../../mol-util'; import { createComputeRenderItem } from '../../mol-gl/webgl/render-item'; import { createComputeRenderable } from '../../mol-gl/renderable'; import { Vec2, Vec3 } from '../../mol-math/linear-algebra'; import { ParamDefinition as PD } from '../../mol-util/param-definition'; import { quad_vert } from '../../mol-gl/shader/quad.vert'; import { postprocessing_frag } from '../../mol-gl/shader/postprocessing.frag'; import { Color } from '../../mol-util/color'; import { FxaaParams, FxaaPass } from './fxaa'; import { SmaaParams, SmaaPass } from './smaa'; import { isTimingMode } from '../../mol-util/debug'; import { BackgroundParams, BackgroundPass } from './background'; import { CasParams, CasPass } from './cas'; import { DofPass, DofParams } from './dof'; import { BloomParams } from './bloom'; import { OutlinePass, OutlineParams } from './outline'; import { ShadowPass, ShadowParams } from './shadow'; import { SsaoPass, SsaoParams } from './ssao'; const PostprocessingSchema = { ...QuadSchema, tSsaoDepth: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'), tSsaoDepthTransparent: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'), tColor: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'), tTransparentColor: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'), dBlendTransparency: DefineSpec('boolean'), tDepthOpaque: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'), tDepthTransparent: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'), tShadows: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'), tOutlines: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'), uTexSize: UniformSpec('v2'), dOrthographic: DefineSpec('number'), uNear: UniformSpec('f'), uFar: UniformSpec('f'), uFogNear: UniformSpec('f'), uFogFar: UniformSpec('f'), uFogColor: UniformSpec('v3'), uOutlineColor: UniformSpec('v3'), uOcclusionColor: UniformSpec('v3'), uTransparentBackground: UniformSpec('b'), dOcclusionEnable: DefineSpec('boolean'), dOcclusionSingleDepth: DefineSpec('boolean'), dOcclusionIncludeOpacity: DefineSpec('boolean'), dOcclusionIncludeTransparency: DefineSpec('boolean'), uOcclusionOffset: UniformSpec('v2'), dShadowEnable: DefineSpec('boolean'), dOutlineEnable: DefineSpec('boolean'), dOutlineScale: DefineSpec('number'), dTransparentOutline: DefineSpec('boolean'), }; function getPostprocessingRenderable(ctx, colorTexture, transparentColorTexture, depthTextureOpaque, depthTextureTransparent, shadowsTexture, outlinesTexture, ssaoDepthTexture, ssaoDepthTransparentTexture, transparentOutline) { const values = { ...QuadValues, tSsaoDepth: ValueCell.create(ssaoDepthTexture), tSsaoDepthTransparent: ValueCell.create(ssaoDepthTransparentTexture), tColor: ValueCell.create(colorTexture), tTransparentColor: ValueCell.create(transparentColorTexture), dBlendTransparency: ValueCell.create(true), tDepthOpaque: ValueCell.create(depthTextureOpaque), tDepthTransparent: ValueCell.create(depthTextureTransparent), tShadows: ValueCell.create(shadowsTexture), tOutlines: ValueCell.create(outlinesTexture), uTexSize: ValueCell.create(Vec2.create(colorTexture.getWidth(), colorTexture.getHeight())), dOrthographic: ValueCell.create(0), uNear: ValueCell.create(1), uFar: ValueCell.create(10000), uFogNear: ValueCell.create(10000), uFogFar: ValueCell.create(10000), uFogColor: ValueCell.create(Vec3.create(1, 1, 1)), uOutlineColor: ValueCell.create(Vec3.create(0, 0, 0)), uOcclusionColor: ValueCell.create(Vec3.create(0, 0, 0)), uTransparentBackground: ValueCell.create(false), dOcclusionEnable: ValueCell.create(true), dOcclusionSingleDepth: ValueCell.create(false), dOcclusionIncludeOpacity: ValueCell.create(true), dOcclusionIncludeTransparency: ValueCell.create(false), uOcclusionOffset: ValueCell.create(Vec2.create(0, 0)), dShadowEnable: ValueCell.create(false), dOutlineEnable: ValueCell.create(false), dOutlineScale: ValueCell.create(1), dTransparentOutline: ValueCell.create(transparentOutline), }; const schema = { ...PostprocessingSchema }; const shaderCode = ShaderCode('postprocessing', quad_vert, postprocessing_frag); const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values); return createComputeRenderable(renderItem, values); } export const PostprocessingParams = { occlusion: PD.MappedStatic('on', { on: PD.Group(SsaoParams), off: PD.Group({}) }, { cycle: true, description: 'Darken occluded crevices with the ambient occlusion effect' }), shadow: PD.MappedStatic('off', { on: PD.Group(ShadowParams), off: PD.Group({}) }, { cycle: true, description: 'Simplistic shadows' }), outline: PD.MappedStatic('off', { on: PD.Group(OutlineParams), off: PD.Group({}) }, { cycle: true, description: 'Draw outline around 3D objects' }), dof: PD.MappedStatic('off', { on: PD.Group(DofParams), off: PD.Group({}) }, { cycle: true, description: 'DOF' }), antialiasing: PD.MappedStatic('smaa', { fxaa: PD.Group(FxaaParams), smaa: PD.Group(SmaaParams), off: PD.Group({}) }, { options: [['fxaa', 'FXAA'], ['smaa', 'SMAA'], ['off', 'Off']], description: 'Smooth pixel edges' }), sharpening: PD.MappedStatic('off', { on: PD.Group(CasParams), off: PD.Group({}) }, { cycle: true, description: 'Contrast Adaptive Sharpening' }), background: PD.Group(BackgroundParams, { isFlat: true }), bloom: PD.MappedStatic('on', { on: PD.Group(BloomParams), off: PD.Group({}) }, { cycle: true, description: 'Bloom' }), }; export class PostprocessingPass { static isEnabled(props) { return SsaoPass.isEnabled(props) || ShadowPass.isEnabled(props) || OutlinePass.isEnabled(props) || props.background.variant.name !== 'off'; } static isTransparentDepthRequired(scene, props) { return DofPass.isEnabled(props) || OutlinePass.isEnabled(props) && PostprocessingPass.isTransparentOutlineEnabled(props) || SsaoPass.isEnabled(props) && PostprocessingPass.isTransparentSsaoEnabled(scene, props); } static isTransparentOutlineEnabled(props) { var _a; return OutlinePass.isEnabled(props) && ((_a = props.outline.params.includeTransparent) !== null && _a !== void 0 ? _a : true); } static isTransparentSsaoEnabled(scene, props) { return SsaoPass.isEnabled(props) && SsaoPass.isTransparentEnabled(scene, props.occlusion.params); } static isSsaoEnabled(props) { return SsaoPass.isEnabled(props); } constructor(webgl, assetManager, drawPass) { this.webgl = webgl; this.drawPass = drawPass; this.occlusionOffset = [0, 0]; this.transparentBackground = false; const { colorTarget, transparentColorTarget, depthTextureOpaque, depthTextureTransparent, packedDepth } = drawPass; const width = colorTarget.getWidth(); const height = colorTarget.getHeight(); // needs to be linear for anti-aliasing pass this.target = webgl.createRenderTarget(width, height, false, 'uint8', 'linear'); this.ssao = new SsaoPass(webgl, width, height, packedDepth, depthTextureOpaque, depthTextureTransparent); this.shadow = new ShadowPass(webgl, width, height, depthTextureOpaque); this.outline = new OutlinePass(webgl, width, height, depthTextureTransparent, depthTextureOpaque); this.renderable = getPostprocessingRenderable(webgl, colorTarget.texture, transparentColorTarget.texture, depthTextureOpaque, depthTextureTransparent, this.shadow.target.texture, this.outline.target.texture, this.ssao.ssaoDepthTexture, this.ssao.ssaoDepthTransparentTexture, true); this.background = new BackgroundPass(webgl, assetManager, width, height); } setSize(width, height) { const [w, h] = this.renderable.values.uTexSize.ref.value; if (width !== w || height !== h) { this.target.setSize(width, height); ValueCell.update(this.renderable.values.uTexSize, Vec2.set(this.renderable.values.uTexSize.ref.value, width, height)); } this.ssao.setSize(width, height); this.shadow.setSize(width, height); this.outline.setSize(width, height); this.background.setSize(width, height); } reset() { this.ssao.reset(); } updateState(camera, scene, transparentBackground, backgroundColor, props, light, ambientColor) { let needsUpdateMain = false; const orthographic = camera.state.mode === 'orthographic' ? 1 : 0; const outlinesEnabled = OutlinePass.isEnabled(props); const shadowsEnabled = ShadowPass.isEnabled(props); const occlusionEnabled = SsaoPass.isEnabled(props); if (occlusionEnabled) { const params = props.occlusion.params; this.ssao.update(camera, scene, params); const includeTransparency = SsaoPass.isTransparentEnabled(scene, params); if (this.renderable.values.dOcclusionIncludeTransparency.ref.value !== includeTransparency) { needsUpdateMain = true; ValueCell.update(this.renderable.values.dOcclusionIncludeTransparency, includeTransparency); } ValueCell.update(this.renderable.values.uOcclusionColor, Color.toVec3Normalized(this.renderable.values.uOcclusionColor.ref.value, params.color)); } if (shadowsEnabled) { this.shadow.update(camera, light, ambientColor, props.shadow.params); } if (outlinesEnabled) { const outlineProps = props.outline.params; const { transparentOutline, outlineScale } = this.outline.update(camera, outlineProps, this.drawPass.depthTextureTransparent, this.drawPass.depthTextureOpaque); ValueCell.update(this.renderable.values.uOutlineColor, Color.toVec3Normalized(this.renderable.values.uOutlineColor.ref.value, outlineProps.color)); if (this.renderable.values.dOutlineScale.ref.value !== outlineScale) { needsUpdateMain = true; ValueCell.update(this.renderable.values.dOutlineScale, outlineScale); } if (this.renderable.values.dTransparentOutline.ref.value !== transparentOutline) { needsUpdateMain = true; ValueCell.update(this.renderable.values.dTransparentOutline, transparentOutline); } } 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); ValueCell.update(this.renderable.values.uFogColor, Color.toVec3Normalized(this.renderable.values.uFogColor.ref.value, backgroundColor)); ValueCell.updateIfChanged(this.renderable.values.uTransparentBackground, transparentBackground); if (this.renderable.values.dOrthographic.ref.value !== orthographic) { needsUpdateMain = true; ValueCell.update(this.renderable.values.dOrthographic, orthographic); } if (this.renderable.values.dOutlineEnable.ref.value !== outlinesEnabled) { needsUpdateMain = true; ValueCell.update(this.renderable.values.dOutlineEnable, outlinesEnabled); } if (this.renderable.values.dShadowEnable.ref.value !== shadowsEnabled) { needsUpdateMain = true; ValueCell.update(this.renderable.values.dShadowEnable, shadowsEnabled); } if (this.renderable.values.dOcclusionEnable.ref.value !== occlusionEnabled) { needsUpdateMain = true; ValueCell.update(this.renderable.values.dOcclusionEnable, occlusionEnabled); } const blendTransparency = scene.opacityAverage < 1; if (this.renderable.values.dBlendTransparency.ref.value !== blendTransparency) { needsUpdateMain = true; ValueCell.update(this.renderable.values.dBlendTransparency, blendTransparency); } if (needsUpdateMain) { this.renderable.update(); } const { gl, state } = this.webgl; state.enable(gl.SCISSOR_TEST); state.disable(gl.BLEND); state.disable(gl.DEPTH_TEST); state.depthMask(false); } setOcclusionOffset(x, y) { this.occlusionOffset[0] = x; this.occlusionOffset[1] = y; ValueCell.update(this.renderable.values.uOcclusionOffset, Vec2.set(this.renderable.values.uOcclusionOffset.ref.value, x, y)); } setTransparentBackground(value) { this.transparentBackground = value; } render(camera, scene, toDrawingBuffer, transparentBackground, backgroundColor, props, light, ambientColor) { if (isTimingMode) this.webgl.timer.mark('PostprocessingPass.render'); this.updateState(camera, scene, transparentBackground, backgroundColor, props, light, ambientColor); const { state } = this.webgl; const { x, y, width, height } = camera.viewport; // don't render occlusion if offset is given, // which will reuse the existing occlusion if (props.occlusion.name === 'on' && this.occlusionOffset[0] === 0 && this.occlusionOffset[1] === 0) { this.ssao.render(camera); } state.viewport(x, y, width, height); state.scissor(x, y, width, height); if (props.outline.name === 'on') { this.outline.render(); } if (props.shadow.name === 'on') { this.shadow.render(); } if (toDrawingBuffer) { this.webgl.unbindFramebuffer(); } else { this.target.bind(); } this.background.update(camera, props.background); this.background.clear(props.background, this.transparentBackground, backgroundColor); this.background.render(props.background); this.renderable.render(); if (isTimingMode) this.webgl.timer.markEnd('PostprocessingPass.render'); } } export class AntialiasingPass { static isEnabled(props) { return props.antialiasing.name !== 'off'; } constructor(webgl, width, height) { this.target = webgl.createRenderTarget(width, height, false); this.internalTarget = webgl.createRenderTarget(width, height, false); this.fxaa = new FxaaPass(webgl, this.target.texture); this.smaa = new SmaaPass(webgl, this.target.texture); this.cas = new CasPass(webgl, this.target.texture); } setSize(width, height) { const w = this.target.texture.getWidth(); const h = this.target.texture.getHeight(); if (width !== w || height !== h) { this.target.setSize(width, height); this.internalTarget.setSize(width, height); this.fxaa.setSize(width, height); if (this.smaa.supported) this.smaa.setSize(width, height); this.cas.setSize(width, height); } } _renderFxaa(camera, input, target, props) { if (props.antialiasing.name !== 'fxaa') return; this.fxaa.update(input, props.antialiasing.params); this.fxaa.render(camera.viewport, target); } _renderSmaa(camera, input, target, props) { if (props.antialiasing.name !== 'smaa') return; this.smaa.update(input, props.antialiasing.params); this.smaa.render(camera.viewport, target); } _renderAntialiasing(camera, input, target, props) { if (props.antialiasing.name === 'fxaa') { this._renderFxaa(camera, input, target, props); } else if (props.antialiasing.name === 'smaa') { this._renderSmaa(camera, input, target, props); } } _renderCas(camera, input, target, props) { if (props.sharpening.name !== 'on') return; if (props.antialiasing.name !== 'off') input = this.internalTarget.texture; this.cas.update(input, props.sharpening.params); this.cas.render(camera.viewport, target); } render(camera, input, toDrawingBuffer, props) { if (props.antialiasing.name === 'off' && props.sharpening.name === 'off') return; if (props.antialiasing.name === 'smaa' && !this.smaa.supported) { console.error('SMAA not supported, missing "HTMLImageElement"'); return; } const target = toDrawingBuffer === true ? undefined : toDrawingBuffer === false ? this.target : toDrawingBuffer; if (props.sharpening.name === 'off') { this._renderAntialiasing(camera, input, target, props); } else if (props.antialiasing.name === 'off') { this._renderCas(camera, input, target, props); } else { this._renderAntialiasing(camera, input, this.internalTarget, props); this._renderCas(camera, input, target, props); } } }