• Stars
    star
    342
  • Rank 123,697 (Top 3 %)
  • Language
    JavaScript
  • License
    Creative Commons ...
  • Created over 1 year ago
  • Updated 5 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

An efficient and visually pleasing implementation of SSAO with an emphasis on temporal stability and artist control.

N8AO

npm version github twitter

An efficient and visually pleasing implementation of SSAO with an emphasis on temporal stability and artist control.

AO is critical for creating a sense of depth in any 3D scene, and it does so by darkening parts of the scene that geometry blocks light from reaching. This effect is most noticeable in corners, crevices, and other areas where light is occluded. I'd recommend using this package in any scene that looks "flat" or "fake", or in any scene with vague depth cues and a lack of strong directional lighting.

Example (Source code in /example)

Scene w/ AO applied:

screenshot

(Left) Scene w/o AO, (Right) Scene w/ AO:

screenshot

Installation

From npm: npm install n8ao -> import {N8AOPass} from "n8ao"

From cdn: import {N8AOPass} from "https://unpkg.com/n8ao@latest/dist/N8AO.js"

Or just download dist/N8AO.js and import {N8AOPass} from "N8AO.js"

In order to ensure maximum compatibility, you must have the packages "three" and "postprocessing" defined in your environment - you can do this by CDN, as in the examples, or by installing them via npm.

Usage

It's very simple - N8AOPass is just a threejs postprocessing pass.

Import EffectComposer and:

const composer = new EffectComposer(renderer);
// N8AOPass replaces RenderPass
const n8aopass = new N8AOPass(
        scene,
        camera,
        width,
        height
    );
composer.addPass(n8aopass);

This is all you need to do. The effect should work out of the box with any vanilla three.js scene, and as long as the depth buffer is being written to, generate ambient occlusion.

Use of SMAAPass (as hardware antialiasing does NOT work with ambient occlusion) is recommended to reduce aliasing.

Gamma correction is enabled by default, but it should be disabled if you have a gamma correction pass later in your postprocessing pipeline, to avoid double-correcting your colors:

n8aopass.configuration.gammaCorrection = false;

Usage (Postprocessing)

If you are using the pmndrs/postprocessing package, N8AO is compatible with it. Simply do:

import { N8AOPostPass } from "n8ao";
import { EffectComposer, RenderPass } from "postprocessing";

// ... 

const composer = new EffectComposer(renderer);
/* Only difference is that N8AOPostPass requires a RenderPass before it, whereas N8AOPass replaces the render pass. Everything else is identical. */
composer.addPass(new RenderPass(scene, camera));
const n8aopass = new N8AOPostPass(
    scene,
    camera,
    width,
    height
);
composer.addPass(n8aopass)

/* SMAA Recommended */
composer.addPass(new EffectPass(camera, new SMAAEffect({
    preset: SMAAPreset.ULTRA
})));

N8AOPostPass's API is identical to that of N8AOPass (so all docs below apply), except it is compatible with pmndrs/postprocessing.

Small note: N8AOPostPass's configuration.gammaCorrection is automatically set to the correct value based on its position in your postprocessing pipeline. If it is the last pass, it is set to true. Otherwise, it is set to false. This is to ensure that gamma correction is only applied once. However, sometimes this automatic detection fails, so if you are getting washed out colors, try setting configuration.gammaCorrection to false, and if you are getting dark colors, try setting it to true.

Usage (Detailed)

N8AOPass is designed to be as easy to use as possible. It works with logarithmic depth buffers and orthographic cameras, supports materials with custom vertex displacement and alpha clipping, and automatically detects the presence of these things so you do not need to deal with user-side configuration.

However, some tweaking is often necessary to make sure your AO effect looks proper and does not suffer from artifacts. There are four principal parameters that control the visual "look" of the AO effect: aoRadius, distanceFalloff, intensity, and color. They can be changed in the following manner:

n8aopass.configuration.aoRadius = 5.0;
n8aopass.configuration.distanceFalloff = 1.0;
n8aopass.configuration.intensity = 5.0;
n8aopass.configuration.color = new THREE.Color(0, 0, 0);

They are covered below:

