Browse Source

moved ssao/outline to postprocessing

Alexander Rose 6 years ago
parent
commit
39dbfbb6a6

+ 44 - 31
src/mol-canvas3d/canvas3d.ts

@@ -31,7 +31,7 @@ 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';
+import { getPostprocessingRenderable, PostprocessingParams } from './helper/postprocessing';
 
 export const Canvas3DParams = {
     // TODO: FPS cap?
@@ -41,7 +41,7 @@ export const Canvas3DParams = {
     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),
+    postprocessing: PD.Group(PostprocessingParams),
     renderer: PD.Group(RendererParams),
     trackball: PD.Group(TrackballControlsParams),
     debug: PD.Group(DebugHelperParams)
@@ -126,7 +126,7 @@ namespace Canvas3D {
         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)
+        const postprocessing = getPostprocessingRenderable(webgl, drawTarget.texture, depthTexture, p.postprocessing)
 
         let pickScale = 0.25 / webgl.pixelRatio
         let pickWidth = Math.round(canvas.width * pickScale)
@@ -213,6 +213,7 @@ namespace Canvas3D {
             // TODO: is this a good fix? Also, setClipping does not work if the user has manually set a clipping plane.
             if (!camera.transition.inTransition) setClipping();
             const cameraChanged = camera.updateMatrices();
+            const postprocessingEnabled = p.postprocessing.occlusionEnable || p.postprocessing.outlineEnable
 
             if (force || cameraChanged) {
                 switch (variant) {
@@ -227,17 +228,23 @@ namespace Canvas3D {
                         break;
                     case 'draw':
                         renderer.setViewport(0, 0, canvas.width, canvas.height);
-                        drawTarget.bind()
+                        if (postprocessingEnabled) {
+                            drawTarget.bind()
+                        } else {
+                            webgl.unbindFramebuffer();
+                        }
                         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()
+                        if (postprocessingEnabled) {
+                            webgl.unbindFramebuffer();
+                            webgl.state.disable(webgl.gl.SCISSOR_TEST)
+                            webgl.state.disable(webgl.gl.BLEND)
+                            webgl.state.disable(webgl.gl.DEPTH_TEST)
+                            postprocessing.render()
+                        }
                         pickDirty = true
                         break;
                 }
@@ -411,34 +418,40 @@ 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.postprocessing) {
+                    if (props.postprocessing.occlusionEnable !== undefined) {
+                        p.postprocessing.occlusionEnable = props.postprocessing.occlusionEnable
+                        ValueCell.update(postprocessing.values.dOcclusionEnable, props.postprocessing.occlusionEnable)
                     }
-                    if (props.ambientOcclusion.kernelSize !== undefined) {
-                        p.ambientOcclusion.kernelSize = props.ambientOcclusion.kernelSize
-                        ValueCell.update(ssaoPass.values.uKernelSize, props.ambientOcclusion.kernelSize)
+                    if (props.postprocessing.occlusionKernelSize !== undefined) {
+                        p.postprocessing.occlusionKernelSize = props.postprocessing.occlusionKernelSize
+                        ValueCell.update(postprocessing.values.uOcclusionKernelSize, props.postprocessing.occlusionKernelSize)
                     }
-                    if (props.ambientOcclusion.bias !== undefined) {
-                        p.ambientOcclusion.bias = props.ambientOcclusion.bias
-                        ValueCell.update(ssaoPass.values.uBias, props.ambientOcclusion.bias)
+                    if (props.postprocessing.occlusionBias !== undefined) {
+                        p.postprocessing.occlusionBias = props.postprocessing.occlusionBias
+                        ValueCell.update(postprocessing.values.uOcclusionBias, props.postprocessing.occlusionBias)
                     }
-                    if (props.ambientOcclusion.radius !== undefined) {
-                        p.ambientOcclusion.radius = props.ambientOcclusion.radius
-                        ValueCell.update(ssaoPass.values.uRadius, props.ambientOcclusion.radius)
+                    if (props.postprocessing.occlusionRadius !== undefined) {
+                        p.postprocessing.occlusionRadius = props.postprocessing.occlusionRadius
+                        ValueCell.update(postprocessing.values.uOcclusionRadius, props.postprocessing.occlusionRadius)
                     }
 
-                    if (props.ambientOcclusion.edgeScale !== undefined) {
-                        p.ambientOcclusion.edgeScale = props.ambientOcclusion.edgeScale
-                        ValueCell.update(ssaoPass.values.uEdgeScale, props.ambientOcclusion.edgeScale * webgl.pixelRatio)
+                    if (props.postprocessing.outlineEnable !== undefined) {
+                        p.postprocessing.outlineEnable = props.postprocessing.outlineEnable
+                        ValueCell.update(postprocessing.values.dOutlineEnable, props.postprocessing.outlineEnable)
+                    }
+                    if (props.postprocessing.outlineScale !== undefined) {
+                        p.postprocessing.outlineScale = props.postprocessing.outlineScale
+                        ValueCell.update(postprocessing.values.uOutlineScale, props.postprocessing.outlineScale * webgl.pixelRatio)
                     }
-                    if (props.ambientOcclusion.edgeThreshold !== undefined) {
-                        p.ambientOcclusion.edgeThreshold = props.ambientOcclusion.edgeThreshold
-                        ValueCell.update(ssaoPass.values.uEdgeThreshold, props.ambientOcclusion.edgeThreshold)
+                    if (props.postprocessing.outlineThreshold !== undefined) {
+                        p.postprocessing.outlineThreshold = props.postprocessing.outlineThreshold
+                        ValueCell.update(postprocessing.values.uOutlineThreshold, props.postprocessing.outlineThreshold)
                     }
+
+                    postprocessing.update()
                 }
-                
+
                 if (props.renderer) renderer.setProps(props.renderer)
                 if (props.trackball) controls.setProps(props.trackball)
                 if (props.debug) debugHelper.setProps(props.debug)
@@ -452,7 +465,7 @@ namespace Canvas3D {
                     clip: p.clip,
                     fog: p.fog,
 
-                    ambientOcclusion: { ...p.ambientOcclusion },
+                    postprocessing: { ...p.postprocessing },
                     renderer: { ...renderer.props },
                     trackball: { ...controls.props },
                     debug: { ...debugHelper.props }
@@ -486,7 +499,7 @@ namespace Canvas3D {
 
             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))
+            ValueCell.update(postprocessing.values.uTexSize, Vec2.set(postprocessing.values.uTexSize.ref.value, canvas.width, canvas.height))
 
             pickScale = 0.25 / webgl.pixelRatio
             pickWidth = Math.round(canvas.width * pickScale)

+ 72 - 0
src/mol-canvas3d/helper/postprocessing.ts

@@ -0,0 +1,72 @@
+/**
+ * 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, DefineSpec } 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 PostprocessingSchema = {
+    ...QuadSchema,
+    tColor: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
+    tDepth: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
+    uTexSize: UniformSpec('v2'),
+
+    dOcclusionEnable: DefineSpec('boolean'),
+    uOcclusionKernelSize: UniformSpec('i'),
+    uOcclusionBias: UniformSpec('f'),
+    uOcclusionRadius: UniformSpec('f'),
+
+    dOutlineEnable: DefineSpec('boolean'),
+    uOutlineScale: UniformSpec('f'),
+    uOutlineThreshold: UniformSpec('f'),
+}
+
+export const PostprocessingParams = {
+    occlusionEnable: PD.Boolean(false),
+    occlusionKernelSize: PD.Numeric(4, { min: 1, max: 100, step: 1 }),
+    occlusionBias: PD.Numeric(0.5, { min: 0, max: 1, step: 0.01 }),
+    occlusionRadius: PD.Numeric(64, { min: 0, max: 256, step: 1 }),
+
+    outlineEnable: PD.Boolean(false),
+    outlineScale: PD.Numeric(1, { min: 0, max: 10, step: 1 }),
+    outlineThreshold: PD.Numeric(0.8, { min: 0, max: 1, step: 0.01 }),
+}
+export type PostprocessingProps = PD.Values<typeof PostprocessingParams>
+
+export function getPostprocessingRenderable(ctx: WebGLContext, colorTexture: Texture, depthTexture: Texture, props: Partial<PostprocessingProps>) {
+    const p = { ...PD.getDefaultValues(PostprocessingParams), props }
+    const values: Values<typeof PostprocessingSchema> = {
+        ...QuadValues,
+        tColor: ValueCell.create(colorTexture),
+        tDepth: ValueCell.create(depthTexture),
+        uTexSize: ValueCell.create(Vec2.create(colorTexture.width, colorTexture.height)),
+
+        dOcclusionEnable: ValueCell.create(p.occlusionEnable),
+        uOcclusionKernelSize: ValueCell.create(p.occlusionKernelSize),
+        uOcclusionBias: ValueCell.create(p.occlusionBias),
+        uOcclusionRadius: ValueCell.create(p.occlusionRadius),
+
+        dOutlineEnable: ValueCell.create(p.outlineEnable),
+        uOutlineScale: ValueCell.create(p.outlineScale * ctx.pixelRatio),
+        uOutlineThreshold: ValueCell.create(p.outlineThreshold),
+    }
+
+    const schema = { ...PostprocessingSchema }
+    const shaderCode = ShaderCode(
+        require('mol-gl/shader/quad.vert').default,
+        require('mol-gl/shader/postprocessing.frag').default
+    )
+    const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values)
+
+    return createComputeRenderable(renderItem, values)
+}

+ 0 - 69
src/mol-canvas3d/passes/ssao-pass.ts

@@ -1,69 +0,0 @@
-/**
- * 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'),
-
-    uEdgeScale: UniformSpec('f'),
-    uEdgeThreshold: 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 }),
-
-    edgeScale: PD.Numeric(1, { min: 0, max: 10, step: 1 }),
-    edgeThreshold: PD.Numeric(0.8, { min: 0, max: 1, step: 0.01 }),
-}
-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),
-
-        uEdgeScale: ValueCell.create(p.edgeScale * ctx.pixelRatio),
-        uEdgeThreshold: ValueCell.create(p.edgeThreshold),
-    }
-
-    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)
-}

+ 26 - 24
src/mol-gl/shader/passes/ssao.frag → src/mol-gl/shader/postprocessing.frag

@@ -6,13 +6,12 @@ uniform sampler2D tColor;
 uniform sampler2D tDepth;
 uniform vec2 uTexSize;
 
-uniform int uEnable;
-uniform int uKernelSize;
-uniform float uBias;
-uniform float uRadius;
+uniform int uOcclusionKernelSize;
+uniform float uOcclusionBias;
+uniform float uOcclusionRadius;
 
-uniform float uEdgeScale;
-uniform float uEdgeThreshold;
+uniform float uOutlineScale;
+uniform float uOutlineThreshold;
 
 const float noiseAmount = 0.0002;
 
@@ -22,32 +21,32 @@ float noise(vec2 coords) {
 	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);
+
+	for (int i = -uOcclusionKernelSize; i <= uOcclusionKernelSize; i++) {
+		for (int j = -uOcclusionKernelSize; j <= uOcclusionKernelSize; j++) {
+			vec2 coordsDelta = coords + uOcclusionRadius / float(uOcclusionKernelSize) * 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 (texture2D(tDepth, coordsDelta).r < depth) occlusionFactor += 1.0;
 		}
 	}
 
-	return occlusionFactor / float((2 * uKernelSize + 1) * (2 * uKernelSize + 1));
+	return occlusionFactor / float((2 * uOcclusionKernelSize + 1) * (2 * uOcclusionKernelSize + 1));
 }
 
 float calcEdgeDepth(in vec2 coords) {
     vec2 invTexSize = 1.0 / uTexSize;
-    float halfScaleFloor = floor(uEdgeScale * 0.5);
-    float halfScaleCeil = ceil(uEdgeScale * 0.5);
+    float halfScaleFloor = floor(uOutlineScale * 0.5);
+    float halfScaleCeil = ceil(uOutlineScale * 0.5);
 
     vec2 bottomLeftUV = coords - invTexSize * halfScaleFloor;
-    vec2 topRightUV = coords + invTexSize * halfScaleCeil;  
+    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);
 
@@ -65,15 +64,18 @@ float calcEdgeDepth(in vec2 coords) {
 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);
-	}
 
-    color.rgb *= (step(calcEdgeDepth(coords), uEdgeThreshold));
-	
+	#ifdef dOcclusionEnable
+		float depth = texture(tDepth, coords).r;
+		if (depth != 1.0) {
+			float occlusionFactor = calcSSAO(coords, depth);
+			color = mix(color, vec4(0.0, 0.0, 0.0, 1.0), uOcclusionBias * occlusionFactor);
+		}
+	#endif
+
+	#ifdef dOutlineEnable
+    	color.rgb *= (step(calcEdgeDepth(coords), uOutlineThreshold));
+	#endif
+
 	gl_FragColor = color;
 }