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
///////////////////////////////////////////////////////////////////////////////////////////////////////////