-
-
Notifications
You must be signed in to change notification settings - Fork 36k
SSGINode: New node for screen-space global illumination. #31839
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
Conversation
Awesome! Looking good! Since it’s using temporal accumulation, perhaps the noise pattern should be randomized every frame to even out the dithering pattern 😄 |
Interesting! Do you have a reference for how this is done? We want to include temporal accumulation in other effects like SSR as well so information about this topic are highly welcome. |
Everywhere “frameNumber” is used here is for hacking in temporal jitter: The temporal jitter checkbox should enable it here: I’m not 1,000,000% sure it will converge, but it might help 😅 I’d love to see a dropdown that switches between the no-SSGI color, the pure SSGI channel, and the composite! |
This looks cool. |
100% agreed! I've just struggled to find a free, good looking glTF asset with an appropriate CC license and material/texture setup. Many assets at Sketchfab bake light and shadows directly into the diffuse textures so there is not even an option to remove the baked lighting which would be possible with separate light maps. I also wanted to avoid to reuse an existing asset in the repository so we can provide fresh visuals. Help in this context would be highly welcome. Maybe someone can recommend or even share an appropriate asset? @zalo Thanks! Let's give this approach a go when the PR got merged. |
The last entry in the GUI named |
Congratulations @Mugen87 , quite amazing. |
Looks very nice! Would it be possible to also get a denoise pass in? Maybe the denoise pass from N8AO by @N8python would be suitable here? (https://github.com/N8python/n8ao/blob/master/src/N8AOPass.js#L433) |
Ah, nope! 😅 It was hidden on mobile until I scrolled down. 🫠 |
A denoise pass is already available as three.js/examples/jsm/tsl/display/DenoiseNode.js Lines 5 to 16 in 642dd50
The respective TSL function three.js/examples/jsm/tsl/display/DenoiseNode.js Lines 321 to 332 in 642dd50
You would use const denoisePass = denoise( giPass, scenePassDepth, scenePassNormal, camera ); |
Hmm, @Mugen87 I think it might be too early to close #29668... I just found the time to sit down and test everything... I think there may be some significant bugs in the SSGI implementation... Here's the #29668 's (three.js GT-VBAO) implementation with 2 slices and 8 steps: And here's the WebGPU TSL SSGI Implementation with 2 slices and 8 steps: The SSGI one seems to get exponentially worse as more steps are added... likewise, I don't think the temporal jitter is working as expected 💀 I wish I could work with TSL as well as GLSL 🫠 |
Hm, the SSRT3 "three.js" implementation is here; which wasn't quite right to the Unity version either 😅 Which implementation should I step through line by line to look for differences? |
The implementation at https://raw.githack.com/zalo/three.js/feat-ssilvb/examples/webgl_postprocessing_ssilvb.html goes darker with higher AOSamples count, that is probably not intended, right? |
@zalo I have used the code from https://github.com/cdrinmatane/SSRT3/tree/main/HDRP as a reference. When visually comparing three.js/examples/jsm/tsl/display/SSGINode.js Lines 556 to 558 in 8683d96
But I did revert to the original version. The way you have computed the horizon did not seem plausible to me so I sticked to the original version as well. Instead of reopening #29668, I would prefer to open a new issue focusing on |
@cdrintherrieno Hi there 👋 ! Glady to report that SSRT3 founds its way in the main repo but it seems there is an issue in our AO implementation that makes the AO too dark the more samples are used (see #31839 (comment)). I could not reproduce this issue own my side so maybe it you could have a look at our demos? You can use below URLs for testing: https://rawcdn.githack.com/Mugen87/three.js/88e013b60298ed85d6557f4f954a57692122ca4a/examples/webgpu_postprocessing_ssgi.html Does the AO/GI look plausible to you? You can visually debug the results of the AO/GI by changing the BTW: The code of the implementation is here: https://github.com/mrdoob/three.js/blob/dev/examples/jsm/tsl/display/SSGINode.js The actual shader code is implemented in the |
@zalo I have tested the other scene with ![]() Here is a link for testing using the latest code from I also can't observe the mentioned darkening with higher step counts. Some areas are getting a more prominent AO, yes, but it does not look like in your second screenshot. It looks as expected to me. |
Woah yeah that is completely different on my phone! I can see the OP’s example work correctly on my phone now… I’ll check to see what’s different about my code or how I called it in the morning in a few hours …. Perhaps 16-bit textures were getting used and messing up the bit counting? In the new mostly correct version in the OP, I am noticing a leftward bias to the darkening (that goes away when you look at the scene from the top-down). ScreenRecording_09-09-2025.06-22-09_1.mp4This behavior does not appear in the new Little Tokyo demo you linked! 🧐 |
@Mugen87 Very nice to see it implemented in ThreeJS! Indeed the AO seems wrong, also it flickers which indicates that the slice parametrization is not symmetric. If I had to guess based on a quick glance at the code, it's probably around these parts: let frontBackHorizon = vec2( dot( pixelToSample, viewDir ), dot( pixelToSampleBackface, viewDir ) );
frontBackHorizon = GTAOFastAcos( clamp( frontBackHorizon, - 1, 1 ) );
frontBackHorizon = clamp( div( mul( samplingDirection, vec2( frontBackHorizon.x.negate(), frontBackHorizon.y ) ).sub( n.sub( HALF_PI ) ), PI ) ); // Port note: This line also required an update because of different uv conventions
frontBackHorizon = directionIsRight.equal( true ).select( frontBackHorizon.yx, frontBackHorizon.xy ); // Front/Back get inverted depending on angle You can also compare with other implementations (although I'm not sure if they use the same coordinate system as WebGPU): |
I think there are two major differences in coordinate space conventions that a port must honor. In WebGPU, the uv (0,0) represents the top-left corner whereas in Unity it should be bottom-left. For matching uv offsets, three's implementation must compute the uv and sampling directions differently. three.js/examples/jsm/tsl/display/SSGINode.js Lines 475 to 476 in 4e1e0d1
I believe this bit is correct. What I'm unsure about is the second difference in view space handedness. In WebGPU and WebGL, the camera looks down the negative Z-axis by default so (0,0,-1). In Unity, it should be (0,0,1) (is that right?). So our viewZ values are also negative. That's why I had to add a three.js/examples/jsm/tsl/display/SSGINode.js Line 469 in bd256a8
This difference in view space handedness could also affect the slice parametrization. Ideally, we don't get it right by trial and error but can understand the math differences in the shader. Can you think of a part of the |
Found one bug which is fixed via #31863. Keep working on potential horizon issue... |
I'm genuinely curious what made you decide to go with SSGI instead of GT-VBGI (https://www.shadertoy.com/view/lfdBWn). Since you already have a functional GT-VBAO implementation, getting GT-VBGI to work should have been straightforward. |
TBH, I'm new to SSGI and I found the SSRT3 solution well documented. And since I have worked with Unity a bit, it was easier to study and port the code. Do you mind explaining the differences between SSRT3 and GT-VBGI? From the comparison screenshots in #29668, I could not tell whether GT-VBAO or SSRT3 produces the better AO. To be clear, the current implementation in |
@zalo I have verified the code one more time and I think the formulas are now correctly ported to the coordinate space conventions of WebGPU and |
For myself I think GT-VBAO/GI looks a lot better, but it was significantly more complicated to port from Shadertoy… When it came time to port the port I had to TSL (the JavaScript method chaining shader language that new three.js shaders use) I found I couldn’t get over the threshold in the spare time I had… I had Claude Code attempt it again yesterday, but something else mysterious was broken on my machine, so it was all doomed to fail…
Usually this means something else is flipped earlier in the pipeline 🧐 |
The differences are basically the same as those outlined in the GT-VBAO docu here: https://www.shadertoy.com/view/XXGSDd. |
Okay, I'll check out GT-VBAO next when I'm happy with the SSRT3 code. In the meanwhile, I would appreciate any help in evaluating our port of SSRT3 so we know the coordinate space sensitive operations are correct. This will also help us when writing the TSL for GT-VBGI. BTW: I would prefer to do this manually and not with an AI so we can evaluate, understand and document (if necessary) each line. @zalo Thank you for your help here ❤️ ! I'm convinced any three.js SSGI and AO implementation will benefit if we get our SSRT3 right. We can document all relevant port changes so it's clear for developers what parts of the reference code have been updated in what way. |
Ahh, so the source of the major discrepancy was that it was falling back to WebGL when I did local development without https (and I guess in other weird contexts); it seems like there's something in here that breaks when emitting GLSL. When forced to https, it uses WebGPU and everything looks as expected. Secondly, I think I found the AO bug! When I change the three.js/examples/jsm/tsl/display/SSGINode.js Line 571 in 9f3ff3c
I'm not sure why, since that correlates with this line which still has it as Sorry for the compression making it rough... Recording.2025-09-09.123545.mp4 |
Indeed, that's it! Awesome! Would you like to make a PR with the fix? |
Makes sense! Indeed, there is an issue on the GLSL side. In some areas, the AO looks darker than in the WGSL version. I'll have a look at this tomorrow unless you find the root cause ahead of me^^. |
Comparing the "fixed" SSGI (SSRT3) shader with the three.js GT-VBAO: Recording.2025-09-09.162623.mp4While the SSRT3 SSGI is certainly an improvement on GTAO, I think GT-VBAO/GI (maybe with distant light sampling?) will be a significant quality improvement even on top of that, if we can work it. Also, this is all raising the necessity for marking pixels "dirty" in the TRAA pass, to prevent ghosting... |
#31873 fixed a bug in The new version produces a noticeably better AO. I guess we need a new comparison video for #31839 (comment) ^^. |
I've got an improvement to the temporal noise coming down the pipe in #31890 And I took a stab at trying to fix the ghosting in the TRAA implementation, but persisting the previous frame's depth to the next frame was too much for me, so I made an issue at #31892 Lastly, I have this Cornell Box SSGI Example Scene, does this seem good? Should I make a PR for this? |
That would be great! |
Unless we want to do a two part TRAA or have the SSGI take in Roughness/Specular to subsume SSR, then I think I'm done for this release. 😄 |
Fixed #29668.
Description
The PR adds a new SSGI post processing node for
WebGPURenderer
. The implementation is based on https://github.com/cdrinmatane/SSRT3 which is a SSGI component for Unity.SSGINode
is a complete port of the Unity code and supports all features that were missing in the experimental versions in #29668. E.g.SSGINode
supports temporal filtering, view-space sampling or the original noise data from the Unity source. The discussions and code from #29668 where a real help and time-saver so many thanks to all contributors!The purpose of SSGI is to add indirect diffuse light and AO to a scene. Similar to other modern SSGI implementations, this one works best with temporal filtering which means it is used together with TAA. In that case, noise that is normally quite visible can be effectively reduced without a dedicated
denoise()
pass. Depending on whether temporal filtering is used or not, the effect should be configured a bit differently. I've added some comments for this in the JSDoc of the class.The default settings of the node are relatively low so the effect runs with 60 FPS on a Pixel 8a and a macMini with a high resolution Studio Display screen. You will notice some temporal instabilities at certain surfaces with a slice count of one which can be mitigate by increasing it to two (like in the demo). The slice count has a huge performance impact though so you will see performance drops when ramping up the quality. It heavily depends on the scene what kind of settings are required so the result looks good. Another thing that you might notice is ghosting when moving the camera which is typical for temporal techniques. We might be able to reduce this by improving the TAA over time though. Keep in mind that SSGI is in general an expensive FX effect so it should be used in a sensible way.
Live demo: https://rawcdn.githack.com/mrdoob/three.js/dev/examples/webgpu_postprocessing_ssgi.html