-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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
RenderPassSsao: improve SSAO blur performance #6684
Conversation
… big pass (2 gaussian for high frequent signal, 2 interleaved for low frequency)
Hi @querielo - that's definitely a good way to speed it up. But I wonder why doing 4 passes instead of just typical two separable passes? |
Hi. @mvaligursky The main idea is that the first two passes weaken the high-frequency signal. The next two passes are used to eliminate low-frequency signal (there are strided/interleaved blurs with a large step, applied diagonally). Experimentally, it seems that increasing the filter kernel size is necessary to remove low-frequency SSAO patterns. For example, using a 17x17 kernel on my computer results in a noticeable slowdown, but it appears to achieve the effect of four passes. The suggested four-pass approach is just one way to implement blurring. An advanced developer could add
By the low-frequency signal, I mean the pattern you can see in the next image (kernelSize=11) |
I had a bit of a play with your branch. My findings:
I think the main reason you need 4 passes is the use of the GAUSSIAN weights instead of BOX. |
And so my recommendation is: switch it to 2 pass BOX filtering. We can expose the number of taps to the user as that controls the quality vs the cost. |
@mvaligursky I switched the blur to 2 pass BOX filtering. |
Offtop: It looks like 6f29e1b breaks the background. |
@@ -308,6 +351,8 @@ class RenderPassSsao extends RenderPassShaderQuad { | |||
} | |||
|
|||
createRenderTarget(name) { | |||
// TODO: consider using a pool of 2 texture buffers |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
considering we're down to 2 blurs, remove the comment
* @param {Vec2} [options.direction] - The direction of the blur. Defaults to (1, 0). | ||
* @param {string} [options.channels] - The color channels to apply the blur to ('r'|'g'|'b'|'a'|'rg'|..|'ba'|'rgb'|'gba'|'rgba'). Defaults to 'rgba'. | ||
*/ | ||
init(renderTarget = null, options = {}) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would not override the init
function, but instead add a setup
function which does all this.
totalWeight += ${weightCoefs[middle].toFixed(4)};`; | ||
|
||
// TODO: move calculating UV coordinates to the vertex shader and pass them as varying | ||
for (let i = 1; i <= kernelWidth; i++) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we're trying to move away from generating shader from javascript as much as possible, as it's a lot harder to understand and modify. We try to create a single shader string as much as possible, and then in code generate a list of defines to pre-pend. See RenderPassCompose
as an example. It'd be great to modify this in a similar fashion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
even though the shader here is trying to be pretty generic, which makes it harder (with the types and channels)
Looking much better, thanks! I'd suggest to remove the option / API to change the blur type. Box looks better than Gaussian, and so the SSAO should just use Box. Only expose values that the user would benefit from adjusting. |
uniform vec2 sourceInvResolution; | ||
uniform int filterSize; | ||
uniform vec2 direction; | ||
uniform float kernel[KERNEL_SIZE]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hide the kernel
behind #ifdef KERNEL
to avoid the cost when the box filter is used
float diff = (sampleDepth - depth); | ||
return max(0.0, 1.0 - diff * diff); | ||
} | ||
this.sourceInvResolutionId?.setValue(sourceInvResolutionValueTmp); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no need for those ?
there as those are transpiled to if
.. we always set those up, so they're never undefined.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
VS Code TS checker highlighted this error, suggesting that sourceInvResolutionId could be undefined as it is not defined during creation. I'll check if Playcanvas linter highlights it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yep, linter highlights it, we have lots of these warnings in the engine, but they're not correct, but we don't see a way to remove them.
super.execute(); | ||
const defines = `#define KERNEL_SIZE ${this.kernelSize}\n`; | ||
|
||
// CHECK: should we destroy the shader? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shaders are expensive to compile, so we don't destroy them, to make it faster when the shader is needed again
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then I will remove the comment.
// simple dithering helps a lot (assumes 8 bits target) | ||
// this is most useful with high quality/large blurs | ||
// ao += ((random(gl_FragCoord.xy) - 0.5) / 255.0); | ||
this.updateShader(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ideally don't call updateShader directly, just set a _shaderDirty flag - this makes it easy to add more properties that modify the shader, and also better handle the case where the property is changed multi times per frame. (not that this is typical, but happens).
@mvaligursky Are you certain? ssao_blur.mov |
yeah interesting. It almost feel like the the bilateral weight function should be improved
to detect the depth discontinuity better, as currently it blurs over those small edges. Maybe we can try something like this to have a control over it by the sigma value (untested, would need more research / testing)
but agreed, the Gaussian blur in general should be slightly better, at a higher cost, so maybe leave it in. |
Hi @querielo - have you had some time to look at these? |
yep, it's on my list to get to in a week or so. Definitely not forgotten. |
@mvaligursky Yes, you can take it. Sorry about that. I'm too busy right now and don't have time to fix it. |
This PR suggests splitting one large blur pass from "RenderPass based SSAO" into four smaller blur passes without compromising quality.
Tested on MacBook 14" Pro (2021), used
device.maxPixelRatio = window.devicePixelRatio;
to increase resolution of framebuffers.I confirm I have read the contributing guidelines and signed the Contributor License Agreement.