feat: add screen-space ambient occlusion (SSAO)#993
Conversation
|
@hubbardp this is very exciting! Looks great from a quick look so far |
| // World→UV scale: wClip is -P.z under perspective and 1 under ortho. | ||
| float wClip = uProjection[2][3] * P.z + uProjection[3][3]; | ||
| float screenRadius = uRadius * uProjection[1][1] / (2.0 * wClip); | ||
| screenRadius = min(screenRadius, MAX_KERNEL_FRACTION); |
There was a problem hiding this comment.
In the link I posted, I can't see any visible difference when changing the radius value, I'm wondering if the value is always larger than MAX_KERNEL_FRACTION
There was a problem hiding this comment.
Thanks for the observation. I checked in what I think is an improvement; see more with your later comment, below.
|
For those who want to try the new functionality without building the code, it's running here: |
|
@hubbardp neuroglancer github actions are set up to create a deployment for every PR, available if you click "view details", it's definitely not obvious: |
|
What is the reason for disabling the effect on highlighted segments? It makes the highlighting rather jarring, though I haven't tested with the alternative behavior. |
I tried not disabling the SSAO darkening on highlighted segments, and they were too dark to see when highlighted. Personally, I like the brighter highlighting. I found that before SSAO, sometimes the randomly-chosen segment color was bright enough that the highlighting seemed barely distinguishable. |
Thanks for the test case. I pushed a fix for the anisotropy. I think it looks better from all view angles.
I have not yet updated http://neuroglancer-ssao.janelia.org/ so use the version from the GitHub action. |
|
awesome thank you agreed looks better on my localhost version! |
seankmartin
left a comment
There was a problem hiding this comment.
Thanks for the changes! My comments are more related to the integration in neuroglancer, as I'll admit that I haven't dug into the SSAO implementation compared to the paper. Are there any known simplifications or deviations taken here over the paper description?
|
|
||
| SSAO is limited to mesh surfaces because only `MeshLayer` and | ||
| `MultiscaleMeshLayer` supply a view-space normal via the three-argument | ||
| `emit(color, pickId, viewNormal)` form. All other opaque geometry (skeletons, |
There was a problem hiding this comment.
I wouldn't see it as blocking, but in particular for the single meshes, I think it could be useful to expand here why they are not supported. Since they have normaIs, I imagine it's because the overhead in making the shader in the single mesh layer shader emitter aware and some duplication of the normal handling basically mimicking the changes made in mesh/frontend.ts
There was a problem hiding this comment.
Thanks for the suggestion. I checked in a revised README that expands on the reasons for omitting annotations, skeletons and single-mesh layers. For the latter, I could add support with some refactoring, so my preference would be to leave it for future work. Let me know what you think.
| // Referenced by glsl_perspectivePanelEmitWithNormals; declared here so any | ||
| // shader using this emitter (mesh, annotation, etc.) gets it without having | ||
| // to know the emit body's internal dependencies. | ||
| builder.addUniform("highp float", "uHighlighted"); |
There was a problem hiding this comment.
I think the use of a uniform for highlighted to disable AO in the shader will up being a bit limited in scope. This assumes that in a single draw everything is either highlighted or unhighlighted, which is a bit limiting.
Perhaps this could move to the responsibility of the mesh layer if this functionality is needed. So meshes zero-out their view normal to the no AO sentinel version. And then the perspective shaders don't have to worry about this anymore.
If other layers want to disable AO dynamically, they can use the same pattern
There was a problem hiding this comment.
Thanks for the suggestion. I checked in code that implements what I think is your idea.
| export const glsl_perspectivePanelEmitWithNormals = ` | ||
| void emit(vec4 color, highp uint pickId, vec3 viewNormal) { | ||
| out_color = color; | ||
| float zValue = 1.0 - gl_FragCoord.z; | ||
| out_z = vec4(zValue, zValue, zValue, 1.0); | ||
| float pickIdFloat = float(pickId); | ||
| out_pickId = vec4(pickIdFloat, pickIdFloat, pickIdFloat, 1.0); | ||
| // Highlighted objects collapse to the zero sentinel so SSAO leaves them alone. | ||
| vec3 packedNormal = (1.0 - uHighlighted) * (normalize(viewNormal) * 0.5 + 0.5); | ||
| out_normal = vec4(packedNormal, 1.0); | ||
| } | ||
| void emit(vec4 color, highp uint pickId) { | ||
| out_color = color; | ||
| float zValue = 1.0 - gl_FragCoord.z; | ||
| out_z = vec4(zValue, zValue, zValue, 1.0); | ||
| float pickIdFloat = float(pickId); | ||
| out_pickId = vec4(pickIdFloat, pickIdFloat, pickIdFloat, 1.0); | ||
| // Zero-RGB sentinel; alpha=1 so the source overwrites dst under | ||
| // blend(SRC_ALPHA, ONE_MINUS_SRC_ALPHA). | ||
| out_normal = vec4(0.0, 0.0, 0.0, 1.0); |
There was a problem hiding this comment.
I think it would be nice if we could reduce the duplication in the bodies here so changes propagate if ever needed and to clarify what's changing between emitters. Maybe something like
const glslEmitBase = `
out_color = color;
float zValue = 1.0 - gl_FragCoord.z;
out_z = vec4(zValue, zValue, zValue, 1.0);
float pickIdFloat = float(pickId);
out_pickId = vec4(pickIdFloat, pickIdFloat, pickIdFloat, 1.0);`;
and then it could be used in both the 2-arg and 3-arg emits, and also be replaced in glsl_perspectivePanelEmit. Don't feel strongly on the name of the var glslEmitBase
There was a problem hiding this comment.
Thanks, I checked in this idea for factoring out shared code.
| // Mixed mesh + volume scenes are not yet supported; the opaque pass and | ||
| // NORMAL writes already happened from the with-normals emitter (minor | ||
| // waste, not a correctness issue). Notify once per panel lifetime via | ||
| // both the console and a dismissable status banner. |
There was a problem hiding this comment.
I'm unsure why volume rendering disables SSAO. SSAO happens in opaque rendering only, so I don't see how the later transparent rendering is interfered with. Could be missing something of course
There was a problem hiding this comment.
I did another test of the code modified to allow SSAO with volume rendering, and the result is not right. The problem is that SSAO involves a final compositing step that multiplies the existing color buffer by the AO darkening. By the time that step runs, the volume has been blended into the color buffer, so AO darkens the volume contribution, too, and not just the mesh underneath. The part of the volume in front of the mesh should hide those AO details, but instead it gets darkened along with them. It should be possible to restructure the pipeline so the volume rendering is added after the SSAO compositng, but that would add to the scale of the code changes. Let me know if you think it is worth doing.
| ); | ||
| const radius = ssaoRadius * this.navigationState.zoomFactor.value; | ||
|
|
||
| this.ssaoManager.render( |
There was a problem hiding this comment.
Just wanted to note that as the complexity of the panel.ts file has increased, moving part of the rendering responsibility out of this file, as done here, seems reasonable and could be a good idea for a later refactoring of the volume rendering path
| // NORMAL writes already happened from the with-normals emitter (minor | ||
| // waste, not a correctness issue). Notify once per panel lifetime via | ||
| // both the console and a dismissable status banner. | ||
| if (ssaoRequested && hasVolumeRendering && !this.ssaoVolumeWarned) { |
There was a problem hiding this comment.
Related to https://github.com/google/neuroglancer/pull/993/changes#r3267587804, if we could leave AO on with volume rendering - maybe we could instead enable this warning message if you have an AO supported mesh layer but with transparent rendering for that layer?
| @@ -0,0 +1,292 @@ | |||
| /** | |||
There was a problem hiding this comment.
As a general comment here, I could be missing it but I don't see a test for a case where ao is between 0 and 1, or in other words a non 1x1 size texture example to check sampling from nearby neighbours and having ao < 1.0 if the centre pixel is being occluded by neighbour pixels. Would that be possible to add?
| SSAO_RADIUS_RANGE.max, | ||
| Math.max(SSAO_RADIUS_RANGE.min, this.viewer.ssaoRadius.value), | ||
| ); | ||
| const radius = ssaoRadius * this.navigationState.zoomFactor.value; |
There was a problem hiding this comment.
removing this operation causes the radius slider to have an effect on my device. It does mean it now becomes softer as you zoom out.
There was a problem hiding this comment.
The old code scaled the radius incorrectly, so the clamping at MAX_KERNEL_FRACTION was happening too often. I checked in new code that clarifies how the slider value affects the sampling kernel and the falloff factor. I think it looks reasonable when zooming out, and lets the slider add some variation. The new default of 2 for the slider works well in every case I tried. I would not mind removing the slider altogether if it is confusing, but it also seems okay to leave it in for completeness.
|
One other thing that might be nice is if we add some kind of While the regular result with color contribution is: Could help for any later fine tuning, seems like there might be a bit of noise showing through in certain cases for e.g. |








Summary
Screen-space ambient occlusion (SSAO) simulates shadows on 3D mesh surfaces by darkening crevices and concavities where ambient light would be occluded. It adds depth cues that help us perceive shapes, and it makes the display more appealing. SSAO is an efficient post-processing effect applied to the perspective view after opaque geometry is drawn. See
src/ssao/README.mdfor more details and example images.Screenshots
Usage
qto toggle SSAO on and off.Known limitations
Performance
Algorithm
GTAO (Ground Truth Ambient Occlusion): Jimenez et al., "Realtime Strategies for Accurate Indirect Occlusion", SIGGRAPH 2016
Testing
src/ssao/shaders.browser_test.tscover composite math, GTAO and composite sentinel paths, blurring pass.