Skip to content

Commit

Permalink
RenderPass based SSAO (#6657)
Browse files Browse the repository at this point in the history
* RenderPass based SSAO

* fix to minAngle

---------

Co-authored-by: Martin Valigursky <mvaligursky@snapchat.com>
  • Loading branch information
mvaligursky and Martin Valigursky committed Jun 6, 2024
1 parent 4db640b commit 9868aa6
Show file tree
Hide file tree
Showing 14 changed files with 712 additions and 47 deletions.
54 changes: 42 additions & 12 deletions examples/src/examples/graphics/ambient-occlusion.controls.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,46 +14,76 @@ export function controls({ observer, ReactPCUI, React, jsx, fragment }) {
jsx(BooleanInput, {
type: 'toggle',
binding: new BindingTwoWay(),
link: { observer, path: 'scripts.ssao.enabled' }
link: { observer, path: 'data.ssao.enabled' }
})
),
jsx(
LabelGroup,
{ text: 'blurEnabled' },
jsx(BooleanInput, {
type: 'toggle',
binding: new BindingTwoWay(),
link: { observer, path: 'data.ssao.blurEnabled' }
})
),
jsx(
LabelGroup,
{ text: 'radius' },
jsx(SliderInput, {
binding: new BindingTwoWay(),
link: { observer, path: 'scripts.ssao.radius' },
max: 10
link: { observer, path: 'data.ssao.radius' },
max: 50
})
),
jsx(
LabelGroup,
{ text: 'samples' },
jsx(SliderInput, {
binding: new BindingTwoWay(),
link: { observer, path: 'scripts.ssao.samples' },
max: 32
link: { observer, path: 'data.ssao.samples' },
min: 1,
max: 64,
precision: 0
})
),
jsx(
LabelGroup,
{ text: 'intensity' },
jsx(SliderInput, {
binding: new BindingTwoWay(),
link: { observer, path: 'data.ssao.intensity' }
})
),
jsx(
LabelGroup,
{ text: 'power' },
jsx(SliderInput, {
binding: new BindingTwoWay(),
link: { observer, path: 'data.ssao.power' },
min: 0.1,
max: 10
})
),
jsx(
LabelGroup,
{ text: 'brightness' },
{ text: 'minAngle' },
jsx(SliderInput, {
binding: new BindingTwoWay(),
link: { observer, path: 'scripts.ssao.brightness' }
link: { observer, path: 'data.ssao.minAngle' },
max: 90
})
),
jsx(
LabelGroup,
{ text: 'downscale' },
{ text: 'scale' },
jsx(SelectInput, {
options: [
{ v: 1, t: 'None' },
{ v: 2, t: '50%' },
{ v: '4', t: '25%' }
{ v: 1.00, t: '100%' },
{ v: 0.75, t: '75%' },
{ v: 0.50, t: '50%' }
],
binding: new BindingTwoWay(),
link: { observer, path: 'scripts.ssao.downscale' }
link: { observer, path: 'data.ssao.scale' }
})
)
)
Expand Down
142 changes: 115 additions & 27 deletions examples/src/examples/graphics/ambient-occlusion.example.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,8 @@ assetListLoader.load(() => {
type: 'directional',
intensity: 0.7,
castShadows: true,
shadowBias: 0.2,
shadowResolution: 4096,
shadowBias: 0.4,
normalOffsetBias: 0.06,
shadowDistance: 600,
shadowUpdateMode: pc.SHADOWUPDATE_THISFRAME
Expand All @@ -141,51 +142,138 @@ assetListLoader.load(() => {
light.setLocalEulerAngles(35, 30, 0);

// Create an Entity with a camera component
const camera = new pc.Entity();
camera.addComponent('camera', {
const cameraEntity = new pc.Entity();
cameraEntity.addComponent('camera', {
clearColor: new pc.Color(0.4, 0.45, 0.5),
nearClip: 1,
farClip: 500
farClip: 600
});

// add orbit camera script
camera.addComponent('script');
camera.script.create('orbitCamera', {
cameraEntity.addComponent('script');
cameraEntity.script.create('orbitCamera', {
attributes: {
inertiaFactor: 0.2,
focusEntity: laboratoryEntity,
distanceMax: 350
}
});
camera.script.create('orbitCameraInputMouse');
camera.script.create('orbitCameraInputTouch');
cameraEntity.script.create('orbitCameraInputMouse');
cameraEntity.script.create('orbitCameraInputTouch');

// add SSAO post-effect
data.set('scripts', {
ssao: {
enabled: true,
radius: 5,
samples: 16,
brightness: 0,
downscale: 1
// position the camera in the world
cameraEntity.setLocalPosition(-60, 30, 60);
app.root.addChild(cameraEntity);

// ------ Custom render passes set up ------

const currentOptions = {
camera: cameraEntity.camera, // camera used to render those passes
samples: 1, // number of samples for multi-sampling
sceneColorMap: false,

// enable the pre-pass to generate the depth buffer, which is needed by the SSAO
prepassEnabled: true,

// enable temporal anti-aliasing
taaEnabled: false,

ssaoEnabled: true,
ssaoBlurEnabled: true
};

const setupRenderPass = () => {
// destroy existing pass if any
if (cameraEntity.camera.renderPasses.length > 0) {
cameraEntity.camera.renderPasses[0].destroy();
}
});

// position the camera in the world
camera.setLocalPosition(-60, 30, 60);
app.root.addChild(camera);
// Use a render pass camera frame, which is a render pass that implements typical rendering of a camera.
// Internally this sets up additional passes it needs, based on the options passed to it.
const renderPassCamera = new pc.RenderPassCameraFrame(app, currentOptions);
renderPassCamera.bloomEnabled = false;

// handle UI values updates
Object.keys(data.get('scripts')).forEach((key) => {
camera.script.create(key, {
attributes: data.get(`scripts.${key}`)
});
});
renderPassCamera.ssaoEnabled = currentOptions.ssaoEnabled;

const composePass = renderPassCamera.composePass;
composePass.toneMapping = pc.TONEMAP_NEUTRAL;
composePass.sharpness = 0;

// and set up these rendering passes to be used by the camera, instead of its default rendering
cameraEntity.camera.renderPasses = [renderPassCamera];

// the render passes render in HDR format, and so disable output tone mapping and gamma correction,
// as that is applied in the final compose pass
app.scene.toneMapping = pc.TONEMAP_LINEAR;
app.scene.gammaCorrection = pc.GAMMA_NONE;

// jitter the camera when TAA is enabled
cameraEntity.camera.jitter = currentOptions.taaEnabled ? 1 : 0;
};

const applySettings = () => {
// if settings require render passes to be re-created
const noPasses = cameraEntity.camera.renderPasses.length === 0;
const ssaoEnabled = data.get('data.ssao.enabled');
const blurEnabled = data.get('data.ssao.blurEnabled');
if (noPasses || ssaoEnabled !== currentOptions.ssaoEnabled || blurEnabled !== currentOptions.ssaoBlurEnabled) {
currentOptions.ssaoEnabled = ssaoEnabled;
currentOptions.ssaoBlurEnabled = blurEnabled;

// create new pass
setupRenderPass();
}

const renderPassCamera = cameraEntity.camera.renderPasses[0];

// ssao settings
const ssaoPass = renderPassCamera.ssaoPass;
if (ssaoPass) {

ssaoPass.intensity = data.get('data.ssao.intensity');
ssaoPass.power = data.get('data.ssao.power');
ssaoPass.radius = data.get('data.ssao.radius');
ssaoPass.sampleCount = data.get('data.ssao.samples');
ssaoPass.minAngle = data.get('data.ssao.minAngle');
ssaoPass.scale = data.get('data.ssao.scale');
}
};

// apply UI changes
let initialValuesSetup = false;
data.on('*:set', (/** @type {string} */ path, value) => {
if (initialValuesSetup)
applySettings();

const pathArray = path.split('.');
camera.script[pathArray[1]][pathArray[2]] = value;
if (pathArray[2] === 'scale') {
// adjust min angle based on scale to avoid depth related artifacts
if (value < 0.6)
data.set('data.ssao.minAngle', 40);
else if (value < 0.8)
data.set('data.ssao.minAngle', 20);
else
data.set('data.ssao.minAngle', 10);
}
});

// initial settings
data.set('data', {
ssao: {
enabled: true,
blurEnabled: true,
radius: 30,
samples: 12,
intensity: 0.4,
power: 6,
minAngle: 10,
scale: 1
}
});

// apply initial settings after all values are set
initialValuesSetup = true;
applySettings();
});

export { app };
Binary file not shown.
Binary file not shown.
2 changes: 2 additions & 0 deletions src/extras/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ export { GltfExporter } from './exporters/gltf-exporter.js';
// RENDER PASSES
export { RenderPassCameraFrame } from './render-passes/render-pass-camera-frame.js';
export { RenderPassCompose } from './render-passes/render-pass-compose.js';
export { RenderPassDepthAwareBlur } from './render-passes/render-pass-depth-aware-blur.js';
export { RenderPassDownsample } from './render-passes/render-pass-downsample.js';
export { RenderPassUpsample } from './render-passes/render-pass-upsample.js';
export { RenderPassBloom } from './render-passes/render-pass-bloom.js';
export { RenderPassSsao } from './render-passes/render-pass-ssao.js';
export { RenderPassTAA } from './render-passes/render-pass-taa.js';

// GIZMOS
Expand Down
3 changes: 3 additions & 0 deletions src/extras/render-passes/render-pass-bloom.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ import { RenderPassUpsample } from './render-pass-upsample.js';

// based on https://learnopengl.com/Guest-Articles/2022/Phys.-Based-Bloom
/**
* Render pass implementation of HDR bloom effect.
*
* @category Graphics
* @ignore
*/
class RenderPassBloom extends RenderPass {
bloomTexture;
Expand Down
Loading

0 comments on commit 9868aa6

Please sign in to comment.