From 0b6dfa8a1f695b9a8a2cdfcfd448b4542289087a Mon Sep 17 00:00:00 2001 From: Alexander Rose <alexander.rose@weirdbyte.de> Date: Mon, 22 Apr 2019 12:01:53 -0700 Subject: [PATCH] wip, ssao --- src/mol-canvas3d/canvas3d.ts | 49 +++++++++++++++++++++-- src/mol-canvas3d/passes/ssao-pass.ts | 60 ++++++++++++++++++++++++++++ src/mol-gl/shader/passes/ssao.frag | 53 ++++++++++++++++++++++++ 3 files changed, 158 insertions(+), 4 deletions(-) create mode 100644 src/mol-canvas3d/passes/ssao-pass.ts create mode 100644 src/mol-gl/shader/passes/ssao.frag diff --git a/src/mol-canvas3d/canvas3d.ts b/src/mol-canvas3d/canvas3d.ts index 4b306554c..a846906bc 100644 --- a/src/mol-canvas3d/canvas3d.ts +++ b/src/mol-canvas3d/canvas3d.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -7,7 +7,7 @@ import { BehaviorSubject, Subscription } from 'rxjs'; import { now } from 'mol-util/now'; -import { Vec3 } from 'mol-math/linear-algebra' +import { Vec3, Vec2 } from 'mol-math/linear-algebra' import InputObserver, { ModifiersKeys, ButtonsType } from 'mol-util/input/input-observer' import Renderer, { RendererStats, RendererParams } from 'mol-gl/renderer' import { GraphicsRenderObject } from 'mol-gl/render-object' @@ -29,6 +29,9 @@ import { BoundingSphereHelper, DebugHelperParams } from './helper/bounding-spher import { decodeFloatRGB } from 'mol-util/float-packing'; import { SetUtils } from 'mol-util/set'; import { Canvas3dInteractionHelper } from './helper/interaction-events'; +import { createTexture } from 'mol-gl/webgl/texture'; +import { ValueCell } from 'mol-util'; +import { getSSAOPassRenderable, SSAOPassParams } from './passes/ssao-pass'; export const Canvas3DParams = { // TODO: FPS cap? @@ -37,6 +40,8 @@ export const Canvas3DParams = { cameraClipDistance: PD.Numeric(0, { min: 0.0, max: 50.0, step: 0.1 }, { description: 'The distance between camera and scene at which to clip regardless of near clipping plane.' }), clip: PD.Interval([1, 100], { min: 1, max: 100, step: 1 }), fog: PD.Interval([50, 100], { min: 1, max: 100, step: 1 }), + + ambientOcclusion: PD.Group(SSAOPassParams), renderer: PD.Group(RendererParams), trackball: PD.Group(TrackballControlsParams), debug: PD.Group(DebugHelperParams) @@ -117,6 +122,12 @@ namespace Canvas3D { const controls = TrackballControls.create(input, camera, p.trackball) const renderer = Renderer.create(webgl, camera, p.renderer) + const drawTarget = createRenderTarget(webgl, canvas.width, canvas.height) + const depthTexture = createTexture(webgl, 'image-depth', 'depth', 'ushort', 'nearest') + depthTexture.define(canvas.width, canvas.height) + depthTexture.attachFramebuffer(drawTarget.framebuffer, 'depth') + const ssaoPass = getSSAOPassRenderable(webgl, drawTarget.texture, depthTexture, p.ambientOcclusion) + let pickScale = 0.25 / webgl.pixelRatio let pickWidth = Math.round(canvas.width * pickScale) let pickHeight = Math.round(canvas.height * pickScale) @@ -215,13 +226,18 @@ namespace Canvas3D { renderer.render(scene, 'pickGroup'); break; case 'draw': - webgl.unbindFramebuffer(); renderer.setViewport(0, 0, canvas.width, canvas.height); - renderer.render(scene, variant); + drawTarget.bind() + renderer.render(scene, 'draw'); if (debugHelper.isEnabled) { debugHelper.syncVisibility() renderer.render(debugHelper.scene, 'draw') } + webgl.unbindFramebuffer(); + webgl.state.disable(webgl.gl.SCISSOR_TEST) + webgl.state.disable(webgl.gl.BLEND) + webgl.state.disable(webgl.gl.DEPTH_TEST) + ssaoPass.render() pickDirty = true break; } @@ -395,6 +411,25 @@ namespace Canvas3D { if (props.clip !== undefined) p.clip = [props.clip[0], props.clip[1]] if (props.fog !== undefined) p.fog = [props.fog[0], props.fog[1]] + if (props.ambientOcclusion) { + if (props.ambientOcclusion.enable !== undefined) { + p.ambientOcclusion.enable = props.ambientOcclusion.enable + ValueCell.update(ssaoPass.values.uEnable, props.ambientOcclusion.enable ? 1 : 0) + } + if (props.ambientOcclusion.kernelSize !== undefined) { + p.ambientOcclusion.kernelSize = props.ambientOcclusion.kernelSize + ValueCell.update(ssaoPass.values.uKernelSize, props.ambientOcclusion.kernelSize) + } + if (props.ambientOcclusion.bias !== undefined) { + p.ambientOcclusion.bias = props.ambientOcclusion.bias + ValueCell.update(ssaoPass.values.uBias, props.ambientOcclusion.bias) + } + if (props.ambientOcclusion.radius !== undefined) { + p.ambientOcclusion.radius = props.ambientOcclusion.radius + ValueCell.update(ssaoPass.values.uRadius, props.ambientOcclusion.radius) + } + } + if (props.renderer) renderer.setProps(props.renderer) if (props.trackball) controls.setProps(props.trackball) if (props.debug) debugHelper.setProps(props.debug) @@ -407,6 +442,8 @@ namespace Canvas3D { cameraClipDistance: p.cameraClipDistance, clip: p.clip, fog: p.fog, + + ambientOcclusion: { ...p.ambientOcclusion }, renderer: { ...renderer.props }, trackball: { ...controls.props }, debug: { ...debugHelper.props } @@ -438,6 +475,10 @@ namespace Canvas3D { Viewport.set(camera.viewport, 0, 0, canvas.width, canvas.height) Viewport.set(controls.viewport, 0, 0, canvas.width, canvas.height) + drawTarget.setSize(canvas.width, canvas.height) + depthTexture.define(canvas.width, canvas.height) + ValueCell.update(ssaoPass.values.uTexSize, Vec2.set(ssaoPass.values.uTexSize.ref.value, canvas.width, canvas.height)) + pickScale = 0.25 / webgl.pixelRatio pickWidth = Math.round(canvas.width * pickScale) pickHeight = Math.round(canvas.height * pickScale) diff --git a/src/mol-canvas3d/passes/ssao-pass.ts b/src/mol-canvas3d/passes/ssao-pass.ts new file mode 100644 index 000000000..91a6dd0fb --- /dev/null +++ b/src/mol-canvas3d/passes/ssao-pass.ts @@ -0,0 +1,60 @@ +/** + * Copyright (c) 2019 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 { TextureSpec, Values, UniformSpec } from 'mol-gl/renderable/schema'; +import { ShaderCode } from 'mol-gl/shader-code'; +import { WebGLContext } from 'mol-gl/webgl/context'; +import { Texture } from 'mol-gl/webgl/texture'; +import { ValueCell } from 'mol-util'; +import { createComputeRenderItem } from 'mol-gl/webgl/render-item'; +import { createComputeRenderable } from 'mol-gl/renderable'; +import { Vec2 } from 'mol-math/linear-algebra'; +import { ParamDefinition as PD } from 'mol-util/param-definition'; + +const SSAOPassSchema = { + ...QuadSchema, + tColor: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'), + tDepth: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'), + uTexSize: UniformSpec('v2'), + + uEnable: UniformSpec('i'), + uKernelSize: UniformSpec('i'), + uBias: UniformSpec('f'), + uRadius: UniformSpec('f'), +} + +export const SSAOPassParams = { + enable: PD.Boolean(true), + kernelSize: PD.Numeric(4, { min: 1, max: 100, step: 1 }), + bias: PD.Numeric(0.5, { min: 0, max: 1, step: 0.01 }), + radius: PD.Numeric(128, { min: 0, max: 256, step: 1 }), +} +export type SSAOPassProps = PD.Values<typeof SSAOPassParams> + +export function getSSAOPassRenderable(ctx: WebGLContext, colorTexture: Texture, depthTexture: Texture, props: Partial<SSAOPassProps>) { + const p = { ...PD.getDefaultValues(SSAOPassParams), props } + const values: Values<typeof SSAOPassSchema> = { + ...QuadValues, + tColor: ValueCell.create(colorTexture), + tDepth: ValueCell.create(depthTexture), + uTexSize: ValueCell.create(Vec2.create(colorTexture.width, colorTexture.height)), + + uEnable: ValueCell.create(p.enable ? 1 : 0), + uKernelSize: ValueCell.create(p.kernelSize), + uBias: ValueCell.create(p.bias), + uRadius: ValueCell.create(p.radius), + } + + const schema = { ...SSAOPassSchema } + const shaderCode = ShaderCode( + require('mol-gl/shader/quad.vert').default, + require('mol-gl/shader/passes/ssao.frag').default + ) + const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values) + + return createComputeRenderable(renderItem, values) +} \ No newline at end of file diff --git a/src/mol-gl/shader/passes/ssao.frag b/src/mol-gl/shader/passes/ssao.frag new file mode 100644 index 000000000..bbef5eba0 --- /dev/null +++ b/src/mol-gl/shader/passes/ssao.frag @@ -0,0 +1,53 @@ +precision highp float; +precision highp int; +precision highp sampler2D; + +uniform sampler2D tColor; +uniform sampler2D tDepth; +uniform vec2 uTexSize; + +uniform int uEnable; +uniform int uKernelSize; +uniform float uBias; +uniform float uRadius; + +const float noiseAmount = 0.0002; + +float noise(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 calcSSAO(in vec2 coords, in float depth) { + float occlusionFactor = 0.0; + + for (int i = -uKernelSize; i <= uKernelSize; i++) { + for (int j = -uKernelSize; j <= uKernelSize; j++) { + vec2 coordsDelta = coords + uRadius / float(uKernelSize) * 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 (texture(tDepth, coordsDelta).r < depth) occlusionFactor += 1.0; + } + } + + return occlusionFactor / float((2 * uKernelSize + 1) * (2 * uKernelSize + 1)); +} + +void main(void) { + vec2 coords = gl_FragCoord.xy / uTexSize; + vec4 color = texture(tColor, coords); + float depth = texture(tDepth, coords).r; + + // calculate screen-space ambient occlusion + if ((uEnable != 0) && (depth != 1.0)) { + float occlusionFactor = calcSSAO(coords, depth); + color = mix(color, vec4(0.0, 0.0, 0.0, 1.0), uBias * occlusionFactor); + } + + gl_FragColor = color; +} \ No newline at end of file -- GitLab