@inweb/viewer-three
Version:
JavaScript library for rendering CAD and BIM files in a browser using Three.js
246 lines (212 loc) • 6.85 kB
JavaScript
///////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2002-2025, Open Design Alliance (the "Alliance").
// All rights reserved.
//
// This software and its documentation and related materials are owned by
// the Alliance. The software may only be incorporated into application
// programs owned by members of the Alliance, subject to a signed
// Membership Agreement and Supplemental Software License Agreement with the
// Alliance. The structure and organization of this software are the valuable
// trade secrets of the Alliance and its suppliers. The software is also
// protected by copyright law and international treaty provisions. Application
// programs incorporating this software must include the following statement
// with their copyright notices:
//
// This application incorporates Open Design Alliance software pursuant to a
// license agreement with Open Design Alliance.
// Open Design Alliance Copyright (C) 2002-2025 by Open Design Alliance.
// All rights reserved.
//
// By use of this software, its documentation or related materials, you
// acknowledge and accept the above terms.
///////////////////////////////////////////////////////////////////////////////
import { AdditiveBlending, Color, HalfFloatType, ShaderMaterial, UniformsUtils, WebGLRenderTarget } from "three";
import { Pass, FullScreenQuad } from "three/examples/jsm/postprocessing/Pass.js";
import { CopyShader } from "three/examples/jsm/shaders/CopyShader.js";
export class SSAARenderPass extends Pass {
constructor(scenes, camera, clearColor = 0x000000, clearAlpha = 0) {
super();
this.scenes = Array.isArray(scenes) ? scenes : [scenes];
this.camera = camera;
this.sampleLevel = 2;
this.unbiased = true;
this.stencilBuffer = false;
this.clearColor = clearColor;
this.clearAlpha = clearAlpha;
this._sampleRenderTarget = null;
this._oldClearColor = new Color();
this._copyUniforms = UniformsUtils.clone(CopyShader.uniforms);
this._copyMaterial = new ShaderMaterial({
uniforms: this._copyUniforms,
vertexShader: CopyShader.vertexShader,
fragmentShader: CopyShader.fragmentShader,
transparent: true,
depthTest: false,
depthWrite: false,
premultipliedAlpha: true,
blending: AdditiveBlending,
});
this._fsQuad = new FullScreenQuad(this._copyMaterial);
}
dispose() {
if (this._sampleRenderTarget) {
this._sampleRenderTarget.dispose();
this._sampleRenderTarget = null;
}
this._copyMaterial.dispose();
this._fsQuad.dispose();
}
setSize(width, height) {
if (this._sampleRenderTarget) this._sampleRenderTarget.setSize(width, height);
}
render(renderer, writeBuffer, readBuffer, deltaTime, maskActive) {
if (!this._sampleRenderTarget) {
this._sampleRenderTarget = new WebGLRenderTarget(readBuffer.width, readBuffer.height, {
type: HalfFloatType,
stencilBuffer: this.stencilBuffer,
});
this._sampleRenderTarget.texture.name = "SSAAMultiRenderPass.sample";
}
const jitterOffsets = _JitterVectors[Math.max(0, Math.min(this.sampleLevel, 5))];
const autoClear = renderer.autoClear;
renderer.autoClear = false;
renderer.getClearColor(this._oldClearColor);
const oldClearAlpha = renderer.getClearAlpha();
const baseSampleWeight = 1.0 / jitterOffsets.length;
const roundingRange = 1 / 32;
this._copyUniforms["tDiffuse"].value = this._sampleRenderTarget.texture;
const viewOffset = {
fullWidth: readBuffer.width,
fullHeight: readBuffer.height,
offsetX: 0,
offsetY: 0,
width: readBuffer.width,
height: readBuffer.height,
};
const originalViewOffset = Object.assign({}, this.camera.view);
if (originalViewOffset.enabled) Object.assign(viewOffset, originalViewOffset);
for (let i = 0; i < jitterOffsets.length; i++) {
const jitterOffset = jitterOffsets[i];
if (this.camera.setViewOffset) {
this.camera.setViewOffset(
viewOffset.fullWidth,
viewOffset.fullHeight,
viewOffset.offsetX + jitterOffset[0] * 0.0625,
viewOffset.offsetY + jitterOffset[1] * 0.0625, // 0.0625 = 1 / 16
viewOffset.width,
viewOffset.height
);
}
let sampleWeight = baseSampleWeight;
if (this.unbiased) {
const uniformCenteredDistribution = -0.5 + (i + 0.5) / jitterOffsets.length;
sampleWeight += roundingRange * uniformCenteredDistribution;
}
this._copyUniforms["opacity"].value = sampleWeight;
renderer.setClearColor(this.clearColor, this.clearAlpha);
renderer.setRenderTarget(this._sampleRenderTarget);
renderer.clear();
this.scenes.forEach((scene) => renderer.render(scene, this.camera));
renderer.setRenderTarget(this.renderToScreen ? null : writeBuffer);
if (i === 0) {
renderer.setClearColor(0x000000, 0.0);
renderer.clear();
}
this._fsQuad.render(renderer);
}
if (this.camera.setViewOffset && originalViewOffset.enabled) {
this.camera.setViewOffset(
originalViewOffset.fullWidth,
originalViewOffset.fullHeight,
originalViewOffset.offsetX,
originalViewOffset.offsetY,
originalViewOffset.width,
originalViewOffset.height
);
} else if (this.camera.clearViewOffset) {
this.camera.clearViewOffset();
}
renderer.autoClear = autoClear;
renderer.setClearColor(this._oldClearColor, oldClearAlpha);
}
}
// These jitter vectors are specified in integers because it is easier.
// I am assuming a [-8,8) integer grid, but it needs to be mapped onto [-0.5,0.5)
// before being used, thus these integers need to be scaled by 1/16.
//
// Sample patterns reference: https://msdn.microsoft.com/en-us/library/windows/desktop/ff476218%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396
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],
],
];