-
-
Couldn't load subscription status.
- Fork 36.1k
SSGI: Reduce ghosting artifacts with improved temporal filtering #32133
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
base: dev
Are you sure you want to change the base?
Conversation
|
Are there any resources like papers or articles Claude refers to? Otherwise it will be very time-intensive to evaluate/verify these changes. |
|
Done! Asked Claude to add academic references and citations for all the changes. |
| // http://www.iryoku.com/downloads/Practical-Realtime-Strategies-for-Accurate-Indirect-Occlusion.pdf | ||
| // Section 4.5: "Temporal Filtering" - More rotation angles improve temporal accumulation quality | ||
| // Doubled from 6 to 12 rotations for better noise distribution across frames | ||
| const _temporalRotations = [ 60, 300, 180, 240, 120, 0, 90, 270, 30, 150, 210, 330 ]; |
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.
Minor feedback: The linked paper has no section 4.5.
The next thing is that the paper only refers to 6 values of temporal rotation values not 12. There is no mentioning in the paper that doubling or in general increasing the number of rotation values beyond the default is preferable. So I'm not sure on what the AI is referring here. Since I see no improvements in the SSGI and the GTAO using these values, I vote to stick to the original reference.
I'll try to check the TAA suggestions of Claude the next days 👍 . The TAA seems to be improved a bit but I'd like to pinpoint (and understand) what change makes the biggest difference and verify the changes according to the resources.
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.
Sounds good!
| // E[X] = mean, E[X²] = second moment, Var(X) = E[X²] - E[X]² | ||
| colorSum.addAssign( colorNeighbor ); | ||
| colorSqSum.addAssign( colorNeighbor.mul( colorNeighbor ) ); | ||
| sampleCount.addAssign( 1.0 ); |
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 was curious about these changes so I've dig into today 🤓.
First of all some resources Claude mentions do not really reflect what the AI changes. It was necessary to read the resources and watch the presentations the AI is referring to realize this.
In any event, the suggested variance clipping from the NVIDIA talk seems to be a good replacement for the default color clamping. I would not mix the values with the traditional color box but just use the variance values like NVIDIA suggests in the GDC presentation.
Also using the magnitude of the motion vector and the world position difference to influence the weight of the current sample does make sense (using motion vectors to influence the blending has been mentioned in the INSIDE presentation). The idea is to give the current sample more weight if a lot of motion is detected. This change makes actually the biggest difference so we should make use of it. However, I'm not sure where the AI gets the multiplication factors (0.5, 10 and 0.25) from. They are not mentioned anywhere in the resources. However, because of the good results I think we should document these values has been suggested by Claude, use them and see how it goes.
I'm unsure about the new constants for the Disocclusion check. These constants are mentioned nowhere in the resources as well and I don't see a noticeable difference between new and old.
@zalo What do you think about this? The world position difference threshold is now considerably larger (0.5), the value for the depth a bit lower (0.00001).
I can make a PR based on this one and update the code and comments according to my findings. I'll wait for @zalo's feedback though.
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.
So the way to visualize the disoccluded regions is to put this at the end of TRAANode.js:
If( strongDisocclusion, () => {
smoothedOutput.assign(vec4(float(1.0), float(0.0), float(0.0), float(1.0)));
} );or, before this PR, use:
If( rejectPixel, () => {
smoothedOutput.assign(vec4(float(1.0), float(0.0), float(0.0), float(1.0)));
} );Comparing the two, it looks like it significantly changed the behavior of the disocclusion algorithm, so it needs a larger threshold...
This video shows before the PR first, and then after:
Recording.2025-10-28.151414.mp4
Should the motion vectors with the TRAA jitter be adding enough motion to trigger a 0.05 unit change? Seems big... I like the idea of using the motion vectors better though 😅
I'll admit I was never thrilled with the worldspace-threshold anyway, since the optimal value varies depending on the size of the scene (how do outline shaders solve this?), but some time should be spent messing with this debug view just to ensure that it's doing what we expect...
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.
Also, unfortunately, the reference Karis (2014) Section 3.2 - "Disocclusion handling" doesn't actually have a section 3.2 or anything on disocclusion handling... :-(
It seems to be referring to this:
https://advances.realtimerendering.com/s2014/#_HIGH-QUALITY_TEMPORAL_SUPERSAMPLING
(Presentation as .pdf, Youtube Video Link )
I like Claude and tried to use it to implement this disocclusion thing four or five times before I had to admit defeat and buckle down and figure out the right way to pass around this depth buffer manually 💀 )
|
I forwarded your feedback to Claude 🤞
Dev: https://raw.githack.com/mrdoob/three.js/dev/examples/webgpu_postprocessing_ssgi.html |
This commit implements several improvements to reduce ghosting in SSGI with temporal filtering: **TRAANode.js improvements:** 1. **Adaptive blend weights based on motion** - Changed from fixed 5% current / 95% history to adaptive 5-30% current based on motion - Uses velocity magnitude to detect fast-moving areas and increase current frame weight - Reduces ghosting in motion while maintaining stability in static areas 2. **Improved disocclusion detection** - Better world position difference threshold (0.5 instead of 0.01) - Separates edge detection from disocclusion to preserve anti-aliasing - More accurate edge threshold (0.00001 instead of 0.0001) 3. **Pure variance-based color clamping** - Implements pure variance clipping as recommended by Salvi (GDC 2016) - Computes mean and standard deviation of 3x3 neighborhood - Clamps history to mean ± gamma * stddev (gamma = 1.25) - Significantly reduces ghosting while preserving detail **SSGINode.js improvements:** 1. **Extended temporal sampling patterns** - Increased rotation angles from 6 to 12 values - Increased spatial offsets from 4 to 8 values - Better temporal distribution reduces noise convergence time - Helps reduce residual ghosting artifacts These changes should significantly reduce ghosting while maintaining the quality of temporal anti-aliasing and SSGI. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
4697f49 to
6fb24bd
Compare
|
Well, I don't think it's worse than It is fun to note INSIDE's TRAA Implementation is MIT Open Source, perhaps Claude can port all the good bits from it and smoketest itself: |
Three.js TRAA vs Playdead's Temporal ReprojectionArchitecture
Color Space
Neighborhood Sampling (Anti-Ghosting)
Blend Weight Calculation
Disocclusion Detection
Velocity Handling
Advanced Features
Performance Characteristics
Key Algorithmic Differences1. Ghosting Reduction StrategyPlaydead: Relies on luminance-based feedback
Three.js: Uses geometric + motion analysis
2. Color ClampingPlaydead: AABB min/max clipping Three.js: Variance clipping (Salvi 2016) Variance clipping is statistically tighter and reduces ghosting better, but costs more. Summary Table
RecommendationsUse Playdead's approach if:
Use Three.js TRAA if:
What Three.js Could Borrow from Playdead
What's Better in Three.js
|
|
Let me know if there's anything of interest there and I'll make it port it. |
|
After some more testing I have noticed that the variance clipping actually worsens the stability of the resolved image 👀 . You can clearly see this in the AO demo. Just compare the occluded area of the plant in the corner: Dev: https://rawcdn.githack.com/mrdoob/three.js/dev/examples/webgpu_postprocessing_ao.html Hence, I would leave this out for now and just merge the change with the adaptive blend weights and updated occlusion test. This change reduces Ghosting but without noticeably worsing another aspect of the TAA. I'll file a PR. |
I'm afraid the citations are still not 100% correct. If you check closely, you see that the AI sometimes generates code that has no references with the mentioned resource whatsoever. TBH, I'm not really a fan of such code since this has nothing to do with scientific correct work. It's not clear if this code is correct or not, if it has been tested or verified like an approach from a real scientific paper (or at least from an article or code reference). Hence, I'm a bit reserved with using the results from this PR. The things I have extracted in #32139 seem okay though so we might want to give it a try. |
@Mugen87 @zalo Some ideas for you guys... 👀
This PR implements several improvements to reduce ghosting while maintaining temporal stability:
TRAANode.js Improvements:
1. Adaptive Blend Weights Based on Motion
2. Improved Disocclusion Detection
3. Pure Variance-Based Color Clamping
mean ± gamma * stddevwhere gamma = 1.25SSGINode.js Improvements:
4. Extended Temporal Sampling Patterns
References
Testing
These changes should provide:
Test with:
Parameters
If fine-tuning is needed, adjustable parameters include:
gamma = 1.25in variance clipping (line 493) - controls clipping tightness0.25multiplier in adaptive weights (line 531) - controls max current frame weight (5-30% range)0.5threshold in disocclusion detection (line 543) - controls when to fully reject history