aoRadius: number - The most important parameter for your ambient occlusion effect. Controls the radius/size of the ambient occlusion in world units. Should be set to how far you want the occlusion to extend from a given object. Set it too low, and AO becomes an edge detector. Too high, and the AO becomes "soft" and might not highlight the details you want. The radius should be one or two magnitudes less than scene scale: if your scene is 10 units across, the radius should be between 0.1 and 1. If its 100, 1 to 10.

Image 1 Image 2 Image 3
Radius 1 Radius 5 Radius 10

distanceFalloff: number - The second most important parameter for your ambient occlusion effect. Controls how fast the ambient occlusion fades away with distance in world units. Generally should be set to ~1/5 of your radius. Decreasing it reduces "haloing" artifacts and improves the accuracy of your occlusion, but making it too small makes the ambient occlusion disappear entirely.

Image 1 Image 2 Image 3
Distance Falloff 0.1 Distance Falloff 1 Distance Falloff 5

intensity: number - A purely artistic control for the intensity of the AO - runs the ao through the function pow(ao, intensity), which has the effect of darkening areas with more ambient occlusion. Useful to make the effect more pronounced. An intensity of 2 generally produces soft ambient occlusion that isn't too noticeable, whereas one of 5 produces heavily prominent ambient occlusion.

color: THREE.Color - The color of the ambient occlusion. By default, it is black, but it can be changed to any color to offer a crude approximation of global illumination. Recommended in scenes where bounced light has a uniform "color", for instance a scene that is predominantly lit by a blue sky. The color is expected to be in the sRGB color space, and is automatically converted to linear space for you. Keep the color pretty dark for sensible results.

Image 1 Image 2 Image 3
Color Black (Normal AO) Color Blue (Appropriate) Color Red (Too Bright)

Screen Space Radius

If you want the AO to calculate the radius based on screen space, you can do so by setting configuration.screenSpaceRadius to true. This is useful for scenes where the camera is moving across different scales a lot, or for scenes where the camera is very close to the objects.

When screenSpaceRadius is set to true, the aoRadius parameter represents the size of the ambient occlusion effect in pixels (recommended to be set between 16 and 64). The distanceFalloff parameter becomes a ratio, representing the percent of the screen space radius at which the AO should fade away - it should be set to 0.2 in most cases, but it accepts any value between 0 and 1 (technically even higher than 1, though that is not recommended).

Using your own render target

If you're utilizing the standard N8AOPass, there might be a situation where you have a pre-existing render target with a depth buffer that you'd prefer N8AOPass to use, instead of it generating a new one. To accomplish this, you can assign your render target to n8aopass.beautyRenderTarget.

N8AOPass will still render to n8aopass.beautyRenderTarget by default, but you can change this by setting n8aopass.configuration.autoRenderBeauty to false. If you do this, it's up to you to render the scene to n8aopass.beautyRenderTarget before using N8AOPass.

Finally, your render target must have a depth buffer attached to it. Otherwise, N8AOPass will not work and may fail silently.

You can attach a depth buffer to your render target by doing:

const renderTarget = new THREE.WebGLRenderTarget(width, height);
// If you just want a depth buffer
renderTarget.depthTexture = new THREE.DepthTexture(width, height, THREE.UnsignedIntType);
renderTarget.depthTexture.format = THREE.DepthFormat;
// If you want a depth buffer and a stencil buffer
renderTarget.depthTexture = new THREE.DepthTexture(width, height, THREE.UnsignedInt248Type);
renderTarget.depthTexture.format = THREE.DepthStencilFormat;

Performance

N8AOPass has a "half-resolution" mode for performance-critical applications. Enabling it is as simple as doing:

n8aopass.configuration.halfRes = true;

This will cause the AO to be calculated at half the resolution of the screen, and then upscaled to the full resolution. This is a great way to get a performance boost (generally 2x-4x) at the cost of some quality (the AO will lack fine details and temporal stability will be slightly reduced).

The half-resolution mode uses depth-aware upscaling by default, and this generally incurs a fixed cost of around 1ms. The AO effect looks horrible without depth-aware upscaling, so it is not recommended to disable it. However, if performance is truly that critical, you can do so by setting configuration.depthAwareUpsampling to false.

On top of half-resolution mode,N8AOPass comes with a wide variety of quality presets (which can be used with or without half-resolution mode), and you can even manually edit the settings to your liking. You can switch between quality modes (the default is Medium) by doing:

n8ao.setQualityMode("Low"); // Or any other quality mode

The quality modes are as follows:

Temporal stability refers to how consistent the AO is from frame to frame - it's important for a smooth experience.

Quality Mode AO Samples Denoise Samples Denoise Radius Best For
Performance (Less temporal stability, a bit noisy) 8 4 12 Mobile, Low-end iGPUs and laptops
Low (Temporally stable, but low-frequency noise) 16 4 12 High-End Mobile, iGPUs, laptops
Medium (Temporally stable and barely any noise) 16 8 12 High-End Mobile, laptops, desktops
High (Significantly sharper AO, barely any noise) 64 8 6 Desktops, dedicated GPUs
Ultra (Sharp AO, No visible noise whatsoever) 64 16 6 Desktops, dedicated GPUs

Generally, half-res mode at "Ultra" quality is slightly slower than full-res mode at "Performance" quality, but produces significantly better results.

If you wish to make entirely custom quality setup, you can manually change aoSamples, denoiseSamples and the denoiseRadius in the configuration object.

n8aopass.configuration.aoSamples = 16;
n8aopass.configuration.denoiseSamples = 8;
n8aopass.configuration.denoiseRadius = 12;

Either way, changing the quality or any of the above variables is expensive as it forces a recompile of all the shaders used in the AO effect. It is recommended to do this only once, at the start of your application. Merely setting the values each frame to some constant does not trigger recompile, however.

Debug Mode

If you want to track the exact amount of time the AO render pass is taking in your scene, you can enable debug mode by doing:

n8aopass.enableDebugMode(); // Disable with n8aopass.disableDebugMode();

Then, the n8aopass.lastTime variable, which is normally undefined, will have a value set each frame which represents the time, in milliseconds, the AO took to render (the value typically lags behind a few frames due to the CPU and GPU being slightly out of sync). This is useful for debugging performance issues.

Note that debug mode relies on WebGL features that are not available in all browsers - so make sure you are using a recent version of a Chromium browser while debugging. If your browser does not support these extra features (EXT_disjoint_timer_query_webgl2), debug mode will not work and an error will be thrown. The rest of the AO will still function as normal.

Display Modes

N8AOPass comes with a variety of display modes that can be used to debug/showcase the AO effect. They can be switched between by doing:

n8aopass.setDisplayMode("AO"); // Or any other display mode

The display modes available are:

Display Mode Description
Combined Standard option, composites the AO effect onto the scene to modulate lighting - what you should use in production screenshot
AO Shows only the AO effect as a black (completely occluded) and white (no occlusion) image screenshot
No AO Shows only the scene without the AO effect screenshot
Split Shows the scene with and without the AO effect side-by-side (divided by a white line in the middle) screenshot
Split AO Shows the AO effect as a black and white image, and the scene with the AO effect applied side-by-side (divided by a white line in the middle) screenshot

Compatibility

N8AOPass is compatible with all modern browsers that support WebGL 2.0 (WebGL 1 is not supported), but using three.js version r152 or later is recommended.

The pass is self-contained, and renders the scene automatically. The render target containing the scene texture (and depth) is available as n8aopass.beautyRenderTarget if you wish to use it for other purposes (for instance, using a depth buffer later in the rendering pipeline). All pass logic is self-contained and the pass should not be hard to modify if necessary.

Limitations

Like all screen space methods, geometry that is offscreen or is blocked by another object will not actually occlude anything. Haloing is still a minor issue in some cases, but it is not very noticeable.

Generally, the effect will appear to be grounded completely in world-space, with few view dependent artifacts. Only if one is specifically looking for them will they be found.

Contributing

Clone the repo:

git clone https://github.com/N8python/n8ao.git

Start the dev server:

npm run dev

The example can be accessed localhost:8080/example. The source code for the example is in /example.

License

A CC0 license is used for this project. You can do whatever you want with it, no attribution is required. However, if you do use it, I'd love to hear about it!

Credits

Too many papers and inspirations to count, but here's a list of resources that have been helpful to me:

https://threejs.org/examples/?q=pcss#webgl_shadowmap_pcss https://www.shadertoy.com/view/4l3yRM https://learnopengl.com/Advanced-Lighting/SSAO https://github.com/mrdoob/three.js/blob/dev/examples/jsm/postprocessing/SSAOPass.js https://pmndrs.github.io/postprocessing/public/docs/class/src/effects/SSAOEffect.js~SSAOEffect.html https://github.com/Calinou/free-blue-noise-textures https://iquilezles.org/articles/multiresaocc/

