mirror of
https://github.com/4ian/GDevelop.git
synced 2026-10-12 02:01:17 +00:00
WIP
This commit is contained in:
@@ -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.
|
||||
|
||||
|
||||
@@ -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
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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": ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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 };
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user