mirror of
https://github.com/4ian/GDevelop.git
synced 2026-10-12 02:01:17 +00:00
Add Ambient Occlusion, Depth-of-field, faster bloom and tone mapping for 3D games (#9180)
Co-authored-by: Davy Hélard <[email protected]>
This commit is contained in:
co-authored by
Davy Hélard
parent
d45fcdb80a
commit
b30074ab24
@@ -15,15 +15,15 @@ namespace gdjs {
|
||||
return new gdjs.PixiFiltersTools.EmptyFilter();
|
||||
}
|
||||
return new (class implements gdjs.PixiFiltersTools.Filter {
|
||||
shaderPass: THREE_ADDONS.UnrealBloomPass;
|
||||
shaderPass: THREE_ADDONS.LowResolutionBloomPass;
|
||||
_isEnabled: boolean;
|
||||
|
||||
constructor() {
|
||||
this.shaderPass = new THREE_ADDONS.UnrealBloomPass(
|
||||
this.shaderPass = new THREE_ADDONS.LowResolutionBloomPass(
|
||||
new THREE.Vector2(256, 256),
|
||||
1,
|
||||
0,
|
||||
0
|
||||
0.5,
|
||||
0.4,
|
||||
0.85
|
||||
);
|
||||
this._isEnabled = false;
|
||||
}
|
||||
@@ -45,7 +45,9 @@ namespace gdjs {
|
||||
if (!(target instanceof gdjs.Layer)) {
|
||||
return false;
|
||||
}
|
||||
target.getRenderer().addPostProcessingPass(this.shaderPass);
|
||||
target
|
||||
.getRenderer()
|
||||
.addPostProcessingPass(this.shaderPass, effectData.name);
|
||||
this._isEnabled = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -43,7 +43,9 @@ namespace gdjs {
|
||||
if (!(target instanceof gdjs.Layer)) {
|
||||
return false;
|
||||
}
|
||||
target.getRenderer().addPostProcessingPass(this.shaderPass);
|
||||
target
|
||||
.getRenderer()
|
||||
.addPostProcessingPass(this.shaderPass, effectData.name);
|
||||
this._isEnabled = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,309 @@
|
||||
namespace gdjs {
|
||||
interface DepthOfFieldFilterNetworkSyncData {
|
||||
fd: number;
|
||||
fr: number;
|
||||
td: number;
|
||||
mb: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* A bokeh blur adapted from Three.js `BokehShader`, with:
|
||||
* - a range around the focus distance staying sharp,
|
||||
* - a blur growing linearly over a transition distance,
|
||||
* - samples weighted to avoid sharp objects bleeding on the blurred
|
||||
* background behind them.
|
||||
*/
|
||||
const depthOfFieldShader = {
|
||||
defines: {
|
||||
PERSPECTIVE_CAMERA: 1,
|
||||
},
|
||||
uniforms: {
|
||||
tDiffuse: { value: null },
|
||||
tDepth: { value: null },
|
||||
focusDistance: { value: 10.0 },
|
||||
focusRange: { value: 1.0 },
|
||||
transitionDistance: { value: 10.0 },
|
||||
maxBlur: { value: 0.01 },
|
||||
aspect: { value: 1.0 },
|
||||
nearClip: { value: 1.0 },
|
||||
farClip: { value: 1000.0 },
|
||||
},
|
||||
vertexShader: /* glsl */ `
|
||||
varying vec2 vUv;
|
||||
|
||||
void main() {
|
||||
vUv = uv;
|
||||
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
||||
}`,
|
||||
fragmentShader: /* glsl */ `
|
||||
#include <common>
|
||||
#include <packing>
|
||||
|
||||
varying vec2 vUv;
|
||||
|
||||
uniform sampler2D tDiffuse;
|
||||
uniform sampler2D tDepth;
|
||||
uniform float focusDistance;
|
||||
uniform float focusRange;
|
||||
uniform float transitionDistance;
|
||||
uniform float maxBlur;
|
||||
uniform float aspect;
|
||||
uniform float nearClip;
|
||||
uniform float farClip;
|
||||
|
||||
float getDistance(const in vec2 uv) {
|
||||
float depth = texture2D(tDepth, uv).x;
|
||||
#if PERSPECTIVE_CAMERA == 1
|
||||
return -perspectiveDepthToViewZ(depth, nearClip, farClip);
|
||||
#else
|
||||
return -orthographicDepthToViewZ(depth, nearClip, farClip);
|
||||
#endif
|
||||
}
|
||||
|
||||
float getBlur(const in float distance) {
|
||||
float outOfFocusDistance =
|
||||
abs(distance - focusDistance) - focusRange * 0.5;
|
||||
return clamp(outOfFocusDistance / transitionDistance, 0.0, 1.0);
|
||||
}
|
||||
|
||||
vec4 color;
|
||||
float totalWeight;
|
||||
float centerDistance;
|
||||
float centerBlur;
|
||||
vec2 blurRadius;
|
||||
|
||||
void addSample(const in vec2 offset) {
|
||||
vec2 uv = vUv + offset * blurRadius;
|
||||
float sampleDistance = getDistance(uv);
|
||||
// A sharper object in front of the pixel must not be spread on it.
|
||||
float weight = sampleDistance < centerDistance
|
||||
? clamp(getBlur(sampleDistance) / centerBlur, 0.0, 1.0)
|
||||
: 1.0;
|
||||
color += texture2D(tDiffuse, uv) * weight;
|
||||
totalWeight += weight;
|
||||
}
|
||||
|
||||
void main() {
|
||||
color = texture2D(tDiffuse, vUv);
|
||||
centerDistance = getDistance(vUv);
|
||||
centerBlur = getBlur(centerDistance);
|
||||
if (centerBlur <= 0.0) {
|
||||
gl_FragColor = color;
|
||||
return;
|
||||
}
|
||||
totalWeight = 1.0;
|
||||
blurRadius = vec2(1.0, aspect) * maxBlur * centerBlur;
|
||||
|
||||
addSample(vec2( 0.0, 1.0 ));
|
||||
addSample(vec2( 0.375, 0.925));
|
||||
addSample(vec2( 0.725, 0.725));
|
||||
addSample(vec2(-0.925, 0.375));
|
||||
addSample(vec2( 1.0, 0.0 ));
|
||||
addSample(vec2( 0.925, -0.375));
|
||||
addSample(vec2( 0.725, -0.725));
|
||||
addSample(vec2(-0.375, -0.925));
|
||||
addSample(vec2( 0.0, -1.0 ));
|
||||
addSample(vec2(-0.375, 0.925));
|
||||
addSample(vec2(-0.725, 0.725));
|
||||
addSample(vec2( 0.925, 0.375));
|
||||
addSample(vec2(-1.0, 0.0 ));
|
||||
addSample(vec2(-0.925, -0.375));
|
||||
addSample(vec2(-0.725, -0.725));
|
||||
addSample(vec2( 0.375, -0.925));
|
||||
|
||||
addSample(vec2( 0.375, 0.925) * 0.9);
|
||||
addSample(vec2(-0.925, 0.375) * 0.9);
|
||||
addSample(vec2( 0.925, -0.375) * 0.9);
|
||||
addSample(vec2(-0.375, -0.925) * 0.9);
|
||||
addSample(vec2(-0.375, 0.925) * 0.9);
|
||||
addSample(vec2( 0.925, 0.375) * 0.9);
|
||||
addSample(vec2(-0.925, -0.375) * 0.9);
|
||||
addSample(vec2( 0.375, -0.925) * 0.9);
|
||||
|
||||
addSample(vec2( 0.725, 0.725) * 0.7);
|
||||
addSample(vec2( 1.0, 0.0 ) * 0.7);
|
||||
addSample(vec2( 0.725, -0.725) * 0.7);
|
||||
addSample(vec2( 0.0, -1.0 ) * 0.7);
|
||||
addSample(vec2(-0.725, 0.725) * 0.7);
|
||||
addSample(vec2(-1.0, 0.0 ) * 0.7);
|
||||
addSample(vec2(-0.725, -0.725) * 0.7);
|
||||
addSample(vec2( 0.0, 1.0 ) * 0.7);
|
||||
|
||||
addSample(vec2( 0.725, 0.725) * 0.4);
|
||||
addSample(vec2( 1.0, 0.0 ) * 0.4);
|
||||
addSample(vec2( 0.725, -0.725) * 0.4);
|
||||
addSample(vec2( 0.0, -1.0 ) * 0.4);
|
||||
addSample(vec2(-0.725, 0.725) * 0.4);
|
||||
addSample(vec2(-1.0, 0.0 ) * 0.4);
|
||||
addSample(vec2(-0.725, -0.725) * 0.4);
|
||||
addSample(vec2( 0.0, 1.0 ) * 0.4);
|
||||
|
||||
gl_FragColor = color / totalWeight;
|
||||
}`,
|
||||
};
|
||||
|
||||
gdjs.PixiFiltersTools.registerFilterCreator(
|
||||
'Scene3D::DepthOfField',
|
||||
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.ShaderPass;
|
||||
_isEnabled: boolean = false;
|
||||
// Distances are in scene units: they are converted to the Three.js
|
||||
// world units before each rendering, as the world scale can change.
|
||||
_focusDistance: float = 500;
|
||||
_focusRange: float = 600;
|
||||
_transitionDistance: float = 1500;
|
||||
// In pixels of the game resolution.
|
||||
_maxBlur: float = 6;
|
||||
|
||||
constructor() {
|
||||
this.shaderPass = new THREE_ADDONS.ShaderPass(
|
||||
gdjs.PixiFiltersTools.clampThreeShaderOutput(depthOfFieldShader)
|
||||
);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
// The depth texture is counted once per enabled effect.
|
||||
if (this._isEnabled) {
|
||||
return true;
|
||||
}
|
||||
const layerRenderer = target.getRenderer();
|
||||
layerRenderer.setSceneDepthTextureNeeded(true);
|
||||
layerRenderer.addPostProcessingPass(
|
||||
this.shaderPass,
|
||||
effectData.name
|
||||
);
|
||||
this._isEnabled = true;
|
||||
return true;
|
||||
}
|
||||
removeEffect(target: EffectsTarget): boolean {
|
||||
if (!(target instanceof gdjs.Layer)) {
|
||||
return false;
|
||||
}
|
||||
if (!this._isEnabled) {
|
||||
return true;
|
||||
}
|
||||
const layerRenderer = target.getRenderer();
|
||||
layerRenderer.removePostProcessingPass(this.shaderPass);
|
||||
layerRenderer.setSceneDepthTextureNeeded(false);
|
||||
this._isEnabled = false;
|
||||
return true;
|
||||
}
|
||||
updatePreRender(target: gdjs.EffectsTarget): any {
|
||||
if (!(target instanceof gdjs.Layer)) {
|
||||
return;
|
||||
}
|
||||
const layerRenderer = target.getRenderer();
|
||||
const camera = layerRenderer.getThreeCamera();
|
||||
const depthTexture = layerRenderer.getSceneDepthTexture();
|
||||
const uniforms = this.shaderPass.uniforms;
|
||||
// Without the depth, the shader would blur everything.
|
||||
this.shaderPass.enabled = !!camera && !!depthTexture;
|
||||
if (!camera || !depthTexture) {
|
||||
return;
|
||||
}
|
||||
uniforms.tDepth.value = depthTexture;
|
||||
uniforms.nearClip.value = camera.near;
|
||||
uniforms.farClip.value = camera.far;
|
||||
const isPerspectiveCamera =
|
||||
camera instanceof THREE.PerspectiveCamera;
|
||||
if (
|
||||
this.shaderPass.material.defines.PERSPECTIVE_CAMERA !==
|
||||
(isPerspectiveCamera ? 1 : 0)
|
||||
) {
|
||||
this.shaderPass.material.defines.PERSPECTIVE_CAMERA =
|
||||
isPerspectiveCamera ? 1 : 0;
|
||||
this.shaderPass.material.needsUpdate = true;
|
||||
}
|
||||
|
||||
const inverseWorldScale = target
|
||||
.getRuntimeScene()
|
||||
.getScene()
|
||||
.getRenderer3DInverseWorldScale();
|
||||
uniforms.focusDistance.value =
|
||||
this._focusDistance * inverseWorldScale;
|
||||
uniforms.focusRange.value = this._focusRange * inverseWorldScale;
|
||||
uniforms.transitionDistance.value = Math.max(
|
||||
0.0001,
|
||||
this._transitionDistance * inverseWorldScale
|
||||
);
|
||||
|
||||
const width = target.getWidth();
|
||||
const height = target.getHeight();
|
||||
uniforms.aspect.value = height > 0 ? width / height : 1;
|
||||
uniforms.maxBlur.value = width > 0 ? this._maxBlur / width : 0;
|
||||
}
|
||||
updateDoubleParameter(parameterName: string, value: number): void {
|
||||
if (parameterName === 'focusDistance') {
|
||||
this._focusDistance = value;
|
||||
} else if (parameterName === 'focusRange') {
|
||||
this._focusRange = Math.max(0, value);
|
||||
} else if (parameterName === 'transitionDistance') {
|
||||
this._transitionDistance = Math.max(0, value);
|
||||
} else if (parameterName === 'maxBlur') {
|
||||
this._maxBlur = Math.max(0, value);
|
||||
}
|
||||
}
|
||||
getDoubleParameter(parameterName: string): number {
|
||||
if (parameterName === 'focusDistance') {
|
||||
return this._focusDistance;
|
||||
}
|
||||
if (parameterName === 'focusRange') {
|
||||
return this._focusRange;
|
||||
}
|
||||
if (parameterName === 'transitionDistance') {
|
||||
return this._transitionDistance;
|
||||
}
|
||||
if (parameterName === 'maxBlur') {
|
||||
return this._maxBlur;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
updateStringParameter(parameterName: string, value: string): void {}
|
||||
updateColorParameter(parameterName: string, value: number): void {}
|
||||
getColorParameter(parameterName: string): number {
|
||||
return 0;
|
||||
}
|
||||
updateBooleanParameter(parameterName: string, value: boolean): void {}
|
||||
getNetworkSyncData(): DepthOfFieldFilterNetworkSyncData {
|
||||
return {
|
||||
fd: this._focusDistance,
|
||||
fr: this._focusRange,
|
||||
td: this._transitionDistance,
|
||||
mb: this._maxBlur,
|
||||
};
|
||||
}
|
||||
updateFromNetworkSyncData(data: DepthOfFieldFilterNetworkSyncData) {
|
||||
this._focusDistance = data.fd;
|
||||
this._focusRange = data.fr;
|
||||
this._transitionDistance = data.td;
|
||||
this._maxBlur = data.mb;
|
||||
}
|
||||
})();
|
||||
}
|
||||
})()
|
||||
);
|
||||
}
|
||||
@@ -40,7 +40,9 @@ namespace gdjs {
|
||||
if (!(target instanceof gdjs.Layer)) {
|
||||
return false;
|
||||
}
|
||||
target.getRenderer().addPostProcessingPass(this.shaderPass);
|
||||
target
|
||||
.getRenderer()
|
||||
.addPostProcessingPass(this.shaderPass, effectData.name);
|
||||
this._isEnabled = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -43,7 +43,9 @@ namespace gdjs {
|
||||
if (!(target instanceof gdjs.Layer)) {
|
||||
return false;
|
||||
}
|
||||
target.getRenderer().addPostProcessingPass(this.shaderPass);
|
||||
target
|
||||
.getRenderer()
|
||||
.addPostProcessingPass(this.shaderPass, effectData.name);
|
||||
this._isEnabled = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2446,7 +2446,9 @@ module.exports = {
|
||||
const effect = extension
|
||||
.addEffect('HueAndSaturation')
|
||||
.setFullName(_('Hue and saturation'))
|
||||
.setDescription(_('Adjust hue and saturation.'))
|
||||
.setDescription(
|
||||
_('Adjust hue and saturation. Put it after the tone mapping, if any.')
|
||||
)
|
||||
.markAsNotWorkingForObjects()
|
||||
.markAsOnlyWorkingFor3D()
|
||||
.addIncludeFile('Extensions/3D/HueAndSaturationEffect.js');
|
||||
@@ -2469,7 +2471,9 @@ module.exports = {
|
||||
const effect = extension
|
||||
.addEffect('Exposure')
|
||||
.setFullName(_('Exposure'))
|
||||
.setDescription(_('Adjust exposure.'))
|
||||
.setDescription(
|
||||
_('Adjust exposure. Put it before the tone mapping, if any.')
|
||||
)
|
||||
.markAsNotWorkingForObjects()
|
||||
.markAsOnlyWorkingFor3D()
|
||||
.addIncludeFile('Extensions/3D/ExposureEffect.js');
|
||||
@@ -2481,39 +2485,81 @@ module.exports = {
|
||||
.setType('number')
|
||||
.setDescription(_('Positive value'));
|
||||
}
|
||||
{
|
||||
const effect = extension
|
||||
.addEffect('ToneMapping')
|
||||
.setFullName(_('Tone mapping'))
|
||||
.setDescription(
|
||||
_(
|
||||
'Map the colors of the scene to the screen like a camera, to keep details in the brightest parts. Put it after the effects on the light of the scene (exposure, depth of field, bloom) and before the color adjustments (brightness and contrast, hue and saturation).'
|
||||
)
|
||||
)
|
||||
.markAsNotWorkingForObjects()
|
||||
.markAsOnlyWorkingFor3D()
|
||||
.addIncludeFile('Extensions/3D/ToneMappingEffect.js');
|
||||
const properties = effect.getProperties();
|
||||
properties
|
||||
.getOrCreate('mode')
|
||||
.setValue('Neutral')
|
||||
.setLabel(_('Mode'))
|
||||
.setType('choice')
|
||||
.addChoice('Neutral', _('Neutral (keeps the colors of the scene)'))
|
||||
.addChoice('ACESFilmic', _('ACES Filmic (cinematic, contrasted)'))
|
||||
.addChoice('AgX', _('AgX (realistic, soft)'))
|
||||
.addChoice('Reinhard', _('Reinhard'))
|
||||
.addChoice('Cineon', _('Cineon'));
|
||||
properties
|
||||
.getOrCreate('exposure')
|
||||
.setValue('1')
|
||||
.setLabel(_('Exposure'))
|
||||
.setType('number')
|
||||
.setDescription(_('Brightness of the scene before the tone mapping.'));
|
||||
}
|
||||
{
|
||||
const effect = extension
|
||||
.addEffect('Bloom')
|
||||
.setFullName(_('Bloom'))
|
||||
.setDescription(_('Apply a bloom effect.'))
|
||||
.setDescription(
|
||||
_(
|
||||
'Make the brightest parts of the scene glow. Put it before the tone mapping, if any.'
|
||||
)
|
||||
)
|
||||
.markAsNotWorkingForObjects()
|
||||
.markAsOnlyWorkingFor3D()
|
||||
.addIncludeFile('Extensions/3D/BloomEffect.js');
|
||||
const properties = effect.getProperties();
|
||||
properties
|
||||
.getOrCreate('strength')
|
||||
.setValue('1')
|
||||
.setValue('0.5')
|
||||
.setLabel(_('Strength'))
|
||||
.setType('number')
|
||||
.setDescription(_('Between 0 and 3'));
|
||||
properties
|
||||
.getOrCreate('radius')
|
||||
.setValue('0')
|
||||
.setValue('0.4')
|
||||
.setLabel(_('Radius'))
|
||||
.setType('number')
|
||||
.setDescription(_('Between 0 and 1'));
|
||||
properties
|
||||
.getOrCreate('threshold')
|
||||
.setValue('0')
|
||||
.setValue('0.85')
|
||||
.setLabel(_('Threshold'))
|
||||
.setType('number')
|
||||
.setDescription(_('Between 0 and 1'));
|
||||
.setDescription(
|
||||
_(
|
||||
'Brightness from which pixels glow, between 0 and 1. With 0, the whole scene glows.'
|
||||
)
|
||||
);
|
||||
}
|
||||
{
|
||||
const effect = extension
|
||||
.addEffect('BrightnessAndContrast')
|
||||
.setFullName(_('Brightness and contrast.'))
|
||||
.setDescription(_('Adjust brightness and contrast.'))
|
||||
.setDescription(
|
||||
_(
|
||||
'Adjust brightness and contrast. Put it after the tone mapping, if any.'
|
||||
)
|
||||
)
|
||||
.markAsNotWorkingForObjects()
|
||||
.markAsOnlyWorkingFor3D()
|
||||
.addIncludeFile('Extensions/3D/BrightnessAndContrastEffect.js');
|
||||
@@ -2531,6 +2577,122 @@ module.exports = {
|
||||
.setType('number')
|
||||
.setDescription(_('Between -1 and 1'));
|
||||
}
|
||||
{
|
||||
const effect = extension
|
||||
.addEffect('N8AO')
|
||||
.setFullName(_('Ambient occlusion'))
|
||||
.setDescription(
|
||||
_(
|
||||
'Darken the creases, holes and corners where the ambient light is blocked by nearby surfaces.'
|
||||
)
|
||||
)
|
||||
.markAsNotWorkingForObjects()
|
||||
.markAsOnlyWorkingFor3D()
|
||||
.addIncludeFile('Extensions/3D/N8AOEffect.js');
|
||||
const properties = effect.getProperties();
|
||||
properties
|
||||
.getOrCreate('radius')
|
||||
.setValue('100')
|
||||
.setLabel(_('Radius'))
|
||||
.setType('number')
|
||||
.setMeasurementUnit(gd.MeasurementUnit.getPixel())
|
||||
.setDescription(
|
||||
_(
|
||||
'Distance up to which surfaces darken each other. A radius close to the size of the objects gives the best results.'
|
||||
)
|
||||
);
|
||||
properties
|
||||
.getOrCreate('intensity')
|
||||
.setValue('3')
|
||||
.setLabel(_('Intensity'))
|
||||
.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.'
|
||||
)
|
||||
)
|
||||
.setAdvanced(true);
|
||||
properties
|
||||
.getOrCreate('color')
|
||||
.setValue('0;0;0')
|
||||
.setLabel(_('Color'))
|
||||
.setType('color')
|
||||
.setAdvanced(true);
|
||||
properties
|
||||
.getOrCreate('quality')
|
||||
.setValue('Performance')
|
||||
.setLabel(_('Quality'))
|
||||
.setType('choice')
|
||||
.addChoice('Performance', _('Fastest (recommended for mobile)'))
|
||||
.addChoice('Low', _('Low'))
|
||||
.addChoice('Medium', _('Medium'))
|
||||
.addChoice('High', _('High'))
|
||||
.addChoice('Ultra', _('Ultra (slowest)'))
|
||||
.setDescription(
|
||||
_(
|
||||
'Higher qualities use more samples, giving less noisy shadows but being slower to render.'
|
||||
)
|
||||
);
|
||||
}
|
||||
{
|
||||
const effect = extension
|
||||
.addEffect('DepthOfField')
|
||||
.setFullName(_('Depth of field'))
|
||||
.setDescription(
|
||||
_(
|
||||
'Blur what is closer or further than the focus distance, like a camera lens. Put it before the bloom and the tone mapping, if any.'
|
||||
)
|
||||
)
|
||||
.markAsNotWorkingForObjects()
|
||||
.markAsOnlyWorkingFor3D()
|
||||
.addIncludeFile('Extensions/3D/DepthOfFieldEffect.js');
|
||||
const properties = effect.getProperties();
|
||||
properties
|
||||
.getOrCreate('focusDistance')
|
||||
.setValue('500')
|
||||
.setLabel(_('Focus distance'))
|
||||
.setType('number')
|
||||
.setMeasurementUnit(gd.MeasurementUnit.getPixel())
|
||||
.setDescription(
|
||||
_('Distance from the camera where the image is the sharpest.')
|
||||
);
|
||||
properties
|
||||
.getOrCreate('focusRange')
|
||||
.setValue('600')
|
||||
.setLabel(_('Focus range'))
|
||||
.setType('number')
|
||||
.setMeasurementUnit(gd.MeasurementUnit.getPixel())
|
||||
.setDescription(
|
||||
_('Depth around the focus distance staying fully sharp.')
|
||||
);
|
||||
properties
|
||||
.getOrCreate('transitionDistance')
|
||||
.setValue('1500')
|
||||
.setLabel(_('Transition distance'))
|
||||
.setType('number')
|
||||
.setMeasurementUnit(gd.MeasurementUnit.getPixel())
|
||||
.setDescription(
|
||||
_(
|
||||
'Distance after the focus range over which the blur increases up to its maximum.'
|
||||
)
|
||||
);
|
||||
properties
|
||||
.getOrCreate('maxBlur')
|
||||
.setValue('6')
|
||||
.setLabel(_('Maximum blur'))
|
||||
.setType('number')
|
||||
.setMeasurementUnit(gd.MeasurementUnit.getPixel())
|
||||
.setDescription(
|
||||
_(
|
||||
'Blur radius of the most out-of-focus parts, in pixels of the game resolution.'
|
||||
)
|
||||
);
|
||||
}
|
||||
// Don't forget to update the alert condition in Model3DEditor.js when
|
||||
// adding a new light.
|
||||
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
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;
|
||||
// In scene units: converted to the Three.js world units before each
|
||||
// rendering, as the world scale can change in the editor.
|
||||
_radius: float = 50;
|
||||
|
||||
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 {
|
||||
this._updateRadius();
|
||||
}
|
||||
_updateRadius(): void {
|
||||
const aoRadius =
|
||||
this._radius *
|
||||
target
|
||||
.getRuntimeScene()
|
||||
.getScene()
|
||||
.getRenderer3DInverseWorldScale();
|
||||
if (this.shaderPass.configuration.aoRadius !== aoRadius) {
|
||||
this.shaderPass.configuration.aoRadius = aoRadius;
|
||||
}
|
||||
}
|
||||
updateDoubleParameter(parameterName: string, value: number): void {
|
||||
if (parameterName === 'radius') {
|
||||
this._radius = value;
|
||||
this._updateRadius();
|
||||
}
|
||||
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._radius;
|
||||
}
|
||||
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') {
|
||||
if (
|
||||
value === 'Performance' ||
|
||||
value === 'Low' ||
|
||||
value === 'Medium' ||
|
||||
value === 'High' ||
|
||||
value === 'Ultra'
|
||||
) {
|
||||
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._radius,
|
||||
d: this.shaderPass.configuration.distanceFalloff,
|
||||
i: this.shaderPass.configuration.intensity,
|
||||
c: this.shaderPass.configuration.color.getHex(),
|
||||
};
|
||||
}
|
||||
updateFromNetworkSyncData(data: N8AOFilterNetworkSyncData) {
|
||||
this._radius = data.r;
|
||||
this._updateRadius();
|
||||
this.shaderPass.configuration.distanceFalloff = data.d;
|
||||
this.shaderPass.configuration.intensity = data.i;
|
||||
this.shaderPass.configuration.color.setHex(data.c);
|
||||
}
|
||||
})();
|
||||
}
|
||||
})()
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
namespace gdjs {
|
||||
interface ToneMappingFilterNetworkSyncData {
|
||||
m: string;
|
||||
e: number;
|
||||
}
|
||||
|
||||
const toneMappingFunctions = {
|
||||
Neutral: 'NeutralToneMapping',
|
||||
ACESFilmic: 'ACESFilmicToneMapping',
|
||||
AgX: 'AgXToneMapping',
|
||||
Reinhard: 'ReinhardToneMapping',
|
||||
Cineon: 'CineonToneMapping',
|
||||
};
|
||||
type ToneMappingMode = keyof typeof toneMappingFunctions;
|
||||
const isToneMappingMode = (mode: string): mode is ToneMappingMode =>
|
||||
toneMappingFunctions.hasOwnProperty(mode);
|
||||
|
||||
const toneMappingShader = {
|
||||
defines: {
|
||||
TONE_MAPPING_FUNCTION: toneMappingFunctions.Neutral,
|
||||
},
|
||||
uniforms: {
|
||||
tDiffuse: { value: null },
|
||||
exposure: { value: 1.0 },
|
||||
},
|
||||
vertexShader: /* glsl */ `
|
||||
varying vec2 vUv;
|
||||
|
||||
void main() {
|
||||
vUv = uv;
|
||||
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
||||
}`,
|
||||
fragmentShader: /* glsl */ `
|
||||
#include <common>
|
||||
#include <tonemapping_pars_fragment>
|
||||
|
||||
uniform sampler2D tDiffuse;
|
||||
uniform float exposure;
|
||||
|
||||
varying vec2 vUv;
|
||||
|
||||
void main() {
|
||||
vec4 color = texture2D(tDiffuse, vUv);
|
||||
gl_FragColor = vec4(TONE_MAPPING_FUNCTION(color.rgb * exposure), color.a);
|
||||
}`,
|
||||
};
|
||||
|
||||
gdjs.PixiFiltersTools.registerFilterCreator(
|
||||
'Scene3D::ToneMapping',
|
||||
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.ShaderPass;
|
||||
_isEnabled: boolean = false;
|
||||
_mode: ToneMappingMode = 'Neutral';
|
||||
|
||||
constructor() {
|
||||
this.shaderPass = new THREE_ADDONS.ShaderPass(toneMappingShader);
|
||||
}
|
||||
|
||||
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()
|
||||
.addPostProcessingPass(this.shaderPass, effectData.name);
|
||||
this._isEnabled = true;
|
||||
return true;
|
||||
}
|
||||
removeEffect(target: EffectsTarget): boolean {
|
||||
if (!(target instanceof gdjs.Layer)) {
|
||||
return false;
|
||||
}
|
||||
target.getRenderer().removePostProcessingPass(this.shaderPass);
|
||||
this._isEnabled = false;
|
||||
return true;
|
||||
}
|
||||
updatePreRender(target: gdjs.EffectsTarget): any {}
|
||||
updateDoubleParameter(parameterName: string, value: number): void {
|
||||
if (parameterName === 'exposure') {
|
||||
this.shaderPass.uniforms.exposure.value = Math.max(0, value);
|
||||
}
|
||||
}
|
||||
getDoubleParameter(parameterName: string): number {
|
||||
if (parameterName === 'exposure') {
|
||||
return this.shaderPass.uniforms.exposure.value;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
updateStringParameter(parameterName: string, value: string): void {
|
||||
if (parameterName === 'mode') {
|
||||
this._setMode(value);
|
||||
}
|
||||
}
|
||||
_setMode(mode: string): void {
|
||||
if (!isToneMappingMode(mode) || mode === this._mode) {
|
||||
return;
|
||||
}
|
||||
this._mode = mode;
|
||||
this.shaderPass.material.defines.TONE_MAPPING_FUNCTION =
|
||||
toneMappingFunctions[this._mode];
|
||||
this.shaderPass.material.needsUpdate = true;
|
||||
}
|
||||
updateColorParameter(parameterName: string, value: number): void {}
|
||||
getColorParameter(parameterName: string): number {
|
||||
return 0;
|
||||
}
|
||||
updateBooleanParameter(parameterName: string, value: boolean): void {}
|
||||
getNetworkSyncData(): ToneMappingFilterNetworkSyncData {
|
||||
return {
|
||||
m: this._mode,
|
||||
e: this.shaderPass.uniforms.exposure.value,
|
||||
};
|
||||
}
|
||||
updateFromNetworkSyncData(data: ToneMappingFilterNetworkSyncData) {
|
||||
this._setMode(data.m);
|
||||
this.shaderPass.uniforms.exposure.value = data.e;
|
||||
}
|
||||
})();
|
||||
}
|
||||
})()
|
||||
);
|
||||
}
|
||||
@@ -712,6 +712,28 @@ namespace gdjs {
|
||||
this._effectsManager.addEffect(effectData, this._rendererEffects, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the order of the effects, which is the order of the
|
||||
* post-processing passes of 3D layers. Effects not in the list are put
|
||||
* after the others.
|
||||
* @param effectNames The names of the effects, in their new order.
|
||||
*/
|
||||
setEffectsOrder(effectNames: string[]): void {
|
||||
const rendererEffects: Record<string, gdjs.PixiFiltersTools.Filter> = {};
|
||||
for (const effectName of effectNames) {
|
||||
if (this._rendererEffects[effectName]) {
|
||||
rendererEffects[effectName] = this._rendererEffects[effectName];
|
||||
}
|
||||
}
|
||||
for (const effectName in this._rendererEffects) {
|
||||
if (!rendererEffects[effectName]) {
|
||||
rendererEffects[effectName] = this._rendererEffects[effectName];
|
||||
}
|
||||
}
|
||||
this._rendererEffects = rendererEffects;
|
||||
this._renderer.updatePostProcessingPassesOrder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the effect with the specified name
|
||||
* @param effectName The name of the effect.
|
||||
|
||||
@@ -1632,6 +1632,9 @@ namespace gdjs {
|
||||
}
|
||||
}
|
||||
});
|
||||
runtimeLayer.setEffectsOrder(
|
||||
newEffectsData.map((effectData) => effectData.name)
|
||||
);
|
||||
}
|
||||
|
||||
_hotReloadRuntimeLayerEffect(
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -7,6 +7,11 @@
|
||||
namespace gdjs {
|
||||
const logger = new gdjs.Logger('LayerPixiRenderer');
|
||||
|
||||
type PostProcessingPass = {
|
||||
pass: THREE_ADDONS.Pass;
|
||||
effectName: string;
|
||||
};
|
||||
|
||||
const FRUSTUM_EDGES: Array<[number, number]> = [
|
||||
// near plane edges
|
||||
[0, 1],
|
||||
@@ -215,6 +220,14 @@ namespace gdjs {
|
||||
| null = null;
|
||||
private _threeCameraDirty: boolean = false;
|
||||
private _threeEffectComposer: THREE_ADDONS.EffectComposer | null = null;
|
||||
private _threeRenderPass: THREE_ADDONS.RenderPass | null = null;
|
||||
private _customRenderPass: THREE_ADDONS.N8AOPass | null = null;
|
||||
private _sceneDepthRenderPass: THREE_ADDONS.SceneDepthRenderPass | null =
|
||||
null;
|
||||
private _sceneDepthTextureUsersCount = 0;
|
||||
private _postProcessingPasses: PostProcessingPass[] = [];
|
||||
// The antialiasing (if any) and output passes, after the effects.
|
||||
private _threeFinalPasses: THREE_ADDONS.Pass[] = [];
|
||||
private _basis: Basis | null = null;
|
||||
private static matrix4: THREE.Matrix4 | null = null;
|
||||
|
||||
@@ -330,41 +343,124 @@ namespace gdjs {
|
||||
return this._threeEffectComposer;
|
||||
}
|
||||
|
||||
addPostProcessingPass(pass: THREE_ADDONS.Pass) {
|
||||
/**
|
||||
* Add a pass applying an effect after the rendering of the scene.
|
||||
* Passes are applied in the order of the effects of the layer.
|
||||
* @param pass The pass to add.
|
||||
* @param effectName The name of the effect adding the pass.
|
||||
*/
|
||||
addPostProcessingPass(pass: THREE_ADDONS.Pass, effectName: string) {
|
||||
if (!this._threeEffectComposer) {
|
||||
return;
|
||||
}
|
||||
const game = this._layer.getRuntimeScene().getGame();
|
||||
// TODO Keep the effects in the same order they are defined
|
||||
// because the order matter for the final result.
|
||||
// There is the same issue with 2D effects too.
|
||||
|
||||
// The composer contains:
|
||||
// - RenderPass
|
||||
// - inserted passes for effects
|
||||
// - SMAAPass (optionally)
|
||||
// - OutputPass
|
||||
const index =
|
||||
this._threeEffectComposer.passes.length -
|
||||
(game.getAntialiasingMode() === 'none' ? 1 : 2);
|
||||
this._threeEffectComposer.insertPass(pass, index);
|
||||
const renderTarget = this._threeEffectComposer.renderTarget1;
|
||||
pass.setSize(renderTarget.width, renderTarget.height);
|
||||
this._postProcessingPasses.push({ pass, effectName });
|
||||
this.updatePostProcessingPassesOrder();
|
||||
}
|
||||
|
||||
removePostProcessingPass(pass: THREE_ADDONS.Pass) {
|
||||
this._postProcessingPasses = this._postProcessingPasses.filter(
|
||||
(postProcessingPass) => postProcessingPass.pass !== pass
|
||||
);
|
||||
this.updatePostProcessingPassesOrder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Put the post-processing passes in the order of the effects of the layer.
|
||||
*/
|
||||
updatePostProcessingPassesOrder() {
|
||||
if (!this._threeEffectComposer) {
|
||||
return;
|
||||
}
|
||||
this._threeEffectComposer.removePass(pass);
|
||||
const effectNames = Object.keys(this._layer.getRendererEffects());
|
||||
this._postProcessingPasses.sort(
|
||||
(a, b) =>
|
||||
effectNames.indexOf(a.effectName) - effectNames.indexOf(b.effectName)
|
||||
);
|
||||
|
||||
const passes = this._threeEffectComposer.passes;
|
||||
passes.splice(
|
||||
1,
|
||||
passes.length - 1,
|
||||
...this._postProcessingPasses.map(({ pass }) => pass),
|
||||
...this._threeFinalPasses
|
||||
);
|
||||
}
|
||||
|
||||
hasPostProcessingPass() {
|
||||
if (!this._threeEffectComposer) {
|
||||
return false;
|
||||
}
|
||||
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;
|
||||
return (
|
||||
this._threeEffectComposer.passes[0] !== this._threeRenderPass ||
|
||||
this._postProcessingPasses.length > 0
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the pass rendering the scene to compute the ambient occlusion.
|
||||
* @param pass The pass rendering the scene, or null to use the default one.
|
||||
*/
|
||||
setCustomRenderPass(pass: THREE_ADDONS.N8AOPass | null) {
|
||||
this._customRenderPass = pass;
|
||||
this._updateSceneRenderPass();
|
||||
}
|
||||
|
||||
/**
|
||||
* Declare that an effect needs the depth of the scene (see
|
||||
* `getSceneDepthTexture`), or doesn't need it anymore.
|
||||
*/
|
||||
setSceneDepthTextureNeeded(isNeeded: boolean) {
|
||||
this._sceneDepthTextureUsersCount = Math.max(
|
||||
0,
|
||||
this._sceneDepthTextureUsersCount + (isNeeded ? 1 : -1)
|
||||
);
|
||||
this._updateSceneRenderPass();
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns The depth of the scene rendered in this frame, if an effect
|
||||
* declared it needed it with `setSceneDepthTextureNeeded`.
|
||||
*/
|
||||
getSceneDepthTexture(): THREE.DepthTexture | null {
|
||||
if (this._customRenderPass) {
|
||||
return this._customRenderPass.beautyRenderTarget.depthTexture;
|
||||
}
|
||||
if (this._sceneDepthTextureUsersCount > 0 && this._sceneDepthRenderPass) {
|
||||
return this._sceneDepthRenderPass.depthTexture;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private _updateSceneRenderPass() {
|
||||
if (
|
||||
!this._threeEffectComposer ||
|
||||
!this._threeRenderPass ||
|
||||
!this._threeScene ||
|
||||
!this._threeCamera
|
||||
) {
|
||||
return;
|
||||
}
|
||||
let sceneRenderPass: THREE_ADDONS.Pass = this._threeRenderPass;
|
||||
if (this._customRenderPass) {
|
||||
sceneRenderPass = this._customRenderPass;
|
||||
} else if (this._sceneDepthTextureUsersCount > 0) {
|
||||
if (!this._sceneDepthRenderPass) {
|
||||
this._sceneDepthRenderPass = new THREE_ADDONS.SceneDepthRenderPass(
|
||||
this._threeScene,
|
||||
this._threeCamera
|
||||
);
|
||||
}
|
||||
sceneRenderPass = this._sceneDepthRenderPass;
|
||||
}
|
||||
if (this._threeEffectComposer.passes[0] === sceneRenderPass) {
|
||||
return;
|
||||
}
|
||||
// The pass was not resized with the composer while it was not in it.
|
||||
const renderTarget = this._threeEffectComposer.renderTarget1;
|
||||
sceneRenderPass.setSize(renderTarget.width, renderTarget.height);
|
||||
this._threeEffectComposer.passes[0] = sceneRenderPass;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -429,18 +525,21 @@ namespace gdjs {
|
||||
const game = this._layer.getRuntimeScene().getGame();
|
||||
const threeRenderer = game.getRenderer().getThreeRenderer();
|
||||
if (threeRenderer) {
|
||||
// When adding more default passes, make sure to update
|
||||
// `addPostProcessingPass` and `hasPostProcessingPass` formulas.
|
||||
this._threeEffectComposer = new THREE_ADDONS.EffectComposer(
|
||||
threeRenderer
|
||||
);
|
||||
this._threeEffectComposer.addPass(
|
||||
new THREE_ADDONS.RenderPass(this._threeScene, this._threeCamera)
|
||||
this._threeRenderPass = new THREE_ADDONS.RenderPass(
|
||||
this._threeScene,
|
||||
this._threeCamera
|
||||
);
|
||||
this._threeEffectComposer.addPass(this._threeRenderPass);
|
||||
if (game.getAntialiasingMode() !== 'none') {
|
||||
this._threeEffectComposer.addPass(new THREE_ADDONS.SMAAPass());
|
||||
this._threeFinalPasses.push(new THREE_ADDONS.SMAAPass());
|
||||
}
|
||||
this._threeFinalPasses.push(new THREE_ADDONS.OutputPass());
|
||||
for (const pass of this._threeFinalPasses) {
|
||||
this._threeEffectComposer.addPass(pass);
|
||||
}
|
||||
this._threeEffectComposer.addPass(new THREE_ADDONS.OutputPass());
|
||||
}
|
||||
|
||||
if (
|
||||
@@ -516,7 +615,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(
|
||||
|
||||
+7
@@ -22,6 +22,10 @@ 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';
|
||||
import { SceneDepthRenderPass } from '../../../SharedLibs/ThreeAddons/src/SceneDepthRenderPass.js';
|
||||
import { LowResolutionBloomPass } from '../../../SharedLibs/ThreeAddons/src/LowResolutionBloomPass.js';
|
||||
|
||||
declare global {
|
||||
namespace THREE_ADDONS {
|
||||
export {
|
||||
@@ -45,6 +49,9 @@ declare global {
|
||||
ColorCorrectionShader,
|
||||
HueSaturationShader,
|
||||
ExposureShader,
|
||||
N8AOPass,
|
||||
SceneDepthRenderPass,
|
||||
LowResolutionBloomPass,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+186
@@ -170,4 +170,190 @@ describe('gdjs.EffectsManager', () => {
|
||||
expect(runtimeLayer.isEffectEnabled('AddedKawaseBlurEffect')).to.be(true);
|
||||
expect(runtimeLayer.isEffectEnabled('InitialKawaseBlurEffect')).to.be(true);
|
||||
});
|
||||
describe('post-processing passes of a 3D layer', () => {
|
||||
/** @type {Map<THREE_ADDONS.Pass, string>} */
|
||||
const effectNamesByPass = new Map();
|
||||
|
||||
/** @type {gdjs.PixiFiltersTools.FilterCreator} */
|
||||
const postProcessingFilterCreator = {
|
||||
makeFilter: (target, effectData) => {
|
||||
const pass = new THREE_ADDONS.ShaderPass(
|
||||
THREE_ADDONS.BrightnessContrastShader
|
||||
);
|
||||
effectNamesByPass.set(pass, effectData.name);
|
||||
let isEnabled = false;
|
||||
/** @type {gdjs.PixiFiltersTools.Filter} */
|
||||
const filter = {
|
||||
isEnabled: () => isEnabled,
|
||||
setEnabled: (target, enabled) =>
|
||||
enabled ? filter.applyEffect(target) : filter.removeEffect(target),
|
||||
applyEffect: (target) => {
|
||||
if (!(target instanceof gdjs.Layer)) return false;
|
||||
target
|
||||
.getRenderer()
|
||||
.addPostProcessingPass(pass, effectData.name);
|
||||
isEnabled = true;
|
||||
return true;
|
||||
},
|
||||
removeEffect: (target) => {
|
||||
if (!(target instanceof gdjs.Layer)) return false;
|
||||
target.getRenderer().removePostProcessingPass(pass);
|
||||
isEnabled = false;
|
||||
return true;
|
||||
},
|
||||
updatePreRender: () => {},
|
||||
updateDoubleParameter: () => {},
|
||||
getDoubleParameter: () => 0,
|
||||
updateStringParameter: () => {},
|
||||
updateColorParameter: () => {},
|
||||
getColorParameter: () => 0,
|
||||
updateBooleanParameter: () => {},
|
||||
getNetworkSyncData: () => ({}),
|
||||
updateFromNetworkSyncData: () => {},
|
||||
};
|
||||
return filter;
|
||||
},
|
||||
};
|
||||
gdjs.PixiFiltersTools.registerFilterCreator(
|
||||
'Test::PostProcessing',
|
||||
postProcessingFilterCreator
|
||||
);
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {boolean} [disabled]
|
||||
* @returns {EffectData}
|
||||
*/
|
||||
const makeEffectData = (name, disabled = false) => ({
|
||||
name,
|
||||
effectType: 'Test::PostProcessing',
|
||||
disabled,
|
||||
stringParameters: {},
|
||||
booleanParameters: {},
|
||||
doubleParameters: {},
|
||||
});
|
||||
|
||||
/**
|
||||
* @param {EffectData[]} effects
|
||||
* @returns {gdjs.Layer}
|
||||
*/
|
||||
const make3DLayerWithEffects = (effects) => {
|
||||
const runtimeGame = gdjs.getPixiRuntimeGame();
|
||||
runtimeGame
|
||||
.getRenderer()
|
||||
.createStandardCanvas(document.createElement('div'));
|
||||
const runtimeScene = new gdjs.RuntimeScene(runtimeGame);
|
||||
runtimeScene.loadFromScene({
|
||||
sceneData: {
|
||||
layers: [
|
||||
{
|
||||
name: '',
|
||||
renderingType: '3d',
|
||||
visibility: true,
|
||||
effects,
|
||||
cameras: [],
|
||||
ambientLightColorR: 0,
|
||||
ambientLightColorG: 0,
|
||||
ambientLightColorB: 0,
|
||||
isLightingLayer: false,
|
||||
followBaseLayerCamera: true,
|
||||
},
|
||||
],
|
||||
variables: [],
|
||||
r: 0,
|
||||
v: 0,
|
||||
b: 0,
|
||||
mangledName: 'Scene1',
|
||||
name: 'Scene1',
|
||||
stopSoundsOnStartup: false,
|
||||
title: '',
|
||||
renderer3DWorldScale: 100,
|
||||
behaviorsSharedData: [],
|
||||
objects: [],
|
||||
objectsGroups: [],
|
||||
instances: [],
|
||||
usedResources: [],
|
||||
uiSettings: {
|
||||
grid: false,
|
||||
gridType: 'rectangular',
|
||||
gridWidth: 10,
|
||||
gridHeight: 10,
|
||||
gridDepth: 10,
|
||||
gridOffsetX: 0,
|
||||
gridOffsetY: 0,
|
||||
gridOffsetZ: 0,
|
||||
gridColor: 0,
|
||||
gridAlpha: 1,
|
||||
snap: false,
|
||||
},
|
||||
},
|
||||
usedExtensionsWithVariablesData: [],
|
||||
});
|
||||
// @ts-ignore - The base layer is a gdjs.Layer.
|
||||
return runtimeScene.getLayer('');
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {gdjs.Layer} layer
|
||||
* @returns {string[]} The names of the passes after the scene rendering.
|
||||
*/
|
||||
const getPostProcessingPassNames = (layer) => {
|
||||
const composer = layer.getRenderer().getThreeEffectComposer();
|
||||
if (!composer) throw new Error('The layer has no effect composer.');
|
||||
expect(
|
||||
composer.passes[composer.passes.length - 1] instanceof
|
||||
THREE_ADDONS.OutputPass
|
||||
).to.be(true);
|
||||
return composer.passes
|
||||
.slice(1)
|
||||
.filter((pass) => effectNamesByPass.has(pass))
|
||||
.map((pass) => effectNamesByPass.get(pass) || '');
|
||||
};
|
||||
|
||||
it('applies the passes in the order of the effects', () => {
|
||||
const layer = make3DLayerWithEffects([
|
||||
makeEffectData('First', true),
|
||||
makeEffectData('Second'),
|
||||
makeEffectData('Third'),
|
||||
]);
|
||||
expect(getPostProcessingPassNames(layer)).to.eql(['Second', 'Third']);
|
||||
|
||||
layer.enableEffect('First', true);
|
||||
expect(getPostProcessingPassNames(layer)).to.eql([
|
||||
'First',
|
||||
'Second',
|
||||
'Third',
|
||||
]);
|
||||
|
||||
layer.enableEffect('Second', false);
|
||||
layer.enableEffect('Second', true);
|
||||
expect(getPostProcessingPassNames(layer)).to.eql([
|
||||
'First',
|
||||
'Second',
|
||||
'Third',
|
||||
]);
|
||||
|
||||
layer.setEffectsOrder(['Third', 'First', 'Second']);
|
||||
expect(getPostProcessingPassNames(layer)).to.eql([
|
||||
'Third',
|
||||
'First',
|
||||
'Second',
|
||||
]);
|
||||
|
||||
layer.addEffect(makeEffectData('AddedLater'));
|
||||
expect(getPostProcessingPassNames(layer)).to.eql([
|
||||
'Third',
|
||||
'First',
|
||||
'Second',
|
||||
'AddedLater',
|
||||
]);
|
||||
|
||||
layer.removeEffect('First');
|
||||
layer.removeEffect('Second');
|
||||
layer.removeEffect('Third');
|
||||
layer.removeEffect('AddedLater');
|
||||
expect(getPostProcessingPassNames(layer)).to.eql([]);
|
||||
expect(layer.getRenderer().hasPostProcessingPass()).to.be(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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(),
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
import { UnrealBloomPass } from "./examples/jsm/postprocessing/UnrealBloomPass.js";
|
||||
|
||||
/**
|
||||
* An `UnrealBloomPass` blurring the bright areas from a quarter of the
|
||||
* resolution instead of the half, with kernels twice smaller. The glow has the
|
||||
* same size on screen, for about 4 times less work and memory.
|
||||
*/
|
||||
class LowResolutionBloomPass extends UnrealBloomPass {
|
||||
/**
|
||||
* @param {THREE.Vector2} resolution
|
||||
* @param {number} strength
|
||||
* @param {number} radius
|
||||
* @param {number} threshold
|
||||
*/
|
||||
constructor(resolution, strength, radius, threshold) {
|
||||
super(resolution, strength, radius, threshold);
|
||||
|
||||
const kernelSizes = [3, 5, 7, 9, 11];
|
||||
for (let i = 0; i < this.nMips; i++) {
|
||||
this.separableBlurMaterials[i].dispose();
|
||||
const material = this._getSeparableBlurMaterial(kernelSizes[i]);
|
||||
// Small kernels cut a bigger part of the Gaussian curve: normalize them
|
||||
// to not darken the glow.
|
||||
const coefficients = material.uniforms.gaussianCoefficients.value;
|
||||
const sum = coefficients.reduce(
|
||||
(sum, coefficient, index) => sum + (index === 0 ? 1 : 2) * coefficient,
|
||||
0,
|
||||
);
|
||||
material.uniforms.gaussianCoefficients.value = coefficients.map(
|
||||
(coefficient) => coefficient / sum,
|
||||
);
|
||||
this.separableBlurMaterials[i] = material;
|
||||
}
|
||||
|
||||
// Each pixel of the bright areas covers 4x4 pixels of the input: average
|
||||
// 4 bilinear samples to take all of them into account (otherwise small
|
||||
// highlights would flicker).
|
||||
this.materialHighPassFilter.fragmentShader = /* glsl */ `
|
||||
uniform sampler2D tDiffuse;
|
||||
uniform vec3 defaultColor;
|
||||
uniform float defaultOpacity;
|
||||
uniform float luminosityThreshold;
|
||||
uniform float smoothWidth;
|
||||
|
||||
varying vec2 vUv;
|
||||
|
||||
vec4 getBrightColor(const in vec2 uv) {
|
||||
vec4 texel = texture2D(tDiffuse, uv);
|
||||
float alpha = smoothstep(
|
||||
luminosityThreshold,
|
||||
luminosityThreshold + smoothWidth,
|
||||
luminance(texel.xyz)
|
||||
);
|
||||
return mix(vec4(defaultColor.rgb, defaultOpacity), texel, alpha);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 offset = 1.0 / vec2(textureSize(tDiffuse, 0));
|
||||
gl_FragColor = 0.25 * (
|
||||
getBrightColor(vUv + vec2(-offset.x, -offset.y)) +
|
||||
getBrightColor(vUv + vec2(offset.x, -offset.y)) +
|
||||
getBrightColor(vUv + vec2(-offset.x, offset.y)) +
|
||||
getBrightColor(vUv + vec2(offset.x, offset.y))
|
||||
);
|
||||
}`;
|
||||
|
||||
this.setSize(resolution.x, resolution.y);
|
||||
}
|
||||
|
||||
setSize(width, height) {
|
||||
// UnrealBloomPass starts its mips at the half of the given size.
|
||||
super.setSize(width / 2, height / 2);
|
||||
}
|
||||
}
|
||||
|
||||
export { LowResolutionBloomPass };
|
||||
@@ -0,0 +1,73 @@
|
||||
import {
|
||||
DepthTexture,
|
||||
HalfFloatType,
|
||||
ShaderMaterial,
|
||||
UniformsUtils,
|
||||
UnsignedIntType,
|
||||
WebGLRenderTarget,
|
||||
} from "three";
|
||||
import { Pass, FullScreenQuad } from "./examples/jsm/postprocessing/Pass.js";
|
||||
import { CopyShader } from "./examples/jsm/shaders/CopyShader.js";
|
||||
|
||||
/**
|
||||
* Render the scene like a `RenderPass`, but in a render target owned by the
|
||||
* pass, with a depth texture that can be read by the next passes (for
|
||||
* instance a depth of field effect).
|
||||
*
|
||||
* The depth texture can't be attached to the render targets of the effect
|
||||
* composer: a pass would then read it while writing in the same render target.
|
||||
*/
|
||||
class SceneDepthRenderPass extends Pass {
|
||||
/**
|
||||
* @param {THREE.Scene} scene
|
||||
* @param {THREE.Camera} camera
|
||||
*/
|
||||
constructor(scene, camera) {
|
||||
super();
|
||||
this.scene = scene;
|
||||
this.camera = camera;
|
||||
// Like a RenderPass, the scene is rendered in the read buffer.
|
||||
this.needsSwap = false;
|
||||
|
||||
this.renderTarget = new WebGLRenderTarget(1, 1, { type: HalfFloatType });
|
||||
this.renderTarget.depthTexture = new DepthTexture(1, 1, UnsignedIntType);
|
||||
|
||||
this._copyMaterial = new ShaderMaterial({
|
||||
uniforms: UniformsUtils.clone(CopyShader.uniforms),
|
||||
vertexShader: CopyShader.vertexShader,
|
||||
fragmentShader: CopyShader.fragmentShader,
|
||||
depthTest: false,
|
||||
depthWrite: false,
|
||||
});
|
||||
this._copyMaterial.uniforms.tDiffuse.value = this.renderTarget.texture;
|
||||
this._copyQuad = new FullScreenQuad(this._copyMaterial);
|
||||
}
|
||||
|
||||
/** @returns {THREE.DepthTexture} */
|
||||
get depthTexture() {
|
||||
return this.renderTarget.depthTexture;
|
||||
}
|
||||
|
||||
setSize(width, height) {
|
||||
this.renderTarget.setSize(width, height);
|
||||
}
|
||||
|
||||
render(renderer, writeBuffer, readBuffer) {
|
||||
renderer.setRenderTarget(this.renderTarget);
|
||||
// The renderer may not clear by itself (autoClear disabled, and the
|
||||
// background being a texture or a cube texture).
|
||||
renderer.clear(true, true, true);
|
||||
renderer.render(this.scene, this.camera);
|
||||
|
||||
renderer.setRenderTarget(this.renderToScreen ? null : readBuffer);
|
||||
this._copyQuad.render(renderer);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.renderTarget.dispose();
|
||||
this._copyMaterial.dispose();
|
||||
this._copyQuad.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export { SceneDepthRenderPass };
|
||||
@@ -3,6 +3,9 @@
|
||||
* @module ThreeAddons
|
||||
*/
|
||||
|
||||
export { N8AOPass } from "./n8ao/N8AOPass";
|
||||
export { SceneDepthRenderPass } from "./SceneDepthRenderPass";
|
||||
|
||||
export { GLTFLoader } from "./examples/jsm/loaders/GLTFLoader";
|
||||
export { DRACOLoader } from "./examples/jsm/loaders/DRACOLoader";
|
||||
|
||||
@@ -18,6 +21,7 @@ export { ShaderPass } from "./examples/jsm/postprocessing/ShaderPass";
|
||||
export { SMAAPass } from "./examples/jsm/postprocessing/SMAAPass";
|
||||
export { OutputPass } from "./examples/jsm/postprocessing/OutputPass";
|
||||
export { UnrealBloomPass } from "./examples/jsm/postprocessing/UnrealBloomPass";
|
||||
export { LowResolutionBloomPass } from "./LowResolutionBloomPass";
|
||||
|
||||
export { BrightnessContrastShader } from "./examples/jsm/shaders/BrightnessContrastShader";
|
||||
export { HueSaturationShader } from "./examples/jsm/shaders/HueSaturationShader";
|
||||
|
||||
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,885 @@
|
||||
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,
|
||||
* transparencyAware: boolean,
|
||||
* accumulate: 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);
|
||||
// GDevelop renders with autoClear disabled, and Three.js only
|
||||
// clears by itself when the background is a color: clear the
|
||||
// previous frame (including its depth) explicitly.
|
||||
renderer.clear(true, true, true);
|
||||
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