Skip to content

Commit

Permalink
release 1.9
Browse files Browse the repository at this point in the history
  • Loading branch information
N8 committed Sep 7, 2024
1 parent b660221 commit c36cae4
Show file tree
Hide file tree
Showing 18 changed files with 556 additions and 322 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
node_modules
.parcel-cache
example_webgpu
.vscode
.idea
*.DS_Store
19 changes: 18 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ n8aopass.configuration.accumulate = true;
For the best results, `denoiseRadius` should be set to 0 and `denoiseSamples` should be set to 1. This will ensure that the AO effect is not blurred, and that the accumulation is purely temporal.

The accumulation effect works best in scenes with no motion - as it does not calculate motion vectors and will only work with the camera still. If the camera is moving, the accumulation effect will be disabled automatically. But if an object is moving, N8AO won't be able to pick up on that and the object's ambient occlusion will become blurred.

# Stencil

N8AOPass supports stencil buffers, but you must enable them via `configuration.stencil`:
Expand Down Expand Up @@ -290,12 +291,28 @@ const n8aopass = new N8AOPostPass(
composer.addPass(n8aopass);
```

# Bias Adjustment

The bias calculated by N8AO can sometimes be wrong and produce artifacts - control it with `configuration.biasOffset` and `configuration.biasMultiplier`.

In-shader, the bias will be calculated as `bias = biasOffset + biasMultiplier * bias`, where `bias` is calculated using heuristic.

Only adjust these values if you are seeing artifacts in your AO effect and you know what you are doing.

# AO Tones

The property `configuration.aoTones` controls the number of tones the AO effect can have. Can be used for toon shading or bevel effects - default is `0`, which means the AO effect is continuous. Any value above `0` will cause the AO effect to be quantized into that many tones.

# 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.
`N8AOPass` is compatible with all modern browsers that support WebGL 2.0 (WebGL 1 is not supported), but using three.js version r161 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.

# WebGPU

N8AO is not yet compatible with WebGPU, but will be in the future - 2.0 perhaps. Stay tuned :D.

# 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.
Expand Down
207 changes: 128 additions & 79 deletions dist/N8AO.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/N8AO.js.map

Large diffs are not rendered by default.

207 changes: 128 additions & 79 deletions example/N8AO.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion example/N8AO.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion example/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

<body>
<script type="importmap">
{ "imports": { "three": "https://cdn.jsdelivr.net/npm/three@0.167.0/build/three.module.js", "three/examples/": "https://cdn.jsdelivr.net/npm/three@0.167.0/examples/", "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.167.0/examples/jsm/", "postprocessing":
{ "imports": { "three": "https://cdn.jsdelivr.net/npm/three@0.168.0/build/three.module.js", "three/examples/": "https://cdn.jsdelivr.net/npm/three@0.168.0/examples/", "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.168.0/examples/jsm/", "postprocessing":
"https://cdn.jsdelivr.net/npm/[email protected]/build/index.js"} }
</script>
<p style="position:absolute;top:100%;transform:translate(4px, -200%);color:white;z-index:100000;background-color: black;">Elapsed Time for AO (<span id="aoMetadata"></span>): <span id="aoTime">0</span>ms </p>
Expand Down
14 changes: 10 additions & 4 deletions example/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,11 @@ async function main() {
scene.add(sponza);
const effectController = {
aoSamples: 16.0,
aoRadius: 5.0,
aoTones: 0.0,
denoiseSamples: 8.0,
denoiseRadius: 12.0,
aoRadius: 5.0,
denoiseIterations: 2.0,
distanceFalloff: 1.0,
screenSpaceRadius: false,
halfRes: false,
Expand All @@ -110,13 +112,15 @@ async function main() {
color: [0, 0, 0],
colorMultiply: true,
stencil: true,
accumulate: false
accumulate: false,
};
const gui = new GUI();
gui.add(effectController, "aoSamples", 1.0, 64.0, 1.0);
const aor = gui.add(effectController, "aoRadius", 1.0, 10.0, 0.01);
gui.add(effectController, "aoTones", 0.0, 8.0, 1.0);
gui.add(effectController, "denoiseSamples", 1.0, 64.0, 1.0);
gui.add(effectController, "denoiseRadius", 0.0, 24.0, 0.01);
const aor = gui.add(effectController, "aoRadius", 1.0, 10.0, 0.01);
gui.add(effectController, "denoiseIterations", 1.0, 10.0, 1.0);
const df = gui.add(effectController, "distanceFalloff", 0.0, 10.0, 0.01);
gui.add(effectController, "screenSpaceRadius").onChange((value) => {
if (value) {
Expand Down Expand Up @@ -200,12 +204,14 @@ async function main() {
lightPos4d
);
n8aopass.configuration.aoRadius = effectController.aoRadius;
n8aopass.configuration.aoSamples = effectController.aoSamples;
n8aopass.configuration.aoTones = effectController.aoTones;
n8aopass.configuration.distanceFalloff = effectController.distanceFalloff;
n8aopass.configuration.transparencyAware = effectController.transparencyAware;
n8aopass.configuration.intensity = effectController.intensity;
n8aopass.configuration.aoSamples = effectController.aoSamples;
n8aopass.configuration.denoiseRadius = effectController.denoiseRadius;
n8aopass.configuration.denoiseSamples = effectController.denoiseSamples;
n8aopass.configuration.denoiseIterations = effectController.denoiseIterations;
n8aopass.configuration.stencil = effectController.stencil;
n8aopass.configuration.renderMode = ["Combined", "AO", "No AO", "Split", "Split AO"].indexOf(effectController.renderMode);
n8aopass.configuration.color = new THREE.Color(effectController.color[0], effectController.color[1], effectController.color[2]);
Expand Down
207 changes: 128 additions & 79 deletions example_postprocessing/N8AO.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion example_postprocessing/N8AO.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion example_postprocessing/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

<body>
<script type="importmap">
{ "imports": { "three": "https://cdn.jsdelivr.net/npm/three@0.167.0/build/three.module.js", "three/examples/": "https://cdn.jsdelivr.net/npm/three@0.167.0/examples/", "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.167.0/examples/jsm/", "postprocessing":
{ "imports": { "three": "https://cdn.jsdelivr.net/npm/three@0.168.0/build/three.module.js", "three/examples/": "https://cdn.jsdelivr.net/npm/three@0.168.0/examples/", "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.168.0/examples/jsm/", "postprocessing":
"https://cdn.jsdelivr.net/npm/[email protected]/build/index.js"} }
</script>
<p style="position:absolute;top:100%;transform:translate(4px, -200%);color:white;z-index:100000;background-color: black;">Elapsed Time for AO (<span id="aoMetadata"></span>): <span id="aoTime">0</span>ms </p>
Expand Down
12 changes: 9 additions & 3 deletions example_postprocessing/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,11 @@ async function main() {
scene.add(sponza);
const effectController = {
aoSamples: 16.0,
aoRadius: 5.0,
aoTones: 0.0,
denoiseSamples: 8.0,
denoiseRadius: 12.0,
aoRadius: 5.0,
denoiseIterations: 2.0,
distanceFalloff: 1.0,
screenSpaceRadius: false,
halfRes: false,
Expand All @@ -110,10 +112,12 @@ async function main() {
accumulate: false
};
const gui = new GUI();
const aor = gui.add(effectController, "aoRadius", 1.0, 10.0, 0.01);
gui.add(effectController, "aoTones", 0.0, 8.0, 1.0);
gui.add(effectController, "aoSamples", 1.0, 64.0, 1.0);
gui.add(effectController, "denoiseSamples", 1.0, 64.0, 1.0);
gui.add(effectController, "denoiseRadius", 0.0, 24.0, 0.01);
const aor = gui.add(effectController, "aoRadius", 1.0, 10.0, 0.01);
gui.add(effectController, "denoiseIterations", 1.0, 10.0, 1.0);
const df = gui.add(effectController, "distanceFalloff", 0.0, 10.0, 0.01);
gui.add(effectController, "screenSpaceRadius").onChange((value) => {
if (value) {
Expand Down Expand Up @@ -217,11 +221,13 @@ async function main() {
lightPos4d
);
n8aopass.configuration.aoRadius = effectController.aoRadius;
n8aopass.configuration.aoSamples = effectController.aoSamples;
n8aopass.configuration.aoTones = effectController.aoTones;
n8aopass.configuration.distanceFalloff = effectController.distanceFalloff;
n8aopass.configuration.intensity = effectController.intensity;
n8aopass.configuration.aoSamples = effectController.aoSamples;
n8aopass.configuration.denoiseRadius = effectController.denoiseRadius;
n8aopass.configuration.denoiseSamples = effectController.denoiseSamples;
n8aopass.configuration.denoiseIterations = effectController.denoiseIterations;
n8aopass.configuration.renderMode = ["Combined", "AO", "No AO", "Split", "Split AO"].indexOf(effectController.renderMode);
n8aopass.configuration.color = new THREE.Color(effectController.color[0], effectController.color[1], effectController.color[2]);
n8aopass.configuration.screenSpaceRadius = effectController.screenSpaceRadius;
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
{
"name": "n8ao",
"version": "1.8.4",
"version": "1.9.0",
"description": "An efficient and visually pleasing implementation of SSAO with an emphasis on temporal stability and artist control.",
"main": "dist/N8AO.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "rm -rf .parcel-cache && parcel build src/N8AOPass.js && cp dist/N8AO.js example/N8AO.js && cp dist/N8AO.js.map example/N8AO.js.map && cp dist/N8AO.js example_postprocessing/N8AO.js && cp dist/N8AO.js.map example_postprocessing/N8AO.js.map",
"build": "rm -rf .parcel-cache && parcel build src/N8AOPass.js && cp dist/N8AO.js example/N8AO.js && cp dist/N8AO.js.map example/N8AO.js.map && cp dist/N8AO.js example_postprocessing/N8AO.js && cp dist/N8AO.js.map example_postprocessing/N8AO.js.map && cp dist/N8AO.js example_webgpu/N8AO.js && cp dist/N8AO.js.map example_webgpu/N8AO.js.map",
"dev": "nodemon --watch src --ext .js --exec 'npm run build' & nodemon dev-server.js"
},
"repository": {
Expand Down
7 changes: 6 additions & 1 deletion src/EffectCompositer.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ const EffectCompositer = {
'fogDensity': { value: 0.0 },
'fogNear': { value: Infinity },
'fogFar': { value: Infinity },
'colorMultiply': { value: true }
'colorMultiply': { value: true },
'aoTones': { value: 0.0 }

},
depthWrite: false,
Expand Down Expand Up @@ -63,6 +64,7 @@ const EffectCompositer = {
uniform float renderMode;
uniform float near;
uniform float far;
uniform float aoTones;
uniform bool gammaCorrection;
uniform bool logDepth;
uniform bool ortho;
Expand Down Expand Up @@ -214,6 +216,9 @@ const EffectCompositer = {
#endif
float finalAo = pow(texel.r, intensity);
if (aoTones > 0.0) {
finalAo = ceil(finalAo * aoTones) / aoTones;
}
float fogFactor;
float fogDepth = distance(
cameraPos,
Expand Down
71 changes: 42 additions & 29 deletions src/EffectShader.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const EffectShader = {
'viewMatrixInv': { value: new THREE.Matrix4() },
'cameraPos': { value: new THREE.Vector3() },
'resolution': { value: new THREE.Vector2() },
'biasAdjustment': { value: new THREE.Vector2() },
'time': { value: 0.0 },
'samples': { value: [] },
'bluenoise': { value: null },
Expand Down Expand Up @@ -47,6 +48,7 @@ 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;
Expand All @@ -71,11 +73,18 @@ uniform sampler2D bluenoise;
float a = farZ / (farZ - nearZ);
float b = farZ * nearZ / (nearZ - farZ);
float linDepth = a + b / depth;
return ortho ? linearize_depth_ortho(
/*return ortho ? linearize_depth_ortho(
linDepth,
nearZ,
farZ
) :linearize_depth(linDepth, nearZ, farZ);
) :linearize_depth(linDepth, nearZ, farZ);*/
#ifdef ORTHO
return linearize_depth_ortho(linDepth, nearZ, farZ);
#else
return linearize_depth(linDepth, nearZ, farZ);
#endif
}
vec3 getWorldPosLog(vec3 posS) {
Expand Down Expand Up @@ -165,10 +174,10 @@ void main() {
}
vec3 tangent = normalize(cross(helperVec, normal));
vec3 bitangent = cross(normal, tangent);
mat3 tbn = mat3(tangent, bitangent, normal) * makeRotationZ(noise.r * 2.0 * 3.1415962) ;
mediump mat3 tbn = mat3(tangent, bitangent, normal) * makeRotationZ( noise.r * 3.1415962 * 2.0) ;
float occluded = 0.0;
float totalWeight = 0.0;
mediump float occluded = 0.0;
mediump float totalWeight = 0.0;
float radiusToUse = screenSpaceRadius ? distance(
worldPos,
getWorldPos(depth, vUv +
Expand All @@ -181,46 +190,50 @@ void main() {
0.1,
distanceFalloffToUse * 0.1
) / near) * fwidth(distance(worldPos, cameraPos)) / radiusToUse;
float phi = 1.61803398875;
float offsetMove = 0.0;
float offsetMoveInv = 1.0 / FSAMPLES;
for(float i = 0.0; i < FSAMPLES; i++) {
vec3 sampleDirection = tbn * samples[int(i)];
float moveAmt = fract(noise.g + offsetMove);
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.xyz = offset.xyz * 0.5 + 0.5;
vec2 diff = gl_FragCoord.xy - floor(offset.xy * resolution);
// From Rabbid76's hbao
vec2 clipRangeCheck = step(vec2(0.0),offset.xy) * step(offset.xy, vec2(1.0));
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 = ortho ? linearize_depth_ortho(sampleDepth, near, far) : linearize_depth(sampleDepth, near, far);
float distSample = (farTimesNear) / (far - sampleDepth * farMinusNear);
#endif
float distWorld = ortho ? linearize_depth_ortho(offset.z, near, far) : linearize_depth(offset.z, near, far);
float rangeCheck = distSample == distWorld ? 0.0 : smoothstep(0.0, 1.0, distanceFalloffToUse / (abs(distSample - distWorld)));
#endif
#ifdef ORTHO
float distWorld = near + farMinusNear * sampleDepth;
#else
float distWorld = (farTimesNear) / (far - offset.z * farMinusNear);
#endif
float sampleValid = (clipRangeCheck.x * clipRangeCheck.y);
occluded += rangeCheck * float(sampleDepth != depth) * float(distSample + bias < distWorld) * step(
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)
) * sampleValid;
);
totalWeight += sampleValid;
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);
Expand Down
Loading

0 comments on commit c36cae4

Please sign in to comment.