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

Distance-based Gaussian weighting for Ambient Occlusion #12316

Open
wants to merge 18 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 9 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
31 changes: 14 additions & 17 deletions packages/engine/Source/Scene/PostProcessStage.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,16 @@ import PostProcessStageSampleMode from "./PostProcessStageSampleMode.js";
function PostProcessStage(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
const {
name = createGuid(),
fragmentShader,
uniforms,
textureScale = 1.0,
forcePowerOfTwo = false,
sampleMode = PostProcessStageSampleMode.NEAREST,
Comment on lines +101 to +102
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method of setting default values is not equivalent to the previous defaultValue calls when the value is null. I just want to make sure that's intended
2024-11-27_16-12

pixelFormat = PixelFormat.RGBA,
pixelDatatype = PixelDatatype.UNSIGNED_BYTE,
clearColor = Color.BLACK,
scissorRectangle,
} = options;

//>>includeStart('debug', pragmas.debug);
Expand All @@ -113,19 +120,13 @@ function PostProcessStage(options) {
//>>includeEnd('debug');

this._fragmentShader = fragmentShader;
this._uniforms = options.uniforms;
this._uniforms = uniforms;
this._textureScale = textureScale;
this._forcePowerOfTwo = defaultValue(options.forcePowerOfTwo, false);
this._sampleMode = defaultValue(
options.sampleMode,
PostProcessStageSampleMode.NEAREST,
);
this._forcePowerOfTwo = forcePowerOfTwo;
this._sampleMode = sampleMode;
this._pixelFormat = pixelFormat;
this._pixelDatatype = defaultValue(
options.pixelDatatype,
PixelDatatype.UNSIGNED_BYTE,
);
this._clearColor = defaultValue(options.clearColor, Color.BLACK);
this._pixelDatatype = pixelDatatype;
this._clearColor = clearColor;

this._uniformMap = undefined;
this._command = undefined;
Expand All @@ -143,18 +144,14 @@ function PostProcessStage(options) {
const passState = new PassState();
passState.scissorTest = {
enabled: true,
rectangle: defined(options.scissorRectangle)
? BoundingRectangle.clone(options.scissorRectangle)
rectangle: defined(scissorRectangle)
? BoundingRectangle.clone(scissorRectangle)
: new BoundingRectangle(),
};
this._passState = passState;

this._ready = false;

let name = options.name;
if (!defined(name)) {
name = createGuid();
}
this._name = name;

this._logDepthChanged = undefined;
Expand Down
18 changes: 18 additions & 0 deletions packages/engine/Source/Scene/PostProcessStageLibrary.js
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,8 @@ PostProcessStageLibrary.createAmbientOcclusionStage = function () {
intensity: 3.0,
bias: 0.1,
lengthCap: 0.26,
directionCount: 16,
stepCount: 64,
stepSize: 1.95,
frustumLength: 1000.0,
randomTexture: undefined,
Expand Down Expand Up @@ -556,6 +558,22 @@ PostProcessStageLibrary.createAmbientOcclusionStage = function () {
generate.uniforms.lengthCap = value;
},
},
directionCount: {
get: function () {
return generate.uniforms.directionCount;
},
set: function (value) {
generate.uniforms.directionCount = value;
},
},
stepCount: {
get: function () {
return generate.uniforms.stepCount;
},
set: function (value) {
generate.uniforms.stepCount = value;
},
},
stepSize: {
get: function () {
return generate.uniforms.stepSize;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ uniform float bias;
uniform float lengthCap;
uniform float stepSize;
uniform float frustumLength;
uniform int stepCount;
uniform int directionCount;

vec3 pixelToEye(vec2 screenCoordinate)
{
Expand Down Expand Up @@ -40,6 +42,13 @@ vec3 getNormalXEdge(vec3 positionEC)
return normalize(cross(dx, dy));
}

const float sqrtTwoPi = sqrt(czm_twoPi);

float gaussian(float x, float standardDeviation) {
float argument = x / standardDeviation;
return exp(-0.5 * argument * argument) / (sqrtTwoPi * standardDeviation);
}

void main(void)
{
vec3 positionEC = pixelToEye(gl_FragCoord.xy);
Expand All @@ -51,11 +60,11 @@ void main(void)
}

vec3 normalEC = getNormalXEdge(positionEC);
float gaussianVariance = lengthCap * sqrt(-positionEC.z);
// TODO: mix of units. Steps are in pixels; variance is in meters.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does czm_metersPerPixel help at all here?

//float stepLength = max(1.0, 3.0 * gaussianVariance / (float(stepCount) + 1.0));

float ao = 0.0;

const int ANGLE_STEPS = 4;
float angleStepScale = 1.0 / float(ANGLE_STEPS);
float angleStepScale = 1.0 / float(directionCount);
float angleStep = angleStepScale * czm_twoPi;
float cosStep = cos(angleStep);
float sinStep = sin(angleStep);
Expand All @@ -67,16 +76,35 @@ void main(void)
float randomVal = texture(randomTexture, randomTexCoord).x;
vec2 sampleDirection = vec2(cos(angleStep * randomVal), sin(angleStep * randomVal));

float ao = 0.0;
// Loop over sampling directions
for (int i = 0; i < ANGLE_STEPS; i++)
#if __VERSION__ == 300
for (int i = 0; i < directionCount; i++)
{
#else
for (int i = 0; i < 64; i++)
{
if (i >= directionCount) {
break;
}
#endif
sampleDirection = rotateStep * sampleDirection;

float localAO = 0.0;
float accumulatedWindowWeights = 0.0;
//vec2 radialStep = stepLength * sampleDirection;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
//vec2 radialStep = stepLength * sampleDirection;

vec2 radialStep = stepSize * sampleDirection;

for (int j = 0; j < 6; j++)
#if __VERSION__ == 300
for (int j = 0; j < stepCount; j++)
{
#else
for (int j = 0; j < 128; j++)
{
if (j >= stepCount) {
break;
}
#endif
// Step along sampling direction, away from output pixel
vec2 newCoords = floor(gl_FragCoord.xy + float(j + 1) * radialStep) + vec2(0.5);

Expand All @@ -86,26 +114,28 @@ void main(void)
break;
}

// Compute step vector from output point to sampled point
vec3 stepPositionEC = pixelToEye(newCoords);
vec3 stepVector = stepPositionEC - positionEC;
float stepLength = length(stepVector);

if (stepLength > lengthCap)
{
break;
}

// Estimate the angle from the surface normal.
float dotVal = clamp(dot(normalEC, normalize(stepVector)), 0.0, 1.0);
if (dotVal < bias)
{
dotVal = 0.0;
}

float weight = stepLength / lengthCap;
weight = 1.0 - weight * weight;
localAO = max(localAO, dotVal * weight);
// Weight contribution based on the distance from the output point
float sampleDistance = length(stepVector);
float weight = gaussian(sampleDistance, gaussianVariance);
localAO += weight * dotVal;

// Compute lateral distance from output point, for weight normalization
// TODO: This is slow! Better to analytically compute window scales
float lateralDistance = length(stepPositionEC.xy - positionEC.xy);
accumulatedWindowWeights += gaussian(lateralDistance, gaussianVariance);
}
ao += localAO;
ao += localAO / accumulatedWindowWeights;
}

ao *= angleStepScale;
Expand Down