Skip to content
Snippets Groups Projects
Commit 0e46b0b2 authored by Alexander Rose's avatar Alexander Rose
Browse files

multisample antialiasing

parent 55b460a5
No related branches found
No related tags found
No related merge requests found
......@@ -32,6 +32,7 @@ import { Canvas3dInteractionHelper } from './helper/interaction-events';
import { createTexture } from 'mol-gl/webgl/texture';
import { ValueCell } from 'mol-util';
import { getPostprocessingRenderable, PostprocessingParams } from './helper/postprocessing';
import { JitterVectors, getComposeRenderable } from './helper/multi-sample';
export const Canvas3DParams = {
// TODO: FPS cap?
......@@ -41,6 +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 }),
postprocessing: PD.Group(PostprocessingParams),
renderer: PD.Group(RendererParams),
trackball: PD.Group(TrackballControlsParams),
......@@ -107,16 +110,17 @@ namespace Canvas3D {
mode: p.cameraMode
})
const gl = getGLContext(canvas, {
const _gl = getGLContext(canvas, {
alpha: false,
antialias: true,
depth: true,
preserveDrawingBuffer: true
})
if (gl === null) {
if (_gl === null) {
throw new Error('Could not create a WebGL rendering context')
}
const webgl = createContext(gl)
const webgl = createContext(_gl)
const { state, gl } = webgl
const scene = Scene.create(webgl)
const controls = TrackballControls.create(input, camera, p.trackball)
......@@ -126,9 +130,15 @@ namespace Canvas3D {
const depthTexture = createTexture(webgl, 'image-depth', 'depth', 'ushort', 'nearest')
depthTexture.define(canvas.width, canvas.height)
depthTexture.attachFramebuffer(drawTarget.framebuffer, 'depth')
const postprocessingTarget = createRenderTarget(webgl, canvas.width, canvas.height)
const postprocessing = getPostprocessingRenderable(webgl, drawTarget.texture, depthTexture, p.postprocessing)
let pickScale = 0.25 / webgl.pixelRatio
const composeTarget = createRenderTarget(webgl, canvas.width, canvas.height)
const compose = getComposeRenderable(webgl, drawTarget.texture)
const pickBaseScale = 0.25
let pickScale = pickBaseScale / webgl.pixelRatio
let pickWidth = Math.round(canvas.width * pickScale)
let pickHeight = Math.round(canvas.height * pickScale)
const objectPickTarget = createRenderTarget(webgl, pickWidth, pickHeight)
......@@ -189,9 +199,9 @@ namespace Canvas3D {
let fogFar = cDist + (bRadius * fogFarFactor)
if (camera.state.mode === 'perspective') {
near = Math.max(0.1, p.cameraClipDistance, near)
near = Math.max(1, p.cameraClipDistance, near)
far = Math.max(1, far)
fogNear = Math.max(0.1, fogNear)
fogNear = Math.max(1, fogNear)
fogFar = Math.max(1, fogFar)
} else if (camera.state.mode === 'orthographic') {
if (p.cameraClipDistance > 0) {
......@@ -205,6 +215,92 @@ namespace Canvas3D {
}
}
function renderDraw() {
renderer.setViewport(0, 0, canvas.width, canvas.height)
renderer.render(scene, 'draw')
if (debugHelper.isEnabled) {
debugHelper.syncVisibility()
renderer.render(debugHelper.scene, 'draw')
}
}
function renderPostprocessing() {
gl.viewport(0, 0, canvas.width, canvas.height)
state.disable(gl.SCISSOR_TEST)
state.disable(gl.BLEND)
state.disable(gl.DEPTH_TEST)
state.depthMask(false)
postprocessing.render()
}
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
// each sample with camera jitter and accumulates the results.
const offsetList = JitterVectors[ Math.max(0, Math.min(p.sampleLevel, 5)) ]
const baseSampleWeight = 1.0 / offsetList.length
const roundingRange = 1 / 32
camera.viewOffset.enabled = true
ValueCell.update(compose.values.tColor, postprocessingEnabled ? postprocessingTarget.texture : drawTarget.texture)
compose.update()
const { width, height } = drawTarget
// render the scene multiple times, each slightly jitter offset
// from the last and accumulate the results.
for (let i = 0; i < offsetList.length; ++i) {
const offset = offsetList[i]
Camera.setViewOffset(camera.viewOffset, width, height, offset[0], offset[1], width, height)
camera.updateMatrices()
// the theory is that equal weights for each sample lead to an accumulation of rounding
// errors. The following equation varies the sampleWeight per sample so that it is uniformly
// distributed across a range of values whose rounding errors cancel each other out.
const uniformCenteredDistribution = -0.5 + (i + 0.5) / offsetList.length
const sampleWeight = baseSampleWeight + roundingRange * uniformCenteredDistribution
ValueCell.update(compose.values.uWeight, sampleWeight)
// render scene and optionally postprocess
drawTarget.bind()
renderDraw()
if (postprocessingEnabled) {
postprocessingTarget.bind()
renderPostprocessing()
}
// compose draw with hold target
composeTarget.bind()
gl.viewport(0, 0, canvas.width, canvas.height)
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 (i === 0) {
webgl.state.clearColor(0, 0, 0, 0)
gl.clear(gl.COLOR_BUFFER_BIT)
}
compose.render()
}
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()
camera.viewOffset.enabled = false
camera.updateMatrices()
}
function render(variant: 'pick' | 'draw', force: boolean) {
if (isIdentifying || isUpdating) return false
......@@ -228,22 +324,16 @@ namespace Canvas3D {
break;
case 'draw':
renderer.setViewport(0, 0, canvas.width, canvas.height);
if (postprocessingEnabled) {
drawTarget.bind()
if (p.sampleLevel > 0) {
renderMultiSample(postprocessingEnabled)
} else {
webgl.unbindFramebuffer();
}
renderer.render(scene, 'draw');
if (debugHelper.isEnabled) {
debugHelper.syncVisibility()
renderer.render(debugHelper.scene, 'draw')
}
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()
if (postprocessingEnabled) drawTarget.bind()
else webgl.unbindFramebuffer()
renderDraw()
if (postprocessingEnabled) {
webgl.unbindFramebuffer()
renderPostprocessing()
}
}
pickDirty = true
break;
......@@ -418,6 +508,8 @@ 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.sampleLevel !== undefined) p.sampleLevel = props.sampleLevel
if (props.postprocessing) {
if (props.postprocessing.occlusionEnable !== undefined) {
p.postprocessing.occlusionEnable = props.postprocessing.occlusionEnable
......@@ -465,6 +557,8 @@ namespace Canvas3D {
clip: p.clip,
fog: p.fog,
sampleLevel: p.sampleLevel,
postprocessing: { ...p.postprocessing },
renderer: { ...renderer.props },
trackball: { ...controls.props },
......@@ -498,10 +592,13 @@ namespace Canvas3D {
Viewport.set(controls.viewport, 0, 0, canvas.width, canvas.height)
drawTarget.setSize(canvas.width, canvas.height)
postprocessingTarget.setSize(canvas.width, canvas.height)
composeTarget.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))
pickScale = 0.25 / webgl.pixelRatio
pickScale = pickBaseScale / webgl.pixelRatio
pickWidth = Math.round(canvas.width * pickScale)
pickHeight = Math.round(canvas.height * pickScale)
objectPickTarget.setSize(pickWidth, pickHeight)
......
/**
* 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, UniformSpec, Values } from 'mol-gl/renderable/schema';
import { Texture } from 'mol-gl/webgl/texture';
import { WebGLContext } from 'mol-gl/webgl/context';
import { ValueCell } from 'mol-util';
import { Vec2 } from 'mol-math/linear-algebra';
import { ShaderCode } from 'mol-gl/shader-code';
import { createComputeRenderItem } from 'mol-gl/webgl/render-item';
import { createComputeRenderable } from 'mol-gl/renderable';
const ComposeSchema = {
...QuadSchema,
tColor: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
uTexSize: UniformSpec('v2'),
uWeight: UniformSpec('f'),
}
export function getComposeRenderable(ctx: WebGLContext, colorTexture: Texture) {
const values: Values<typeof ComposeSchema> = {
...QuadValues,
tColor: ValueCell.create(colorTexture),
uTexSize: ValueCell.create(Vec2.create(colorTexture.width, colorTexture.height)),
uWeight: ValueCell.create(1.0),
}
const schema = { ...ComposeSchema }
const shaderCode = ShaderCode(
require('mol-gl/shader/quad.vert').default,
require('mol-gl/shader/compose.frag').default
)
const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values)
return createComputeRenderable(renderItem, values)
}
export const JitterVectors = [
[
[ 0, 0 ]
],
[
[ 4, 4 ], [ -4, -4 ]
],
[
[ -2, -6 ], [ 6, -2 ], [ -6, 2 ], [ 2, 6 ]
],
[
[ 1, -3 ], [ -1, 3 ], [ 5, 1 ], [ -3, -5 ],
[ -5, 5 ], [ -7, -1 ], [ 3, 7 ], [ 7, -7 ]
],
[
[ 1, 1 ], [ -1, -3 ], [ -3, 2 ], [ 4, -1 ],
[ -5, -2 ], [ 2, 5 ], [ 5, 3 ], [ 3, -5 ],
[ -2, 6 ], [ 0, -7 ], [ -4, -6 ], [ -6, 4 ],
[ -8, 0 ], [ 7, -4 ], [ 6, 7 ], [ -7, -8 ]
],
[
[ -4, -7 ], [ -7, -5 ], [ -3, -5 ], [ -5, -4 ],
[ -1, -4 ], [ -2, -2 ], [ -6, -1 ], [ -4, 0 ],
[ -7, 1 ], [ -1, 2 ], [ -6, 3 ], [ -3, 3 ],
[ -7, 6 ], [ -3, 6 ], [ -5, 7 ], [ -1, 7 ],
[ 5, -7 ], [ 1, -6 ], [ 6, -5 ], [ 4, -4 ],
[ 2, -3 ], [ 7, -2 ], [ 1, -1 ], [ 4, -1 ],
[ 2, 1 ], [ 6, 2 ], [ 0, 4 ], [ 4, 4 ],
[ 2, 5 ], [ 7, 5 ], [ 5, 6 ], [ 3, 7 ]
]
]
JitterVectors.forEach(offsetList => {
offsetList.forEach(offset => {
// 0.0625 = 1 / 16
offset[0] *= 0.0625
offset[1] *= 0.0625
})
})
\ No newline at end of file
precision highp float;
precision highp sampler2D;
uniform sampler2D tColor;
uniform vec2 uTexSize;
uniform float uWeight;
void main() {
vec2 coords = gl_FragCoord.xy / uTexSize;
gl_FragColor = texture2D(tColor, coords) * uWeight;
}
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment