Browse Source

temporal msaa

Alexander Rose 5 years ago
parent
commit
162e63fd68
2 changed files with 134 additions and 7 deletions
  1. 133 6
      src/mol-canvas3d/canvas3d.ts
  2. 1 1
      src/mol-canvas3d/helper/multi-sample.ts

+ 133 - 6
src/mol-canvas3d/canvas3d.ts

@@ -42,7 +42,8 @@ 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 }),
 
-    sampleLevel: PD.Numeric(0, { min: 0, max: 5, step: 1 }),
+    multiSample: PD.Select('off', [['off', 'Off'], ['on', 'On'], ['temporal', 'Temporal']]),
+    sampleLevel: PD.Numeric(2, { min: 0, max: 5, step: 1 }),
 
     postprocessing: PD.Group(PostprocessingParams),
     renderer: PD.Group(RendererParams),
@@ -135,6 +136,7 @@ namespace Canvas3D {
         const postprocessing = getPostprocessingRenderable(webgl, drawTarget.texture, depthTexture, p.postprocessing)
 
         const composeTarget = createRenderTarget(webgl, canvas.width, canvas.height)
+        const holdTarget = createRenderTarget(webgl, canvas.width, canvas.height)
         const compose = getComposeRenderable(webgl, drawTarget.texture)
 
         const pickBaseScale = 0.25
@@ -150,6 +152,9 @@ namespace Canvas3D {
         let isUpdating = false
         let drawPending = false
 
+        let multiSampleIndex = -1
+        let multiSample = false
+
         const debugHelper = new BoundingSphereHelper(webgl, scene, p.debug);
         const interactionHelper = new Canvas3dInteractionHelper(identify, getLoci, input);
 
@@ -233,11 +238,112 @@ namespace Canvas3D {
             postprocessing.render()
         }
 
+        function renderTemporalMultiSample(postprocessingEnabled: boolean) {
+            // based on the Multisample Anti-Aliasing Render Pass
+            // contributed to three.js by bhouston / http://clara.io/
+            //
+            // This manual approach to MSAA re-renders the scene once for
+            // each sample with camera jitter and accumulates the results.
+            const offsetList = JitterVectors[ Math.max(0, Math.min(p.sampleLevel, 5)) ]
+
+            if (multiSampleIndex === -1) return
+            if (multiSampleIndex >= offsetList.length) {
+                multiSampleIndex = -1
+                return
+            }
+
+            const i = multiSampleIndex
+
+            if (i === 0) {
+                drawTarget.bind()
+                renderDraw()
+                if (postprocessingEnabled) {
+                    postprocessingTarget.bind()
+                    renderPostprocessing()
+                }
+                ValueCell.update(compose.values.uWeight, 1.0)
+                ValueCell.update(compose.values.tColor, postprocessingEnabled ? postprocessingTarget.texture : drawTarget.texture)
+                compose.update()
+
+                holdTarget.bind()
+                state.disable(gl.BLEND)
+                compose.render()
+            }
+
+            const sampleWeight = 1.0 / offsetList.length
+
+            camera.viewOffset.enabled = true
+            ValueCell.update(compose.values.tColor, postprocessingEnabled ? postprocessingTarget.texture : drawTarget.texture)
+            ValueCell.update(compose.values.uWeight, sampleWeight)
+            compose.update()
+
+            const { width, height } = drawTarget
+
+            // render the scene multiple times, each slightly jitter offset
+            // from the last and accumulate the results.
+            const numSamplesPerFrame = Math.pow(2, p.sampleLevel)
+            for (let i = 0; i < numSamplesPerFrame; ++i) {
+                const offset = offsetList[multiSampleIndex]
+                Camera.setViewOffset(camera.viewOffset, width, height, offset[0], offset[1], width, height)
+                camera.updateMatrices()
+
+                // render scene and optionally postprocess
+                drawTarget.bind()
+                renderDraw()
+                if (postprocessingEnabled) {
+                    postprocessingTarget.bind()
+                    renderPostprocessing()
+                }
+
+                // compose rendered scene with compose target
+                composeTarget.bind()
+                state.enable(gl.BLEND)
+                state.blendEquationSeparate(gl.FUNC_ADD, gl.FUNC_ADD)
+                state.blendFuncSeparate(gl.ONE, gl.ONE, gl.ONE, gl.ONE)
+                state.disable(gl.DEPTH_TEST)
+                state.disable(gl.SCISSOR_TEST)
+                state.depthMask(false)
+                if (multiSampleIndex === 0) {
+                    webgl.state.clearColor(0, 0, 0, 0)
+                    gl.clear(gl.COLOR_BUFFER_BIT)
+                }
+                compose.render()
+
+                multiSampleIndex += 1
+                if (multiSampleIndex >= offsetList.length ) break
+            }
+
+            const accumulationWeight = multiSampleIndex * sampleWeight
+            if (accumulationWeight > 0) {
+                ValueCell.update(compose.values.uWeight, 1.0)
+                ValueCell.update(compose.values.tColor, composeTarget.texture)
+                compose.update()
+                webgl.unbindFramebuffer()
+                gl.viewport(0, 0, canvas.width, canvas.height)
+                state.disable(gl.BLEND)
+                compose.render()
+            }
+            if (accumulationWeight < 1.0) {
+                ValueCell.update(compose.values.uWeight, 1.0 - accumulationWeight)
+                ValueCell.update(compose.values.tColor, holdTarget.texture)
+                compose.update()
+                webgl.unbindFramebuffer()
+                gl.viewport(0, 0, canvas.width, canvas.height)
+                if (accumulationWeight === 0) state.disable(gl.BLEND)
+                else state.enable(gl.BLEND)
+                compose.render()
+            }
+
+            camera.viewOffset.enabled = false
+            camera.updateMatrices()
+            if (multiSampleIndex >= offsetList.length) multiSampleIndex = -1
+        }
+
         function renderMultiSample(postprocessingEnabled: boolean) {
             // based on the Multisample Anti-Aliasing Render Pass
             // contributed to three.js by bhouston / http://clara.io/
             //
-            // This manual approach to MSAA re-renders the scene ones for
+            // This manual approach to MSAA re-renders the scene once for
             // each sample with camera jitter and accumulates the results.
             const offsetList = JitterVectors[ Math.max(0, Math.min(p.sampleLevel, 5)) ]
 
@@ -272,7 +378,7 @@ namespace Canvas3D {
                     renderPostprocessing()
                 }
 
-                // compose rendered scene with hold target
+                // compose rendered scene with compose target
                 composeTarget.bind()
                 gl.viewport(0, 0, canvas.width, canvas.height)
                 state.enable(gl.BLEND)
@@ -309,9 +415,22 @@ 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();
+            if (force || cameraChanged) lastRenderTime = now()
+            if (p.multiSample === 'temporal') {
+                if (currentTime - lastRenderTime > 200) {
+                    multiSample = multiSampleIndex !== -1
+                } else {
+                    multiSampleIndex = 0
+                    multiSample = false
+                }
+            } else if (p.multiSample === 'on') {
+                multiSample = true
+            } else {
+                multiSample = false
+            }
             const postprocessingEnabled = p.postprocessing.occlusionEnable || p.postprocessing.outlineEnable
 
-            if (force || cameraChanged) {
+            if (force || cameraChanged || multiSample) {
                 switch (variant) {
                     case 'pick':
                         renderer.setViewport(0, 0, pickWidth, pickHeight);
@@ -324,8 +443,12 @@ namespace Canvas3D {
                         break;
                     case 'draw':
                         renderer.setViewport(0, 0, canvas.width, canvas.height);
-                        if (p.sampleLevel > 0) {
-                            renderMultiSample(postprocessingEnabled)
+                        if (multiSample) {
+                            if (p.multiSample === 'temporal') {
+                                renderTemporalMultiSample(postprocessingEnabled)
+                            } else {
+                                renderMultiSample(postprocessingEnabled)
+                            }
                         } else {
                             if (postprocessingEnabled) drawTarget.bind()
                             else webgl.unbindFramebuffer()
@@ -346,6 +469,7 @@ namespace Canvas3D {
 
         let forceNextDraw = false;
         let currentTime = 0;
+        let lastRenderTime = 0;
 
         function draw(force?: boolean) {
             if (render('draw', !!force || forceNextDraw)) {
@@ -508,6 +632,7 @@ 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.multiSample !== undefined) p.multiSample = props.multiSample
                 if (props.sampleLevel !== undefined) p.sampleLevel = props.sampleLevel
 
                 if (props.postprocessing) {
@@ -557,6 +682,7 @@ namespace Canvas3D {
                     clip: p.clip,
                     fog: p.fog,
 
+                    multiSample: p.multiSample,
                     sampleLevel: p.sampleLevel,
 
                     postprocessing: { ...p.postprocessing },
@@ -594,6 +720,7 @@ namespace Canvas3D {
             drawTarget.setSize(canvas.width, canvas.height)
             postprocessingTarget.setSize(canvas.width, canvas.height)
             composeTarget.setSize(canvas.width, canvas.height)
+            holdTarget.setSize(canvas.width, canvas.height)
             depthTexture.define(canvas.width, canvas.height)
             ValueCell.update(postprocessing.values.uTexSize, Vec2.set(postprocessing.values.uTexSize.ref.value, canvas.width, canvas.height))
             ValueCell.update(compose.values.uTexSize, Vec2.set(compose.values.uTexSize.ref.value, canvas.width, canvas.height))

+ 1 - 1
src/mol-canvas3d/helper/multi-sample.ts

@@ -70,7 +70,7 @@ export const JitterVectors = [
         [ 2, 5 ], [ 7, 5 ], [ 5, 6 ], [ 3, 7 ]
     ]
 ]
-  
+
 JitterVectors.forEach(offsetList => {
     offsetList.forEach(offset => {
         // 0.0625 = 1 / 16