Hi!
- I'm using ReBLUR and ReLAX as learning resources to improve my own denoiser, and after looking into the temporal accumulation stage, I got interested in 2.5D motion vectors. However, this seems to be a rather uncommon concept — I haven’t found anything similar besides regular 2D MVs, neither in Unity (including HDRP, which adopted ReBLUR for ray-traced reflections), nor in UE5, AMD denoisers, or most other public repositories and engines. Where can I find more information about 2.5D MVs and, more specifically, how to compute them? I noticed an example of per-object 2.5D MV computation in this issue thread, in the first post. Is that code correct?
If I understand it right, I’d need to modify the MV shader pass and the camera’s MV output to include the ViewZ delta in the 3rd component. For now, I’m using a per-pixel mask to mark moving objects and relax disocclusion weights for their pixels to avoid harsh history rejection, but your approach looks like a more appropriate and elegant solution.
-
In TemporalAccumulation in ReBLUR (line 229) and ReLAX (line 135), I noticed that after loading history normals, you transform them as: smbNavg = Geometry::RotateVector( gWorldPrevToWorld, smbNavg );. What’s the reason for this transformation? Aren’t both current and previous normals stored in absolute world space and therefore directly comparable?
-
Looking at the original presentation (slide 30), I see that temporal disocclusion was initially computed based on tangent plane distance. But now, it seems to rely mostly on the angle between the view vector and the surface normal (along with several lerps and adjustments):
float3 V = GetViewVector( X );
float NoV = abs( dot( N, V ) );
float NoVstrict = lerp( NoV, 1.0, saturate( smbParallaxInPixelsMax / 30.0 ) );
float4 smbDisocclusionThreshold = GetDisocclusionThreshold( disocclusionThreshold, frustumSize, NoVstrict );
smbDisocclusionThreshold *= float( dot( smbNavg, Navg ) > thresholdAngle ); // good for smb
smbDisocclusionThreshold *= IsInScreenBilinear( smbBilinearFilter.origin, gRectSizePrev );
smbDisocclusionThreshold -= NRD_EPS;. What was the reason behind this change or am I misentepreting the code
What was the reason behind this change, or am I misinterpreting the code?
I’m asking because most plane-based rejection functions require the world positions of both the sample and center pixels (and sometimes even the normal of the sample pixel). You seem to get away with using only the ViewZ of sample pixels, which is much cheaper, but it no longer looks like a pure plane weight.
Thank you!
Hi!
If I understand it right, I’d need to modify the MV shader pass and the camera’s MV output to include the ViewZ delta in the 3rd component. For now, I’m using a per-pixel mask to mark moving objects and relax disocclusion weights for their pixels to avoid harsh history rejection, but your approach looks like a more appropriate and elegant solution.
In TemporalAccumulation in ReBLUR (line 229) and ReLAX (line 135), I noticed that after loading history normals, you transform them as:
smbNavg = Geometry::RotateVector( gWorldPrevToWorld, smbNavg );. What’s the reason for this transformation? Aren’t both current and previous normals stored in absolute world space and therefore directly comparable?Looking at the original presentation (slide 30), I see that temporal disocclusion was initially computed based on tangent plane distance. But now, it seems to rely mostly on the angle between the view vector and the surface normal (along with several lerps and adjustments):
What was the reason behind this change, or am I misinterpreting the code?
I’m asking because most plane-based rejection functions require the world positions of both the sample and center pixels (and sometimes even the normal of the sample pixel). You seem to get away with using only the ViewZ of sample pixels, which is much cheaper, but it no longer looks like a pure plane weight.
Thank you!