More Repositories

1

N8GI

A heavy and visually pleasing implementation of world-space global illumination with an emphasis on temporal stability and artist control.
JavaScript
61
star
2

havokDemo

JavaScript
26
star
3

theIsland

A procedurally generated island that uses noise and other algorithms to create realistic terrain and foliage - which is then augmented with post-processing based water, screen-space ambient occlusion, and crepuscular lighting.
JavaScript
21
star
4

caustics

JavaScript
21
star
5

voxelGI

Realtime voxel global illumination in WebGL.
JavaScript
19
star
6

ssao

Cool demo.
JavaScript
19
star
7

diamonds

JavaScript
18
star
8

nektoFlare

A beautiful and performant lens flare.
JavaScript
16
star
9

rugpull

From RAGs to riches. A simple library for RAG visualization.
JavaScript
16
star
10

city

A procedurally generated city that dynamically creates a road network and then populates the surrounding area with buildings. Pedestrians and cars then intelligently traverse the roads and sidewalks. Dynamic clouds, glowing windows, dynamic shadows, and baked ambient occlusion make the city feel all the better to explore.
JavaScript
14
star
11

mnistLatentSpace

Explore the wonderful word of the MNIST latent space!
JavaScript
13
star
12

subsurfaceScatteringMat

JavaScript
11
star
13

2dgi

2-dimensional global illumination.
JavaScript
10
star
14

Modol

The future of templating.
JavaScript
9
star
15

galaxyThing

Galaxy.
JavaScript
9
star
16

potatoz2

Who thought that potatoz could take over the world? This one cat did. Take the role of Bonnie, starting as a single cat in her owner’s front yard, gradually building a potato empire, first by taking over the backyard, then with other cats, and finally by taking over the world. Play now, and start the potatopocalypse.
JavaScript
9
star
17

fogEditor

An interactive program that allows the user to experiment with raymarched volumetric fog, which is generated frame-by-frame using fractal noise.
JavaScript
8
star
18

sdfGI

JavaScript
7
star
19

theRitual

JavaScript
6
star
20

slicer

JavaScript
6
star
21

goodGodRays

JavaScript
6
star
22

sdfReflections

The future is now.
JavaScript
6
star
23

catCreator

Uses PCA (Principal Component Analysis) to explore the latent space of an autoencoder trained on thousands of images of cats.
JavaScript
6
star
24

proceduralWorld

Procedural terrain generation. As of now, the terrain looks like a golf course.
JavaScript
5
star
25

minecraftClone

A raytraced minecraft clone that uses a 3D DDA voxel intersection algorithm to render and edit a 3d world, complete with multiple block types, ambient occlusion, water, and shadows.
JavaScript
5
star
26

ssgi

Screen space global illumination (wip)
JavaScript
4
star
27

germCity

A to-be simulation of a outbreak in a city (based off COVID-19).
JavaScript
3
star
28

machinelearning

Here I try to implement various machine learning algorithms.
JavaScript
3
star
29

theOffice

Corporate dystopia haha.
JavaScript
3
star
30

BoombaZoomba

A fun fight game with stick figure rag dolls.
JavaScript
3
star
31

weirdTerrain

3
star
32

walkingAI

It's an AI... that sort of walks.
JavaScript
3
star
33

beSoMusical

Online music lesson planning.
JavaScript
3
star
34

qformula

Quadratic Formula Explanation.
JavaScript
3
star
35

terrainzjs

2d terrain generation in JavaScript with custom biomes and coloration
JavaScript
3
star
36

diamond

JavaScript
3
star
37

trazenlae

A asynchronous web tool for easy rendering of language translations.
JavaScript
3
star
38

strangeSand

It's been far too long.
JavaScript
3
star
39

marchingcubeseditor

Unoptimized marching cubes editor.
JavaScript
2
star
40

N8Bench

30 questions to tell the smart LLMs from the dumb.
JavaScript
2
star
41

Chatbot-Libraries

Cool Chatbot functions and a sample bot.
Python
2
star
42

shadowsYay

Yay shadows.
JavaScript
2
star
43

rectLightShadow

JavaScript
2
star
44

