This commit is contained in:
Davy Hélard
2026-10-03 21:52:38 +02:00
parent 6eb968e1bf
commit 3cd287b149
19 changed files with 4145 additions and 8 deletions
+45
View File
@@ -2511,6 +2511,51 @@ module.exports = {
.setType('number')
.setDescription(_('Between -1 and 1'));
}
{
const effect = extension
.addEffect('N8AO')
.setFullName(_('Ambient occlusion'))
.setDescription(_('Shadow for ambient light.'))
.markAsNotWorkingForObjects()
.markAsOnlyWorkingFor3D()
.addIncludeFile('Extensions/3D/N8AOEffect.js');
const properties = effect.getProperties();
properties
.getOrCreate('radius')
.setValue('5')
.setLabel(_('Radius'))
.setType('number');
properties
.getOrCreate('distanceFalloff')
.setValue('1')
.setLabel(_('Distance falloff'))
.setType('number')
.setDescription(
_(
'How fast the ambient occlusion fades away with distance in proportion to its radius.'
)
);
properties
.getOrCreate('intensity')
.setValue('5')
.setLabel(_('Intensity'))
.setType('number');
properties
.getOrCreate('color')
.setValue('0;0;0')
.setLabel(_('Color'))
.setType('color');
properties
.getOrCreate('quality')
.setValue('Performance')
.setLabel(_('Shadow quality'))
.setType('choice')
.addChoice('Performance', _('Very low quality'))
.addChoice('Low', _('Low quality'))
.addChoice('Medium', _('Medium quality'))
.addChoice('High', _('High quality'))
.addChoice('Ultra', _('Ultra-high quality'));
}
// Don't forget to update the alert condition in Model3DEditor.js when
// adding a new light.
+133
View File
@@ -0,0 +1,133 @@
namespace gdjs {
interface N8AOFilterNetworkSyncData {
r: number;
d: number;
i: number;
c: number;
}
gdjs.PixiFiltersTools.registerFilterCreator(
'Scene3D::N8AO',
new (class implements gdjs.PixiFiltersTools.FilterCreator {
makeFilter(
target: EffectsTarget,
effectData: EffectData
): gdjs.PixiFiltersTools.Filter {
if (typeof THREE === 'undefined') {
return new gdjs.PixiFiltersTools.EmptyFilter();
}
return new (class implements gdjs.PixiFiltersTools.Filter {
shaderPass: THREE_ADDONS.N8AOPass;
_isEnabled: boolean;
constructor() {
const layer = target.getRuntimeLayer!()!;
const scene = layer.get3DRendererObject()!;
const camera = layer.getRenderer().getThreeCamera()!;
this.shaderPass = new THREE_ADDONS.N8AOPass(
scene,
camera,
layer.getWidth(),
layer.getHeight()
);
this.shaderPass.configuration.halfRes = true;
this.shaderPass.configuration.gammaCorrection = false;
this.shaderPass.configuration.accumulate = false;
this.shaderPass.configuration.transparencyAware = false;
this.shaderPass.autoDetectTransparency = false;
this._isEnabled = false;
}
isEnabled(target: EffectsTarget): boolean {
return this._isEnabled;
}
setEnabled(target: EffectsTarget, enabled: boolean): boolean {
if (this._isEnabled === enabled) {
return true;
}
if (enabled) {
return this.applyEffect(target);
} else {
return this.removeEffect(target);
}
}
applyEffect(target: EffectsTarget): boolean {
if (!(target instanceof gdjs.Layer)) {
return false;
}
target.getRenderer().setCustomRenderPass(this.shaderPass);
this._isEnabled = true;
return true;
}
removeEffect(target: EffectsTarget): boolean {
if (!(target instanceof gdjs.Layer)) {
return false;
}
target.getRenderer().setCustomRenderPass(null);
this._isEnabled = false;
return true;
}
updatePreRender(target: gdjs.EffectsTarget): any {}
updateDoubleParameter(parameterName: string, value: number): void {
if (parameterName === 'radius') {
this.shaderPass.configuration.aoRadius = value;
}
if (parameterName === 'distanceFalloff') {
this.shaderPass.configuration.distanceFalloff = value;
}
if (parameterName === 'intensity') {
this.shaderPass.configuration.intensity = value;
}
}
getDoubleParameter(parameterName: string): number {
if (parameterName === 'radius') {
return this.shaderPass.configuration.aoRadius;
}
if (parameterName === 'distanceFalloff') {
return this.shaderPass.configuration.distanceFalloff;
}
if (parameterName === 'intensity') {
return this.shaderPass.configuration.intensity;
}
return 0;
}
updateStringParameter(parameterName: string, value: string): void {
if (parameterName === 'quality') {
this.shaderPass.setQualityMode(value);
}
if (parameterName === 'color') {
this.shaderPass.configuration.color = new THREE.Color(
gdjs.rgbOrHexStringToNumber(value)
);
}
}
updateColorParameter(parameterName: string, value: number): void {
if (parameterName === 'color') {
this.shaderPass.configuration.color.setHex(value);
}
}
getColorParameter(parameterName: string): number {
if (parameterName === 'color') {
return this.shaderPass.configuration.color.getHex();
}
return 0;
}
updateBooleanParameter(parameterName: string, value: boolean): void {}
getNetworkSyncData(): N8AOFilterNetworkSyncData {
return {
r: this.shaderPass.configuration.aoRadius,
d: this.shaderPass.configuration.distanceFalloff,
i: this.shaderPass.configuration.intensity,
c: this.shaderPass.configuration.color.getHex(),
};
}
updateFromNetworkSyncData(data: N8AOFilterNetworkSyncData) {
this.shaderPass.configuration.aoRadius = data.r;
this.shaderPass.configuration.distanceFalloff = data.d;
this.shaderPass.configuration.intensity = data.i;
this.shaderPass.configuration.color.setHex(data.c);
}
})();
}
})()
);
}
File diff suppressed because one or more lines are too long
@@ -215,6 +215,7 @@ namespace gdjs {
| null = null;
private _threeCameraDirty: boolean = false;
private _threeEffectComposer: THREE_ADDONS.EffectComposer | null = null;
private hasCustomRenderPass = false;
private _basis: Basis | null = null;
private static matrix4: THREE.Matrix4 | null = null;
@@ -361,12 +362,39 @@ namespace gdjs {
if (!this._threeEffectComposer) {
return false;
}
if (this.hasCustomRenderPass) {
return true;
}
const game = this._layer.getRuntimeScene().getGame();
// RenderPass, OutputPass and optionally SMAAPass are default passes.
const emptyCount = game.getAntialiasingMode() === 'none' ? 2 : 3;
return this._threeEffectComposer.passes.length > emptyCount;
}
setCustomRenderPass(pass: THREE_ADDONS.Pass | null) {
if (
!this._threeEffectComposer ||
!this._threeScene ||
!this._threeCamera
) {
return;
}
if (!this.hasCustomRenderPass && !pass) {
return;
}
const oldRenderPass = this._threeEffectComposer.passes[0];
this._threeEffectComposer.passes[0] = pass
? pass
: new THREE_ADDONS.RenderPass(this._threeScene, this._threeCamera);
if (!this.hasCustomRenderPass) {
// Dispose the default render pass
oldRenderPass.dispose();
}
this.hasCustomRenderPass = !!pass;
}
/**
* The sprite, displaying the "render texture" of the layer, to display
* for a lighting layer.
@@ -516,7 +544,7 @@ namespace gdjs {
this._threePlaneMaterial = new THREE.ShaderMaterial(
noGammaCorrectionShader
);
this._threePlaneMaterial;
this._threePlaneMaterial.depthWrite = false;
// Finally, create the mesh shown in the scene.
this._threePlaneMesh = new THREE.Mesh(
+3
View File
@@ -22,6 +22,8 @@ import { ColorCorrectionShader } from 'three/examples/jsm/shaders/ColorCorrectio
import { HueSaturationShader } from 'three/examples/jsm/shaders/HueSaturationShader';
import { ExposureShader } from 'three/examples/jsm/shaders/ExposureShader';
import { N8AOPass } from '../../../SharedLibs/ThreeAddons/src/n8ao/N8AOPass.js';
declare global {
namespace THREE_ADDONS {
export {
@@ -45,6 +47,7 @@ declare global {
ColorCorrectionShader,
HueSaturationShader,
ExposureShader,
N8AOPass,
};
}
}
+7 -6
View File
@@ -14,14 +14,15 @@
"format": "prettier --write \"src/**/*.ts\"",
"check-format": "prettier --list-different \"src/**/*.ts\""
},
"dependencies": {
},
"dependencies": {},
"devDependencies": {
"three": "0.185.1",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^13.1.3",
"rollup": "^2.66.1",
"@rollup/plugin-terser": "^0.4.0",
"prettier": "2.1.2"
"n8ao": "2.0.1",
"prettier": "2.1.2",
"rollup": "^2.66.1",
"three": "0.185.1"
},
"repository": {
"type": "git",
@@ -34,4 +35,4 @@
"url": ""
},
"homepage": ""
}
}
+3
View File
@@ -1,6 +1,7 @@
//import pkg from "./package.json";
import resolve from '@rollup/plugin-node-resolve';
import terser from '@rollup/plugin-terser';
import json from '@rollup/plugin-json';
export default [
{
@@ -29,6 +30,7 @@ export default [
extensions: ['.js'],
}),
terser(),
json(),
],
},
{
@@ -61,6 +63,7 @@ export default [
extensions: ['.js'],
}),
terser(),
json(),
],
},
];
+2
View File
@@ -3,6 +3,8 @@
* @module ThreeAddons
*/
export { N8AOPass } from "./n8ao/N8AOPass";
export { GLTFLoader } from "./examples/jsm/loaders/GLTFLoader";
export { DRACOLoader } from "./examples/jsm/loaders/DRACOLoader";
File diff suppressed because one or more lines are too long
@@ -0,0 +1,138 @@
import * as THREE from 'three';
const DepthDownSample = {
uniforms: {
'sceneDepth': { value: null },
'resolution': { value: /* @__PURE__ */ new THREE.Vector2() },
'near': { value: 0.1 },
'far': { value: 1000.0 },
'viewMatrixInv': { value: /* @__PURE__ */ new THREE.Matrix4() },
'projectionMatrixInv': { value: /* @__PURE__ */ new THREE.Matrix4() },
'logDepth': { value: false },
'ortho': { value: false }
},
depthWrite: false,
depthTest: false,
vertexShader: /* glsl */ `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = vec4(position, 1);
}`,
fragmentShader: /* glsl */ `
uniform highp sampler2D sceneDepth;
uniform vec2 resolution;
uniform float near;
uniform float far;
uniform bool logDepth;
uniform bool ortho;
uniform mat4 viewMatrixInv;
uniform mat4 projectionMatrixInv;
varying vec2 vUv;
layout(location = 1) out vec4 gNormal;
vec3 getWorldPosLog(vec3 posS) {
vec2 uv = posS.xy;
float z = posS.z;
float nearZ =near;
float farZ = far;
float depth = pow(2.0, z * log2(farZ + 1.0)) - 1.0;
float a = farZ / (farZ - nearZ);
float b = farZ * nearZ / (nearZ - farZ);
float linDepth = a + b / depth;
vec4 clipVec = vec4(uv, linDepth, 1.0) * 2.0 - 1.0;
vec4 wpos = projectionMatrixInv * clipVec;
return wpos.xyz / wpos.w;
}
vec3 getWorldPos(float depth, vec2 coord) {
if (logDepth && !ortho) {
return getWorldPosLog(vec3(coord, depth));
}
if (ortho) {
#ifdef REVERSEDEPTH
float z = depth;
#else
float z = depth * 2. - 1.;
#endif
vec4 clipSpacePosition = vec4(coord * 2. - 1., z, 1.);
vec4 viewSpacePosition = projectionMatrixInv * clipSpacePosition;
viewSpacePosition.xyz /= viewSpacePosition.w;
return viewSpacePosition.xyz;
}
vec2 ndc = coord * 2. - 1.;
#ifdef REVERSEDEPTH
float ndcZ = depth;
#else
float ndcZ = depth * 2. - 1.;
#endif
mat4 Q = projectionMatrixInv;
vec3 view = vec3(Q[0][0] * ndc.x + Q[3][0], Q[1][1] * ndc.y + Q[3][1], Q[3][2]);
float invW = 1. / (Q[2][3] * ndcZ + Q[3][3]);
return view * invW;
}
vec3 computeNormal(vec3 worldPos, vec2 vUv) {
ivec2 p = ivec2(vUv * resolution);
float c0 = texelFetch(sceneDepth, p, 0).x;
float l2 = texelFetch(sceneDepth, p - ivec2(2, 0), 0).x;
float l1 = texelFetch(sceneDepth, p - ivec2(1, 0), 0).x;
float r1 = texelFetch(sceneDepth, p + ivec2(1, 0), 0).x;
float r2 = texelFetch(sceneDepth, p + ivec2(2, 0), 0).x;
float b2 = texelFetch(sceneDepth, p - ivec2(0, 2), 0).x;
float b1 = texelFetch(sceneDepth, p - ivec2(0, 1), 0).x;
float t1 = texelFetch(sceneDepth, p + ivec2(0, 1), 0).x;
float t2 = texelFetch(sceneDepth, p + ivec2(0, 2), 0).x;
float dl = abs((2.0 * l1 - l2) - c0);
float dr = abs((2.0 * r1 - r2) - c0);
float db = abs((2.0 * b1 - b2) - c0);
float dt = abs((2.0 * t1 - t2) - c0);
vec3 ce = getWorldPos(c0, vUv).xyz;
vec3 dpdx = (dl < dr) ? ce - getWorldPos(l1, (vUv - vec2(1.0 / resolution.x, 0.0))).xyz
: -ce + getWorldPos(r1, (vUv + vec2(1.0 / resolution.x, 0.0))).xyz;
vec3 dpdy = (db < dt) ? ce - getWorldPos(b1, (vUv - vec2(0.0, 1.0 / resolution.y))).xyz
: -ce + getWorldPos(t1, (vUv + vec2(0.0, 1.0 / resolution.y))).xyz;
return normalize(cross(dpdx, dpdy));
}
void main() {
vec2 uv = vUv - vec2(0.5) / resolution;
vec2 pixelSize = vec2(1.0) / resolution;
highp vec2[4] uvSamples;
uvSamples[0] = uv;
uvSamples[1] = uv + vec2(pixelSize.x, 0.0);
uvSamples[2] = uv + vec2(0.0, pixelSize.y);
uvSamples[3] = uv + pixelSize;
float depth00 = texture2D(sceneDepth, uvSamples[0]).r;
float depth10 = texture2D(sceneDepth, uvSamples[1]).r;
float depth01 = texture2D(sceneDepth, uvSamples[2]).r;
float depth11 = texture2D(sceneDepth, uvSamples[3]).r;
float minDepth = min(min(depth00, depth10), min(depth01, depth11));
float maxDepth = max(max(depth00, depth10), max(depth01, depth11));
float targetDepth = minDepth;
// Checkerboard pattern to avoid artifacts
if (mod(gl_FragCoord.x + gl_FragCoord.y, 2.0) > 0.5) {
targetDepth = maxDepth;
}
int chosenIndex = 0;
float[4] samples;
samples[0] = depth00;
samples[1] = depth10;
samples[2] = depth01;
samples[3] = depth11;
for(int i = 0; i < 4; ++i) {
if (samples[i] == targetDepth) {
chosenIndex = i;
break;
}
}
gl_FragColor = vec4(samples[chosenIndex], 0.0, 0.0, 1.0);
gNormal = vec4(computeNormal(
getWorldPos(samples[chosenIndex], uvSamples[chosenIndex]), uvSamples[chosenIndex]
), 0.0);
}`
};
export { DepthDownSample };
@@ -0,0 +1,289 @@
import * as THREE from 'three';
const EffectCompositer = {
uniforms: {
'sceneDiffuse': { value: null },
'sceneDepth': { value: null },
'tDiffuse': { value: null },
'transparencyDWFalse': { value: null },
'transparencyDWTrue': { value: null },
'transparencyDWTrueDepth': { value: null },
'transparencyAware': { value: false },
'projMat': { value: /* @__PURE__ */ new THREE.Matrix4() },
'viewMat': { value: /* @__PURE__ */ new THREE.Matrix4() },
'projectionMatrixInv': { value: /* @__PURE__ */ new THREE.Matrix4() },
'resolution': { value: /* @__PURE__ */ new THREE.Vector2() },
'color': { value: /* @__PURE__ */ new THREE.Vector3(0, 0, 0) },
'blueNoise': { value: null },
'downsampledDepth': { value: null },
'time': { value: 0.0 },
'intensity': { value: 10.0 },
'renderMode': { value: 0.0 },
"gammaCorrection": { value: false },
"ortho": { value: false },
"near": { value: 0.1 },
"far": { value: 1000.0 },
"screenSpaceRadius": { value: false },
"radius": { value: 0.0 },
"distanceFalloff": { value: 1.0 },
'fog': { value: false },
'fogExp': { value: false },
'fogDensity': { value: 0.0 },
'fogNear': { value: Infinity },
'fogFar': { value: Infinity },
'colorMultiply': { value: true },
'aoTones': { value: 0.0 }
},
depthWrite: false,
depthTest: false,
vertexShader: /* glsl */ `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = vec4(position, 1);
}`,
fragmentShader: /* glsl */ `
uniform sampler2D sceneDiffuse;
uniform highp sampler2D sceneDepth;
uniform highp sampler2D downsampledDepth;
uniform highp sampler2D transparencyDWFalse;
uniform highp sampler2D transparencyDWTrue;
uniform highp sampler2D transparencyDWTrueDepth;
uniform sampler2D tDiffuse;
uniform sampler2D blueNoise;
uniform vec2 resolution;
uniform vec3 color;
uniform mat4 projectionMatrixInv;
uniform float intensity;
uniform float renderMode;
uniform float near;
uniform float far;
uniform float aoTones;
uniform bool gammaCorrection;
uniform bool ortho;
uniform bool screenSpaceRadius;
uniform bool fog;
uniform bool fogExp;
uniform bool colorMultiply;
uniform bool transparencyAware;
uniform float fogDensity;
uniform float fogNear;
uniform float fogFar;
uniform float radius;
uniform float distanceFalloff;
varying vec2 vUv;
highp float linearize_depth(highp float d, highp float zNear,highp float zFar)
{
return (zFar * zNear) / (zFar - d * (zFar - zNear));
}
highp float linearize_depth_ortho(highp float d, highp float nearZ, highp float farZ) {
return nearZ + (farZ - nearZ) * d;
}
highp float linearize_depth_log(highp float d, highp float nearZ,highp float farZ) {
float depth = pow(2.0, d * log2(farZ + 1.0)) - 1.0;
float a = farZ / (farZ - nearZ);
float b = farZ * nearZ / (nearZ - farZ);
float linDepth = a + b / depth;
return ortho ? linearize_depth_ortho(
linDepth,
nearZ,
farZ
) :linearize_depth(linDepth, nearZ, farZ);
}
vec3 getWorldPosLog(vec3 posS) {
vec2 uv = posS.xy;
float z = posS.z;
float nearZ =near;
float farZ = far;
float depth = pow(2.0, z * log2(farZ + 1.0)) - 1.0;
float a = farZ / (farZ - nearZ);
float b = farZ * nearZ / (nearZ - farZ);
float linDepth = a + b / depth;
vec4 clipVec = vec4(uv, linDepth, 1.0) * 2.0 - 1.0;
vec4 wpos = projectionMatrixInv * clipVec;
return wpos.xyz / wpos.w;
}
float depthToClipZ(float depth) {
#ifdef REVERSEDEPTH
return depth;
#else
return depth * 2.0 - 1.0;
#endif
}
bool isBackgroundDepth(float depth) {
#ifdef REVERSEDEPTH
return depth == 0.0;
#else
return depth == 1.0;
#endif
}
vec3 getWorldPos(float depth, vec2 coord) {
#ifdef LOGDEPTH
#ifndef ORTHO
return getWorldPosLog(vec3(coord, depth));
#endif
#endif
// }
#ifdef ORTHO
float z = depthToClipZ(depth);
vec4 clipSpacePosition = vec4(coord * 2. - 1., z, 1.);
vec4 viewSpacePosition = projectionMatrixInv * clipSpacePosition;
viewSpacePosition.xyz /= viewSpacePosition.w;
return viewSpacePosition.xyz;
#else
vec2 ndc = coord * 2. - 1.;
float ndcZ = depthToClipZ(depth);
mat4 Q = projectionMatrixInv;
vec3 view = vec3(Q[0][0] * ndc.x + Q[3][0], Q[1][1] * ndc.y + Q[3][1], Q[3][2]);
float invW = 1. / (Q[2][3] * ndcZ + Q[3][3]);
return view * invW;
#endif
}
vec3 computeNormal(vec3 worldPos, vec2 vUv) {
ivec2 p = ivec2(vUv * resolution);
float c0 = texelFetch(sceneDepth, p, 0).x;
float l2 = texelFetch(sceneDepth, p - ivec2(2, 0), 0).x;
float l1 = texelFetch(sceneDepth, p - ivec2(1, 0), 0).x;
float r1 = texelFetch(sceneDepth, p + ivec2(1, 0), 0).x;
float r2 = texelFetch(sceneDepth, p + ivec2(2, 0), 0).x;
float b2 = texelFetch(sceneDepth, p - ivec2(0, 2), 0).x;
float b1 = texelFetch(sceneDepth, p - ivec2(0, 1), 0).x;
float t1 = texelFetch(sceneDepth, p + ivec2(0, 1), 0).x;
float t2 = texelFetch(sceneDepth, p + ivec2(0, 2), 0).x;
float dl = abs((2.0 * l1 - l2) - c0);
float dr = abs((2.0 * r1 - r2) - c0);
float db = abs((2.0 * b1 - b2) - c0);
float dt = abs((2.0 * t1 - t2) - c0);
vec3 ce = getWorldPos(c0, vUv).xyz;
vec3 dpdx = (dl < dr) ? ce - getWorldPos(l1, (vUv - vec2(1.0 / resolution.x, 0.0))).xyz
: -ce + getWorldPos(r1, (vUv + vec2(1.0 / resolution.x, 0.0))).xyz;
vec3 dpdy = (db < dt) ? ce - getWorldPos(b1, (vUv - vec2(0.0, 1.0 / resolution.y))).xyz
: -ce + getWorldPos(t1, (vUv + vec2(0.0, 1.0 / resolution.y))).xyz;
return normalize(cross(dpdx, dpdy));
}
#include <common>
#include <dithering_pars_fragment>
void main() {
//vec4 texel = texture2D(tDiffuse, vUv);//vec3(0.0);
vec4 sceneTexel = texture2D(sceneDiffuse, vUv);
float depth = texture2D(sceneDepth, vUv).x;
#ifdef HALFRES
vec4 texel = vec4(0.0);
if (isBackgroundDepth(depth)) {
texel = vec4(1.0, 0.0, 0.0, 1.0);
} else {
vec3 worldPos = getWorldPos(depth, vUv);
vec3 normal = computeNormal(getWorldPos(depth, vUv), vUv);
// vec4 texel = texture2D(tDiffuse, vUv);
// Find closest depth;
float totalWeight = 0.0;
float radiusToUse = screenSpaceRadius ? distance(
worldPos,
getWorldPos(depth, vUv +
vec2(radius, 0.0) / resolution)
) : radius;
float distanceFalloffToUse =screenSpaceRadius ?
radiusToUse * distanceFalloff
: distanceFalloff;
for(float x = -1.0; x <= 1.0; x++) {
for(float y = -1.0; y <= 1.0; y++) {
vec2 offset = vec2(x, y);
ivec2 p = ivec2(
(vUv * resolution * 0.5) + offset
);
vec2 pUv = vec2(p) / (resolution * 0.5);
float sampleDepth = texelFetch(downsampledDepth,p, 0).x;
vec4 sampleInfo = texelFetch(tDiffuse, p, 0);
vec3 normalSample = sampleInfo.gba * 2.0 - 1.0;
vec3 worldPosSample = getWorldPos(sampleDepth, pUv);
float tangentPlaneDist = abs(dot(worldPosSample - worldPos, normal));
float rangeCheck = exp(-1.0 * tangentPlaneDist * (1.0 / distanceFalloffToUse)) * max(dot(normal, normalSample), 0.0);
float weight = rangeCheck;
totalWeight += weight;
texel += sampleInfo * weight;
}
}
if (totalWeight == 0.0) {
texel = texture2D(tDiffuse, vUv);
} else {
texel /= totalWeight;
}
}
#else
vec4 texel = texture2D(tDiffuse, vUv);
#endif
#ifdef LOGDEPTH
texel.r = clamp(texel.r, 0.0, 1.0);
if (texel.r == 0.0) {
texel.r = 1.0;
}
#endif
float finalAo = pow(texel.r, intensity);
if (aoTones > 0.0) {
finalAo = ceil(finalAo * aoTones) / aoTones;
}
float fogFactor = 0.0;
if (fog) {
float fogDepth = -getWorldPos(depth, vUv).z;
if (fogExp) {
fogFactor = 1.0 - exp( - fogDensity * fogDensity * fogDepth * fogDepth );
} else {
fogFactor = smoothstep( fogNear, fogFar, fogDepth );
}
}
if (transparencyAware) {
float transparencyDWOff = texture2D(transparencyDWFalse, vUv).a;
float transparencyDWOn = texture2D(transparencyDWTrue, vUv).a;
float adjustmentFactorOff = transparencyDWOff;
float depthSample = texture2D(sceneDepth, vUv).r;
float trueDepthSample = texture2D(transparencyDWTrueDepth, vUv).r;
float adjustmentFactorOn = (1.0 - transparencyDWOn) * (
trueDepthSample == depthSample ? 1.0 : 0.0
);
float adjustmentFactor = max(adjustmentFactorOff, adjustmentFactorOn);
finalAo = mix(finalAo, 1.0, adjustmentFactor);
}
finalAo = mix(finalAo, 1.0, fogFactor);
vec3 aoApplied = color * mix(vec3(1.0), sceneTexel.rgb, float(colorMultiply));
if (renderMode == 0.0) {
gl_FragColor = vec4( mix(sceneTexel.rgb, aoApplied, 1.0 - finalAo), sceneTexel.a);
} else if (renderMode == 1.0) {
gl_FragColor = vec4( mix(vec3(1.0), aoApplied, 1.0 - finalAo), sceneTexel.a);
} else if (renderMode == 2.0) {
gl_FragColor = vec4( sceneTexel.rgb, sceneTexel.a);
} else if (renderMode == 3.0) {
if (vUv.x < 0.5) {
gl_FragColor = vec4( sceneTexel.rgb, sceneTexel.a);
} else if (abs(vUv.x - 0.5) < 1.0 / resolution.x) {
gl_FragColor = vec4(1.0);
} else {
gl_FragColor = vec4( mix(sceneTexel.rgb, aoApplied, 1.0 - finalAo), sceneTexel.a);
}
} else if (renderMode == 4.0) {
if (vUv.x < 0.5) {
gl_FragColor = vec4( sceneTexel.rgb, sceneTexel.a);
} else if (abs(vUv.x - 0.5) < 1.0 / resolution.x) {
gl_FragColor = vec4(1.0);
} else {
gl_FragColor = vec4( mix(vec3(1.0), aoApplied, 1.0 - finalAo), sceneTexel.a);
}
}
#include <dithering_fragment>
if (gammaCorrection) {
gl_FragColor = sRGBTransferOETF(gl_FragColor);
}
}
`
}
export { EffectCompositer };
@@ -0,0 +1,286 @@
import * as THREE from 'three';
const EffectShader = {
uniforms: {
'sceneDiffuse': { value: null },
'sceneDepth': { value: null },
'sceneNormal': { value: null },
'projMat': { value: /* @__PURE__ */ new THREE.Matrix4() },
'viewMat': { value: /* @__PURE__ */ new THREE.Matrix4() },
'projViewMat': { value: /* @__PURE__ */ new THREE.Matrix4() },
'projectionMatrixInv': { value: /* @__PURE__ */ new THREE.Matrix4() },
'viewMatrixInv': { value: /* @__PURE__ */ new THREE.Matrix4() },
'cameraPos': { value: /* @__PURE__ */ new THREE.Vector3() },
'resolution': { value: /* @__PURE__ */ new THREE.Vector2() },
'biasAdjustment': { value: /* @__PURE__ */ new THREE.Vector2() },
'time': { value: 0.0 },
'samples': { value: [] },
'bluenoise': { value: null },
'distanceFalloff': { value: 1.0 },
'radius': { value: 5.0 },
'near': { value: 0.1 },
'far': { value: 1000.0 },
'ortho': { value: false },
'screenSpaceRadius': { value: false },
'frame': { value: 0.0 }
},
depthWrite: false,
depthTest: false,
vertexShader: /* glsl */ `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = vec4(position, 1);
}`,
fragmentShader: /* glsl */ `
#define SAMPLES 16
#define FSAMPLES 16.0
uniform sampler2D sceneDiffuse;
uniform highp sampler2D sceneNormal;
uniform highp sampler2D sceneDepth;
uniform mat4 projectionMatrixInv;
uniform mat4 viewMatrixInv;
uniform mat4 projMat;
uniform mat4 viewMat;
uniform mat4 projViewMat;
uniform vec3 cameraPos;
uniform vec2 resolution;
uniform vec2 biasAdjustment;
uniform float time;
uniform vec3[SAMPLES] samples;
uniform float radius;
uniform float distanceFalloff;
uniform float near;
uniform float far;
uniform float frame;
uniform bool ortho;
uniform bool screenSpaceRadius;
uniform sampler2D bluenoise;
varying vec2 vUv;
highp float linearize_depth(highp float d, highp float zNear,highp float zFar)
{
return (zFar * zNear) / (zFar - d * (zFar - zNear));
}
highp float linearize_depth_ortho(highp float d, highp float nearZ, highp float farZ) {
return nearZ + (farZ - nearZ) * d;
}
highp float linearize_depth_log(highp float d, highp float nearZ,highp float farZ) {
float depth = pow(2.0, d * log2(farZ + 1.0)) - 1.0;
float a = farZ / (farZ - nearZ);
float b = farZ * nearZ / (nearZ - farZ);
float linDepth = a + b / depth;
/*return ortho ? linearize_depth_ortho(
linDepth,
nearZ,
farZ
) :linearize_depth(linDepth, nearZ, farZ);*/
#ifdef ORTHO
return linearize_depth_ortho(d, nearZ, farZ);
#else
return linearize_depth(linDepth, nearZ, farZ);
#endif
}
vec3 getWorldPosLog(vec3 posS) {
vec2 uv = posS.xy;
float z = posS.z;
float nearZ =near;
float farZ = far;
float depth = pow(2.0, z * log2(farZ + 1.0)) - 1.0;
float a = farZ / (farZ - nearZ);
float b = farZ * nearZ / (nearZ - farZ);
float linDepth = a + b / depth;
vec4 clipVec = vec4(uv, linDepth, 1.0) * 2.0 - 1.0;
vec4 wpos = projectionMatrixInv * clipVec;
return wpos.xyz / wpos.w;
}
float depthToClipZ(float depth) {
#ifdef REVERSEDEPTH
return depth;
#else
return depth * 2.0 - 1.0;
#endif
}
bool isBackgroundDepth(float depth) {
#ifdef REVERSEDEPTH
return depth == 0.0;
#else
return depth == 1.0;
#endif
}
vec3 getWorldPos(float depth, vec2 coord) {
#ifdef LOGDEPTH
#ifndef ORTHO
return getWorldPosLog(vec3(coord, depth));
#endif
#endif
#ifdef ORTHO
float z = depthToClipZ(depth);
vec4 clipSpacePosition = vec4(coord * 2. - 1., z, 1.);
vec4 viewSpacePosition = projectionMatrixInv * clipSpacePosition;
viewSpacePosition.xyz /= viewSpacePosition.w;
return viewSpacePosition.xyz;
#else
vec2 ndc = coord * 2. - 1.;
float ndcZ = depthToClipZ(depth);
mat4 Q = projectionMatrixInv;
vec3 view = vec3(Q[0][0] * ndc.x + Q[3][0], Q[1][1] * ndc.y + Q[3][1], Q[3][2]);
float invW = 1. / (Q[2][3] * ndcZ + Q[3][3]);
return view * invW;
#endif
}
vec3 computeNormal(vec3 worldPos, vec2 vUv) {
ivec2 p = ivec2(vUv * resolution);
float c0 = texelFetch(sceneDepth, p, 0).x;
float l2 = texelFetch(sceneDepth, p - ivec2(2, 0), 0).x;
float l1 = texelFetch(sceneDepth, p - ivec2(1, 0), 0).x;
float r1 = texelFetch(sceneDepth, p + ivec2(1, 0), 0).x;
float r2 = texelFetch(sceneDepth, p + ivec2(2, 0), 0).x;
float b2 = texelFetch(sceneDepth, p - ivec2(0, 2), 0).x;
float b1 = texelFetch(sceneDepth, p - ivec2(0, 1), 0).x;
float t1 = texelFetch(sceneDepth, p + ivec2(0, 1), 0).x;
float t2 = texelFetch(sceneDepth, p + ivec2(0, 2), 0).x;
float dl = abs((2.0 * l1 - l2) - c0);
float dr = abs((2.0 * r1 - r2) - c0);
float db = abs((2.0 * b1 - b2) - c0);
float dt = abs((2.0 * t1 - t2) - c0);
vec3 ce = getWorldPos(c0, vUv).xyz;
vec3 dpdx = (dl < dr) ? ce - getWorldPos(l1, (vUv - vec2(1.0 / resolution.x, 0.0))).xyz
: -ce + getWorldPos(r1, (vUv + vec2(1.0 / resolution.x, 0.0))).xyz;
vec3 dpdy = (db < dt) ? ce - getWorldPos(b1, (vUv - vec2(0.0, 1.0 / resolution.y))).xyz
: -ce + getWorldPos(t1, (vUv + vec2(0.0, 1.0 / resolution.y))).xyz;
return normalize(cross(dpdx, dpdy));
}
mat3 makeRotationZ(float theta) {
float c = cos(theta);
float s = sin(theta);
return mat3(c, - s, 0,
s, c, 0,
0, 0, 1);
}
void main() {
vec4 diffuse = texture2D(sceneDiffuse, vUv);
float depth = texture2D(sceneDepth, vUv).x;
if (isBackgroundDepth(depth)) {
gl_FragColor = vec4(vec3(1.0), 1.0);
return;
}
vec3 worldPos = getWorldPos(depth, vUv);
#ifdef HALFRES
vec3 normal = texture2D(sceneNormal, vUv).rgb;
#else
vec3 normal = computeNormal(worldPos, vUv);
#endif
vec4 noise = texture2D(bluenoise, gl_FragCoord.xy / 128.0);
vec2 harmoniousNumbers = vec2(
1.618033988749895,
1.324717957244746
);
noise.rg += harmoniousNumbers * frame;
noise.rg = fract(noise.rg);
vec3 helperVec = vec3(0.0, 1.0, 0.0);
if (dot(helperVec, normal) > 0.99) {
helperVec = vec3(1.0, 0.0, 0.0);
}
vec3 tangent = normalize(cross(helperVec, normal));
vec3 bitangent = cross(normal, tangent);
mediump mat3 tbn = mat3(tangent, bitangent, normal) * makeRotationZ( noise.r * 3.1415962 * 2.0) ;
mediump float occluded = 0.0;
mediump float totalWeight = 0.0;
float radiusToUse = screenSpaceRadius ? distance(
worldPos,
getWorldPos(depth, vUv +
vec2(radius, 0.0) / resolution)
) : radius;
float distanceFalloffToUse =screenSpaceRadius ?
radiusToUse * distanceFalloff
: radiusToUse * distanceFalloff * 0.2;
float bias = (min(
0.1,
distanceFalloffToUse * 0.1
) / near) * fwidth(distance(worldPos, cameraPos)) / radiusToUse;
bias = biasAdjustment.x + biasAdjustment.y * bias;
mediump float offsetMove = noise.g;
mediump float offsetMoveInv = 1.0 / FSAMPLES;
float farTimesNear = far * near;
float farMinusNear = far - near;
for(int i = 0; i < SAMPLES; i++) {
mediump vec3 sampleDirection = tbn * samples[i];
float moveAmt = fract(offsetMove);
offsetMove += offsetMoveInv;
vec3 samplePos = worldPos + radiusToUse * moveAmt * sampleDirection;
vec4 offset = projMat * vec4(samplePos, 1.0);
offset.xyz /= offset.w;
offset.xy = offset.xy * 0.5 + 0.5;
#ifndef REVERSEDEPTH
offset.z = offset.z * 0.5 + 0.5;
#endif
if (all(greaterThan(offset.xyz * (1.0 - offset.xyz), vec3(0.0)))) {
float sampleDepth = textureLod(sceneDepth, offset.xy, 0.0).x;
/*#ifdef LOGDEPTH
float distSample = linearize_depth_log(sampleDepth, near, far);
#else
#ifdef ORTHO
float distSample = near + farMinusNear * sampleDepth;
#else
float distSample = (farTimesNear) / (far - sampleDepth * farMinusNear);
#endif
#endif*/
#ifdef REVERSEDEPTH
float distSample = -getWorldPos(sampleDepth, offset.xy).z;
#else
#ifdef ORTHO
float distSample = near + sampleDepth * farMinusNear;
#else
#ifdef LOGDEPTH
float distSample = linearize_depth_log(sampleDepth, near, far);
#else
float distSample = (farTimesNear) / (far - sampleDepth * farMinusNear);
#endif
#endif
#endif
#ifdef REVERSEDEPTH
float distWorld = -samplePos.z;
#else
#ifdef ORTHO
float distWorld = near + offset.z * farMinusNear;
#else
float distWorld = (farTimesNear) / (far - offset.z * farMinusNear);
#endif
#endif
mediump float rangeCheck = smoothstep(0.0, 1.0, distanceFalloffToUse / (abs(distSample - distWorld)));
vec2 diff = gl_FragCoord.xy - floor(offset.xy * resolution);
occluded += rangeCheck * float(distSample != distWorld) * float(sampleDepth != depth) * step(distSample + bias, distWorld) * step(
1.0,
dot(diff, diff)
);
totalWeight ++;
}
}
float occ = clamp(1.0 - occluded / (totalWeight == 0.0 ? 1.0 : totalWeight), 0.0, 1.0);
gl_FragColor = vec4(occ, 0.5 + 0.5 * normal);
}`
};
export { EffectShader };
@@ -0,0 +1,40 @@
import * as THREE from 'three';
class FullScreenTriangleGeometry extends THREE.BufferGeometry {
boundingSphere = new THREE.Sphere();
constructor() {
super()
this.setAttribute('position', new THREE.BufferAttribute(new Float32Array([-1, -1, 3, -1, -1, 3]), 2));
this.setAttribute('uv', new THREE.BufferAttribute(new Float32Array([0, 0, 2, 0, 0, 2]), 2));
}
computeBoundingSphere() {}
}
const _geometry = /* @__PURE__ */ new FullScreenTriangleGeometry();
const _camera = /* @__PURE__ */ new THREE.OrthographicCamera();
export class FullScreenTriangle {
constructor(material) {
this._mesh = new THREE.Mesh(_geometry, material);
this._mesh.frustumCulled = false;
}
render(renderer) {
renderer.render(this._mesh, _camera);
}
get material() {
return this._mesh.material;
}
set material(value) {
this._mesh.material = value;
}
dispose() {
this._mesh.material.dispose();
this._mesh.geometry.dispose();
}
}
+879
View File
@@ -0,0 +1,879 @@
import * as THREE from 'three';
import { Pass } from "three/examples/jsm/postprocessing/Pass.js";
import { FullScreenTriangle } from './FullScreenTriangle.js';
import { EffectShader } from './EffectShader.js';
import { EffectCompositer } from './EffectCompositer.js';
import { PoissionBlur } from './PoissionBlur.js';
import { DepthDownSample } from "./DepthDownSample.js";
import { N8AOPostPass } from './N8AOPostPass.js';
import bluenoiseBits from './BlueNoise.js';
import { WebGLMultipleRenderTargetsCompat } from './compat.js';
/**
*
* @param {*} timerQuery
* @param {THREE.WebGLRenderer} gl
* @param {N8AOPass} pass
*/
function checkTimerQuery(timerQuery, gl, pass) {
const available = gl.getQueryParameter(timerQuery, gl.QUERY_RESULT_AVAILABLE);
if (available) {
const elapsedTimeInNs = gl.getQueryParameter(timerQuery, gl.QUERY_RESULT);
const elapsedTimeInMs = elapsedTimeInNs / 1000000;
pass.lastTime = pass.lastTime === 0 ? elapsedTimeInMs : pass.timeRollingAverage * pass.lastTime + (1 - pass.timeRollingAverage) * elapsedTimeInMs;
} else {
// If the result is not available yet, check again after a delay
setTimeout(() => {
checkTimerQuery(timerQuery, gl, pass);
}, 1);
}
}
export const DepthType = {
Default: 1,
Log: 2,
Reverse: 3,
};
class N8AOPass extends Pass {
/**
*
* @param {THREE.Scene} scene
* @param {THREE.Camera} camera
* @param {number} width
* @param {number} height
*
* @property {THREE.Scene} scene
* @property {THREE.Camera} camera
* @property {number} width
* @property {number} height
*/
constructor(scene, camera, width = 512, height = 512) {
super();
this.width = Math.floor(width);
this.height = Math.floor(height);
this.clear = true;
this._neuralDenoiseWarningKey = null;
this.camera = camera;
this.scene = scene;
/**
* @type {Proxy & {
* aoSamples: number,
* aoRadius: number,
* denoiseSamples: number,
* denoiseRadius: number,
* distanceFalloff: number,
* intensity: number,
* denoiseIterations: number,
* renderMode: 0 | 1 | 2 | 3 | 4,
* color: THREE.Color,
* gammaCorrection: boolean,
* depthBufferType: 1 | 2 | 3,
* screenSpaceRadius: boolean,
* halfRes: boolean,
* depthAwareUpsampling: boolean,
* autoRenderBeauty: boolean
* colorMultiply: boolean,
* neuralDenoise: boolean
* }
*/
this.configuration = new Proxy({
aoSamples: 16,
aoRadius: 5.0,
aoTones: 0.0,
denoiseSamples: 8,
denoiseRadius: 12,
distanceFalloff: 1.0,
intensity: 5,
denoiseIterations: 2.0,
renderMode: 0,
biasOffset: 0.0,
biasMultiplier: 0.0,
color: new THREE.Color(0, 0, 0),
gammaCorrection: true,
depthBufferType: DepthType.Default,
screenSpaceRadius: false,
halfRes: false,
depthAwareUpsampling: true,
autoRenderBeauty: true,
colorMultiply: true,
transparencyAware: false,
stencil: false,
accumulate: false,
neuralDenoise: false
}, {
set: (target, propName, value) => {
const oldProp = target[propName];
target[propName] = value;
if (value.equals) {
if (!value.equals(oldProp)) {
this.firstFrame();
}
} else {
if (oldProp !== value) {
this.firstFrame();
}
}
if (propName === 'aoSamples' && oldProp !== value) {
this.configureAOPass(this.configuration.depthBufferType, this.camera.isOrthographicCamera);
}
if (propName === 'denoiseSamples' && oldProp !== value) {
this.configureDenoisePass(this.configuration.depthBufferType, this.camera.isOrthographicCamera);
}
if (propName === 'neuralDenoise' && oldProp !== value) {
const reconfigureAO = value && (target.aoSamples !== 16 || target.halfRes);
const disableHalfRes = value && target.halfRes;
if (value) {
target.aoSamples = 16;
if (![4, 8, 16].includes(target.denoiseSamples)) {
target.denoiseSamples = 8;
}
target.denoiseRadius = 12;
target.denoiseIterations = 2;
target.halfRes = false;
}
if (reconfigureAO) {
this.configureAOPass(this.configuration.depthBufferType, this.camera.isOrthographicCamera);
}
if (disableHalfRes) {
this.configureHalfResTargets();
this.configureEffectCompositer(this.configuration.depthBufferType, this.camera.isOrthographicCamera);
this.setSize(this.width, this.height);
}
this.configureDenoisePass(this.configuration.depthBufferType, this.camera.isOrthographicCamera);
}
if (propName === "halfRes" && oldProp !== value) {
this.configureAOPass(this.configuration.depthBufferType, this.camera.isOrthographicCamera);
this.configureHalfResTargets();
this.configureEffectCompositer(this.configuration.depthBufferType, this.camera.isOrthographicCamera);
this.setSize(this.width, this.height);
}
if (propName === "depthAwareUpsampling" && oldProp !== value) {
this.configureEffectCompositer(this.configuration.depthBufferType, this.camera.isOrthographicCamera);
}
if (propName === "transparencyAware" && oldProp !== value) {
this.autoDetectTransparency = false;
this.configureTransparencyTarget();
}
if (propName === "stencil" && oldProp !== value) {
/* this.beautyRenderTarget.stencilBuffer = value;
this.beautyRenderTarget.depthTexture.format = value ? THREE.DepthStencilFormat : THREE.DepthFormat;
this.beautyRenderTarget.depthTexture.type = value ? THREE.UnsignedInt248Type : THREE.UnsignedIntType;
this.beautyRenderTarget.depthTexture.needsUpdate = true;
this.beautyRenderTarget.needsUpdate = true;*/
this.beautyRenderTarget.dispose();
this.beautyRenderTarget = new THREE.WebGLRenderTarget(this.width, this.height, {
minFilter: THREE.LinearFilter,
magFilter: THREE.NearestFilter,
type: THREE.HalfFloatType,
format: THREE.RGBAFormat,
stencilBuffer: value
});
this.beautyRenderTarget.depthTexture = new THREE.DepthTexture(this.width, this.height, value ? THREE.UnsignedInt248Type : THREE.UnsignedIntType);
this.beautyRenderTarget.depthTexture.format = value ? THREE.DepthStencilFormat : THREE.DepthFormat;
}
return true;
}
});
/** @type {THREE.Vector3[]} */
this.samples = [];
/** @type {THREE.Vector2[]} */
this.samplesDenoise = [];
this.autoDetectTransparency = true;
this.frame = 0;
this.lastViewMatrix = new THREE.Matrix4();
this.lastProjectionMatrix = new THREE.Matrix4();
this.beautyRenderTarget = new THREE.WebGLRenderTarget(this.width, this.height, {
minFilter: THREE.LinearFilter,
magFilter: THREE.NearestFilter,
type: THREE.HalfFloatType,
format: THREE.RGBAFormat,
stencilBuffer: false
});
this.beautyRenderTarget.depthTexture = new THREE.DepthTexture(this.width, this.height, THREE.UnsignedIntType);
this.beautyRenderTarget.depthTexture.format = THREE.DepthFormat;
this.configureEffectCompositer(this.configuration.depthBufferType, this.camera.isOrthographicCamera);
this.configureSampleDependentPasses();
this.configureHalfResTargets();
this.detectTransparency();
this.configureTransparencyTarget();
this.writeTargetInternal = new THREE.WebGLRenderTarget(this.width, this.height, {
minFilter: THREE.LinearFilter,
magFilter: THREE.LinearFilter,
depthBuffer: false,
format: THREE.RGBAFormat
});
this.readTargetInternal = new THREE.WebGLRenderTarget(this.width, this.height, {
minFilter: THREE.LinearFilter,
magFilter: THREE.LinearFilter,
depthBuffer: false,
format: THREE.RGBAFormat
});
this.accumulationRenderTarget = new THREE.WebGLRenderTarget(this.width, this.height, {
minFilter: THREE.LinearFilter,
magFilter: THREE.LinearFilter,
depthBuffer: false,
format: THREE.RGBAFormat,
type: THREE.HalfFloatType,
stencilBuffer: false,
depthBuffer: false,
alpha: true
});
/** @type {THREE.DataTexture} */
this.bluenoise = //bluenoise;
new THREE.DataTexture(
bluenoiseBits,
128,
128
);
this.accumulationQuad = new FullScreenTriangle(new THREE.ShaderMaterial({
uniforms: {
frame: { value: 0 },
tDiffuse: { value: null }
},
transparent: true,
opacity: 1,
vertexShader: `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = vec4(position, 1);
}`,
fragmentShader: `
uniform sampler2D tDiffuse;
uniform float frame;
varying vec2 vUv;
void main() {
vec4 color = texture2D(tDiffuse, vUv);
gl_FragColor = vec4(color.rgb, 1.0 / (frame + 1.0));
}
`
}));
this.bluenoise.colorSpace = THREE.NoColorSpace;
this.bluenoise.wrapS = THREE.RepeatWrapping;
this.bluenoise.wrapT = THREE.RepeatWrapping;
this.bluenoise.minFilter = THREE.NearestFilter;
this.bluenoise.magFilter = THREE.NearestFilter;
this.bluenoise.needsUpdate = true;
this.lastTime = 0;
this.timeRollingAverage = 0.99;
this._r = new THREE.Vector2();
this._c = new THREE.Color();
}
configureHalfResTargets() {
this.firstFrame();
if (this.configuration.halfRes) {
this.depthDownsampleTarget = new WebGLMultipleRenderTargetsCompat(
Math.floor(this.width / 2),
Math.floor(this.height / 2),
2
);
if (THREE.REVISION <= 161) {
this.depthDownsampleTarget.textures = this.depthDownsampleTarget.texture;
}
this.depthDownsampleTarget.textures[0].format = THREE.RedFormat;
this.depthDownsampleTarget.textures[0].type = THREE.FloatType;
this.depthDownsampleTarget.textures[0].minFilter = THREE.NearestFilter;
this.depthDownsampleTarget.textures[0].magFilter = THREE.NearestFilter;
this.depthDownsampleTarget.textures[0].depthBuffer = false;
this.depthDownsampleTarget.textures[1].format = THREE.RGBAFormat;
this.depthDownsampleTarget.textures[1].type = THREE.HalfFloatType;
this.depthDownsampleTarget.textures[1].minFilter = THREE.NearestFilter;
this.depthDownsampleTarget.textures[1].magFilter = THREE.NearestFilter;
this.depthDownsampleTarget.textures[1].depthBuffer = false;
const e = {...DepthDownSample };
if (this.configuration.depthBufferType === DepthType.Reverse) {
e.fragmentShader = "#define REVERSEDEPTH\n" + e.fragmentShader;
}
this.depthDownsampleQuad = new FullScreenTriangle(new THREE.ShaderMaterial(e));
} else {
if (this.depthDownsampleTarget) {
this.depthDownsampleTarget.dispose();
this.depthDownsampleTarget = null;
}
if (this.depthDownsampleQuad) {
this.depthDownsampleQuad.dispose();
this.depthDownsampleQuad = null;
}
}
}
detectTransparency() {
if (this.autoDetectTransparency) {
let isTransparency = false;
this.scene.traverse((obj) => {
if (obj.material && obj.material.transparent) {
isTransparency = true;
}
});
this.configuration.transparencyAware = isTransparency;
}
}
configureTransparencyTarget() {
if (this.configuration.transparencyAware) {
this.transparencyRenderTargetDWFalse = new THREE.WebGLRenderTarget(this.width, this.height, {
minFilter: THREE.LinearFilter,
magFilter: THREE.NearestFilter,
type: THREE.HalfFloatType,
format: THREE.RGBAFormat
});
this.transparencyRenderTargetDWTrue = new THREE.WebGLRenderTarget(this.width, this.height, {
minFilter: THREE.LinearFilter,
magFilter: THREE.NearestFilter,
type: THREE.HalfFloatType,
format: THREE.RGBAFormat
});
this.transparencyRenderTargetDWTrue.depthTexture = new THREE.DepthTexture(this.width, this.height, THREE.UnsignedIntType);
this.depthCopyPass = new FullScreenTriangle(new THREE.ShaderMaterial({
uniforms: {
depthTexture: { value: this.depthTexture },
reverseDepthBuffer: { value: this.configuration.depthBufferType === DepthType.Reverse },
},
vertexShader: /* glsl */ `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = vec4(position, 1);
}`,
fragmentShader: /* glsl */ `
uniform sampler2D depthTexture;
uniform bool reverseDepthBuffer;
varying vec2 vUv;
void main() {
if (reverseDepthBuffer) {
float d = 1.0 - texture2D(depthTexture, vUv).r;
d += 0.00001;
gl_FragDepth = 1.0 - d;
} else {
float d = texture2D(depthTexture, vUv).r;
d += 0.00001;
gl_FragDepth = d;
}
gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);
}
`,
}));
} else {
if (this.transparencyRenderTargetDWFalse) {
this.transparencyRenderTargetDWFalse.dispose();
this.transparencyRenderTargetDWFalse = null;
}
if (this.transparencyRenderTargetDWTrue) {
this.transparencyRenderTargetDWTrue.dispose();
this.transparencyRenderTargetDWTrue = null;
}
if (this.depthCopyPass) {
this.depthCopyPass.dispose();
this.depthCopyPass = null;
}
}
}
renderTransparency(renderer) {
const oldBackground = this.scene.background;
const oldClearColor = renderer.getClearColor(new THREE.Color());
const oldClearAlpha = renderer.getClearAlpha();
const oldVisibility = new Map();
const oldAutoClearDepth = renderer.autoClearDepth;
this.scene.traverse((obj) => {
oldVisibility.set(obj, obj.visible);
});
// Override the state
this.scene.background = null;
renderer.autoClearDepth = false;
renderer.setClearColor(new THREE.Color(0, 0, 0), 0);
this.depthCopyPass.material.uniforms.depthTexture.value = this.beautyRenderTarget.depthTexture;
this.depthCopyPass.material.uniforms.reverseDepthBuffer.value = this.configuration.depthBufferType === DepthType.Reverse;
// Render out transparent objects WITHOUT depth write
renderer.setRenderTarget(this.transparencyRenderTargetDWFalse);
this.scene.traverse((obj) => {
if (obj.material) {
obj.visible = oldVisibility.get(obj) && ((obj.material.transparent && !obj.material.depthWrite && !obj.userData.treatAsOpaque) || !!obj.userData.cannotReceiveAO);
}
});
renderer.clear(true, true, true);
this.depthCopyPass.render(renderer);
renderer.render(this.scene, this.camera);
// Render out transparent objects WITH depth write
renderer.setRenderTarget(this.transparencyRenderTargetDWTrue);
this.scene.traverse((obj) => {
if (obj.material) {
obj.visible = oldVisibility.get(obj) && obj.material.transparent && obj.material.depthWrite && !obj.userData.treatAsOpaque;
}
});
renderer.clear(true, true, true);
this.depthCopyPass.render(renderer);
renderer.render(this.scene, this.camera);
// Restore
this.scene.traverse((obj) => {
obj.visible = oldVisibility.get(obj);
});
renderer.setClearColor(oldClearColor, oldClearAlpha);
this.scene.background = oldBackground;
renderer.autoClearDepth = oldAutoClearDepth;
}
configureSampleDependentPasses() {
this.firstFrame();
this.configureAOPass(this.configuration.depthBufferType, this.camera.isOrthographicCamera);
this.configureDenoisePass(this.configuration.depthBufferType, this.camera.isOrthographicCamera);
}
configureAOPass(depthBufferType = DepthType.Default, ortho = false) {
this.firstFrame();
this.samples = this.generateHemisphereSamples(this.configuration.aoSamples);
const e = {...EffectShader };
e.fragmentShader = e.fragmentShader.replace("16", this.configuration.aoSamples).replace("16.0", this.configuration.aoSamples + ".0");
if (depthBufferType === DepthType.Log) {
e.fragmentShader = "#define LOGDEPTH\n" + e.fragmentShader;
} else if (depthBufferType === DepthType.Reverse) {
e.fragmentShader = "#define REVERSEDEPTH\n" + e.fragmentShader;
}
if (ortho) {
e.fragmentShader = "#define ORTHO\n" + e.fragmentShader;
}
if (this.configuration.halfRes) {
e.fragmentShader = "#define HALFRES\n" + e.fragmentShader;
}
if (this.effectShaderQuad) {
this.effectShaderQuad.material.dispose();
this.effectShaderQuad.material = new THREE.ShaderMaterial(e);
} else {
this.effectShaderQuad = new FullScreenTriangle(new THREE.ShaderMaterial(e));
}
}
configureDenoisePass(depthBufferType = DepthType.Default, ortho = false) {
this.firstFrame();
this.samplesDenoise = this.generateDenoiseSamples(this.configuration.denoiseSamples, 11);
const p = {...PoissionBlur };
p.fragmentShader = p.fragmentShader.replace(
"__N8AO_DENOISE_SAMPLES__",
this.configuration.denoiseSamples
);
if (depthBufferType === DepthType.Log) {
p.fragmentShader = "#define LOGDEPTH\n" + p.fragmentShader;
} else if (depthBufferType === DepthType.Reverse) {
p.fragmentShader = "#define REVERSEDEPTH\n" + p.fragmentShader;
}
if (ortho) {
p.fragmentShader = "#define ORTHO\n" + p.fragmentShader;
}
if (this.standardDenoiseMaterial) {
this.standardDenoiseMaterial.dispose();
}
if (this.neuralDenoiseMaterial) {
this.neuralDenoiseMaterial.dispose();
}
this.standardDenoiseMaterial = new THREE.ShaderMaterial(p);
this.neuralDenoiseMaterial = this.configuration.neuralDenoise
&& [4, 8, 16].includes(this.configuration.denoiseSamples)
? new THREE.ShaderMaterial({
...p,
glslVersion: THREE.GLSL3,
fragmentShader: "#define NEURAL_DENOISE\n"
+ "layout(location = 0) out highp vec4 neuralFragColor;\n"
+ "#define gl_FragColor neuralFragColor\n"
+ p.fragmentShader
})
: null;
if (this.poissonBlurQuad) {
this.poissonBlurQuad.material = this.standardDenoiseMaterial;
} else {
this.poissonBlurQuad = new FullScreenTriangle(this.standardDenoiseMaterial);
}
}
shouldUseNeuralDenoise() {
if (!this.configuration.neuralDenoise) {
this._neuralDenoiseWarningKey = null;
return false;
}
const incompatibilities = [];
if (this.configuration.aoSamples !== 16) {
incompatibilities.push(`aoSamples is ${this.configuration.aoSamples} (expected 16)`);
}
if (![4, 8, 16].includes(this.configuration.denoiseSamples)) {
incompatibilities.push(`denoiseSamples is ${this.configuration.denoiseSamples} (expected 4, 8, or 16)`);
}
if (this.configuration.denoiseRadius !== 12) {
incompatibilities.push(`denoiseRadius is ${this.configuration.denoiseRadius} (expected 12)`);
}
if (this.configuration.denoiseIterations !== 2) {
incompatibilities.push(`denoiseIterations is ${this.configuration.denoiseIterations} (expected 2)`);
}
if (this.configuration.halfRes) {
incompatibilities.push('halfRes is enabled (expected full resolution)');
}
if (incompatibilities.length > 0) {
const warningKey = incompatibilities.join('; ');
if (warningKey !== this._neuralDenoiseWarningKey) {
console.warn(
`[N8AO] neuralDenoise is enabled but cannot run: ${warningKey}. `
+ 'Neural denoising has been disabled; standard denoising will be used.'
);
this._neuralDenoiseWarningKey = warningKey;
}
return false;
}
this._neuralDenoiseWarningKey = null;
return this.neuralDenoiseMaterial !== null;
}
configureEffectCompositer(depthBufferType = DepthType.Default, ortho = false) {
this.firstFrame();
const e = {...EffectCompositer };
if (depthBufferType === DepthType.Log) {
e.fragmentShader = "#define LOGDEPTH\n" + e.fragmentShader;
} else if (depthBufferType === DepthType.Reverse) {
e.fragmentShader = "#define REVERSEDEPTH\n" + e.fragmentShader;
}
if (ortho) {
e.fragmentShader = "#define ORTHO\n" + e.fragmentShader;
}
if (this.configuration.halfRes && this.configuration.depthAwareUpsampling) {
e.fragmentShader = "#define HALFRES\n" + e.fragmentShader;
}
if (this.effectCompositerQuad) {
this.effectCompositerQuad.material.dispose();
this.effectCompositerQuad.material = new THREE.ShaderMaterial(e);
} else {
this.effectCompositerQuad = new FullScreenTriangle(new THREE.ShaderMaterial(e));
}
}
/**
*
* @param {Number} n
* @returns {THREE.Vector3[]}
*/
generateHemisphereSamples(n) {
const points = [];
for (let k = 0; k < n; k++) {
const theta = 2.399963 * k;
let r = (Math.sqrt(k + 0.5) / Math.sqrt(n));
const x = r * Math.cos(theta);
const y = r * Math.sin(theta);
// Project to hemisphere
const z = Math.sqrt(1 - (x * x + y * y));
points.push(new THREE.Vector3(x, y, z));
}
return points;
}
/**
*
* @param {number} numSamples
* @param {number} numRings
* @returns {THREE.Vector2[]}
*/
generateDenoiseSamples(numSamples, numRings) {
const angleStep = 2 * Math.PI * numRings / numSamples;
const invNumSamples = 1.0 / numSamples;
const radiusStep = invNumSamples;
const samples = [];
let radius = invNumSamples;
let angle = 0;
for (let i = 0; i < numSamples; i++) {
samples.push(new THREE.Vector2(Math.cos(angle), Math.sin(angle)).multiplyScalar(Math.pow(radius, 0.75)));
radius += radiusStep;
angle += angleStep;
}
return samples;
}
setSize(width, height) {
this.firstFrame();
this.width = Math.floor(width);
this.height = Math.floor(height);
const internalWidth = this.configuration.halfRes ? Math.floor(this.width / 2) : this.width;
const internalHeight = this.configuration.halfRes ? Math.floor(this.height / 2) : this.height;
this.beautyRenderTarget.setSize(this.width, this.height);
this.writeTargetInternal.setSize(internalWidth, internalHeight);
this.readTargetInternal.setSize(internalWidth, internalHeight);
this.accumulationRenderTarget.setSize(internalWidth, internalHeight);
if (this.configuration.halfRes) {
this.depthDownsampleTarget.setSize(internalWidth, internalHeight);
}
if (this.configuration.transparencyAware) {
this.transparencyRenderTargetDWFalse.setSize(this.width, this.height);
this.transparencyRenderTargetDWTrue.setSize(this.width, this.height);
}
}
firstFrame() {
this.needsFrame = true;
}
render(renderer, writeBuffer, readBuffer, deltaTime, maskActive) {
const rendererUsesReverseDepth = renderer.capabilities.reversedDepthBuffer === true
|| renderer.capabilities.reverseDepthBuffer === true;
const rendererDepthType = rendererUsesReverseDepth
? DepthType.Reverse
: renderer.capabilities.logarithmicDepthBuffer
? DepthType.Log
: DepthType.Default;
if (this.configuration.depthBufferType !== rendererDepthType) {
this.configuration.depthBufferType = rendererDepthType;
this.configureAOPass(this.configuration.depthBufferType, this.camera.isOrthographicCamera);
this.configureDenoisePass(this.configuration.depthBufferType, this.camera.isOrthographicCamera);
this.configureEffectCompositer(this.configuration.depthBufferType, this.camera.isOrthographicCamera);
}
this.detectTransparency();
this.camera.updateMatrixWorld();
if (this.lastViewMatrix.equals(this.camera.matrixWorldInverse) && this.lastProjectionMatrix.equals(this.camera.projectionMatrix) && this.configuration.accumulate && !this.needsFrame) {
this.frame++;
} else {
if (this.configuration.accumulate) {
renderer.setRenderTarget(this.accumulationRenderTarget);
renderer.clear(true, true, true);
}
this.frame = 0;
this.needsFrame = false;
}
this.lastViewMatrix.copy(this.camera.matrixWorldInverse);
this.lastProjectionMatrix.copy(this.camera.projectionMatrix);
let gl;
let ext;
let timerQuery;
if (this.debugMode) {
gl = renderer.getContext();
ext = gl.getExtension('EXT_disjoint_timer_query_webgl2');
if (ext === null) {
console.error("EXT_disjoint_timer_query_webgl2 not available, disabling debug mode.");
this.debugMode = false;
}
}
if (this.configuration.autoRenderBeauty) {
renderer.setRenderTarget(this.beautyRenderTarget);
renderer.render(this.scene, this.camera);
if (this.configuration.transparencyAware) {
this.renderTransparency(renderer);
}
}
if (this.debugMode) {
timerQuery = gl.createQuery();
gl.beginQuery(ext.TIME_ELAPSED_EXT, timerQuery);
}
const xrEnabled = renderer.xr.enabled;
renderer.xr.enabled = false;
this._r.set(this.width, this.height);
let trueRadius = this.configuration.aoRadius;
if (this.configuration.halfRes && this.configuration.screenSpaceRadius) {
trueRadius *= 0.5;
}
if (this.frame < 1024 / this.configuration.aoSamples) {
if (this.configuration.halfRes) {
renderer.setRenderTarget(this.depthDownsampleTarget);
this.depthDownsampleQuad.material.uniforms.sceneDepth.value = this.beautyRenderTarget.depthTexture;
this.depthDownsampleQuad.material.uniforms.resolution.value = this._r;
this.depthDownsampleQuad.material.uniforms["near"].value = this.camera.near;
this.depthDownsampleQuad.material.uniforms["far"].value = this.camera.far;
this.depthDownsampleQuad.material.uniforms["projectionMatrixInv"].value = this.camera.projectionMatrixInverse;
this.depthDownsampleQuad.material.uniforms["viewMatrixInv"].value = this.camera.matrixWorld;
this.depthDownsampleQuad.material.uniforms["logDepth"].value = this.configuration.depthBufferType === DepthType.Log;
this.depthDownsampleQuad.material.uniforms["ortho"].value = this.camera.isOrthographicCamera;
this.depthDownsampleQuad.render(renderer);
}
this.effectShaderQuad.material.uniforms["sceneDiffuse"].value = this.beautyRenderTarget.texture;
this.effectShaderQuad.material.uniforms["sceneDepth"].value = this.configuration.halfRes ? this.depthDownsampleTarget.textures[0] : this.beautyRenderTarget.depthTexture;
this.effectShaderQuad.material.uniforms["sceneNormal"].value = this.configuration.halfRes ? this.depthDownsampleTarget.textures[1] : null;
this.effectShaderQuad.material.uniforms["projMat"].value = this.camera.projectionMatrix;
this.effectShaderQuad.material.uniforms["viewMat"].value = this.camera.matrixWorldInverse;
this.effectShaderQuad.material.uniforms["projViewMat"].value = this.camera.projectionMatrix.clone().multiply(this.camera.matrixWorldInverse.clone());
this.effectShaderQuad.material.uniforms["projectionMatrixInv"].value = this.camera.projectionMatrixInverse;
this.effectShaderQuad.material.uniforms["viewMatrixInv"].value = this.camera.matrixWorld;
this.effectShaderQuad.material.uniforms["cameraPos"].value = this.camera.getWorldPosition(new THREE.Vector3());
this.effectShaderQuad.material.uniforms['biasAdjustment'].value = new THREE.Vector2(this.configuration.biasOffset, this.configuration.biasMultiplier);
this.effectShaderQuad.material.uniforms['resolution'].value = (this.configuration.halfRes ? this._r.clone().multiplyScalar(1 / 2).floor() : this._r);
this.effectShaderQuad.material.uniforms['time'].value = performance.now() / 1000;
this.effectShaderQuad.material.uniforms['samples'].value = this.samples;
this.effectShaderQuad.material.uniforms['bluenoise'].value = this.bluenoise;
this.effectShaderQuad.material.uniforms['radius'].value = trueRadius;
this.effectShaderQuad.material.uniforms['distanceFalloff'].value = this.configuration.distanceFalloff;
this.effectShaderQuad.material.uniforms["near"].value = this.camera.near;
this.effectShaderQuad.material.uniforms["far"].value = this.camera.far;
this.effectShaderQuad.material.uniforms["ortho"].value = this.camera.isOrthographicCamera;
this.effectShaderQuad.material.uniforms["screenSpaceRadius"].value = this.configuration.screenSpaceRadius;
this.effectShaderQuad.material.uniforms["frame"].value = this.frame;
// Start the AO
renderer.setRenderTarget(this.writeTargetInternal);
this.effectShaderQuad.render(renderer);
// End the AO
// Start the blur
const useNeuralDenoise = this.shouldUseNeuralDenoise();
for (let i = 0; i < this.configuration.denoiseIterations; i++) {
[this.writeTargetInternal, this.readTargetInternal] = [this.readTargetInternal, this.writeTargetInternal];
this.poissonBlurQuad.material = useNeuralDenoise && i === 1
? this.neuralDenoiseMaterial
: this.standardDenoiseMaterial;
this.poissonBlurQuad.material.uniforms["tDiffuse"].value = this.readTargetInternal.texture;
this.poissonBlurQuad.material.uniforms["sceneDepth"].value = this.configuration.halfRes ? this.depthDownsampleTarget.textures[0] : this.beautyRenderTarget.depthTexture;
this.poissonBlurQuad.material.uniforms["projMat"].value = this.camera.projectionMatrix;
this.poissonBlurQuad.material.uniforms["viewMat"].value = this.camera.matrixWorldInverse;
this.poissonBlurQuad.material.uniforms["projectionMatrixInv"].value = this.camera.projectionMatrixInverse;
this.poissonBlurQuad.material.uniforms["viewMatrixInv"].value = this.camera.matrixWorld;
this.poissonBlurQuad.material.uniforms["cameraPos"].value = this.camera.getWorldPosition(new THREE.Vector3());
this.poissonBlurQuad.material.uniforms['resolution'].value = (this.configuration.halfRes ? this._r.clone().multiplyScalar(1 / 2).floor() : this._r);
this.poissonBlurQuad.material.uniforms['time'].value = performance.now() / 1000;
this.poissonBlurQuad.material.uniforms['blueNoise'].value = this.bluenoise;
this.poissonBlurQuad.material.uniforms['radius'].value = this.configuration.denoiseRadius * (
this.configuration.halfRes ? 1 / 2 : 1
);
this.poissonBlurQuad.material.uniforms['worldRadius'].value = trueRadius;
this.poissonBlurQuad.material.uniforms['distanceFalloff'].value = this.configuration.distanceFalloff;
this.poissonBlurQuad.material.uniforms['index'].value = i;
this.poissonBlurQuad.material.uniforms['poissonDisk'].value = this.samplesDenoise;
this.poissonBlurQuad.material.uniforms["near"].value = this.camera.near;
this.poissonBlurQuad.material.uniforms["far"].value = this.camera.far;
this.poissonBlurQuad.material.uniforms["screenSpaceRadius"].value = this.configuration.screenSpaceRadius;
renderer.setRenderTarget(this.writeTargetInternal);
this.poissonBlurQuad.render(renderer);
}
renderer.setRenderTarget(this.accumulationRenderTarget);
const oldAutoClear = renderer.autoClear;
renderer.autoClear = false;
this.accumulationQuad.material.uniforms["tDiffuse"].value = this.writeTargetInternal.texture;
this.accumulationQuad.material.uniforms["frame"].value = this.frame;
this.accumulationQuad.render(renderer);
renderer.autoClear = oldAutoClear;
}
// Now, we have the blurred AO in writeTargetInternal
// End the blur
// Start the composition
if (this.configuration.transparencyAware) {
this.effectCompositerQuad.material.uniforms["transparencyDWFalse"].value = this.transparencyRenderTargetDWFalse.texture;
this.effectCompositerQuad.material.uniforms["transparencyDWTrue"].value = this.transparencyRenderTargetDWTrue.texture;
this.effectCompositerQuad.material.uniforms["transparencyDWTrueDepth"].value = this.transparencyRenderTargetDWTrue.depthTexture;
this.effectCompositerQuad.material.uniforms["transparencyAware"].value = true;
}
this.effectCompositerQuad.material.uniforms["sceneDiffuse"].value = this.beautyRenderTarget.texture;
this.effectCompositerQuad.material.uniforms["sceneDepth"].value = this.beautyRenderTarget.depthTexture;
this.effectCompositerQuad.material.uniforms["aoTones"].value = this.configuration.aoTones;
this.effectCompositerQuad.material.uniforms["near"].value = this.camera.near;
this.effectCompositerQuad.material.uniforms["far"].value = this.camera.far;
this.effectCompositerQuad.material.uniforms["projectionMatrixInv"].value = this.camera.projectionMatrixInverse;
this.effectCompositerQuad.material.uniforms["ortho"].value = this.camera.isOrthographicCamera;
this.effectCompositerQuad.material.uniforms["downsampledDepth"].value = this.configuration.halfRes ? this.depthDownsampleTarget.textures[0] : this.beautyRenderTarget.depthTexture;
this.effectCompositerQuad.material.uniforms["resolution"].value = this._r;
this.effectCompositerQuad.material.uniforms["blueNoise"].value = this.bluenoise;
this.effectCompositerQuad.material.uniforms["intensity"].value = this.configuration.intensity;
this.effectCompositerQuad.material.uniforms["renderMode"].value = this.configuration.renderMode;
this.effectCompositerQuad.material.uniforms["screenSpaceRadius"].value = this.configuration.screenSpaceRadius;
this.effectCompositerQuad.material.uniforms['radius'].value = trueRadius;
this.effectCompositerQuad.material.uniforms['distanceFalloff'].value = this.configuration.distanceFalloff;
this.effectCompositerQuad.material.uniforms["gammaCorrection"].value = this.configuration.gammaCorrection;
this.effectCompositerQuad.material.uniforms["tDiffuse"].value = this.accumulationRenderTarget.texture;
this.effectCompositerQuad.material.uniforms["color"].value = this._c.copy(
this.configuration.color
).convertSRGBToLinear();
this.effectCompositerQuad.material.uniforms["colorMultiply"].value = this.configuration.colorMultiply;
this.effectCompositerQuad.material.uniforms["fog"].value = !!this.scene.fog;
if (this.scene.fog) {
if (
this.scene.fog.isFog
) {
this.effectCompositerQuad.material.uniforms["fogExp"].value = false;
this.effectCompositerQuad.material.uniforms["fogNear"].value = this.scene.fog.near;
this.effectCompositerQuad.material.uniforms["fogFar"].value = this.scene.fog.far;
} else if (
this.scene.fog.isFogExp2
) {
this.effectCompositerQuad.material.uniforms["fogExp"].value = true;
this.effectCompositerQuad.material.uniforms["fogDensity"].value = this.scene.fog.density;
} else {
console.error(`Unsupported fog type ${this.scene.fog.constructor.name} in SSAOPass.`);
}
}
renderer.setRenderTarget(
this.renderToScreen ? null :
writeBuffer
);
this.effectCompositerQuad.render(renderer);
if (this.debugMode) {
gl.endQuery(ext.TIME_ELAPSED_EXT);
checkTimerQuery(timerQuery, gl, this);
}
renderer.xr.enabled = xrEnabled;
}
/**
* Enables the debug mode of the AO, meaning the lastTime value will be updated.
*/
enableDebugMode() {
this.debugMode = true;
}
/**
* Disables the debug mode of the AO, meaning the lastTime value will not be updated.
*/
disableDebugMode() {
this.debugMode = false;
}
/**
* Sets the display mode of the AO
* @param {"Combined" | "AO" | "No AO" | "Split" | "Split AO"} mode - The display mode.
*/
setDisplayMode(mode) {
this.configuration.renderMode = ["Combined", "AO", "No AO", "Split", "Split AO"].indexOf(mode);
}
/**
*
* @param {"Performance" | "Low" | "Medium" | "High" | "Ultra" | "Neural-Low" | "Neural-Medium" | "Neural-High"} mode
*/
setQualityMode(mode) {
if (mode === "Performance") {
this.configuration.neuralDenoise = false;
this.configuration.aoSamples = 8;
this.configuration.denoiseSamples = 4;
this.configuration.denoiseRadius = 12;
} else if (mode === "Low") {
this.configuration.neuralDenoise = false;
this.configuration.aoSamples = 16;
this.configuration.denoiseSamples = 4;
this.configuration.denoiseRadius = 12;
} else if (mode === "Medium") {
this.configuration.neuralDenoise = false;
this.configuration.aoSamples = 16;
this.configuration.denoiseSamples = 8;
this.configuration.denoiseRadius = 12;
} else if (mode === "High") {
this.configuration.neuralDenoise = false;
this.configuration.aoSamples = 64;
this.configuration.denoiseSamples = 8;
this.configuration.denoiseRadius = 6;
} else if (mode === "Ultra") {
this.configuration.neuralDenoise = false;
this.configuration.aoSamples = 64;
this.configuration.denoiseSamples = 16;
this.configuration.denoiseRadius = 6;
} else if (["Neural-Low", "Neural-Medium", "Neural-High"].includes(mode)) {
this.configuration.aoSamples = 16;
this.configuration.denoiseSamples = {
"Neural-Low": 4,
"Neural-Medium": 8,
"Neural-High": 16
}[mode];
this.configuration.denoiseRadius = 12;
this.configuration.denoiseIterations = 2;
this.configuration.halfRes = false;
this.configuration.neuralDenoise = true;
}
}
}
export { N8AOPass, N8AOPostPass };
@@ -0,0 +1,898 @@
import * as THREE from 'three';
import { Pass } from "postprocessing";
import { FullScreenTriangle } from "./FullScreenTriangle.js";
import { EffectShader } from './EffectShader.js';
import { EffectCompositer } from './EffectCompositer.js';
import { PoissionBlur } from './PoissionBlur.js';
import { DepthDownSample } from "./DepthDownSample.js";
import bluenoiseBits from './BlueNoise.js';
import { N8AOPass, DepthType } from './N8AOPass.js';
import { WebGLMultipleRenderTargetsCompat } from './compat.js';
/**
*
* @param {*} timerQuery
* @param {THREE.WebGLRenderer} gl
* @param {N8AOPostPass | N8AOPass} pass
*/
function checkTimerQuery(timerQuery, gl, pass) {
const available = gl.getQueryParameter(timerQuery, gl.QUERY_RESULT_AVAILABLE);
if (available) {
const elapsedTimeInNs = gl.getQueryParameter(timerQuery, gl.QUERY_RESULT);
const elapsedTimeInMs = elapsedTimeInNs / 1000000;
pass.lastTime = pass.lastTime === 0 ? elapsedTimeInMs : pass.timeRollingAverage * pass.lastTime + (1 - pass.timeRollingAverage) * elapsedTimeInMs;
} else {
// If the result is not available yet, check again after a delay
setTimeout(() => {
checkTimerQuery(timerQuery, gl, pass);
}, 1);
}
}
class N8AOPostPass extends Pass {
/**
*
* @param {THREE.Scene} scene
* @param {THREE.Camera} camera
* @param {number} width
* @param {number} height
*
* @property {THREE.Scene} scene
* @property {THREE.Camera} camera
* @property {number} width
* @property {number} height
*/
constructor(scene, camera, width = 512, height = 512) {
super();
this.width = Math.floor(width);
this.height = Math.floor(height);
this.clear = true;
this._neuralDenoiseWarningKey = null;
this.camera = camera;
this.scene = scene;
/**
* @type {Proxy & {
* aoSamples: number,
* aoRadius: number,
* denoiseSamples: number,
* denoiseRadius: number,
* distanceFalloff: number,
* intensity: number,
* denoiseIterations: number,
* renderMode: 0 | 1 | 2 | 3 | 4,
* color: THREE.Color,
* gammaCorrection: boolean,
* depthBufferType: 1 | 2 | 3,
* screenSpaceRadius: boolean,
* halfRes: boolean,
* depthAwareUpsampling: boolean
* colorMultiply: boolean,
* neuralDenoise: boolean
* }
*/
this.autosetGamma = true;
this.configuration = new Proxy({
aoSamples: 16,
aoRadius: 5.0,
aoTones: 0.0,
denoiseSamples: 8,
denoiseRadius: 12,
distanceFalloff: 1.0,
intensity: 5,
denoiseIterations: 2.0,
renderMode: 0,
biasOffset: 0.0,
biasMultiplier: 0.0,
color: new THREE.Color(0, 0, 0),
gammaCorrection: true,
depthBufferType: DepthType.Default,
screenSpaceRadius: false,
halfRes: false,
depthAwareUpsampling: true,
colorMultiply: true,
transparencyAware: false,
accumulate: false,
neuralDenoise: false
}, {
set: (target, propName, value) => {
const oldProp = target[propName];
target[propName] = value;
if (value.equals) {
if (!value.equals(oldProp)) {
this.firstFrame();
}
} else {
if (oldProp !== value) {
this.firstFrame();
}
}
if (propName === 'aoSamples' && oldProp !== value) {
this.configureAOPass(this.configuration.depthBufferType, this.camera.isOrthographicCamera);
}
if (propName === 'denoiseSamples' && oldProp !== value) {
this.configureDenoisePass(this.configuration.depthBufferType, this.camera.isOrthographicCamera);
}
if (propName === 'neuralDenoise' && oldProp !== value) {
const reconfigureAO = value && (target.aoSamples !== 16 || target.halfRes);
const disableHalfRes = value && target.halfRes;
if (value) {
target.aoSamples = 16;
if (![4, 8, 16].includes(target.denoiseSamples)) {
target.denoiseSamples = 8;
}
target.denoiseRadius = 12;
target.denoiseIterations = 2;
target.halfRes = false;
}
if (reconfigureAO) {
this.configureAOPass(this.configuration.depthBufferType, this.camera.isOrthographicCamera);
}
if (disableHalfRes) {
this.configureHalfResTargets();
this.configureEffectCompositer(this.configuration.depthBufferType, this.camera.isOrthographicCamera);
this.setSize(this.width, this.height);
}
this.configureDenoisePass(this.configuration.depthBufferType, this.camera.isOrthographicCamera);
}
if (propName === "halfRes" && oldProp !== value) {
this.configureAOPass(this.configuration.depthBufferType, this.camera.isOrthographicCamera);
this.configureHalfResTargets();
this.configureEffectCompositer(this.configuration.depthBufferType, this.camera.isOrthographicCamera);
this.setSize(this.width, this.height);
}
if (propName === "depthAwareUpsampling" && oldProp !== value) {
this.configureEffectCompositer(this.configuration.depthBufferType, this.camera.isOrthographicCamera);
}
if (propName === 'gammaCorrection') {
this.autosetGamma = false;
}
if (propName === "transparencyAware" && oldProp !== value) {
this.autoDetectTransparency = false;
this.configureTransparencyTarget();
}
return true;
}
});
/** @type {THREE.Vector3[]} */
this.samples = [];
/** @type {THREE.Vector2[]} */
this.samplesDenoise = [];
this.autoDetectTransparency = true;
this.frames = 0;
this.lastViewMatrix = new THREE.Matrix4();
this.lastProjectionMatrix = new THREE.Matrix4();
this.configureEffectCompositer(this.configuration.depthBufferType);
this.configureSampleDependentPasses();
this.configureHalfResTargets();
this.detectTransparency();
this.configureTransparencyTarget();
// this.effectCompisterQuad = new FullScreenTriangle(new THREE.ShaderMaterial(EffectCompositer));
this.copyQuad = new FullScreenTriangle(new THREE.ShaderMaterial({
uniforms: {
tDiffuse: {
value: null
}
},
depthWrite: false,
vertexShader: `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = vec4(position, 1);
}
`,
fragmentShader: `
uniform sampler2D tDiffuse;
varying vec2 vUv;
void main() {
gl_FragColor = texture2D(tDiffuse, vUv);
}
`
}))
this.writeTargetInternal = new THREE.WebGLRenderTarget(this.width, this.height, {
minFilter: THREE.LinearFilter,
magFilter: THREE.LinearFilter,
depthBuffer: false,
format: THREE.RGBAFormat
});
this.readTargetInternal = new THREE.WebGLRenderTarget(this.width, this.height, {
minFilter: THREE.LinearFilter,
magFilter: THREE.LinearFilter,
depthBuffer: false,
format: THREE.RGBAFormat
});
this.outputTargetInternal = new THREE.WebGLRenderTarget(this.width, this.height, {
minFilter: THREE.LinearFilter,
magFilter: THREE.LinearFilter,
depthBuffer: false
});
this.accumulationRenderTarget = new THREE.WebGLRenderTarget(this.width, this.height, {
minFilter: THREE.LinearFilter,
magFilter: THREE.LinearFilter,
depthBuffer: false,
format: THREE.RGBAFormat,
type: THREE.HalfFloatType,
stencilBuffer: false,
depthBuffer: false,
alpha: true
});
this.accumulationQuad = new FullScreenTriangle(new THREE.ShaderMaterial({
uniforms: {
frame: { value: 0 },
tDiffuse: { value: null }
},
transparent: true,
opacity: 1,
vertexShader: `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = vec4(position, 1);
}`,
fragmentShader: `
uniform sampler2D tDiffuse;
uniform float frame;
varying vec2 vUv;
void main() {
vec4 color = texture2D(tDiffuse, vUv);
gl_FragColor = vec4(color.rgb, 1.0 / (frame + 1.0));
}
`
}));
/** @type {THREE.DataTexture} */
this.bluenoise = //bluenoise;
new THREE.DataTexture(
bluenoiseBits,
128,
128
);
this.bluenoise.colorSpace = THREE.NoColorSpace;
this.bluenoise.wrapS = THREE.RepeatWrapping;
this.bluenoise.wrapT = THREE.RepeatWrapping;
this.bluenoise.minFilter = THREE.NearestFilter;
this.bluenoise.magFilter = THREE.NearestFilter;
this.bluenoise.needsUpdate = true;
this.lastTime = 0;
this.timeRollingAverage = 0.99;
this.needsDepthTexture = true;
this.needsSwap = true;
this._r = new THREE.Vector2();
this._c = new THREE.Color();
}
configureHalfResTargets() {
this.firstFrame();
if (this.configuration.halfRes) {
this.depthDownsampleTarget = new WebGLMultipleRenderTargetsCompat(
Math.floor(this.width / 2),
Math.floor(this.height / 2),
2
);
if (THREE.REVISION <= 161) {
this.depthDownsampleTarget.textures = this.depthDownsampleTarget.texture;
}
this.depthDownsampleTarget.textures[0].format = THREE.RedFormat;
this.depthDownsampleTarget.textures[0].type = THREE.FloatType;
this.depthDownsampleTarget.textures[0].minFilter = THREE.NearestFilter;
this.depthDownsampleTarget.textures[0].magFilter = THREE.NearestFilter;
this.depthDownsampleTarget.textures[0].depthBuffer = false;
this.depthDownsampleTarget.textures[1].format = THREE.RGBAFormat;
this.depthDownsampleTarget.textures[1].type = THREE.HalfFloatType;
this.depthDownsampleTarget.textures[1].minFilter = THREE.NearestFilter;
this.depthDownsampleTarget.textures[1].magFilter = THREE.NearestFilter;
this.depthDownsampleTarget.textures[1].depthBuffer = false;
this.depthDownsampleQuad = new FullScreenTriangle(new THREE.ShaderMaterial(DepthDownSample));
} else {
if (this.depthDownsampleTarget) {
this.depthDownsampleTarget.dispose();
this.depthDownsampleTarget = null;
}
if (this.depthDownsampleQuad) {
this.depthDownsampleQuad.dispose();
this.depthDownsampleQuad = null;
}
}
}
detectTransparency() {
if (this.autoDetectTransparency) {
let isTransparency = false;
this.scene.traverse((obj) => {
if (obj.material && obj.material.transparent) {
isTransparency = true;
}
});
if (isTransparency) {
this.configuration.transparencyAware = true;
}
}
}
configureTransparencyTarget() {
if (this.configuration.transparencyAware) {
this.transparencyRenderTargetDWFalse = new THREE.WebGLRenderTarget(this.width, this.height, {
minFilter: THREE.LinearFilter,
magFilter: THREE.NearestFilter,
type: THREE.HalfFloatType,
format: THREE.RGBAFormat
});
this.transparencyRenderTargetDWTrue = new THREE.WebGLRenderTarget(this.width, this.height, {
minFilter: THREE.LinearFilter,
magFilter: THREE.NearestFilter,
type: THREE.HalfFloatType,
format: THREE.RGBAFormat
});
this.transparencyRenderTargetDWTrue.depthTexture = new THREE.DepthTexture(this.width, this.height, THREE.UnsignedIntType);
this.depthCopyPass = new FullScreenTriangle(new THREE.ShaderMaterial({
uniforms: {
depthTexture: { value: this.depthTexture },
reverseDepthBuffer: { value: this.configuration.depthBufferType === DepthType.Reverse },
},
vertexShader: /* glsl */ `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = vec4(position, 1);
}`,
fragmentShader: /* glsl */ `
uniform sampler2D depthTexture;
uniform bool reverseDepthBuffer;
varying vec2 vUv;
void main() {
if (reverseDepthBuffer) {
float d = 1.0 - texture2D(depthTexture, vUv).r;
d += 0.00001;
gl_FragDepth = 1.0 - d;
} else {
float d = texture2D(depthTexture, vUv).r;
d += 0.00001;
gl_FragDepth = d;
}
gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);
}
`,
}));
} else {
if (this.transparencyRenderTargetDWFalse) {
this.transparencyRenderTargetDWFalse.dispose();
this.transparencyRenderTargetDWFalse = null;
}
if (this.transparencyRenderTargetDWTrue) {
this.transparencyRenderTargetDWTrue.dispose();
this.transparencyRenderTargetDWTrue = null;
}
if (this.depthCopyPass) {
this.depthCopyPass.dispose();
this.depthCopyPass = null;
}
}
}
renderTransparency(renderer) {
const oldBackground = this.scene.background;
const oldClearColor = renderer.getClearColor(new THREE.Color());
const oldClearAlpha = renderer.getClearAlpha();
const oldVisibility = new Map();
const oldAutoClearDepth = renderer.autoClearDepth;
this.scene.traverse((obj) => {
oldVisibility.set(obj, obj.visible);
});
// Override the state
this.scene.background = null;
renderer.autoClearDepth = false;
renderer.setClearColor(new THREE.Color(0, 0, 0), 0);
this.depthCopyPass.material.uniforms.depthTexture.value = this.depthTexture;
this.depthCopyPass.material.uniforms.reverseDepthBuffer.value = this.configuration.depthBufferType === DepthType.Reverse;
// Render out transparent objects WITHOUT depth write
renderer.setRenderTarget(this.transparencyRenderTargetDWFalse);
this.scene.traverse((obj) => {
if (obj.material) {
obj.visible = oldVisibility.get(obj) && ((obj.material.transparent && !obj.material.depthWrite && !obj.userData.treatAsOpaque) || !!obj.userData.cannotReceiveAO);
}
});
renderer.clear(true, true, true);
this.depthCopyPass.render(renderer);
renderer.render(this.scene, this.camera);
// Render out transparent objects WITH depth write
renderer.setRenderTarget(this.transparencyRenderTargetDWTrue);
this.scene.traverse((obj) => {
if (obj.material) {
obj.visible = oldVisibility.get(obj) && obj.material.transparent && obj.material.depthWrite && !obj.userData.treatAsOpaque;
}
});
renderer.clear(true, true, true);
this.depthCopyPass.render(renderer);
renderer.render(this.scene, this.camera);
// Restore
this.scene.traverse((obj) => {
obj.visible = oldVisibility.get(obj);
});
renderer.setClearColor(oldClearColor, oldClearAlpha);
this.scene.background = oldBackground;
renderer.autoClearDepth = oldAutoClearDepth;
}
configureSampleDependentPasses() {
this.configureAOPass(this.configuration.depthBufferType, this.camera.isOrthographicCamera);
this.configureDenoisePass(this.configuration.depthBufferType, this.camera.isOrthographicCamera);
}
configureAOPass(depthBufferType = DepthType.Default, ortho = false) {
this.firstFrame();
this.samples = this.generateHemisphereSamples(this.configuration.aoSamples);
const e = {...EffectShader };
e.fragmentShader = e.fragmentShader.replace("16", this.configuration.aoSamples).replace("16.0", this.configuration.aoSamples + ".0");
if (depthBufferType === DepthType.Log) {
e.fragmentShader = "#define LOGDEPTH\n" + e.fragmentShader;
} else if (depthBufferType === DepthType.Reverse) {
e.fragmentShader = "#define REVERSEDEPTH\n" + e.fragmentShader;
}
if (ortho) {
e.fragmentShader = "#define ORTHO\n" + e.fragmentShader;
}
if (this.configuration.halfRes) {
e.fragmentShader = "#define HALFRES\n" + e.fragmentShader;
}
if (this.effectShaderQuad) {
this.effectShaderQuad.material.dispose();
this.effectShaderQuad.material = new THREE.ShaderMaterial(e);
} else {
this.effectShaderQuad = new FullScreenTriangle(new THREE.ShaderMaterial(e));
}
}
configureDenoisePass(depthBufferType = DepthType.Default, ortho = false) {
this.firstFrame();
this.samplesDenoise = this.generateDenoiseSamples(this.configuration.denoiseSamples, 11);
const p = {...PoissionBlur };
p.fragmentShader = p.fragmentShader.replace(
"__N8AO_DENOISE_SAMPLES__",
this.configuration.denoiseSamples
);
if (depthBufferType === DepthType.Log) {
p.fragmentShader = "#define LOGDEPTH\n" + p.fragmentShader;
} else if (depthBufferType === DepthType.Reverse) {
p.fragmentShader = "#define REVERSEDEPTH\n" + p.fragmentShader;
}
if (ortho) {
p.fragmentShader = "#define ORTHO\n" + p.fragmentShader;
}
if (this.standardDenoiseMaterial) {
this.standardDenoiseMaterial.dispose();
}
if (this.neuralDenoiseMaterial) {
this.neuralDenoiseMaterial.dispose();
}
this.standardDenoiseMaterial = new THREE.ShaderMaterial(p);
this.neuralDenoiseMaterial = this.configuration.neuralDenoise
&& [4, 8, 16].includes(this.configuration.denoiseSamples)
? new THREE.ShaderMaterial({
...p,
glslVersion: THREE.GLSL3,
fragmentShader: "#define NEURAL_DENOISE\n"
+ "layout(location = 0) out highp vec4 neuralFragColor;\n"
+ "#define gl_FragColor neuralFragColor\n"
+ p.fragmentShader
})
: null;
if (this.poissonBlurQuad) {
this.poissonBlurQuad.material = this.standardDenoiseMaterial;
} else {
this.poissonBlurQuad = new FullScreenTriangle(this.standardDenoiseMaterial);
}
}
shouldUseNeuralDenoise() {
if (!this.configuration.neuralDenoise) {
this._neuralDenoiseWarningKey = null;
return false;
}
const incompatibilities = [];
if (this.configuration.aoSamples !== 16) {
incompatibilities.push(`aoSamples is ${this.configuration.aoSamples} (expected 16)`);
}
if (![4, 8, 16].includes(this.configuration.denoiseSamples)) {
incompatibilities.push(`denoiseSamples is ${this.configuration.denoiseSamples} (expected 4, 8, or 16)`);
}
if (this.configuration.denoiseRadius !== 12) {
incompatibilities.push(`denoiseRadius is ${this.configuration.denoiseRadius} (expected 12)`);
}
if (this.configuration.denoiseIterations !== 2) {
incompatibilities.push(`denoiseIterations is ${this.configuration.denoiseIterations} (expected 2)`);
}
if (this.configuration.halfRes) {
incompatibilities.push('halfRes is enabled (expected full resolution)');
}
if (incompatibilities.length > 0) {
const warningKey = incompatibilities.join('; ');
if (warningKey !== this._neuralDenoiseWarningKey) {
console.warn(
`[N8AO] neuralDenoise is enabled but cannot run: ${warningKey}. `
+ 'Neural denoising has been disabled; standard denoising will be used.'
);
this._neuralDenoiseWarningKey = warningKey;
}
return false;
}
this._neuralDenoiseWarningKey = null;
return this.neuralDenoiseMaterial !== null;
}
configureEffectCompositer(depthBufferType = DepthType.Default, ortho = false) {
this.firstFrame();
const e = {...EffectCompositer };
if (depthBufferType === DepthType.Log) {
e.fragmentShader = "#define LOGDEPTH\n" + e.fragmentShader;
} else if (depthBufferType === DepthType.Reverse) {
e.fragmentShader = "#define REVERSEDEPTH\n" + e.fragmentShader;
}
if (ortho) {
e.fragmentShader = "#define ORTHO\n" + e.fragmentShader;
}
if (this.configuration.halfRes && this.configuration.depthAwareUpsampling) {
e.fragmentShader = "#define HALFRES\n" + e.fragmentShader;
}
if (this.effectCompositerQuad) {
this.effectCompositerQuad.material.dispose();
this.effectCompositerQuad.material = new THREE.ShaderMaterial(e);
} else {
this.effectCompositerQuad = new FullScreenTriangle(new THREE.ShaderMaterial(e));
}
}
/**
*
* @param {Number} n
* @returns {THREE.Vector3[]}
*/
generateHemisphereSamples(n) {
const points = [];
for (let k = 0; k < n; k++) {
const theta = 2.399963 * k;
const r = (Math.sqrt(k + 0.5) / Math.sqrt(n));
const x = r * Math.cos(theta);
const y = r * Math.sin(theta);
// Project to hemisphere
const z = Math.sqrt(1 - (x * x + y * y));
points.push(new THREE.Vector3(x, y, z));
}
return points;
}
/**
*
* @param {number} numSamples
* @param {number} numRings
* @returns {THREE.Vector2[]}
*/
generateDenoiseSamples(numSamples, numRings) {
const angleStep = 2 * Math.PI * numRings / numSamples;
const invNumSamples = 1.0 / numSamples;
const radiusStep = invNumSamples;
const samples = [];
let radius = invNumSamples;
let angle = 0;
for (let i = 0; i < numSamples; i++) {
samples.push(new THREE.Vector2(Math.cos(angle), Math.sin(angle)).multiplyScalar(Math.pow(radius, 0.75)));
radius += radiusStep;
angle += angleStep;
}
return samples;
}
setSize(width, height) {
this.firstFrame();
this.width = Math.floor(width);
this.height = Math.floor(height);
const internalWidth = this.configuration.halfRes ? Math.floor(this.width / 2) : this.width;
const internalHeight = this.configuration.halfRes ? Math.floor(this.height / 2) : this.height;
this.writeTargetInternal.setSize(internalWidth, internalHeight);
this.readTargetInternal.setSize(internalWidth, internalHeight);
this.accumulationRenderTarget.setSize(internalWidth, internalHeight);
if (this.configuration.halfRes) {
this.depthDownsampleTarget.setSize(internalWidth, internalHeight);
}
if (this.configuration.transparencyAware) {
this.transparencyRenderTargetDWFalse.setSize(this.width, this.height);
this.transparencyRenderTargetDWTrue.setSize(this.width, this.height);
}
this.outputTargetInternal.setSize(this.width, this.height);
}
setDepthTexture(depthTexture) {
this.depthTexture = depthTexture;
}
firstFrame() {
this.needsFrame = true;
}
render(renderer, inputBuffer, outputBuffer) {
const xrEnabled = renderer.xr.enabled;
renderer.xr.enabled = false;
// Copy inputBuffer to outputBuffer
//renderer.setRenderTarget(outputBuffer);
// this.copyQuad.material.uniforms.tDiffuse.value = inputBuffer.texture;
// this.copyQuad.render(renderer);
const rendererUsesReverseDepth = renderer.capabilities.reversedDepthBuffer === true
|| renderer.capabilities.reverseDepthBuffer === true;
const rendererDepthType = rendererUsesReverseDepth
? DepthType.Reverse
: renderer.capabilities.logarithmicDepthBuffer
? DepthType.Log
: DepthType.Default;
if (this.configuration.depthBufferType !== rendererDepthType) {
this.configuration.depthBufferType = rendererDepthType;
this.configureAOPass(this.configuration.depthBufferType, this.camera.isOrthographicCamera);
this.configureDenoisePass(this.configuration.depthBufferType, this.camera.isOrthographicCamera);
this.configureEffectCompositer(this.configuration.depthBufferType, this.camera.isOrthographicCamera);
}
this.detectTransparency();
if (inputBuffer.texture.type !== this.outputTargetInternal.texture.type ||
inputBuffer.texture.format !== this.outputTargetInternal.texture.format
) {
this.outputTargetInternal.texture.type = inputBuffer.texture.type;
this.outputTargetInternal.texture.format = inputBuffer.texture.format;
this.outputTargetInternal.texture.needsUpdate = true;
}
this.camera.updateMatrixWorld();
if (this.lastViewMatrix.equals(this.camera.matrixWorldInverse) && this.lastProjectionMatrix.equals(this.camera.projectionMatrix) && this.configuration.accumulate && !this.needsFrame) {
this.frame++;
} else {
if (this.configuration.accumulate) {
renderer.setRenderTarget(this.accumulationRenderTarget);
renderer.clear(true, true, true);
}
this.frame = 0;
this.needsFrame = false;
}
this.lastViewMatrix.copy(this.camera.matrixWorldInverse);
this.lastProjectionMatrix.copy(this.camera.projectionMatrix);
let gl;
let ext;
let timerQuery;
if (this.debugMode) {
gl = renderer.getContext();
ext = gl.getExtension('EXT_disjoint_timer_query_webgl2');
if (ext === null) {
console.error("EXT_disjoint_timer_query_webgl2 not available, disabling debug mode.");
this.debugMode = false;
}
}
if (this.debugMode) {
timerQuery = gl.createQuery();
gl.beginQuery(ext.TIME_ELAPSED_EXT, timerQuery);
}
if (this.configuration.transparencyAware) {
this.renderTransparency(renderer);
}
this._r.set(this.width, this.height);
let trueRadius = this.configuration.aoRadius;
if (this.configuration.halfRes && this.configuration.screenSpaceRadius) {
trueRadius *= 0.5;
}
if (this.frame < 1024 / this.configuration.aoSamples) {
if (this.configuration.halfRes) {
renderer.setRenderTarget(this.depthDownsampleTarget);
this.depthDownsampleQuad.material.uniforms.sceneDepth.value = this.depthTexture;
this.depthDownsampleQuad.material.uniforms.resolution.value = this._r;
this.depthDownsampleQuad.material.uniforms["near"].value = this.camera.near;
this.depthDownsampleQuad.material.uniforms["far"].value = this.camera.far;
this.depthDownsampleQuad.material.uniforms["projectionMatrixInv"].value = this.camera.projectionMatrixInverse;
this.depthDownsampleQuad.material.uniforms["viewMatrixInv"].value = this.camera.matrixWorld;
this.depthDownsampleQuad.material.uniforms["logDepth"].value = this.configuration.depthBufferType === DepthType.Log;
this.depthDownsampleQuad.material.uniforms["ortho"].value = this.camera.isOrthographicCamera;
this.depthDownsampleQuad.render(renderer);
}
this.effectShaderQuad.material.uniforms["sceneDiffuse"].value = inputBuffer.texture;
this.effectShaderQuad.material.uniforms["sceneDepth"].value = this.configuration.halfRes ? this.depthDownsampleTarget.textures[0] : this.depthTexture;
this.effectShaderQuad.material.uniforms["sceneNormal"].value = this.configuration.halfRes ? this.depthDownsampleTarget.textures[1] : null;
this.effectShaderQuad.material.uniforms["projMat"].value = this.camera.projectionMatrix;
this.effectShaderQuad.material.uniforms["viewMat"].value = this.camera.matrixWorldInverse;
this.effectShaderQuad.material.uniforms["projViewMat"].value = this.camera.projectionMatrix.clone().multiply(this.camera.matrixWorldInverse.clone());
this.effectShaderQuad.material.uniforms["projectionMatrixInv"].value = this.camera.projectionMatrixInverse;
this.effectShaderQuad.material.uniforms["viewMatrixInv"].value = this.camera.matrixWorld;
this.effectShaderQuad.material.uniforms["cameraPos"].value = this.camera.getWorldPosition(new THREE.Vector3());
this.effectShaderQuad.material.uniforms['biasAdjustment'].value = new THREE.Vector2(this.configuration.biasOffset, this.configuration.biasMultiplier);
this.effectShaderQuad.material.uniforms['resolution'].value = (this.configuration.halfRes ? this._r.clone().multiplyScalar(1 / 2).floor() : this._r);
this.effectShaderQuad.material.uniforms['time'].value = performance.now() / 1000;
this.effectShaderQuad.material.uniforms['samples'].value = this.samples;
this.effectShaderQuad.material.uniforms['bluenoise'].value = this.bluenoise;
this.effectShaderQuad.material.uniforms['radius'].value = trueRadius;
this.effectShaderQuad.material.uniforms['distanceFalloff'].value = this.configuration.distanceFalloff;
this.effectShaderQuad.material.uniforms["near"].value = this.camera.near;
this.effectShaderQuad.material.uniforms["far"].value = this.camera.far;
this.effectShaderQuad.material.uniforms["ortho"].value = this.camera.isOrthographicCamera;
this.effectShaderQuad.material.uniforms["screenSpaceRadius"].value = this.configuration.screenSpaceRadius;
this.effectShaderQuad.material.uniforms["frame"].value = this.frame;
// Start the AO
renderer.setRenderTarget(this.writeTargetInternal);
this.effectShaderQuad.render(renderer);
// End the AO
// Start the blur
const useNeuralDenoise = this.shouldUseNeuralDenoise();
for (let i = 0; i < this.configuration.denoiseIterations; i++) {
[this.writeTargetInternal, this.readTargetInternal] = [this.readTargetInternal, this.writeTargetInternal];
this.poissonBlurQuad.material = useNeuralDenoise && i === 1
? this.neuralDenoiseMaterial
: this.standardDenoiseMaterial;
this.poissonBlurQuad.material.uniforms["tDiffuse"].value = this.readTargetInternal.texture;
this.poissonBlurQuad.material.uniforms["sceneDepth"].value = this.configuration.halfRes ? this.depthDownsampleTarget.textures[0] : this.depthTexture;
this.poissonBlurQuad.material.uniforms["projMat"].value = this.camera.projectionMatrix;
this.poissonBlurQuad.material.uniforms["viewMat"].value = this.camera.matrixWorldInverse;
this.poissonBlurQuad.material.uniforms["projectionMatrixInv"].value = this.camera.projectionMatrixInverse;
this.poissonBlurQuad.material.uniforms["viewMatrixInv"].value = this.camera.matrixWorld;
this.poissonBlurQuad.material.uniforms["cameraPos"].value = this.camera.getWorldPosition(new THREE.Vector3());
this.poissonBlurQuad.material.uniforms['resolution'].value = (this.configuration.halfRes ? this._r.clone().multiplyScalar(1 / 2).floor() : this._r);
this.poissonBlurQuad.material.uniforms['time'].value = performance.now() / 1000;
this.poissonBlurQuad.material.uniforms['blueNoise'].value = this.bluenoise;
this.poissonBlurQuad.material.uniforms['radius'].value = this.configuration.denoiseRadius * (
this.configuration.halfRes ? 1 / 2 : 1
);
this.poissonBlurQuad.material.uniforms['worldRadius'].value = trueRadius;
this.poissonBlurQuad.material.uniforms['distanceFalloff'].value = this.configuration.distanceFalloff;
this.poissonBlurQuad.material.uniforms['index'].value = i;
this.poissonBlurQuad.material.uniforms['poissonDisk'].value = this.samplesDenoise;
this.poissonBlurQuad.material.uniforms["near"].value = this.camera.near;
this.poissonBlurQuad.material.uniforms["far"].value = this.camera.far;
this.poissonBlurQuad.material.uniforms["screenSpaceRadius"].value = this.configuration.screenSpaceRadius;
renderer.setRenderTarget(this.writeTargetInternal);
this.poissonBlurQuad.render(renderer);
}
renderer.setRenderTarget(this.accumulationRenderTarget);
const oldAutoClear = renderer.autoClear;
renderer.autoClear = false;
this.accumulationQuad.material.uniforms["tDiffuse"].value = this.writeTargetInternal.texture;
this.accumulationQuad.material.uniforms["frame"].value = this.frame;
this.accumulationQuad.render(renderer);
renderer.autoClear = oldAutoClear;
}
// Now, we have the blurred AO in writeTargetInternal
// End the blur
// Start the composition
if (this.configuration.transparencyAware) {
this.effectCompositerQuad.material.uniforms["transparencyDWFalse"].value = this.transparencyRenderTargetDWFalse.texture;
this.effectCompositerQuad.material.uniforms["transparencyDWTrue"].value = this.transparencyRenderTargetDWTrue.texture;
this.effectCompositerQuad.material.uniforms["transparencyDWTrueDepth"].value = this.transparencyRenderTargetDWTrue.depthTexture;
this.effectCompositerQuad.material.uniforms["transparencyAware"].value = true;
}
this.effectCompositerQuad.material.uniforms["sceneDiffuse"].value = inputBuffer.texture;
this.effectCompositerQuad.material.uniforms["sceneDepth"].value = this.depthTexture;
this.effectCompositerQuad.material.uniforms["aoTones"].value = this.configuration.aoTones;
this.effectCompositerQuad.material.uniforms["near"].value = this.camera.near;
this.effectCompositerQuad.material.uniforms["far"].value = this.camera.far;
this.effectCompositerQuad.material.uniforms["projectionMatrixInv"].value = this.camera.projectionMatrixInverse;
this.effectCompositerQuad.material.uniforms["ortho"].value = this.camera.isOrthographicCamera;
this.effectCompositerQuad.material.uniforms["downsampledDepth"].value = this.configuration.halfRes ? this.depthDownsampleTarget.textures[0] : this.depthTexture;
this.effectCompositerQuad.material.uniforms["resolution"].value = this._r;
this.effectCompositerQuad.material.uniforms["blueNoise"].value = this.bluenoise;
this.effectCompositerQuad.material.uniforms["intensity"].value = this.configuration.intensity;
this.effectCompositerQuad.material.uniforms["renderMode"].value = this.configuration.renderMode;
this.effectCompositerQuad.material.uniforms["screenSpaceRadius"].value = this.configuration.screenSpaceRadius;
this.effectCompositerQuad.material.uniforms['radius'].value = trueRadius;
this.effectCompositerQuad.material.uniforms['distanceFalloff'].value = this.configuration.distanceFalloff;
this.effectCompositerQuad.material.uniforms["gammaCorrection"].value = this.autosetGamma ?
this.renderToScreen :
this.configuration.gammaCorrection;
this.effectCompositerQuad.material.uniforms["tDiffuse"].value = this.accumulationRenderTarget.texture;
this.effectCompositerQuad.material.uniforms["color"].value =
this._c.copy(
this.configuration.color
).convertSRGBToLinear();
this.effectCompositerQuad.material.uniforms["colorMultiply"].value = this.configuration.colorMultiply;
this.effectCompositerQuad.material.uniforms["fog"].value = !!this.scene.fog;
if (this.scene.fog) {
if (
this.scene.fog.isFog
) {
this.effectCompositerQuad.material.uniforms["fogExp"].value = false;
this.effectCompositerQuad.material.uniforms["fogNear"].value = this.scene.fog.near;
this.effectCompositerQuad.material.uniforms["fogFar"].value = this.scene.fog.far;
} else if (
this.scene.fog.isFogExp2
) {
this.effectCompositerQuad.material.uniforms["fogExp"].value = true;
this.effectCompositerQuad.material.uniforms["fogDensity"].value = this.scene.fog.density;
} else {
console.error(`Unsupported fog type ${this.scene.fog.constructor.name} in SSAOPass.`);
}
}
renderer.setRenderTarget(
/* this.renderToScreen ? null :
outputBuffer*/
this.outputTargetInternal
);
this.effectCompositerQuad.render(renderer);
renderer.setRenderTarget(
this.renderToScreen ? null :
outputBuffer
);
this.copyQuad.material.uniforms["tDiffuse"].value = this.outputTargetInternal.texture;
this.copyQuad.render(renderer);
if (this.debugMode) {
gl.endQuery(ext.TIME_ELAPSED_EXT);
checkTimerQuery(timerQuery, gl, this);
}
renderer.xr.enabled = xrEnabled;
}
/**
* Enables the debug mode of the AO, meaning the lastTime value will be updated.
*/
enableDebugMode() {
this.debugMode = true;
}
/**
* Disables the debug mode of the AO, meaning the lastTime value will not be updated.
*/
disableDebugMode() {
this.debugMode = false;
}
/**
* Sets the display mode of the AO
* @param {"Combined" | "AO" | "No AO" | "Split" | "Split AO"} mode - The display mode.
*/
setDisplayMode(mode) {
this.configuration.renderMode = ["Combined", "AO", "No AO", "Split", "Split AO"].indexOf(mode);
}
/**
*
* @param {"Performance" | "Low" | "Medium" | "High" | "Ultra" | "Neural-Low" | "Neural-Medium" | "Neural-High"} mode
*/
setQualityMode(mode) {
if (mode === "Performance") {
this.configuration.neuralDenoise = false;
this.configuration.aoSamples = 8;
this.configuration.denoiseSamples = 4;
this.configuration.denoiseRadius = 12;
} else if (mode === "Low") {
this.configuration.neuralDenoise = false;
this.configuration.aoSamples = 16;
this.configuration.denoiseSamples = 4;
this.configuration.denoiseRadius = 12;
} else if (mode === "Medium") {
this.configuration.neuralDenoise = false;
this.configuration.aoSamples = 16;
this.configuration.denoiseSamples = 8;
this.configuration.denoiseRadius = 12;
} else if (mode === "High") {
this.configuration.neuralDenoise = false;
this.configuration.aoSamples = 64;
this.configuration.denoiseSamples = 8;
this.configuration.denoiseRadius = 6;
} else if (mode === "Ultra") {
this.configuration.neuralDenoise = false;
this.configuration.aoSamples = 64;
this.configuration.denoiseSamples = 16;
this.configuration.denoiseRadius = 6;
} else if (["Neural-Low", "Neural-Medium", "Neural-High"].includes(mode)) {
this.configuration.aoSamples = 16;
this.configuration.denoiseSamples = {
"Neural-Low": 4,
"Neural-Medium": 8,
"Neural-High": 16
}[mode];
this.configuration.denoiseRadius = 12;
this.configuration.denoiseIterations = 2;
this.configuration.halfRes = false;
this.configuration.neuralDenoise = true;
}
}
}
export { N8AOPostPass };
@@ -0,0 +1,278 @@
import model from './NeuralDenoiseModel.json';
const MATRIX_LAYOUTS = [
['tapInputWeight', 'tapInputWeights', 8, 9],
['tapOutputWeight', 'tapOutputWeights', 8, 8],
['globalWeight', 'globalWeights', 8, 3],
['keyWeight', 'keyProjectionWeights', 8, 8],
['valueWeight', 'valueProjectionWeights', 8, 8],
['headWeight', 'headWeights', 8, 32],
['outputWeight', 'outputWeights', 1, 8]
];
const scale = (name) => {
const value = model.quantization?.scales?.[name];
if (!(value > 0) || !Number.isFinite(value)) {
throw new Error(`The bundled N8AO neural model has no valid ${name} scale.`);
}
return value;
};
if (
model.architecture !== 'attention-v3-int8'
|| model.formatVersion !== 3
|| model.quantization?.scheme !== 'symmetric-int8-per-tensor'
|| model.quantization?.zeroPoint !== 0
|| model.supportedDenoiseSamples?.join(',') !== '4,8,16'
|| MATRIX_LAYOUTS.some(([, field, rows, columns]) => (
model[field]?.length !== rows * columns
|| model[field].some((value) => !Number.isInteger(value) || value < -127 || value > 127)
))
) {
throw new Error('The bundled N8AO neural denoise model has an unsupported layout.');
}
const glslFloat = (value) => {
if (!Number.isFinite(value)) {
throw new Error('The bundled N8AO neural model contains a non-finite value.');
}
if (Object.is(value, -0)) {
return '0.0';
}
const text = Number(value).toString();
return /[.eE]/.test(text) ? text : `${text}.0`;
};
const COMPONENTS = ['x', 'y', 'z', 'w'];
const tokenAccessors = (name) => [
...COMPONENTS.map((component) => `${name}.lo.${component}`),
...COMPONENTS.map((component) => `${name}.hi.${component}`)
];
const weightedTerm = (coefficient, input) => {
if (coefficient === 0) {
return null;
}
if (coefficient === 1) {
return input;
}
if (coefficient === -1) {
return `(-${input})`;
}
if (coefficient < 0) {
return `(-${glslFloat(-coefficient)} * ${input})`;
}
return `${glslFloat(coefficient)} * ${input}`;
};
const floatWeightedTerm = (coefficient, input) => {
if (coefficient === 0) {
return null;
}
return `${glslFloat(coefficient)} * ${input}`;
};
const sumTerms = (terms) => terms.filter(Boolean).join(' + ') || '0.0';
const quantizedRow = (weights, row, width, inputs, tensorScale, bias) => {
const terms = inputs.map((input, column) => (
weightedTerm(weights[row * width + column], input)
));
return `${glslFloat(tensorScale)} * (${sumTerms(terms)}) + ${glslFloat(bias[row])}`;
};
const vec4 = (values, indent = ' ') => `vec4(\n${values
.map((value) => `${indent} ${value}`)
.join(',\n')}\n${indent})`;
const tokenLayer = ({ functionName, scaleName, weights, bias, width = 8, relu = false }) => {
const inputs = tokenAccessors('inputToken');
const tensorScale = scale(scaleName);
const rows = Array.from({ length: 8 }, (_, row) => (
quantizedRow(weights, row, width, inputs, tensorScale, bias)
));
const low = vec4(rows.slice(0, 4));
const high = vec4(rows.slice(4));
const wrap = (value) => relu ? `max(${value}, vec4(0.0))` : value;
return `
NeuralToken neural${functionName[0].toUpperCase()}${functionName.slice(1)}(NeuralToken inputToken) {
return NeuralToken(
${wrap(low)},
${wrap(high)}
);
}
`;
};
const foldedBias = (
weights,
bias,
means,
inverseStandardDeviations,
rows,
width,
tensorScale,
constantInputs = {}
) => Array.from({ length: rows }, (_, row) => {
let value = bias[row];
for (let column = 0; column < width; column++) {
const weight = weights[row * width + column] * tensorScale;
value -= weight * inverseStandardDeviations[column] * means[column];
if (Object.hasOwn(constantInputs, column)) {
value += weight * inverseStandardDeviations[column] * constantInputs[column];
}
}
return value;
});
const tapInputScale = scale('tapInputWeight');
const tapInputBias = foldedBias(
model.tapInputWeights,
model.tapInputBias,
model.tapFeatureMean,
model.tapFeatureInverseStandardDeviation,
8,
9,
tapInputScale,
{ 8: 1 }
);
const tapScaledAccessors = tokenAccessors('scaledInput');
const tapInputRows = Array.from({ length: 8 }, (_, row) => (
quantizedRow(
model.tapInputWeights,
row,
9,
tapScaledAccessors,
tapInputScale,
tapInputBias
)
));
const tapInputShader = `
NeuralToken neuralTapInput(NeuralToken raw) {
NeuralToken scaledInput = NeuralToken(
raw.lo * ${vec4(model.tapFeatureInverseStandardDeviation.slice(0, 4), ' ')},
raw.hi * ${vec4(model.tapFeatureInverseStandardDeviation.slice(4, 8), ' ')}
);
return NeuralToken(
max(${vec4(tapInputRows.slice(0, 4))}, vec4(0.0)),
max(${vec4(tapInputRows.slice(4))}, vec4(0.0))
);
}
`;
const globalScale = scale('globalWeight');
const globalBias = foldedBias(
model.globalWeights,
model.globalBias,
model.globalFeatureMean,
model.globalFeatureInverseStandardDeviation,
8,
3,
globalScale
);
const globalInputs = COMPONENTS.slice(0, 3).map((component) => `scaledInput.${component}`);
const globalRows = Array.from({ length: 8 }, (_, row) => (
quantizedRow(model.globalWeights, row, 3, globalInputs, globalScale, globalBias)
));
const globalInputShader = `
NeuralToken neuralEncodeGlobal(vec4 raw) {
vec3 scaledInput = raw.xyz * vec3(
${model.globalFeatureInverseStandardDeviation.map(glslFloat).join(', ')}
);
return NeuralToken(
max(${vec4(globalRows.slice(0, 4))}, vec4(0.0)),
max(${vec4(globalRows.slice(4))}, vec4(0.0))
);
}
`;
const queryInputs = tokenAccessors('key');
const queryRows = Array.from({ length: 4 }, (_, query) => sumTerms(
queryInputs.map((input, column) => floatWeightedTerm(
model.summaryQueries[query * 8 + column],
input
))
));
const queryShader = `
vec4 neuralQueryScores(NeuralToken key) {
return ${vec4(queryRows)};
}
`;
const summaryInputs = [];
for (let query = 0; query < 4; query++) {
summaryInputs.push(
...COMPONENTS.map((component) => `runningSummaryLo[${query}].${component}`),
...COMPONENTS.map((component) => `runningSummaryHi[${query}].${component}`)
);
}
const headScale = scale('headWeight');
const headRows = Array.from({ length: 8 }, (_, row) => (
quantizedRow(model.headWeights, row, 32, summaryInputs, headScale, model.headBias)
));
const headShader = `
NeuralToken neuralHead(
vec4 runningSummaryLo[4],
vec4 runningSummaryHi[4]
) {
return NeuralToken(
max(${vec4(headRows.slice(0, 4))}, vec4(0.0)),
max(${vec4(headRows.slice(4))}, vec4(0.0))
);
}
`;
const outputExpression = quantizedRow(
model.outputWeights,
0,
8,
tokenAccessors('head'),
scale('outputWeight'),
[model.outputBias]
);
const outputShader = `
float neuralOutput(NeuralToken head) {
return ${outputExpression};
}
`;
const neuralDenoiseShader = [
tapInputShader,
tokenLayer({
functionName: 'tapOutput',
scaleName: 'tapOutputWeight',
weights: model.tapOutputWeights,
bias: model.tapOutputBias,
relu: true
}),
globalInputShader,
tokenLayer({
functionName: 'keyProject',
scaleName: 'keyWeight',
weights: model.keyProjectionWeights,
bias: new Array(8).fill(0)
}),
tokenLayer({
functionName: 'valueProject',
scaleName: 'valueWeight',
weights: model.valueProjectionWeights,
bias: new Array(8).fill(0)
}),
queryShader,
headShader,
outputShader
].join('\n');
const int8WeightCount = MATRIX_LAYOUTS.reduce((sum, [, field]) => sum + model[field].length, 0);
const nonzeroInt8WeightCount = MATRIX_LAYOUTS.reduce(
(sum, [, field]) => sum + model[field].filter((value) => value !== 0).length,
0
);
export {
model as neuralDenoiseModel,
neuralDenoiseShader,
int8WeightCount,
nonzeroInt8WeightCount
};
@@ -0,0 +1,696 @@
{
"architecture": "attention-v3-int8",
"formatVersion": 3,
"globalBias": [
-0.32877659797668457,
0.4370867609977722,
-0.05251404270529747,
1.3072023391723633,
0.0477047860622406,
0.24477416276931763,
0.009111796505749226,
-0.17459993064403534
],
"globalFeatureInverseStandardDeviation": [
10.771836280822754,
1.9548665285110474,
1.612365484237671
],
"globalFeatureMean": [
0.9198138117790222,
-0.49808526039123535,
0.03374629095196724
],
"globalWeights": [
101,
-4,
7,
-127,
6,
-11,
3,
1,
0,
-16,
-7,
8,
-8,
-1,
7,
-4,
0,
0,
-12,
-3,
-13,
1,
0,
2
],
"headBias": [
-0.15895532071590424,
0.007501596584916115,
-0.47742825746536255,
0.01632097363471985,
-0.48355796933174133,
-0.1052703931927681,
-0.8414919376373291,
-0.21046382188796997
],
"headWeights": [
-45,
-7,
-45,
-20,
7,
-13,
120,
-24,
-15,
-26,
-19,
1,
27,
-48,
-4,
-10,
1,
-5,
-24,
64,
91,
-1,
-68,
39,
54,
39,
101,
-40,
-127,
64,
-41,
-17,
-23,
-19,
3,
35,
-2,
33,
3,
9,
-64,
-32,
30,
42,
-112,
12,
28,
-11,
15,
2,
-4,
-7,
7,
-3,
-5,
1,
76,
48,
-34,
-67,
103,
-40,
-26,
1,
58,
-11,
46,
-41,
5,
-6,
-17,
-8,
13,
17,
-35,
45,
27,
-17,
-28,
7,
-53,
12,
-51,
6,
-32,
-5,
58,
-9,
-28,
-21,
37,
-12,
1,
20,
2,
2,
11,
7,
-4,
-9,
2,
-15,
-1,
-8,
16,
12,
-27,
-1,
61,
-5,
-1,
4,
-7,
-2,
7,
4,
1,
4,
-2,
4,
-18,
-16,
28,
6,
-65,
16,
3,
-3,
-5,
-2,
0,
-40,
-21,
-22,
14,
30,
-21,
49,
15,
-64,
43,
19,
23,
18,
5,
-15,
21,
21,
30,
17,
-11,
-6,
22,
-38,
-20,
97,
-46,
-5,
-13,
-59,
26,
-13,
11,
-2,
-10,
-8,
2,
-15,
-17,
-27,
-9,
26,
7,
-7,
6,
9,
-32,
5,
-9,
12,
49,
17,
-1,
24,
20,
35,
14,
-33,
-50,
-1,
-4,
-26,
11,
11,
9,
-80,
30,
9,
36,
6,
-12,
-4,
-7,
39,
-10,
-30,
-49,
1,
-43,
-20,
-34,
76,
-36,
-10,
15,
-8,
43,
31,
38,
-42,
39,
37,
-7,
7,
8,
16,
28,
-83,
32,
9,
23,
-13,
39,
119,
23,
-127,
-24,
8,
-48,
-29,
-7,
-33,
-12,
58,
-24,
-29,
-19,
12,
-55,
-90,
0,
126,
26,
42,
54,
22
],
"keyProjectionWeights": [
0,
-1,
-1,
1,
1,
1,
1,
1,
-64,
32,
49,
23,
-25,
4,
27,
-22,
0,
1,
-1,
-1,
-1,
1,
1,
0,
-34,
38,
64,
88,
13,
-53,
-41,
58,
-7,
-4,
79,
-41,
27,
26,
14,
2,
-2,
1,
1,
1,
-1,
-1,
1,
1,
1,
-1,
-1,
-1,
-1,
-1,
1,
1,
4,
-3,
126,
42,
-40,
-116,
35,
20
],
"name": "residual-attention-v3-50m-qat-int8-epoch-25-zo-278w",
"outputBias": -0.0005526235327124596,
"outputWeights": [
11,
11,
14,
-27,
9,
-22,
127,
6
],
"quantization": {
"scales": {
"globalWeight": 0.021090541950849095,
"headWeight": 0.04935851140909355,
"keyWeight": 0.1733924937791441,
"outputWeight": 0.0030087142047955295,
"tapInputWeight": 0.1096231754049479,
"tapOutputWeight": 0.017949438644286102,
"valueWeight": 0.013986751242596301
},
"scheme": "symmetric-int8-per-tensor",
"zeroPoint": 0
},
"summaryQueries": [
0.0038647791370749474,
0.09565000981092453,
0.002756686182692647,
-0.08183622360229492,
-0.15209506452083588,
-0.0006105066277086735,
0.0010439646430313587,
-0.03020688332617283,
0.005065929610282183,
0.14488759636878967,
0.003160916268825531,
-0.0855727270245552,
-0.3123375475406647,
0.00039022407145239413,
0.0037786494940519333,
0.1451321840286255,
-0.002009483054280281,
0.0597594790160656,
0.0045239729806780815,
-0.08765853196382523,
-0.13884992897510529,
-0.0021647117100656033,
0.003985927440226078,
0.09727758169174194,
0.007170629221946001,
0.0786278173327446,
0.004103775601834059,
-0.1198369711637497,
-0.2925199568271637,
-0.002055276418104768,
0.0030450925696641207,
0.14401987195014954
],
"supportedDenoiseSamples": [
4,
8,
16
],
"tapFeatureInverseStandardDeviation": [
2.283243417739868,
0.8810898065567017,
0.8210930228233337,
3.752316474914551,
3.6375720500946045,
2.670454978942871,
10.249449729919434,
0.12639354169368744,
100.0
],
"tapFeatureMean": [
0.010318092070519924,
0.01364430133253336,
0.13411010801792145,
-0.004725644364953041,
0.10053129494190216,
0.8384788632392883,
0.9203217625617981,
1.7139031887054443,
1.0
],
"tapInputBias": [
0.4780646860599518,
-0.45214661955833435,
0.289407879114151,
0.34804567694664,
-0.1320028454065323,
0.17722633481025696,
0.011480014771223068,
-0.26692497730255127
],
"tapInputWeights": [
0,
1,
7,
0,
-1,
-7,
0,
31,
-2,
-1,
14,
-126,
0,
0,
-1,
2,
26,
-2,
0,
66,
-73,
0,
0,
0,
-12,
22,
-3,
0,
-76,
-90,
0,
-1,
-7,
-3,
58,
-1,
0,
2,
6,
0,
0,
3,
4,
-40,
0,
0,
3,
-13,
0,
0,
1,
-7,
-13,
0,
0,
-7,
10,
0,
-2,
-7,
0,
-39,
-2,
0,
-13,
-19,
0,
-2,
20,
-1,
3,
-1
],
"tapOutputBias": [
-0.3867710530757904,
0.1349504142999649,
0.35706064105033875,
-0.5938405394554138,
-0.031154220923781395,
1.4079623222351074,
-1.9221038818359375,
0.6029739379882812
],
"tapOutputWeights": [
18,
-4,
47,
19,
74,
-94,
-21,
-9,
73,
-59,
88,
-10,
-3,
71,
-7,
24,
20,
74,
24,
-31,
-12,
-10,
-15,
-45,
-126,
2,
-4,
27,
-5,
24,
35,
-11,
-4,
9,
-8,
26,
-10,
27,
26,
-20,
17,
-50,
5,
-35,
0,
-5,
12,
-71,
89,
-58,
22,
-83,
-115,
7,
-16,
-89,
89,
22,
1,
-20,
-22,
-25,
-26,
44
],
"valueProjectionWeights": [
-16,
-6,
40,
-17,
84,
-76,
59,
51,
-10,
-2,
-10,
-1,
-23,
74,
-70,
23,
-5,
4,
4,
-7,
-77,
127,
20,
-48,
-36,
4,
-17,
-12,
-4,
10,
29,
-27,
-2,
11,
-47,
-50,
-54,
-3,
14,
11,
-23,
-1,
109,
31,
4,
-100,
-33,
-36,
-19,
0,
-8,
20,
-35,
-24,
79,
2,
44,
2,
5,
7,
22,
-70,
-67,
-35
]
}
@@ -0,0 +1,346 @@
import * as THREE from 'three';
import { neuralDenoiseShader } from './NeuralDenoise.js';
const PoissionBlur = {
uniforms: {
'sceneDiffuse': { value: null },
'sceneDepth': { value: null },
'tDiffuse': { value: null },
'projMat': { value: /* @__PURE__ */ new THREE.Matrix4() },
'viewMat': { value: /* @__PURE__ */ new THREE.Matrix4() },
'projectionMatrixInv': { value: /* @__PURE__ */ new THREE.Matrix4() },
'viewMatrixInv': { value: /* @__PURE__ */ new THREE.Matrix4() },
'cameraPos': { value: /* @__PURE__ */ new THREE.Vector3() },
'resolution': { value: /* @__PURE__ */ new THREE.Vector2() },
'time': { value: 0.0 },
'r': { value: 5.0 },
'blueNoise': { value: null },
'radius': { value: 12.0 },
'worldRadius': { value: 5.0 },
'index': { value: 0.0 },
"poissonDisk": { value: [] },
"distanceFalloff": { value: 1.0 },
'near': { value: 0.1 },
'far': { value: 1000.0 },
'screenSpaceRadius': { value: false }
},
depthWrite: false,
depthTest: false,
vertexShader: /* glsl */ `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = vec4(position, 1.0);
}`,
fragmentShader: /* glsl */ `
uniform sampler2D sceneDiffuse;
uniform highp sampler2D sceneDepth;
uniform sampler2D tDiffuse;
uniform sampler2D blueNoise;
uniform mat4 projectionMatrixInv;
uniform mat4 viewMatrixInv;
uniform vec2 resolution;
uniform float r;
uniform float radius;
uniform float worldRadius;
uniform float index;
uniform float near;
uniform float far;
uniform float distanceFalloff;
uniform bool screenSpaceRadius;
varying vec2 vUv;
highp float linearize_depth(highp float d, highp float zNear,highp float zFar)
{
highp float z_n = 2.0 * d - 1.0;
return 2.0 * zNear * zFar / (zFar + zNear - z_n * (zFar - zNear));
}
highp float linearize_depth_log(highp float d, highp float nearZ,highp float farZ) {
float depth = pow(2.0, d * log2(farZ + 1.0)) - 1.0;
float a = farZ / (farZ - nearZ);
float b = farZ * nearZ / (nearZ - farZ);
float linDepth = a + b / depth;
return linearize_depth(linDepth, nearZ, farZ);
}
highp float linearize_depth_ortho(highp float d, highp float nearZ, highp float farZ) {
return nearZ + (farZ - nearZ) * d;
}
float depthToClipZ(float depth) {
#ifdef REVERSEDEPTH
return depth;
#else
return depth * 2.0 - 1.0;
#endif
}
bool isBackgroundDepth(float depth) {
#ifdef REVERSEDEPTH
return depth == 0.0;
#else
return depth == 1.0;
#endif
}
vec3 getWorldPosLog(vec3 posS) {
vec2 uv = posS.xy;
float z = posS.z;
float nearZ =near;
float farZ = far;
float depth = pow(2.0, z * log2(farZ + 1.0)) - 1.0;
float a = farZ / (farZ - nearZ);
float b = farZ * nearZ / (nearZ - farZ);
float linDepth = a + b / depth;
vec4 clipVec = vec4(uv, linDepth, 1.0) * 2.0 - 1.0;
vec4 wpos = projectionMatrixInv * clipVec;
return wpos.xyz / wpos.w;
}
vec3 getWorldPos(float depth, vec2 coord) {
#ifdef LOGDEPTH
#ifndef ORTHO
return getWorldPosLog(vec3(coord, depth));
#endif
#endif
#ifdef ORTHO
float z = depthToClipZ(depth);
vec4 clipSpacePosition = vec4(coord * 2. - 1., z, 1.);
vec4 viewSpacePosition = projectionMatrixInv * clipSpacePosition;
viewSpacePosition.xyz /= viewSpacePosition.w;
return viewSpacePosition.xyz;
#else
vec2 ndc = coord * 2. - 1.;
float ndcZ = depthToClipZ(depth);
mat4 Q = projectionMatrixInv;
vec3 view = vec3(Q[0][0] * ndc.x + Q[3][0], Q[1][1] * ndc.y + Q[3][1], Q[3][2]);
float invW = 1.0 / (Q[2][3] * ndcZ + Q[3][3]);
return view * invW;
#endif
}
#ifdef NEURAL_DENOISE
struct NeuralToken {
highp vec4 lo;
highp vec4 hi;
};
${neuralDenoiseShader}
vec3 neuralSafeNormalize(vec3 value, vec3 fallback) {
float lengthSquared = dot(value, value);
return lengthSquared > 1e-12 ? value * inversesqrt(lengthSquared) : fallback;
}
mat3 neuralLocalFrame(vec3 inputNormal) {
vec3 frameNormal = neuralSafeNormalize(inputNormal, vec3(0.0, 0.0, 1.0));
vec3 helper = abs(frameNormal.z) < 0.999
? vec3(0.0, 0.0, 1.0)
: vec3(0.0, 1.0, 0.0);
vec3 tangent = neuralSafeNormalize(
cross(helper, frameNormal),
vec3(1.0, 0.0, 0.0)
);
vec3 bitangent = cross(frameNormal, tangent);
return transpose(mat3(tangent, bitangent, frameNormal));
}
void neuralConsumeToken(
NeuralToken token,
inout vec4 runningMaximum,
inout vec4 runningDenominator,
inout vec4 runningSummaryLo[4],
inout vec4 runningSummaryHi[4]
) {
NeuralToken key = neuralKeyProject(token);
NeuralToken value = neuralValueProject(token);
vec4 score = neuralQueryScores(key) * 0.3535533905932738;
vec4 newMaximum = max(runningMaximum, score);
vec4 oldScale = exp(runningMaximum - newMaximum);
vec4 newScale = exp(score - newMaximum);
runningSummaryLo[0] = runningSummaryLo[0] * oldScale.x + value.lo * newScale.x;
runningSummaryHi[0] = runningSummaryHi[0] * oldScale.x + value.hi * newScale.x;
runningSummaryLo[1] = runningSummaryLo[1] * oldScale.y + value.lo * newScale.y;
runningSummaryHi[1] = runningSummaryHi[1] * oldScale.y + value.hi * newScale.y;
runningSummaryLo[2] = runningSummaryLo[2] * oldScale.z + value.lo * newScale.z;
runningSummaryHi[2] = runningSummaryHi[2] * oldScale.z + value.hi * newScale.z;
runningSummaryLo[3] = runningSummaryLo[3] * oldScale.w + value.lo * newScale.w;
runningSummaryHi[3] = runningSummaryHi[3] * oldScale.w + value.hi * newScale.w;
runningDenominator = runningDenominator * oldScale + newScale;
runningMaximum = newMaximum;
}
void neuralEncodeTap(
NeuralToken raw,
inout vec4 runningMaximum,
inout vec4 runningDenominator,
inout vec4 runningSummaryLo[4],
inout vec4 runningSummaryHi[4]
) {
NeuralToken first = neuralTapInput(raw);
NeuralToken token = neuralTapOutput(first);
neuralConsumeToken(
token,
runningMaximum,
runningDenominator,
runningSummaryLo,
runningSummaryHi
);
}
float neuralFinish(
float baselineAO,
inout vec4 runningMaximum,
inout vec4 runningDenominator,
inout vec4 runningSummaryLo[4],
inout vec4 runningSummaryHi[4]
) {
vec4 raw = vec4(
baselineAO,
log(max(worldRadius, 1e-6)),
log(max(distanceFalloff, 1e-6)),
0.0
);
NeuralToken token = neuralEncodeGlobal(raw);
neuralConsumeToken(
token,
runningMaximum,
runningDenominator,
runningSummaryLo,
runningSummaryHi
);
vec4 inverseDenominator = 1.0 / max(runningDenominator, vec4(1e-12));
runningSummaryLo[0] *= inverseDenominator.x;
runningSummaryHi[0] *= inverseDenominator.x;
runningSummaryLo[1] *= inverseDenominator.y;
runningSummaryHi[1] *= inverseDenominator.y;
runningSummaryLo[2] *= inverseDenominator.z;
runningSummaryHi[2] *= inverseDenominator.z;
runningSummaryLo[3] *= inverseDenominator.w;
runningSummaryHi[3] *= inverseDenominator.w;
NeuralToken head = neuralHead(runningSummaryLo, runningSummaryHi);
return neuralOutput(head);
}
#endif
#include <common>
#define NUM_SAMPLES __N8AO_DENOISE_SAMPLES__
uniform vec2 poissonDisk[NUM_SAMPLES];
void main() {
const float pi = 3.14159;
vec2 texelSize = vec2(1.0 / resolution.x, 1.0 / resolution.y);
vec2 uv = vUv;
vec4 data = texture2D(tDiffuse, vUv);
float occlusion = data.r;
float baseOcc = data.r;
vec3 normal = data.gba * 2.0 - 1.0;
float count = 1.0;
float d = texture2D(sceneDepth, vUv).x;
if (isBackgroundDepth(d)) {
gl_FragColor = data;
return;
}
vec3 worldPos = getWorldPos(d, vUv);
float size = radius;
float angle;
#ifdef NEURAL_DENOISE
// The neural material is only bound for denoise iteration two.
angle = texture2D(blueNoise, gl_FragCoord.xy / 128.0).z * PI2;
#else
if (index == 0.0) {
angle = texture2D(blueNoise, gl_FragCoord.xy / 128.0).w * PI2;
} else if (index == 1.0) {
angle = texture2D(blueNoise, gl_FragCoord.xy / 128.0).z * PI2;
} else if (index == 2.0) {
angle = texture2D(blueNoise, gl_FragCoord.xy / 128.0).y * PI2;
} else {
angle = texture2D(blueNoise, gl_FragCoord.xy / 128.0).x * PI2;
}
#endif
mat2 rotationMatrix = mat2(cos(angle), -sin(angle), sin(angle), cos(angle));
float radiusToUse = screenSpaceRadius ? distance(
worldPos,
getWorldPos(d, vUv +
vec2(worldRadius, 0.0) / resolution)
) : worldRadius;
float distanceFalloffToUse =screenSpaceRadius ?
radiusToUse * distanceFalloff
: radiusToUse * distanceFalloff * 0.2;
float invDistance = (1.0 / distanceFalloffToUse);
#ifdef NEURAL_DENOISE
mat3 neuralWorldToLocal = neuralLocalFrame(normal);
float neuralInverseRadius = 1.0 / max(radiusToUse, 1e-6);
float neuralInverseDistance = 1.0 / max(distanceFalloffToUse, 1e-6);
vec4 neuralMaximum = vec4(-1e30);
vec4 neuralDenominator = vec4(0.0);
vec4 neuralSummaryLo[4];
vec4 neuralSummaryHi[4];
for (int query = 0; query < 4; query++) {
neuralSummaryLo[query] = vec4(0.0);
neuralSummaryHi[query] = vec4(0.0);
}
#endif
for(int i = 0; i < NUM_SAMPLES; i++) {
vec2 offset = (rotationMatrix * poissonDisk[i]) * texelSize * size;
vec4 dataSample = texture2D(tDiffuse, uv + offset);
float occSample = dataSample.r;
vec3 normalSample = dataSample.gba * 2.0 - 1.0;
float dSample = texture2D(sceneDepth, uv + offset).x;
vec3 worldPosSample = getWorldPos(dSample, uv + offset);
float tangentPlaneDist = abs(dot(worldPosSample - worldPos, normal));
float rangeCheck = float(!isBackgroundDepth(dSample)) * exp(-1.0 * tangentPlaneDist * invDistance ) * max(dot(normal, normalSample), 0.0);
occlusion += occSample * rangeCheck;
count += rangeCheck;
#ifdef NEURAL_DENOISE
if (!isBackgroundDepth(dSample)) {
vec3 localDelta = (neuralWorldToLocal * (worldPosSample - worldPos))
* neuralInverseRadius;
vec3 localNormal = neuralWorldToLocal
* neuralSafeNormalize(normalSample, vec3(0.0, 0.0, 1.0));
NeuralToken rawTap = NeuralToken(
vec4(localDelta, localNormal.x),
vec4(
localNormal.y,
localNormal.z,
occSample,
tangentPlaneDist * neuralInverseDistance
)
);
neuralEncodeTap(
rawTap,
neuralMaximum,
neuralDenominator,
neuralSummaryLo,
neuralSummaryHi
);
}
#endif
}
if (count > 0.0) {
occlusion /= count;
}
occlusion = clamp(occlusion, 0.0, 1.0);
if (occlusion == 0.0) {
occlusion = 1.0;
}
#ifdef NEURAL_DENOISE
occlusion = clamp(
occlusion + neuralFinish(
occlusion,
neuralMaximum,
neuralDenominator,
neuralSummaryLo,
neuralSummaryHi
),
0.0,
1.0
);
#endif
gl_FragColor = vec4(occlusion, 0.5 + 0.5 * normal);
}
`
}
export { PoissionBlur };
+67
View File
@@ -0,0 +1,67 @@
import * as THREE from "three";
const version = /* @__PURE__ */ (() =>
parseInt(THREE.REVISION.replace(/\D+/g, "")))();
// NOTE: WebGLMultipleRenderTargets is removed since r172, so we implement it ourselves.
// https://github.com/mrdoob/three.js/pull/26427
export const WebGLMultipleRenderTargetsCompat =
version >= 162
? class extends THREE.WebGLRenderTarget {
constructor(width = 1, height = 1, count = 1, options = {}) {
super(width, height, { ...options, count });
this.isWebGLMultipleRenderTargets = true;
}
get texture() {
return this.textures;
}
}
: class extends THREE.WebGLRenderTarget {
constructor(width = 1, height = 1, count = 1, options = {}) {
super(width, height, options);
this.isWebGLMultipleRenderTargets = true;
const texture = this.texture;
this.texture = [];
for (let i = 0; i < count; i++) {
this.texture[i] = texture.clone();
this.texture[i].isRenderTargetTexture = true;
}
}
setSize(width, height, depth = 1) {
if (
this.width !== width ||
this.height !== height ||
this.depth !== depth
) {
this.width = width;
this.height = height;
this.depth = depth;
for (let i = 0, il = this.texture.length; i < il; i++) {
this.texture[i].image.width = width;
this.texture[i].image.height = height;
this.texture[i].image.depth = depth;
}
this.dispose();
}
this.viewport.set(0, 0, width, height);
this.scissor.set(0, 0, width, height);
}
copy(source) {
this.dispose();
this.width = source.width;
this.height = source.height;
this.depth = source.depth;
this.scissor.copy(source.scissor);
this.scissorTest = source.scissorTest;
this.viewport.copy(source.viewport);
this.depthBuffer = source.depthBuffer;
this.stencilBuffer = source.stencilBuffer;
if (source.depthTexture !== null)
this.depthTexture = source.depthTexture.clone();
this.texture.length = 0;
for (let i = 0, il = source.texture.length; i < il; i++) {
this.texture[i] = source.texture[i].clone();
this.texture[i].isRenderTargetTexture = true;
}
return this;
}
};