// Copyright Epic Games, Inc. All Rights Reserved. #include "Common.ush" #include "ScreenPass.ush" #if MSAA_SAMPLE_COUNT > 1 Texture2DMS EditorPrimitivesDepth; Texture2DMS EditorPrimitivesStencil; #else // Note: opengl compiler doesn't like Texture2D Texture2D EditorPrimitivesDepth; Texture2D EditorPrimitivesStencil; #endif Texture2D ColorTexture; SamplerState ColorSampler; Texture2D DepthTexture; SamplerState DepthSampler; SCREEN_PASS_TEXTURE_VIEWPORT(Color) SCREEN_PASS_TEXTURE_VIEWPORT(Depth) FScreenTransform ColorToDepth; // @param DeviceZPlane plane in screenspace .x: ddx(DeviceZ), y: ddy(DeviceZ) z:DeviceZ float ReconstructDeviceZ(float3 DeviceZPlane, float2 PixelOffset) { return dot(DeviceZPlane, float3(PixelOffset.xy, 1)); } // @param DeviceZPlane plane in screenspace .x: ddx(DeviceZ), y: ddy(DeviceZ) z:DeviceZ bool IsOccluded(int2 PixelPos, int SampleID, int OffsetX, int OffsetY, float3 DeviceZPlane, float2 DeviceZMinMax) { PixelPos += int2(OffsetX, OffsetY); // more stable on the silhouette float2 ReconstructionOffset = (View.TemporalAAParams.zw * Color_Extent * Depth_ExtentInverse); #if MSAA_SAMPLE_COUNT > 1 float DeviceZ = EditorPrimitivesDepth.Load(PixelPos, SampleID).r; #if !COMPILER_GLSL && !COMPILER_PSSL && !COMPILER_METAL && !COMPILER_HLSLCC // not yet supported on OpenGL, slightly better quality ReconstructionOffset += EditorPrimitivesDepth.GetSamplePosition(SampleID); #endif #else float DeviceZ = EditorPrimitivesDepth.Load(int3(PixelPos, 0)).r; #endif float SceneDeviceZ = ReconstructDeviceZ(DeviceZPlane, ReconstructionOffset); // clamp SceneDeviceZ in (DeviceZMinMax.x .. DeviceZMinMax.z) // this avoids flicking artifacts on the silhouette by limiting the depth reconstruction error SceneDeviceZ = max(SceneDeviceZ, DeviceZMinMax.x); SceneDeviceZ = min(SceneDeviceZ, DeviceZMinMax.y); // Soft Bias with DeviceZ for best quality (larger bias than usual because SceneDeviceZ is only approximated) const float DeviceDepthFade = 0.000005f; const float fOccluded = ((SceneDeviceZ - DeviceZ) / DeviceDepthFade); const bool bOccluded = fOccluded <= 1.0f; return bOccluded; } // Should this sample be greyed out bool PerSample(int2 PixelPos, int SampleID, bool IsCenterPixelOccluded, float3 DeviceZPlane, float2 DeviceZMinMax) { if (IsCenterPixelOccluded) { return true; } // [0..3]: borders bool IsPixelOccluded[4]; // Diagonal cross is thicker than vertical/horizontal cross. IsPixelOccluded[0] = IsOccluded(PixelPos, SampleID, 1, 1, DeviceZPlane, DeviceZMinMax); IsPixelOccluded[1] = IsOccluded(PixelPos, SampleID, -1, -1, DeviceZPlane, DeviceZMinMax); IsPixelOccluded[2] = IsOccluded(PixelPos, SampleID, 1, -1, DeviceZPlane, DeviceZMinMax); IsPixelOccluded[3] = IsOccluded(PixelPos, SampleID, -1, 1, DeviceZPlane, DeviceZMinMax); if (IsPixelOccluded[0] || IsPixelOccluded[1] || IsPixelOccluded[2] || IsPixelOccluded[3]) { return true; } return false; } float4 GetPixelGreyColor(float4 SceneColor, float SceneColorIntensity) { float Grey = Luminance(SceneColor.rgb); float4 GreyColor = float4(Grey, Grey, Grey, 1.0); GreyColor = lerp(GreyColor, float4(1., 1., 1., 1.), 0.5); return lerp(GreyColor, SceneColor, SceneColorIntensity); } // Computes min and max at once. void MinMax(inout int2 Var, int Value) { Var.x = min(Var.x, Value); Var.y = max(Var.y, Value); } void MainPS( noperspective float4 UVAndScreenPos : TEXCOORD0, float4 SvPosition : SV_POSITION, out float4 OutColor : SV_Target0) { const float2 ColorUV = UVAndScreenPos.xy; const int2 ColorPixelPos = int2(ColorUV * Color_Extent); #if MSAA_SAMPLE_COUNT > 1 const int StencilVal = EditorPrimitivesStencil.Load(ColorPixelPos, 0) STENCIL_COMPONENT_SWIZZLE; #else const int StencilVal = EditorPrimitivesStencil.Load(int3(ColorPixelPos, 0)) STENCIL_COMPONENT_SWIZZLE; #endif const float4 SceneColor = ColorTexture.Load(int3(ColorPixelPos, 0)); if (StencilVal == 2) { // This is a Nanite pixel and occlusion has been handled already OutColor = SceneColor; } else if (StencilVal != 0) { const float2 DepthUV = ApplyScreenTransform(ColorUV, ColorToDepth); const float2 DepthUVColorPixelSize = Pow2(Color_ExtentInverse) * Depth_Extent; const float2 DepthUVOffsets[4] = { { -DepthUVColorPixelSize.x, 0 }, { DepthUVColorPixelSize.x, 0 }, { 0, -DepthUVColorPixelSize.y }, { 0, DepthUVColorPixelSize.y }, }; float Center = Texture2DSampleLevel(DepthTexture, DepthSampler, DepthUV, 0).r; float Left = Texture2DSampleLevel(DepthTexture, DepthSampler, DepthUV + DepthUVOffsets[0], 0).r; float Right = Texture2DSampleLevel(DepthTexture, DepthSampler, DepthUV + DepthUVOffsets[1], 0).r; float Top = Texture2DSampleLevel(DepthTexture, DepthSampler, DepthUV + DepthUVOffsets[2], 0).r; float Bottom = Texture2DSampleLevel(DepthTexture, DepthSampler, DepthUV + DepthUVOffsets[3], 0).r; // This allows to reconstruct depth with a small pixel offset without many texture lookups (4xMSAA * 5 neighbors -> 20 samples) // It's an approximation assuming the surface is a plane. float3 DeviceZPlane; DeviceZPlane.x = (Right - Left) / 2; DeviceZPlane.y = (Bottom - Top) / 2; DeviceZPlane.z = Center; float2 DeviceZMinMax; DeviceZMinMax.x = min(Center, min(min(Left, Right), min(Top, Bottom))); DeviceZMinMax.y = max(Center, max(max(Left, Right), max(Top, Bottom))); bool IsPixelOccluded = IsOccluded(ColorPixelPos, 0, 0, 0, DeviceZPlane, DeviceZMinMax); int Sum = 0; #if COMPILER_GLSL && FEATURE_LEVEL >= FEATURE_LEVEL_SM4 UNROLL #endif for (int SampleID = 0; SampleID < MSAA_SAMPLE_COUNT; ++SampleID) { if (PerSample(ColorPixelPos, SampleID, IsPixelOccluded, DeviceZPlane, DeviceZMinMax)) { ++Sum; } } const float SceneColorIntensity = Sum / MSAA_SAMPLE_COUNT; OutColor = GetPixelGreyColor(SceneColor, SceneColorIntensity); } else { // Always grey out stencil val == 0 const float SceneColorIntensity = 0.0; OutColor = GetPixelGreyColor(SceneColor, SceneColorIntensity); } }