Distortion-aware depth fade

Hello, I have encountered the same problem, but the above link in your post has expired. Can you replace it? This related result will be very useful to me. thank you very much! !

Link is fine. Just have in mind, that the code was not updated for 4.19.

Hi , after the website link of github is opened, it is a 404 page. I have already registered my account and activated it. I don’t know if it’s not my opening method is incorrect. I took a screenshot and hoped to get help…

have you linked your epic account to github? link def works, im currently on it

thank you very much! The problem has been resolved after linking to my account.
It’s really useful! QAQ

Thank you for sharing, I have got the desired results, you helped me a great favor ~ (´∀`)

Would love to have this updated for 4.19 I get ‘ResolvedView.SceneTextureMinMax’ invalid

Hi any chance that you could update the code for 4.19? I’m getting the error mentioned by James: ‘ResolvedView.SceneTextureMinMax’ invalid

Thanks,

Hello,
Recently, due to the update of the Speedtree 8.0 version, I started working with the 4.19 version of ue4.
As you said, when I put this custom node into version 4.19 it has an error.
I spent a few days experimenting with the methods I could think of, because it looked like the problem with ‘SceneTextureMinMax’. But it didn’t work.
I want to ask why, because I don’t know anything about HLSL. Can you provide a way to implement this function using material nodes, because although I know I need to eliminate artifacts by perturbation values, I can’t think of how to implement them. Or can you update this code to make it work again? I really need a high quality water material in my work.
Google Translate,
Thank you very much (*/ω*)

I ask an excuse for and I was also looking at this problem. The steps to fix an issue like this, is to search the engine source code for the place where that attribute for ResolvedView is created and its value feed. Doing this procedure I have found the code for it and this is how that line should look:

//Gets SceneColor min/max coords.
float4 SceneColorRect=(((float)EffectiveViewRect.Min.X / BufferSize.X),
((float)EffectiveViewRect.Min.Y / BufferSize.Y),
(((float)EffectiveViewRect.Max.X / BufferSize.X) - OneScenePixelUVSize.X),
(((float)EffectiveViewRect.Max.Y / BufferSize.Y) - OneScenePixelUVSize.Y));

I have just copied and pasted the values, without even checking if the elements would be present or not (this would be expected) and simply changed in the code, which immediately worked (at least no errors), and you might assume it is ok, but I strongly recommend you to compare both project versions. The snipet above was taken from UE 4.17 and placed into 4.20 and the material compiled in the editor.

Hi NilsonLima
Thank you for your help! How can I use the above code? Is it directly copied to the material custom node and replaced with the following code?
//Gets SceneColor min/max coords.
Float4 SceneColorRect=ResolvedView.SceneTextureMinMax;
I tried to do this, I was prompted with an error in version 4.19, and there was no error in version 4.20, but the material did not run correctly.
Or do I need to modify and recompile the engine myself?

Google Translate,
Thank you very much (*/ω*)

Yes, you replace like that, but it will compile OK only on 4.20, I haven’t tested it on 4.19. About not running properly, that is why I asked for a comparison, because I was not sure if it would behave properly. The math of that piece of code is OK, but it was intended on the pixel shader C++ code, so probably there are internals for EffectiveViewRect and BufferSize which are the correct properties to use. OneScenePixelUVSize seems to exist already, so it might not cause issues.

This kind of change seems to have taken place to reduce the pre-calculated values on the pixel shader C++ code for a speed-up (really a marginal gain) and since there were no other code referencing that property, they decided to strip it out.

Since I was using that same code on my water materials, I will take a look and try to find out which properties must fill those roles for EffectiveViewRect and BufferSize and also double check that OneScenePixelUVSize. The answer is there in the code, I am just not used to that part of the code yet, needs a lot of digging.

Hi NilsonLima
Thank you for your patience and look forward to your success!
At the same time, I will start to learn about the code, although it can not take effect immediately, but I hope that one day I can use this knowledge to solve similar problems.
Thank you very much! (〃ノωノ)

​​​​​​​Google Translate

Gettting: [SM5] /Engine/Generated/Material.ush(1444,32-48): error X3004: undeclared identifier ‘EffectiveViewRect’

I hope you solve the issue NilsonLima for 4.19. Thanks!

I will only try to check this for 4.20, which is hard already, but I postponed this issue for now, since there are other things popping up here at work! Know that when you decide to use a custom node and you make use of internal functions, you will always be subject to this code breaking when engine version changes. The correct approach would be find all the functions and unfold then inside your custom node to prevent this and this is the path I am choosing to fix this as soon I got the time to look at.

been playing around with refraction lately, and I’ve found some flaws in how UE4 handles refraction which now make me suspect the point of the improvements in this thread might be just victims of patching a mis-behavior… potentially in the wrong place.

under the hood unreal converts the refraction values to accumulated screen-space UV offsets which accounts exactly for what in UE3 was just called “distortion” (and at this point in the shader it’s also called “distortion”), so note: whenever I refer to “distortion” I mean the screen-space UV offset caused by the refraction.
so these are my observations:

  1. for one I’ve realized that the distortion becomes stronger the further away the camera is from the object. this leads to severe over-refraction in things like frosted glass or water the further away they are from the camera.
  2. the over-refraction described above isn’t really evident because of one tiny bit in the shader that takes any distorted UVs that went out of the boundaries of the screenUV and reset them to have no distortion at all (in short, when you have too much refraction, you get no refraction at all). this also causes refraction to stop working near screen edges. for me it’d be more natural to clamp the screenUV (so you’d get repeated screen edge pixels) or ping-pong them (so you get “something” even if it’s inaccurate). - I now see you the optional mirror version in your solution which is good
  3. refraction does not scale the distortion offset values based on a depth comparison. so for a distorted refracting plane (i.e. water or frosted glass) one would expect there to be more distortion because the view rays travel further away from the object
  4. the same principle should apply (inversely) for points closer to the refracting surface all the way to having zero refraction on the contact point (as long as the refracting object is paper-thin), which would in theory solve the problem of the original post here

atm I’m changing the engine shaders locally to try to address these issues in a work project
in the DistortAccumulatePS.usf I’ve solved point 1) by dividing the BufferUVDistortion by the scenedepth/255 which gives me fairly consistent refraction regardless of the camera distance
then in DistortApplyScreenPS.usf I solved point 2) by clamping the NewBufferUV to the buffer minmax (instead of the “set to 0 distortion” that Epic did)
now I’ll be trying to solve points 3) and 4) simply by scaling the distortion with a depth comparison, and I hope it doesn’t give me too much artifacts on underlying mesh edges :slight_smile:

edit: tried the depth comparison but I never could get any sort of result. for now I’m just doing the depth comparison in the material to alter the refraction value

https://github.com//UnrealEngine/blob/ReceiverPlaneDepthBias_4.16/Engine/Shaders/Private/DistortionAwareFadeCustomNode.usf
is the link unavailable now? I can’t find it anymore…

Nothing wrong with the link, but you need your account linked with Epic’s source code repository to be able to see it.

For anyone, who stumbles across this ancient thread applicable to water, you can use a much simpler and efficient workaround by calculating water fog in a post process, but instead of using post process material, use an unlit translucent material with disabled separate translucency, applied to actual plane mesh, placed at camera near plane. This fog will then be properly affected by distortions from the water mesh.

Updated code:

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Custom node input parameters:
//float2 Refraction; R = IOR, the same as material refraction input, G = Refraction Bias, the same as in material/material instance settings.
//float3 MaterialNormal;
//float DepthFadeDistance
//float Opacity

// Custom node output:
//X,Y= Distorted Screen UV,
//Z=Distorted Scene Depth,
//W= Distortion-aware depth fade.
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//Tweakable parameters
#define DIVDELTA 0.0000001 // Used to avoid division by zero. Should be kept at something low. Makes no visible difference.
#define USE_MIRRORED_WRAP_EDGES 0 // Distortion edge mirroring code by Kalle_H
#define MATCH_CLIP_CODE 1 // Matches a certain threshold in original code. Should be left at 1 preferably.
#define DEBUG_SCENE_RECT 0 // Debugs scene color size. Custom node will return (Min.X,Min.Y,Max.X,Max.Y)
#define DEBUG_DISTORTION 0 // Debugs distortion. Custom node will return (Distortion.X,Distortion.Y,UncorrectedDistortion.X,UncorrectedDistortion.Y)
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

static const float OffsetFudgeFactor = 0.00023;

//Gets view parameters for distortion
float4 DistortionParams=float4(
GetCotanHalfFieldOfView().r,
ResolvedView.ViewSizeAndInvSize.r/ResolvedView.ViewSizeAndInvSize.g,
ResolvedView.ViewSizeAndInvSize.r,
ResolvedView.ViewSizeAndInvSize.g
);

//Transforms either tangent or world normal into view space
#if MATERIAL_TANGENTSPACENORMAL
half3 ViewNormal = normalize(TransformTangentVectorToView(Parameters, Normal));
#else
half3 ViewNormal = normalize(TransformWorldVectorToView(Normal));
#endif

//Calculates Distortion from material normals.
#if REFRACTION_USE_PIXEL_NORMAL_OFFSET
half3 ViewVertexNormal = TransformTangentVectorToView(Parameters, float3(0, 0, 1));

// Treat Refraction of 1 as no refraction, to be consistent with IOR mode
float2 Distortion = (ViewVertexNormal.xy - ViewNormal.xy) * (Refraction.x - 1);
#else
// we assume the camera is in air
float AirIOR = 1.0f;
float2 Distortion = ViewNormal.xy * (Refraction.x - AirIOR);
#endif

// Should match clip in original code. Might make sense to branch.
FLATTEN if (dot(Distortion, Distortion) < 0.00001)
{
Distortion*=0;
}

// Prevent silhouettes from geometry that is in front of distortion from being seen in the distortion
float2 NDC = (Parameters.ScreenPosition.xy /Parameters.ScreenPosition.w);
float2 ScreenUV = NDC * ResolvedView.ScreenPositionScaleBias.xy + ResolvedView.ScreenPositionScaleBias.wz;

//Fix for Fov and aspect.
float InvTanHalfFov = DistortionParams.x;
float Ratio = DistortionParams.y;
float2 FovFix = float2(InvTanHalfFov,Ratio*InvTanHalfFov);
Distortion *= DistortionParams.zw * float2(OffsetFudgeFactor,-OffsetFudgeFactor) * FovFix;

// Sample depth at distortion offset
float2 NewBufferUV = ScreenUV + Distortion;

#if (ES2_PROFILE || ES3_1_PROFILE)
float DistortSceneDepth = ConvertFromDeviceZ(Texture2DSampleLevel(SceneDepthTexture, SceneDepthTextureSampler, NewBufferUV, 0).r);
#else
float DistortSceneDepth = CalcSceneDepth(NewBufferUV);
#endif // (ES2_PROFILE || ES3_1_PROFILE)

// Soft thresholding
float Bias = -Refraction.y;
float Range = clamp(abs(Bias * 0.5f), 0, 50);
float Z = DistortSceneDepth;
float ZCompare = Parameters.ScreenPosition.w;
float InvWidth = 1.0f / max(1.0f, Range );

#if DEBUG_DISTORTION
float2 UncorrectedDistortion=Distortion;
#endif

Distortion *= saturate((Z - ZCompare) * InvWidth + Bias);

NewBufferUV = ScreenUV.xy + Distortion;

//optionally toggle edge mirroring instead of clamping
#if !USE_MIRRORED_WRAP_EDGES
// If we’re about to sample outside the valid View.BufferBilinearUVMinMax, set to 0 distortion.
FLATTEN if ( NewBufferUV.x < View.BufferBilinearUVMinMax.x || NewBufferUV.x > View.BufferBilinearUVMinMax.z ||
NewBufferUV.y < View.BufferBilinearUVMinMax.y || NewBufferUV.y > View.BufferBilinearUVMinMax.w )
{
NewBufferUV = ScreenUV.xy;
}
#else
// Apply mirror distortion if NewBufferUV is outside of borders.
if (NewBufferUV.x < View.BufferBilinearUVMinMax.x || NewBufferUV.x > View.BufferBilinearUVMinMax.z)
Distortion.x = -Distortion.x;
if (NewBufferUV.y < View.BufferBilinearUVMinMax.y || NewBufferUV.y > View.BufferBilinearUVMinMax.w)
Distortion.y = -Distortion.y;
NewBufferUV = ScreenUV + Distortion;
#endif //USE_MIRRORED_WRAP_EDGES

//Sample scene depth again with adjusted distortion UVs
#if (ES2_PROFILE || ES3_1_PROFILE)
DistortSceneDepth = ConvertFromDeviceZ(Texture2DSampleLevel(SceneDepthTexture, SceneDepthTextureSampler, NewBufferUV, 0).r);
#else
DistortSceneDepth = CalcSceneDepth(NewBufferUV);
#endif // (ES2_PROFILE || ES3_1_PROFILE)

// returs result with debug option
#if DEBUG_SCENE_RECT
return View.BufferBilinearUVMinMax;
#elif DEBUG_DISTORTION
return float4(Distortion,UncorrectedDistortion);
#else
return float4(
NewBufferUV,
DistortSceneDepth,
Opacity * saturate((DistortSceneDepth - ZCompare) / max(DepthFadeDistance, DIVDELTA))
);
#endif
///////////////////////////////////////////////////////////////////////////////////////////////////////////