Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Per frame randomized SSAO sampling & debug mode in composite pass #7139

Merged
merged 4 commits into from
Nov 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions examples/src/examples/graphics/ambient-occlusion.controls.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,15 @@ export const controls = ({ observer, ReactPCUI, React, jsx, fragment }) => {
link: { observer, path: 'data.ssao.blurEnabled' }
})
),
jsx(
LabelGroup,
{ text: 'randomize' },
jsx(BooleanInput, {
type: 'toggle',
binding: new BindingTwoWay(),
link: { observer, path: 'data.ssao.randomize' }
})
),
jsx(
LabelGroup,
{ text: 'radius' },
Expand Down Expand Up @@ -92,6 +101,24 @@ export const controls = ({ observer, ReactPCUI, React, jsx, fragment }) => {
binding: new BindingTwoWay(),
link: { observer, path: 'data.ssao.scale' }
})
),
jsx(
LabelGroup,
{ text: 'TAA' },
jsx(BooleanInput, {
type: 'toggle',
binding: new BindingTwoWay(),
link: { observer, path: 'data.ssao.taa' }
})
),
jsx(
LabelGroup,
{ text: 'debug' },
jsx(BooleanInput, {
type: 'toggle',
binding: new BindingTwoWay(),
link: { observer, path: 'data.ssao.debug' }
})
)
)
);
Expand Down
17 changes: 15 additions & 2 deletions examples/src/examples/graphics/ambient-occlusion.example.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -168,9 +168,11 @@ assetListLoader.load(() => {
// ------ Custom render passes set up ------

const cameraFrame = new pc.CameraFrame(app, cameraEntity.camera);
cameraFrame.rendering.samples = 4;
cameraFrame.rendering.toneMapping = pc.TONEMAP_NEUTRAL;

// use 16but render target for better precision, improves quality with TAA and randomized SSAO
cameraFrame.rendering.renderFormats = [pc.PIXELFORMAT_RGBA16F];

const applySettings = () => {

cameraFrame.ssao.type = data.get('data.ssao.type');
Expand All @@ -181,6 +183,14 @@ assetListLoader.load(() => {
cameraFrame.ssao.samples = data.get('data.ssao.samples');
cameraFrame.ssao.minAngle = data.get('data.ssao.minAngle');
cameraFrame.ssao.scale = data.get('data.ssao.scale');
cameraFrame.ssao.randomize = data.get('data.ssao.randomize');
cameraFrame.debug = data.get('data.ssao.debug') ? 'ssao' : null;

// TAA or MSAA
const taa = data.get('data.ssao.taa');
cameraFrame.taa.enabled = taa;
cameraFrame.rendering.samples = taa ? 1 : 4; // disable MSAA when TAA is enabled
cameraFrame.rendering.sharpness = taa ? 1 : 0; // sharpen the image when TAA is enabled

cameraFrame.update();
};
Expand Down Expand Up @@ -213,7 +223,10 @@ assetListLoader.load(() => {
intensity: 0.4,
power: 6,
minAngle: 10,
scale: 1
scale: 1,
taa: false,
randomize: false,
debug: false
}
});
});
Expand Down
15 changes: 15 additions & 0 deletions examples/src/examples/graphics/post-processing.controls.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,21 @@ export const controls = ({ observer, ReactPCUI, React, jsx, fragment }) => {
{ v: pc.TONEMAP_NEUTRAL, t: 'NEUTRAL' }
]
})
),
jsx(
LabelGroup,
{ text: 'Debug' },
jsx(SelectInput, {
binding: new BindingTwoWay(),
link: { observer, path: 'data.scene.debug' },
type: 'number',
options: [
{ v: 0, t: 'NONE' },
{ v: 1, t: 'BLOOM' },
{ v: 2, t: 'VIGNETTE' },
{ v: 3, t: 'SCENE' }
]
})
)
),
jsx(
Expand Down
11 changes: 10 additions & 1 deletion examples/src/examples/graphics/post-processing.example.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,14 @@ assetListLoader.load(() => {
// fringing
cameraFrame.fringing.intensity = data.get('data.fringing.enabled') ? data.get('data.fringing.intensity') : 0;

// debug
switch (data.get('data.scene.debug')) {
case 0: cameraFrame.debug = null; break;
case 1: cameraFrame.debug = 'bloom'; break;
case 2: cameraFrame.debug = 'vignette'; break;
case 3: cameraFrame.debug = 'scene'; break;
}

// apply all settings
cameraFrame.update();
};
Expand All @@ -279,7 +287,8 @@ assetListLoader.load(() => {
scale: 1.8,
background: 6,
emissive: 200,
tonemapping: pc.TONEMAP_ACES
tonemapping: pc.TONEMAP_ACES,
debug: 0
},
bloom: {
enabled: true,
Expand Down
18 changes: 18 additions & 0 deletions scripts/utils/camera-frame.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,15 @@ const RenderFormat = {
RGBA32: 14 // PIXELFORMAT_RGBA32F
};

/** @enum {string} */
const DebugType = {
NONE: '',
SCENE: 'scene',
SSAO: 'ssao',
BLOOM: 'bloom',
VIGNETTE: 'vignette'
};

/** @interface */
class Rendering {
/**
Expand Down Expand Up @@ -79,6 +88,12 @@ class Rendering {
* @step 0.001
*/
sharpness = 0.0;

/**
* @attribute
* @type {DebugType}
*/
debug = DebugType.NONE;
}

/** @interface */
Expand Down Expand Up @@ -369,6 +384,9 @@ class CameraFrame extends Script {
const dstFringing = cf.fringing;
dstFringing.intensity = fringing.enabled ? fringing.intensity : 0;

// debugging
cf.debug = rendering.debug;

cf.update();
}
}
Expand Down
16 changes: 16 additions & 0 deletions src/extras/render-passes/camera-frame.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ import { CameraFrameOptions, RenderPassCameraFrame } from './render-pass-camera-
* - {@link SSAOTYPE_COMBINE}
*
* @property {boolean} blurEnabled - Whether the SSAO effect is blurred. Defaults to true.
* @property {boolean} randomize - Whether the SSAO sampling is randomized. Useful when used instead
* of blur effect together with TAA. Defaults to false.
* @property {number} intensity - The intensity of the SSAO effect, 0-1 range. Defaults to 0.5.
* @property {number} radius - The radius of the SSAO effect, 0-100 range. Defaults to 30.
* @property {number} samples - The number of samples of the SSAO effect, 1-64 range. Defaults to 12.
Expand Down Expand Up @@ -174,6 +176,7 @@ class CameraFrame {
ssao = {
type: SSAOTYPE_NONE,
blurEnabled: true,
randomize: false,
intensity: 0.5,
radius: 30,
samples: 12,
Expand Down Expand Up @@ -236,6 +239,13 @@ class CameraFrame {
intensity: 0
};

/**
* Debug rendering. Set to null to disable.
*
* @type {null|'scene'|'ssao'|'bloom'|'vignette'}
*/
debug = null;

options = new CameraFrameOptions();

/**
Expand Down Expand Up @@ -357,6 +367,7 @@ class CameraFrame {
ssaoPass.sampleCount = ssao.samples;
ssaoPass.minAngle = ssao.minAngle;
ssaoPass.scale = ssao.scale;
ssaoPass.randomize = ssao.randomize;
}

composePass.gradingEnabled = grading.enabled;
Expand All @@ -382,6 +393,11 @@ class CameraFrame {

// enable camera jitter if taa is enabled
cameraComponent.jitter = taa.enabled ? taa.jitter : 0;

// debug rendering
composePass.debug = this.debug;
if (composePass.debug === 'ssao' && options.ssaoType === SSAOTYPE_NONE) composePass.debug = null;
if (composePass.debug === 'vignette' && !composePass.vignetteEnabled) composePass.debug = null;
}
}

Expand Down
59 changes: 55 additions & 4 deletions src/extras/render-passes/render-pass-compose.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ const fragmentShader = /* glsl */ `
#endif

#ifdef SSAO
#define SSAO_TEXTURE
#endif

#if DEBUG_COMPOSE == ssao
#define SSAO_TEXTURE
#endif

#ifdef SSAO_TEXTURE
uniform sampler2D ssaoTexture;
#endif

Expand Down Expand Up @@ -146,8 +154,12 @@ const fragmentShader = /* glsl */ `
result = cas(result, uv, sharpness);
#endif

#ifdef SSAO_TEXTURE
mediump float ssao = texture2DLodEXT(ssaoTexture, uv0, 0.0).r;
#endif

#ifdef SSAO
result *= texture2DLodEXT(ssaoTexture, uv0, 0.0).r;
result *= ssao;
#endif

#ifdef FRINGING
Expand All @@ -167,7 +179,31 @@ const fragmentShader = /* glsl */ `
result = toneMap(result);

#ifdef VIGNETTE
result *= vignette(uv);
mediump float vig = vignette(uv);
result *= vig;
#endif

// debug output
#ifdef DEBUG_COMPOSE

#ifdef BLOOM
#if DEBUG_COMPOSE == bloom
result = bloom * bloomIntensity;
#endif
#endif

#if DEBUG_COMPOSE == ssao
result = vec3(ssao);
#endif

#if DEBUG_COMPOSE == vignette
result = vec3(vig);
#endif

#if DEBUG_COMPOSE == scene
result = scene.rgb;
#endif

#endif

#ifdef GAMMA_CORRECT_OUTPUT
Expand Down Expand Up @@ -229,6 +265,8 @@ class RenderPassCompose extends RenderPassShaderQuad {

_key = '';

_debug = null;

constructor(graphicsDevice) {
super(graphicsDevice);

Expand All @@ -246,6 +284,17 @@ class RenderPassCompose extends RenderPassShaderQuad {
this.sharpnessId = scope.resolve('sharpness');
}

set debug(value) {
if (this._debug !== value) {
this._debug = value;
this._shaderDirty = true;
}
}

get debug() {
return this._debug;
}

set bloomTexture(value) {
if (this._bloomTexture !== value) {
this._bloomTexture = value;
Expand Down Expand Up @@ -367,7 +416,8 @@ class RenderPassCompose extends RenderPassShaderQuad {
`-${this.fringingEnabled ? 'fringing' : 'nofringing'}` +
`-${this.taaEnabled ? 'taa' : 'notaa'}` +
`-${this.isSharpnessEnabled ? 'cas' : 'nocas'}` +
`-${this._srgb ? 'srgb' : 'linear'}`;
`-${this._srgb ? 'srgb' : 'linear'}` +
`-${this._debug ?? ''}`;

if (this._key !== key) {
this._key = key;
Expand All @@ -380,7 +430,8 @@ class RenderPassCompose extends RenderPassShaderQuad {
(this.fringingEnabled ? '#define FRINGING\n' : '') +
(this.taaEnabled ? '#define TAA\n' : '') +
(this.isSharpnessEnabled ? '#define CAS\n' : '') +
(this._srgb ? '' : '#define GAMMA_CORRECT_OUTPUT\n');
(this._srgb ? '' : '#define GAMMA_CORRECT_OUTPUT\n') +
(this._debug ? `#define DEBUG_COMPOSE ${this._debug}\n` : '');

const fsChunks =
shaderChunks.decodePS +
Expand Down
18 changes: 15 additions & 3 deletions src/extras/render-passes/render-pass-ssao.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { BlueNoise } from '../../core/math/blue-noise.js';
import { Color } from '../../core/math/color.js';
import { ADDRESS_CLAMP_TO_EDGE, FILTER_NEAREST, PIXELFORMAT_R8 } from '../../platform/graphics/constants.js';
import { RenderTarget } from '../../platform/graphics/render-target.js';
Expand Down Expand Up @@ -150,9 +151,10 @@ const fs = /* glsl */`

uniform float uProjectionScaleRadius;
uniform float uIntensity;
uniform float uRandomize;

float scalableAmbientObscurance(highp vec2 uv, highp vec3 origin, vec3 normal) {
float noise = random(getFragCoord());
float noise = random(getFragCoord()) + uRandomize;
highp vec2 tapPosition = startPosition(noise);
highp mat2 angleStep = tapAngleStep();

Expand Down Expand Up @@ -234,6 +236,12 @@ class RenderPassSsao extends RenderPassShaderQuad {
*/
minAngle = 5;

/**
* Enable randomization of the sample pattern. Useful when TAA is used to remove the noise,
* instead of blurring.
*/
randomize = false;

/**
* The texture containing the occlusion information in the red channel.
*
Expand All @@ -245,6 +253,8 @@ class RenderPassSsao extends RenderPassShaderQuad {
/** @type {number} */
_scale = 1;

_blueNoise = new BlueNoise(19);

constructor(device, sourceTexture, cameraComponent, blurEnabled) {
super(device);
this.sourceTexture = sourceTexture;
Expand Down Expand Up @@ -356,11 +366,12 @@ class RenderPassSsao extends RenderPassShaderQuad {

const spiralTurns = 10.0;
const step = (1.0 / (sampleCount - 0.5)) * spiralTurns * 2.0 * 3.141;
const radius = this.radius * scale;
const radius = this.radius / scale;

const bias = 0.001;
const peak = 0.1 * radius;
const intensity = 2 * (peak * 2.0 * 3.141) * this.intensity / sampleCount;
const projectionScale = 0.5 * sourceTexture.height * scale;
const projectionScale = 0.5 * sourceTexture.height;
scope.resolve('uSpiralTurns').setValue(spiralTurns);
scope.resolve('uAngleIncCosSin').setValue([Math.cos(step), Math.sin(step)]);
scope.resolve('uMaxLevel').setValue(0.0);
Expand All @@ -370,6 +381,7 @@ class RenderPassSsao extends RenderPassShaderQuad {
scope.resolve('uIntensity').setValue(intensity);
scope.resolve('uPower').setValue(this.power);
scope.resolve('uProjectionScaleRadius').setValue(projectionScale * radius);
scope.resolve('uRandomize').setValue(this.randomize ? this._blueNoise.value() : 0);

super.execute();
}
Expand Down