DecisionTree

A Java class to create interactive yes-no "Choose Your Own Adventure" stories.
Java
2
star
45

2dlights

SDF-Accelerated Raymarching for Multiple Shadow-Casting 2D Lights.
JavaScript
2
star
46

randomPhysics

Idk its kinda cool.
JavaScript
2
star
47

eternal-void

JavaScript
2
star
48

zappers

A meh-looking arcade shooter space thing.
JavaScript
2
star
49

rectAreaLightShadow

JavaScript
2
star
50

ssProject

A museum 3d model for a social studies project.
JavaScript
2
star
51

lil-chess

A short & brief (and nearly complete) implementation of chess - Now with AI!
JavaScript
2
star
52

ashley-gram

JavaScript
2
star
53

Photonic

A tile-based sandbox game where you try and light up the world (work in progress)
JavaScript
2
star
54

fallingmountain.github.io

HTML
2
star
55

safeQuerySelector

A typescript querySelector that can be used for static client side apps to get elements via querySelector, that you know exist. (Without strict null checks).
TypeScript
2
star
56

villageSandbox

A game about steampunk robots on prehistoric islands. Don't ask. No official title.
JavaScript
2
star
57

Rangz

JavaScript
2
star
58

socialsimulator

A simulation of the social interactions between people.
JavaScript
2
star
59

pathfindingai

A small game based off pathfinding AI and it's numerous uses.
JavaScript
2
star
60

potatoz3

If you want to finish it.
JavaScript
2
star
61

ross-gram

JavaScript
1
star
62

NEAT

An implementation of NEAT. More work on API to be done soon.
JavaScript
1
star
63

cody-void

JavaScript
1
star
64

server-test

HTML
1
star
65

lucasGram

JavaScript
1
star
66

NeuralNets

My fledgling attempts at neural networks. Currently contains: Feedforward Neural Nets (No Backpropagation Yet)
JavaScript
1
star
67

mini-galaga

JavaScript
1
star
68

clock

A basic clock drawn on the canvas.
JavaScript
1
star
69

architectai

A genetic algorithm evolves taller and taller structures.
JavaScript
1
star
70

finnigansWakeGen

1
star
71

potatoz

A fast-paced clicker game about turning the world into potatoz.
JavaScript
1
star
72

genAbstractArt

A small abstract art generator using perlin noise.
JavaScript
1
star
73

voidSwarmer

JavaScript
1
star
74

heightmap13k

Getting prepped.
GLSL
1
star
75

statica.js

Static types in Javascript
1
star
76

dae-gram

JavaScript
1
star
77

SYNTH-8

An open-source voice-enabled chatbot. Many features will come soon.
JavaScript
1
star
78

cache.js

Cached functions.
JavaScript
1
star
79

record

Immutable objects with WITH functions in JS.
JavaScript
1
star
80

fakecats

Cats generated by autoencoder. Not amazing, not horrible.
1
star
81

pixl

A DSL for creating pixel art on the web.
JavaScript
1
star
82

maskBlur

For a drei thing
JavaScript
1
star
83

wordvectors

HTML
1
star
84

hadley-gram

JavaScript
1
star
85

blog

HTML
1
star
86

JSObject

A completely dynamic Map in C#, able to store all types except functions.
C#
1
star
87

fmait

An asynchronous, concurrent library for creating efficient, easy-to-understand promise pipelines.
JavaScript
1
star
88

menorah-man

JavaScript
1
star
89

overloadPlus

True plus operator overloading in JS.
JavaScript
1
star
90

goo-sim

A goo simulator - fun to spend hours moving the goo around
JavaScript
1
star
91

mission

HTML
1
star
92

irises

I used tensorflow.js to train a simple feedforward neural net to recognize irises based of a list of features. A classic machine learning "hello world".
JavaScript
1
star
93

warriorsdontcry

JavaScript
1
star
94

cookie-test

HTML
1
star
95

3dwebsite

A three dimensional portfolio. It looks pretty good.
JavaScript
1
star
96

mom-gram

1
star
97

mnistClassifier

A quick classifier made with tensorflow.js & the mnist dataset.
JavaScript
1
star
98

sir-model

JavaScript
1
star
99

dualDuel

New first-person melee combat game I am working on.
JavaScript
1
star
100

david-gram

JavaScript
1
star