diff --git a/src/mol-canvas3d/passes/fxaa.ts b/src/mol-canvas3d/passes/fxaa.ts new file mode 100644 index 0000000000000000000000000000000000000000..3f1d8c4ac28d355f96fb8e88c30d4a462d88ee5c --- /dev/null +++ b/src/mol-canvas3d/passes/fxaa.ts @@ -0,0 +1,106 @@ +/** + * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { QuadSchema, QuadValues } from '../../mol-gl/compute/util'; +import { ComputeRenderable, createComputeRenderable } from '../../mol-gl/renderable'; +import { TextureSpec, UniformSpec, DefineSpec, Values } from '../../mol-gl/renderable/schema'; +import { ShaderCode } from '../../mol-gl/shader-code'; +import { WebGLContext } from '../../mol-gl/webgl/context'; +import { createComputeRenderItem } from '../../mol-gl/webgl/render-item'; +import { Texture } from '../../mol-gl/webgl/texture'; +import { Vec2 } from '../../mol-math/linear-algebra'; +import { ValueCell } from '../../mol-util'; +import { ParamDefinition as PD } from '../../mol-util/param-definition'; +import quad_vert from '../../mol-gl/shader/quad.vert'; +import fxaa_frag from '../../mol-gl/shader/fxaa.frag'; + +export const FxaaParams = { + edgeThresholdMin: PD.Numeric(0.0312, { min: 0.0312, max: 0.0833, step: 0.0001 }, { description: 'Trims the algorithm from processing darks.' }), + edgeThresholdMax: PD.Numeric(0.063, { min: 0.063, max: 0.333, step: 0.001 }, { description: 'The minimum amount of local contrast required to apply algorithm.' }), + iterations: PD.Numeric(12, { min: 0, max: 16, step: 1 }, { description: 'Number of edge exploration steps.' }), + subpixelQuality: PD.Numeric(0.30, { min: 0.00, max: 1.00, step: 0.01 }, { description: 'Choose the amount of sub-pixel aliasing removal.' }), +}; +export type FxaaProps = PD.Values<typeof FxaaParams> + +export class FxaaPass { + private readonly renderable: FxaaRenderable + + constructor(webgl: WebGLContext, input: Texture) { + this.renderable = getFxaaRenderable(webgl, input); + } + + setSize(width: number, height: number) { + ValueCell.update(this.renderable.values.uTexSizeInv, Vec2.set(this.renderable.values.uTexSizeInv.ref.value, 1 / width, 1 / height)); + } + + update(input: Texture, props: FxaaProps) { + const { values } = this.renderable; + const { edgeThresholdMin, edgeThresholdMax, iterations, subpixelQuality } = props; + + let needsUpdate = false; + + if (values.tColor.ref.value !== input) { + ValueCell.update(this.renderable.values.tColor, input); + needsUpdate = true; + } + + if (values.dEdgeThresholdMin.ref.value !== edgeThresholdMin) needsUpdate = true; + ValueCell.updateIfChanged(values.dEdgeThresholdMin, edgeThresholdMin); + + if (values.dEdgeThresholdMax.ref.value !== edgeThresholdMax) needsUpdate = true; + ValueCell.updateIfChanged(values.dEdgeThresholdMax, edgeThresholdMax); + + if (values.dIterations.ref.value !== iterations) needsUpdate = true; + ValueCell.updateIfChanged(values.dIterations, iterations); + + if (values.dSubpixelQuality.ref.value !== subpixelQuality) needsUpdate = true; + ValueCell.updateIfChanged(values.dSubpixelQuality, subpixelQuality); + + if (needsUpdate) { + this.renderable.update(); + } + } + + render() { + this.renderable.render(); + } +} + +// + +const FxaaSchema = { + ...QuadSchema, + tColor: TextureSpec('texture', 'rgba', 'ubyte', 'linear'), + uTexSizeInv: UniformSpec('v2'), + + dEdgeThresholdMin: DefineSpec('number'), + dEdgeThresholdMax: DefineSpec('number'), + dIterations: DefineSpec('number'), + dSubpixelQuality: DefineSpec('number'), +}; +const FxaaShaderCode = ShaderCode('fxaa', quad_vert, fxaa_frag); +type FxaaRenderable = ComputeRenderable<Values<typeof FxaaSchema>> + +function getFxaaRenderable(ctx: WebGLContext, colorTexture: Texture): FxaaRenderable { + const width = colorTexture.getWidth(); + const height = colorTexture.getHeight(); + + const values: Values<typeof FxaaSchema> = { + ...QuadValues, + tColor: ValueCell.create(colorTexture), + uTexSizeInv: ValueCell.create(Vec2.create(1 / width, 1 / height)), + + dEdgeThresholdMin: ValueCell.create(0.0312), + dEdgeThresholdMax: ValueCell.create(0.125), + dIterations: ValueCell.create(12), + dSubpixelQuality: ValueCell.create(0.3), + }; + + const schema = { ...FxaaSchema }; + const renderItem = createComputeRenderItem(ctx, 'triangles', FxaaShaderCode, schema, values); + + return createComputeRenderable(renderItem, values); +} \ No newline at end of file diff --git a/src/mol-canvas3d/passes/postprocessing.ts b/src/mol-canvas3d/passes/postprocessing.ts index b95b573d05eb687f9889910f49b0cd84ca35d6bb..27e5e06f40e679fcb3b1ab03aa366fd1d8fb6de7 100644 --- a/src/mol-canvas3d/passes/postprocessing.ts +++ b/src/mol-canvas3d/passes/postprocessing.ts @@ -19,8 +19,8 @@ import { DrawPass } from './draw'; import { Camera, ICamera } from '../../mol-canvas3d/camera'; import quad_vert from '../../mol-gl/shader/quad.vert'; import postprocessing_frag from '../../mol-gl/shader/postprocessing.frag'; -import fxaa_frag from '../../mol-gl/shader/fxaa.frag'; import { StereoCamera } from '../camera/stereo'; +import { FxaaParams, FxaaPass } from './fxaa'; const PostprocessingSchema = { ...QuadSchema, @@ -97,27 +97,22 @@ export const PostprocessingParams = { off: PD.Group({}) }, { cycle: true, description: 'Draw outline around 3D objects' }), antialiasing: PD.MappedStatic('on', { - on: PD.Group({ - edgeThresholdMin:PD.Numeric(0.0312, { min: 0.0312, max: 0.0833, step: 0.0001 }, { description: 'Trims the algorithm from processing darks.' }), - edgeThresholdMax: PD.Numeric(0.063, { min: 0.063, max: 0.333, step: 0.001 }, { description: 'The minimum amount of local contrast required to apply algorithm.' }), - iterations: PD.Numeric(12, { min: 0, max: 32, step: 1 }, { description: 'Number of edge exploration steps.' }), - subpixelQuality: PD.Numeric(0.30, { min: 0.00, max: 1.00, step: 0.01 }, { description: 'Choose the amount of sub-pixel aliasing removal.' }), - }), + on: PD.Group(FxaaParams), off: PD.Group({}) - }, { cycle: true, description: 'Fast Approximate Anti-Aliasing (FXAA)' }), + }, { cycle: true, description: 'Smooth pixel edges' }), }; 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 === 'on'; + return props.occlusion.name === 'on' || props.outline.name === 'on' || props.antialiasing.name !== 'off'; } readonly target: RenderTarget private readonly tmpTarget: RenderTarget private readonly renderable: PostprocessingRenderable - private readonly fxaa: FxaaRenderable + private readonly fxaa: FxaaPass constructor(private webgl: WebGLContext, private drawPass: DrawPass) { const { colorTarget, depthTexture } = drawPass; @@ -127,7 +122,7 @@ export class PostprocessingPass { 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 = getFxaaRenderable(webgl, this.tmpTarget.texture); + this.fxaa = new FxaaPass(webgl, this.tmpTarget.texture); } syncSize() { @@ -139,7 +134,7 @@ export class PostprocessingPass { 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)); - ValueCell.update(this.fxaa.values.uTexSizeInv, Vec2.set(this.fxaa.values.uTexSizeInv.ref.value, 1 / width, 1 / height)); + this.fxaa.setSize(width, height); } } @@ -196,7 +191,7 @@ export class PostprocessingPass { this.renderable.update(); } - if (props.antialiasing.name === 'on') { + if (props.antialiasing.name !== 'off') { this.tmpTarget.bind(); } else if (toDrawingBuffer) { this.webgl.unbindFramebuffer(); @@ -209,32 +204,11 @@ export class PostprocessingPass { } private _renderFxaa(camera: ICamera, toDrawingBuffer: boolean, props: PostprocessingProps) { - if (props.antialiasing.name === 'off') return; - - const { values } = this.fxaa; - - let needsUpdate = false; + if (props.antialiasing.name !== 'on') return; const input = (props.occlusion.name === 'on' || props.outline.name === 'on') ? this.tmpTarget.texture : this.drawPass.colorTarget.texture; - if (values.tColor.ref.value !== input) { - ValueCell.update(this.fxaa.values.tColor, input); - needsUpdate = true; - } - - const { edgeThresholdMin, edgeThresholdMax, iterations, subpixelQuality } = props.antialiasing.params; - if (values.dEdgeThresholdMin.ref.value !== edgeThresholdMin) needsUpdate = true; - ValueCell.updateIfChanged(values.dEdgeThresholdMin, edgeThresholdMin); - if (values.dEdgeThresholdMax.ref.value !== edgeThresholdMax) needsUpdate = true; - ValueCell.updateIfChanged(values.dEdgeThresholdMax, edgeThresholdMax); - if (values.dIterations.ref.value !== iterations) needsUpdate = true; - ValueCell.updateIfChanged(values.dIterations, iterations); - if (values.dSubpixelQuality.ref.value !== subpixelQuality) needsUpdate = true; - ValueCell.updateIfChanged(values.dSubpixelQuality, subpixelQuality); - - if (needsUpdate) { - this.fxaa.update(); - } + this.fxaa.update(input, props.antialiasing.params); if (toDrawingBuffer) { this.webgl.unbindFramebuffer(); @@ -266,38 +240,3 @@ export class PostprocessingPass { } } -// - -const FxaaSchema = { - ...QuadSchema, - tColor: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'), - uTexSizeInv: UniformSpec('v2'), - - dEdgeThresholdMin: DefineSpec('number'), - dEdgeThresholdMax: DefineSpec('number'), - dIterations: DefineSpec('number'), - dSubpixelQuality: DefineSpec('number'), -}; -const FxaaShaderCode = ShaderCode('fxaa', quad_vert, fxaa_frag); -type FxaaRenderable = ComputeRenderable<Values<typeof FxaaSchema>> - -function getFxaaRenderable(ctx: WebGLContext, colorTexture: Texture): FxaaRenderable { - const width = colorTexture.getWidth(); - const height = colorTexture.getHeight(); - - const values: Values<typeof FxaaSchema> = { - ...QuadValues, - tColor: ValueCell.create(colorTexture), - uTexSizeInv: ValueCell.create(Vec2.create(1 / width, 1 / height)), - - dEdgeThresholdMin: ValueCell.create(0.0312), - dEdgeThresholdMax: ValueCell.create(0.125), - dIterations: ValueCell.create(12), - dSubpixelQuality: ValueCell.create(0.3), - }; - - const schema = { ...FxaaSchema }; - const renderItem = createComputeRenderItem(ctx, 'triangles', FxaaShaderCode, schema, values); - - return createComputeRenderable(renderItem, values); -}