molstar
Version:
A comprehensive macromolecular library.
520 lines (519 loc) • 29.7 kB
JavaScript
/**
* Copyright (c) 2019-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Áron Samuel Kovács <aron.kovacs@mail.muni.cz>
* @author Ludovic Autin <ludovic.autin@gmail.com>
* @author Gianluca Tomasello <giagitom@gmail.com>
*/
import { createCopyRenderable, QuadSchema, QuadValues } from '../../mol-gl/compute/util';
import { TextureSpec, UniformSpec, DefineSpec } from '../../mol-gl/renderable/schema';
import { ShaderCode } from '../../mol-gl/shader-code';
import { deepEqual, ValueCell } from '../../mol-util';
import { createComputeRenderItem } from '../../mol-gl/webgl/render-item';
import { createComputeRenderable } from '../../mol-gl/renderable';
import { Mat4, Vec2, Vec3, Vec4 } from '../../mol-math/linear-algebra';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { quad_vert } from '../../mol-gl/shader/quad.vert';
import { ssao_frag } from '../../mol-gl/shader/ssao.frag';
import { ssaoBlur_frag } from '../../mol-gl/shader/ssao-blur.frag';
import { Color } from '../../mol-util/color';
import { isTimingMode } from '../../mol-util/debug';
export const SsaoParams = {
samples: PD.Numeric(32, { min: 1, max: 256, step: 1 }),
multiScale: PD.MappedStatic('off', {
on: PD.Group({
levels: PD.ObjectList({
radius: PD.Numeric(5, { min: 0, max: 20, step: 0.1 }, { description: 'Final occlusion radius is 2^x' }),
bias: PD.Numeric(1, { min: 0, max: 3, step: 0.1 }),
}, o => `${o.radius}, ${o.bias}`, { defaultValue: [
{ radius: 2, bias: 1 },
{ radius: 5, bias: 1 },
{ radius: 8, bias: 1 },
{ radius: 11, bias: 1 },
] }),
nearThreshold: PD.Numeric(10, { min: 0, max: 50, step: 1 }),
farThreshold: PD.Numeric(1500, { min: 0, max: 10000, step: 100 }),
}),
off: PD.Group({})
}, { cycle: true }),
radius: PD.Numeric(5, { min: 0, max: 20, step: 0.1 }, { description: 'Final occlusion radius is 2^x', hideIf: p => (p === null || p === void 0 ? void 0 : p.multiScale.name) === 'on' }),
bias: PD.Numeric(0.8, { min: 0, max: 3, step: 0.1 }),
blurKernelSize: PD.Numeric(15, { min: 1, max: 25, step: 2 }),
blurDepthBias: PD.Numeric(0.5, { min: 0, max: 1, step: 0.01 }),
resolutionScale: PD.Numeric(1, { min: 0.1, max: 1, step: 0.05 }, { description: 'Adjust resolution of occlusion calculation' }),
color: PD.Color(Color(0x000000)),
transparentThreshold: PD.Numeric(0.4, { min: 0, max: 1, step: 0.05 }),
};
function getLevels(props, levels) {
const count = props.length;
const { radius, bias } = levels || {
radius: (new Array(count * 3)).fill(0),
bias: (new Array(count * 3)).fill(0),
};
props = props.slice().sort((a, b) => a.radius - b.radius);
for (let i = 0; i < count; ++i) {
const p = props[i];
radius[i] = Math.pow(2, p.radius);
bias[i] = p.bias;
}
return { count, radius, bias };
}
export class SsaoPass {
static isEnabled(props) {
return props.occlusion.name !== 'off';
}
static isTransparentEnabled(scene, props) {
return scene.opacityAverage < 1 && scene.transparencyMin < props.transparentThreshold;
}
calcSsaoScale(resolutionScale) {
// downscale ssao for high pixel-ratios
return Math.min(1, 1 / this.webgl.pixelRatio) * resolutionScale;
}
getDepthTexture() {
return this.ssaoScale === 1 ? this.depthTextureOpaque : this.downsampledDepthTargetOpaque.texture;
}
getTransparentDepthTexture() {
return this.ssaoScale === 1 ? this.depthTextureTransparent : this.downsampledDepthTargetTransparent.texture;
}
constructor(webgl, width, height, packedDepth, depthTextureOpaque, depthTextureTransparent) {
this.webgl = webgl;
const { textureFloatLinear } = webgl.extensions;
this.depthTextureOpaque = depthTextureOpaque;
this.depthTextureTransparent = depthTextureTransparent;
this.nSamples = 1;
this.blurKernelSize = 1;
this.ssaoScale = this.calcSsaoScale(1);
this.texSize = [width, height];
this.levels = [];
this.framebuffer = webgl.resources.framebuffer();
this.blurFirstPassFramebuffer = webgl.resources.framebuffer();
this.blurSecondPassFramebuffer = webgl.resources.framebuffer();
const sw = Math.floor(width * this.ssaoScale);
const sh = Math.floor(height * this.ssaoScale);
const hw = Math.max(1, Math.floor(sw * 0.5));
const hh = Math.max(1, Math.floor(sh * 0.5));
const qw = Math.max(1, Math.floor(sw * 0.25));
const qh = Math.max(1, Math.floor(sh * 0.25));
const filter = textureFloatLinear ? 'linear' : 'nearest';
this.downsampledDepthTargetOpaque = packedDepth
? webgl.createRenderTarget(sw, sh, false, 'uint8', 'linear', 'rgba')
: webgl.createRenderTarget(sw, sh, false, 'float32', filter, webgl.isWebGL2 ? 'alpha' : 'rgba');
this.downsampleDepthRenderableOpaque = createCopyRenderable(webgl, depthTextureOpaque);
const depthTexture = this.getDepthTexture();
this.depthHalfTargetOpaque = packedDepth
? webgl.createRenderTarget(hw, hh, false, 'uint8', 'linear', 'rgba')
: webgl.createRenderTarget(hw, hh, false, 'float32', filter, webgl.isWebGL2 ? 'alpha' : 'rgba');
this.depthHalfRenderableOpaque = createCopyRenderable(webgl, depthTexture);
this.depthQuarterTargetOpaque = packedDepth
? webgl.createRenderTarget(qw, qh, false, 'uint8', 'linear', 'rgba')
: webgl.createRenderTarget(qw, qh, false, 'float32', filter, webgl.isWebGL2 ? 'alpha' : 'rgba');
this.depthQuarterRenderableOpaque = createCopyRenderable(webgl, this.depthHalfTargetOpaque.texture);
this.downsampledDepthTargetTransparent = webgl.createRenderTarget(sw, sh, false, 'uint8', 'linear', 'rgba');
this.downsampleDepthRenderableTransparent = createCopyRenderable(webgl, depthTextureTransparent);
const transparentDepthTexture = this.getTransparentDepthTexture();
this.depthHalfTargetTransparent = webgl.createRenderTarget(hw, hh, false, 'uint8', 'linear', 'rgba');
this.depthHalfRenderableTransparent = createCopyRenderable(webgl, transparentDepthTexture);
this.depthQuarterTargetTransparent = webgl.createRenderTarget(qw, qh, false, 'uint8', 'linear', 'rgba');
this.depthQuarterRenderableTransparent = createCopyRenderable(webgl, this.depthHalfTargetTransparent.texture);
this.ssaoDepthTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
this.ssaoDepthTexture.define(sw, sh);
this.ssaoDepthTexture.attachFramebuffer(this.framebuffer, 'color0');
this.ssaoDepthTransparentTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
this.ssaoDepthTransparentTexture.define(sw, sh);
this.depthBlurProxyTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
this.depthBlurProxyTexture.define(sw, sh);
this.depthBlurProxyTexture.attachFramebuffer(this.blurFirstPassFramebuffer, 'color0');
this.renderable = getSsaoRenderable(webgl, depthTexture, this.depthHalfTargetOpaque.texture, this.depthQuarterTargetOpaque.texture, transparentDepthTexture, this.depthHalfTargetTransparent.texture, this.depthQuarterTargetTransparent.texture);
this.blurFirstPassRenderable = getSsaoBlurRenderable(webgl, this.ssaoDepthTransparentTexture, 'horizontal');
this.blurSecondPassRenderable = getSsaoBlurRenderable(webgl, this.depthBlurProxyTexture, 'vertical');
}
setSize(width, height) {
const [w, h] = this.texSize;
const ssaoScale = this.calcSsaoScale(1);
if (width !== w || height !== h || this.ssaoScale !== ssaoScale) {
this.texSize.splice(0, 2, width, height);
const sw = Math.floor(width * this.ssaoScale);
const sh = Math.floor(height * this.ssaoScale);
this.ssaoDepthTexture.define(sw, sh);
this.ssaoDepthTransparentTexture.define(sw, sh);
this.depthBlurProxyTexture.define(sw, sh);
const hw = Math.max(1, Math.floor(sw * 0.5));
const hh = Math.max(1, Math.floor(sh * 0.5));
const qw = Math.max(1, Math.floor(sw * 0.25));
const qh = Math.max(1, Math.floor(sh * 0.25));
this.downsampledDepthTargetOpaque.setSize(sw, sh);
this.depthHalfTargetOpaque.setSize(hw, hh);
this.depthQuarterTargetOpaque.setSize(qw, qh);
ValueCell.update(this.downsampleDepthRenderableOpaque.values.uTexSize, Vec2.set(this.downsampleDepthRenderableOpaque.values.uTexSize.ref.value, sw, sh));
ValueCell.update(this.depthHalfRenderableOpaque.values.uTexSize, Vec2.set(this.depthHalfRenderableOpaque.values.uTexSize.ref.value, hw, hh));
ValueCell.update(this.depthQuarterRenderableOpaque.values.uTexSize, Vec2.set(this.depthQuarterRenderableOpaque.values.uTexSize.ref.value, qw, qh));
this.downsampledDepthTargetTransparent.setSize(sw, sh);
this.depthHalfTargetTransparent.setSize(hw, hh);
this.depthQuarterTargetTransparent.setSize(qw, qh);
ValueCell.update(this.downsampleDepthRenderableTransparent.values.uTexSize, Vec2.set(this.downsampleDepthRenderableTransparent.values.uTexSize.ref.value, sw, sh));
ValueCell.update(this.depthHalfRenderableTransparent.values.uTexSize, Vec2.set(this.depthHalfRenderableTransparent.values.uTexSize.ref.value, hw, hh));
ValueCell.update(this.depthQuarterRenderableTransparent.values.uTexSize, Vec2.set(this.depthQuarterRenderableTransparent.values.uTexSize.ref.value, qw, qh));
ValueCell.update(this.renderable.values.uTexSize, Vec2.set(this.renderable.values.uTexSize.ref.value, sw, sh));
ValueCell.update(this.blurFirstPassRenderable.values.uTexSize, Vec2.set(this.blurFirstPassRenderable.values.uTexSize.ref.value, sw, sh));
ValueCell.update(this.blurSecondPassRenderable.values.uTexSize, Vec2.set(this.blurSecondPassRenderable.values.uTexSize.ref.value, sw, sh));
const depthTexture = this.getDepthTexture();
const transparentDepthTexture = this.getTransparentDepthTexture();
ValueCell.update(this.depthHalfRenderableOpaque.values.tColor, depthTexture);
ValueCell.update(this.depthHalfRenderableTransparent.values.tColor, transparentDepthTexture);
ValueCell.update(this.renderable.values.tDepth, depthTexture);
ValueCell.update(this.renderable.values.tDepthTransparent, transparentDepthTexture);
this.depthHalfRenderableOpaque.update();
this.depthHalfRenderableTransparent.update();
this.renderable.update();
}
}
reset() {
this.ssaoDepthTexture.attachFramebuffer(this.framebuffer, 'color0');
this.depthBlurProxyTexture.attachFramebuffer(this.blurFirstPassFramebuffer, 'color0');
}
update(camera, scene, props, illuminationMode = false) {
let needsUpdateSsao = false;
let needsUpdateSsaoBlur = false;
let needsUpdateDepthHalf = false;
const orthographic = camera.state.mode === 'orthographic' ? 1 : 0;
const invProjection = Mat4.identity();
Mat4.invert(invProjection, camera.projection);
const [w, h] = this.texSize;
const v = camera.viewport;
ValueCell.update(this.renderable.values.uProjection, camera.projection);
ValueCell.update(this.renderable.values.uInvProjection, invProjection);
const b = this.renderable.values.uBounds;
const s = this.ssaoScale;
Vec4.set(b.ref.value, Math.floor(v.x * s) / (w * s), Math.floor(v.y * s) / (h * s), Math.ceil((v.x + v.width) * s) / (w * s), Math.ceil((v.y + v.height) * s) / (h * s));
ValueCell.update(b, b.ref.value);
ValueCell.update(this.blurFirstPassRenderable.values.uBounds, b.ref.value);
ValueCell.update(this.blurSecondPassRenderable.values.uBounds, b.ref.value);
ValueCell.updateIfChanged(this.blurFirstPassRenderable.values.uNear, camera.near);
ValueCell.updateIfChanged(this.blurSecondPassRenderable.values.uNear, camera.near);
ValueCell.updateIfChanged(this.blurFirstPassRenderable.values.uFar, camera.far);
ValueCell.updateIfChanged(this.blurSecondPassRenderable.values.uFar, camera.far);
ValueCell.update(this.blurFirstPassRenderable.values.uInvProjection, invProjection);
ValueCell.update(this.blurSecondPassRenderable.values.uInvProjection, invProjection);
ValueCell.update(this.blurFirstPassRenderable.values.uBlurDepthBias, props.blurDepthBias);
ValueCell.update(this.blurSecondPassRenderable.values.uBlurDepthBias, props.blurDepthBias);
if (this.blurFirstPassRenderable.values.dOrthographic.ref.value !== orthographic) {
needsUpdateSsaoBlur = true;
ValueCell.update(this.blurFirstPassRenderable.values.dOrthographic, orthographic);
ValueCell.update(this.blurSecondPassRenderable.values.dOrthographic, orthographic);
}
const includeTransparent = SsaoPass.isTransparentEnabled(scene, props);
if (this.renderable.values.dIncludeTransparent.ref.value !== includeTransparent) {
needsUpdateSsao = true;
ValueCell.update(this.renderable.values.dIncludeTransparent, includeTransparent);
}
if (this.renderable.values.dIllumination.ref.value !== illuminationMode) {
needsUpdateSsao = true;
ValueCell.update(this.renderable.values.dIllumination, illuminationMode);
}
if (this.nSamples !== props.samples) {
needsUpdateSsao = true;
this.nSamples = props.samples;
ValueCell.update(this.renderable.values.uSamples, getSamples(this.nSamples));
ValueCell.updateIfChanged(this.renderable.values.dNSamples, this.nSamples);
}
const multiScale = props.multiScale.name === 'on';
if (this.renderable.values.dMultiScale.ref.value !== multiScale) {
needsUpdateSsao = true;
ValueCell.update(this.renderable.values.dMultiScale, multiScale);
}
if (props.multiScale.name === 'on') {
const mp = props.multiScale.params;
if (!deepEqual(this.levels, mp.levels)) {
needsUpdateSsao = true;
this.levels = mp.levels;
const levels = getLevels(mp.levels);
ValueCell.updateIfChanged(this.renderable.values.dLevels, levels.count);
ValueCell.update(this.renderable.values.uLevelRadius, levels.radius);
ValueCell.update(this.renderable.values.uLevelBias, levels.bias);
}
ValueCell.updateIfChanged(this.renderable.values.uNearThreshold, mp.nearThreshold);
ValueCell.updateIfChanged(this.renderable.values.uFarThreshold, mp.farThreshold);
}
else {
ValueCell.updateIfChanged(this.renderable.values.uRadius, Math.pow(2, props.radius));
}
ValueCell.updateIfChanged(this.renderable.values.uBias, props.bias);
if (this.blurKernelSize !== props.blurKernelSize) {
needsUpdateSsaoBlur = true;
this.blurKernelSize = props.blurKernelSize;
const kernel = getBlurKernel(this.blurKernelSize);
ValueCell.update(this.blurFirstPassRenderable.values.uKernel, kernel);
ValueCell.update(this.blurSecondPassRenderable.values.uKernel, kernel);
ValueCell.update(this.blurFirstPassRenderable.values.dOcclusionKernelSize, this.blurKernelSize);
ValueCell.update(this.blurSecondPassRenderable.values.dOcclusionKernelSize, this.blurKernelSize);
}
const ssaoScale = this.calcSsaoScale(props.resolutionScale);
if (this.ssaoScale !== ssaoScale) {
needsUpdateSsao = true;
needsUpdateDepthHalf = true;
this.ssaoScale = ssaoScale;
const sw = Math.floor(w * this.ssaoScale);
const sh = Math.floor(h * this.ssaoScale);
this.ssaoDepthTexture.define(sw, sh);
this.ssaoDepthTransparentTexture.define(sw, sh);
this.depthBlurProxyTexture.define(sw, sh);
const hw = Math.floor(sw * 0.5);
const hh = Math.floor(sh * 0.5);
const qw = Math.floor(sw * 0.25);
const qh = Math.floor(sh * 0.25);
this.downsampledDepthTargetOpaque.setSize(sw, sh);
this.depthHalfTargetOpaque.setSize(hw, hh);
this.depthQuarterTargetOpaque.setSize(qw, qh);
const depthTexture = this.getDepthTexture();
ValueCell.update(this.depthHalfRenderableOpaque.values.tColor, depthTexture);
ValueCell.update(this.renderable.values.tDepth, depthTexture);
ValueCell.update(this.renderable.values.tDepthHalf, this.depthHalfTargetOpaque.texture);
ValueCell.update(this.renderable.values.tDepthQuarter, this.depthQuarterTargetOpaque.texture);
ValueCell.update(this.downsampleDepthRenderableOpaque.values.uTexSize, Vec2.set(this.downsampleDepthRenderableOpaque.values.uTexSize.ref.value, sw, sh));
ValueCell.update(this.depthHalfRenderableOpaque.values.uTexSize, Vec2.set(this.depthHalfRenderableOpaque.values.uTexSize.ref.value, hw, hh));
ValueCell.update(this.depthQuarterRenderableOpaque.values.uTexSize, Vec2.set(this.depthQuarterRenderableOpaque.values.uTexSize.ref.value, qw, qh));
this.downsampledDepthTargetTransparent.setSize(sw, sh);
this.depthHalfTargetTransparent.setSize(hw, hh);
this.depthQuarterTargetTransparent.setSize(qw, qh);
const transparentDepthTexture = this.getTransparentDepthTexture();
ValueCell.update(this.depthHalfRenderableTransparent.values.tColor, transparentDepthTexture);
ValueCell.update(this.renderable.values.tDepthTransparent, transparentDepthTexture);
ValueCell.update(this.renderable.values.tDepthHalfTransparent, this.depthHalfTargetTransparent.texture);
ValueCell.update(this.renderable.values.tDepthQuarterTransparent, this.depthQuarterTargetTransparent.texture);
ValueCell.update(this.downsampleDepthRenderableTransparent.values.uTexSize, Vec2.set(this.downsampleDepthRenderableTransparent.values.uTexSize.ref.value, sw, sh));
ValueCell.update(this.depthHalfRenderableTransparent.values.uTexSize, Vec2.set(this.depthHalfRenderableTransparent.values.uTexSize.ref.value, hw, hh));
ValueCell.update(this.depthQuarterRenderableTransparent.values.uTexSize, Vec2.set(this.depthQuarterRenderableTransparent.values.uTexSize.ref.value, qw, qh));
ValueCell.update(this.renderable.values.uTexSize, Vec2.set(this.renderable.values.uTexSize.ref.value, sw, sh));
ValueCell.update(this.blurFirstPassRenderable.values.uTexSize, Vec2.set(this.blurFirstPassRenderable.values.uTexSize.ref.value, sw, sh));
ValueCell.update(this.blurSecondPassRenderable.values.uTexSize, Vec2.set(this.blurSecondPassRenderable.values.uTexSize.ref.value, sw, sh));
}
if (needsUpdateSsao) {
this.renderable.update();
}
if (needsUpdateSsaoBlur) {
this.blurFirstPassRenderable.update();
this.blurSecondPassRenderable.update();
}
if (needsUpdateDepthHalf) {
this.depthHalfRenderableOpaque.update();
this.depthHalfRenderableTransparent.update();
}
}
render(camera) {
if (isTimingMode)
this.webgl.timer.mark('SSAO.render');
const { state } = this.webgl;
const { x, y, width, height } = camera.viewport;
const includeTransparent = this.renderable.values.dIncludeTransparent.ref.value;
const multiScale = this.renderable.values.dMultiScale.ref.value;
const sx = Math.floor(x * this.ssaoScale);
const sy = Math.floor(y * this.ssaoScale);
const sw = Math.ceil(width * this.ssaoScale);
const sh = Math.ceil(height * this.ssaoScale);
state.viewport(sx, sy, sw, sh);
state.scissor(sx, sy, sw, sh);
if (this.ssaoScale < 1) {
if (isTimingMode)
this.webgl.timer.mark('SSAO.downsample');
this.downsampledDepthTargetOpaque.bind();
this.downsampleDepthRenderableOpaque.render();
if (includeTransparent) {
this.downsampledDepthTargetTransparent.bind();
this.downsampleDepthRenderableTransparent.render();
}
if (isTimingMode)
this.webgl.timer.markEnd('SSAO.downsample');
}
if (isTimingMode)
this.webgl.timer.mark('SSAO.half');
if (multiScale) {
this.depthHalfTargetOpaque.bind();
this.depthHalfRenderableOpaque.render();
}
if (multiScale && includeTransparent) {
this.depthHalfTargetTransparent.bind();
this.depthHalfRenderableTransparent.render();
}
if (isTimingMode)
this.webgl.timer.markEnd('SSAO.half');
if (isTimingMode)
this.webgl.timer.mark('SSAO.quarter');
if (multiScale) {
this.depthQuarterTargetOpaque.bind();
this.depthQuarterRenderableOpaque.render();
}
if (multiScale && includeTransparent) {
this.depthQuarterTargetTransparent.bind();
this.depthQuarterRenderableTransparent.render();
}
if (isTimingMode)
this.webgl.timer.markEnd('SSAO.quarter');
if (isTimingMode)
this.webgl.timer.mark('SSAO.opaque');
this.ssaoDepthTexture.attachFramebuffer(this.framebuffer, 'color0');
ValueCell.update(this.renderable.values.uTransparencyFlag, 0);
this.framebuffer.bind();
this.renderable.render();
if (isTimingMode)
this.webgl.timer.markEnd('SSAO.opaque');
if (isTimingMode)
this.webgl.timer.mark('SSAO.blurOpaque');
ValueCell.update(this.blurFirstPassRenderable.values.tSsaoDepth, this.ssaoDepthTexture);
this.blurFirstPassRenderable.update();
this.blurFirstPassFramebuffer.bind();
this.blurFirstPassRenderable.render();
this.ssaoDepthTexture.attachFramebuffer(this.blurSecondPassFramebuffer, 'color0');
this.blurSecondPassFramebuffer.bind();
this.blurSecondPassRenderable.render();
if (isTimingMode)
this.webgl.timer.markEnd('SSAO.blurOpaque');
if (includeTransparent) {
if (isTimingMode)
this.webgl.timer.mark('SSAO.transparent ');
this.ssaoDepthTransparentTexture.attachFramebuffer(this.framebuffer, 'color0');
ValueCell.update(this.renderable.values.uTransparencyFlag, 1);
this.framebuffer.bind();
this.renderable.render();
if (isTimingMode)
this.webgl.timer.markEnd('SSAO.transparent ');
if (isTimingMode)
this.webgl.timer.mark('SSAO.blurTransparent ');
ValueCell.update(this.blurFirstPassRenderable.values.tSsaoDepth, this.ssaoDepthTransparentTexture);
this.blurFirstPassRenderable.update();
this.blurFirstPassFramebuffer.bind();
this.blurFirstPassRenderable.render();
this.ssaoDepthTransparentTexture.attachFramebuffer(this.blurSecondPassFramebuffer, 'color0');
this.blurSecondPassFramebuffer.bind();
this.blurSecondPassRenderable.render();
if (isTimingMode)
this.webgl.timer.markEnd('SSAO.blurTransparent ');
}
if (isTimingMode)
this.webgl.timer.markEnd('SSAO.render');
}
}
const SsaoSchema = {
...QuadSchema,
tDepth: TextureSpec('texture', 'rgba', 'ubyte', 'linear'),
tDepthHalf: TextureSpec('texture', 'rgba', 'ubyte', 'linear'),
tDepthQuarter: TextureSpec('texture', 'rgba', 'ubyte', 'linear'),
dIllumination: DefineSpec('boolean'),
uTransparencyFlag: UniformSpec('i'),
dIncludeTransparent: DefineSpec('boolean'),
tDepthTransparent: TextureSpec('texture', 'rgba', 'ubyte', 'linear'),
tDepthHalfTransparent: TextureSpec('texture', 'rgba', 'ubyte', 'linear'),
tDepthQuarterTransparent: TextureSpec('texture', 'rgba', 'ubyte', 'linear'),
uSamples: UniformSpec('v3[]'),
dNSamples: DefineSpec('number'),
uProjection: UniformSpec('m4'),
uInvProjection: UniformSpec('m4'),
uBounds: UniformSpec('v4'),
uTexSize: UniformSpec('v2'),
uRadius: UniformSpec('f'),
uBias: UniformSpec('f'),
dMultiScale: DefineSpec('boolean'),
dLevels: DefineSpec('number'),
uLevelRadius: UniformSpec('f[]'),
uLevelBias: UniformSpec('f[]'),
uNearThreshold: UniformSpec('f'),
uFarThreshold: UniformSpec('f'),
};
function getSsaoRenderable(ctx, depthTexture, depthHalfTexture, depthQuarterTexture, transparentDepthTexture, transparentDepthHalfTexture, transparentDepthQuarterTexture) {
const values = {
...QuadValues,
tDepth: ValueCell.create(depthTexture),
tDepthHalf: ValueCell.create(depthHalfTexture),
tDepthQuarter: ValueCell.create(depthQuarterTexture),
dIllumination: ValueCell.create(false),
dIncludeTransparent: ValueCell.create(true),
uTransparencyFlag: ValueCell.create(0),
tDepthTransparent: ValueCell.create(transparentDepthTexture),
tDepthHalfTransparent: ValueCell.create(transparentDepthHalfTexture),
tDepthQuarterTransparent: ValueCell.create(transparentDepthQuarterTexture),
uSamples: ValueCell.create(getSamples(32)),
dNSamples: ValueCell.create(32),
uProjection: ValueCell.create(Mat4.identity()),
uInvProjection: ValueCell.create(Mat4.identity()),
uBounds: ValueCell.create(Vec4()),
uTexSize: ValueCell.create(Vec2.create(ctx.gl.drawingBufferWidth, ctx.gl.drawingBufferHeight)),
uRadius: ValueCell.create(Math.pow(2, 5)),
uBias: ValueCell.create(0.8),
dMultiScale: ValueCell.create(false),
dLevels: ValueCell.create(3),
uLevelRadius: ValueCell.create([Math.pow(2, 2), Math.pow(2, 5), Math.pow(2, 8)]),
uLevelBias: ValueCell.create([0.8, 0.8, 0.8]),
uNearThreshold: ValueCell.create(10.0),
uFarThreshold: ValueCell.create(1500.0),
};
const schema = { ...SsaoSchema };
const shaderCode = ShaderCode('ssao', quad_vert, ssao_frag);
const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values);
return createComputeRenderable(renderItem, values);
}
const SsaoBlurSchema = {
...QuadSchema,
tSsaoDepth: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
uTexSize: UniformSpec('v2'),
uKernel: UniformSpec('f[]'),
dOcclusionKernelSize: DefineSpec('number'),
uBlurDepthBias: UniformSpec('f'),
uBlurDirectionX: UniformSpec('f'),
uBlurDirectionY: UniformSpec('f'),
uInvProjection: UniformSpec('m4'),
uNear: UniformSpec('f'),
uFar: UniformSpec('f'),
uBounds: UniformSpec('v4'),
dOrthographic: DefineSpec('number'),
};
function getSsaoBlurRenderable(ctx, ssaoDepthTexture, direction) {
const values = {
...QuadValues,
tSsaoDepth: ValueCell.create(ssaoDepthTexture),
uTexSize: ValueCell.create(Vec2.create(ssaoDepthTexture.getWidth(), ssaoDepthTexture.getHeight())),
uKernel: ValueCell.create(getBlurKernel(15)),
dOcclusionKernelSize: ValueCell.create(15),
uBlurDepthBias: ValueCell.create(0.5),
uBlurDirectionX: ValueCell.create(direction === 'horizontal' ? 1 : 0),
uBlurDirectionY: ValueCell.create(direction === 'vertical' ? 1 : 0),
uInvProjection: ValueCell.create(Mat4.identity()),
uNear: ValueCell.create(0.0),
uFar: ValueCell.create(10000.0),
uBounds: ValueCell.create(Vec4()),
dOrthographic: ValueCell.create(0),
};
const schema = { ...SsaoBlurSchema };
const shaderCode = ShaderCode('ssao_blur', quad_vert, ssaoBlur_frag);
const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values);
return createComputeRenderable(renderItem, values);
}
function getBlurKernel(kernelSize) {
const sigma = kernelSize / 3.0;
const halfKernelSize = Math.floor((kernelSize + 1) / 2);
const kernel = [];
for (let x = 0; x < halfKernelSize; x++) {
kernel.push((1.0 / ((Math.sqrt(2 * Math.PI)) * sigma)) * Math.exp(-x * x / (2 * sigma * sigma)));
}
return kernel;
}
const RandomHemisphereVector = [];
for (let i = 0; i < 256; i++) {
const v = Vec3();
v[0] = Math.random() * 2.0 - 1.0;
v[1] = Math.random() * 2.0 - 1.0;
v[2] = Math.random();
Vec3.normalize(v, v);
Vec3.scale(v, v, Math.random());
RandomHemisphereVector.push(v);
}
function getSamples(nSamples) {
const samples = [];
for (let i = 0; i < nSamples; i++) {
let scale = (i * i + 2.0 * i + 1) / (nSamples * nSamples);
scale = 0.1 + scale * (1.0 - 0.1);
samples.push(RandomHemisphereVector[i][0] * scale);
samples.push(RandomHemisphereVector[i][1] * scale);
samples.push(RandomHemisphereVector[i][2] * scale);
}
return samples;
}