Files
UnrealEngine/Engine/Shaders/Private/Nanite/NaniteVisualize.usf
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

1027 lines
35 KiB
HLSL

// Copyright Epic Games, Inc. All Rights Reserved.
#include "../Common.ush"
#include "../ShaderPrint.ush"
#include "../SceneData.ush"
#include "../DeferredShadingCommon.ush"
#include "../ColorMap.ush"
#include "../Visualization.ush"
#include "../VirtualShadowMaps/VirtualShadowMapPageCacheCommon.ush"
#include "../VirtualShadowMaps/VirtualShadowMapVisualize.ush"
#include "../SplineMeshCommon.ush"
#include "../MeshPaintTextureCommon.ush"
#define NUM_VIRTUALTEXTURE_SAMPLES 1
#include "../VirtualTextureCommon.ush"
#include "NaniteDataDecode.ush"
#include "NaniteAttributeDecode.ush"
#include "NaniteCullingCommon.ush"
#include "NaniteVertexFetch.ush"
#include "NaniteVertexDeformation.ush"
#include "NaniteEditorCommon.ush"
RWTexture2D<float4> DebugOutput;
Texture2D<UlongType> VisBuffer64;
Texture2D<UlongType> DbgBuffer64;
Texture2D<uint> DbgBuffer32;
Texture2D<float> SceneDepth;
Texture2D<float> SceneZDecoded;
Texture2D<uint4> SceneZLayout;
Texture2D<uint> ShadingMask;
Texture2D<uint> FastClearTileVis;
Texture2D<float4> MeshPaintTexture;
StructuredBuffer<FNaniteRasterBinMeta> RasterBinMeta;
ByteAddressBuffer ShadingBinData;
ByteAddressBuffer AssemblyMeta;
int4 VisualizeConfig;
int4 VisualizeScales;
uint RegularMaterialRasterBinCount;
int2 PickingPixelPos;
uint MeshPaintTextureCoordinate;
// NOTE: Just hardcoded to ENaniteMeshPass::BasePass as an optimization, as all these shaders are only concerned with base pass materials
static const uint MeshPassIndex = 0;
RWStructuredBuffer<FNanitePickingFeedback> FeedbackBuffer;
uint GetVisualizeMode()
{
return VisualizeConfig.x;
}
uint GetPickingDomain()
{
return select(
GetVisualizeMode() == NANITE_VISUALIZE_PICKING,
VisualizeConfig.y,
NANITE_PICKING_DOMAIN_TRIANGLE
);
}
uint GetPixelProgrammableVisMode()
{
return select(
GetVisualizeMode() == NANITE_VISUALIZE_PIXEL_PROGRAMMABLE_RASTER,
VisualizeConfig.y,
NANITE_PIXEL_PROG_VIS_MODE_DEFAULT
);
}
uint GetMeshPaintingShowMode()
{
return select(
GetVisualizeMode() == NANITE_VISUALIZE_VERTEX_COLOR || GetVisualizeMode() == NANITE_VISUALIZE_MESH_PAINT_TEXTURE,
VisualizeConfig.y & 0x1,
NANITE_MESH_PAINTING_SHOW_ALL
);
}
uint GetMeshPaintingChannelMode()
{
return select(
GetVisualizeMode() == NANITE_VISUALIZE_VERTEX_COLOR || GetVisualizeMode() == NANITE_VISUALIZE_MESH_PAINT_TEXTURE,
(VisualizeConfig.y >> 1) & 0x7,
NANITE_MESH_PAINTING_CHANNELS_RGB
);
}
uint GetMeshPaintingTextureMode()
{
return select(
GetVisualizeMode() == NANITE_VISUALIZE_MESH_PAINT_TEXTURE,
(VisualizeConfig.y >> 4) & 0x1,
NANITE_MESH_PAINTING_TEXTURE_DEFAULT
);
}
float3 ApplyMeshPaintingChannelMode(uint Mode, float4 Color)
{
switch (Mode)
{
case NANITE_MESH_PAINTING_CHANNELS_R: return float3(Color.r, 0, 0);
case NANITE_MESH_PAINTING_CHANNELS_G: return float3(0, Color.g, 0);
case NANITE_MESH_PAINTING_CHANNELS_B: return float3(0, 0, Color.b);
case NANITE_MESH_PAINTING_CHANNELS_A: return Color.aaa;
case NANITE_MESH_PAINTING_CHANNELS_RGB:
default: return Color.rgb;
}
}
// Apply this to color output because we composite the visualization result with post-tone-mapped space.
// Call this for visualization modes where it matters, but possibly we should do this in all modes.
float3 LinearToPostTonemapSpace(float3 Color)
{
return LinearToSrgbBranchless(Color);
}
float GetOverdrawScale()
{
return clamp(float(VisualizeScales.x), 0.0f, 100.0f) / 100.0f;
}
float GetComplexityScale()
{
return clamp(float(VisualizeScales.y), 0.0f, 100.0f) / 100.0f;
}
uint GetShadingExportCount()
{
return uint(VisualizeScales.z);
}
bool GetCompositeWithSceneDepth()
{
return VisualizeConfig.z != 0;
}
bool ShouldApplySobelFilter()
{
return (VisualizeConfig.w & NANITE_VISUALIZE_FLAG_ENABLE_OUTLINE) != 0;
}
bool ShouldApplyDebugShading()
{
return (VisualizeConfig.w & NANITE_VISUALIZE_FLAG_ENABLE_DEBUG_SHADING) != 0;
}
struct FVisualizeEffects
{
float3 OutlineColor;
bool bDarkOutline;
bool bApplySobel;
bool bApplyDebugShading;
};
FVisualizeEffects GetVisualizeModeEffects(uint VisualizeMode)
{
FVisualizeEffects Output;
Output.OutlineColor = 1;
Output.bApplySobel = true;
Output.bDarkOutline = false;
Output.bApplyDebugShading = true;
switch (VisualizeMode)
{
case NANITE_VISUALIZE_TRIANGLES:
case NANITE_VISUALIZE_PATCHES:
case NANITE_VISUALIZE_CLUSTERS:
case NANITE_VISUALIZE_ASSEMBLIES:
Output.bDarkOutline = true;
break;
case NANITE_VISUALIZE_SCENE_Z_MIN:
case NANITE_VISUALIZE_SCENE_Z_MAX:
case NANITE_VISUALIZE_SCENE_Z_DELTA:
case NANITE_VISUALIZE_SCENE_Z_DECODED:
Output.bApplySobel = false;
Output.bApplyDebugShading = false;
break;
case NANITE_VISUALIZE_PICKING:
Output.bApplySobel = false;
break;
case NANITE_VISUALIZE_SHADOW_CASTERS:
Output.bApplySobel = false;
break;
}
// Let these features be externally disabled
Output.bApplySobel &= ShouldApplySobelFilter();
Output.bApplyDebugShading &= ShouldApplyDebugShading();
return Output;
}
float3 ApplySobelFilter(uint2 PixelPosXY, uint DepthInt, float3 Color, FVisualizeEffects Effects)
{
// Sobel edge detect depth
static const int SobelX[] =
{
1, 0, -1,
2, -2,
1, 0, -1
};
static const int SobelY[] =
{
1, 2, 1,
0, 0,
-1, -2, -1
};
static const uint2 UVSample[] =
{
{-1, 1}, {0, 1}, {1, 1},
{-1, 0}, {1, 0},
{-1, -1}, {0, -1}, {1, -1}
};
float3 DepthGradX = float3(0.0f, 0.0f, 0.0f);
float3 DepthGradY = float3(0.0f, 0.0f, 0.0f);
uint DepthIntCurrent;
uint VisibleClusterIndexCurrent;
uint TriIndexCurrent;
// Get the center tap first
UlongType VisPixelCurrent = VisBuffer64[PixelPosXY];
UnpackVisPixel(VisPixelCurrent, DepthIntCurrent, VisibleClusterIndexCurrent, TriIndexCurrent);
const float CenterDepth = asfloat(DepthIntCurrent);
const bool bInnerBorder = GetCompositeWithSceneDepth() && !Effects.bDarkOutline;
for (uint Tap = 0; Tap < 8; ++Tap)
{
VisPixelCurrent = VisBuffer64[PixelPosXY + UVSample[Tap]];
UnpackVisPixel(VisPixelCurrent, DepthIntCurrent, VisibleClusterIndexCurrent, TriIndexCurrent);
float TapDepth = asfloat(DepthIntCurrent);
// We generally only want outer outlines, so don't consider further depth than center
// Exception is when not applying the filter outside of Nanite pixels and Nanite borders sky or non-Nanite
TapDepth = (TapDepth == 0.0f && bInnerBorder) ? 0.0f : max(TapDepth, CenterDepth);
float SampleDensityDepth = log2( ConvertFromDeviceZ(TapDepth) + 1.0 ) * 10.0;
DepthGradX += SobelX[Tap] * SampleDensityDepth;
DepthGradY += SobelY[Tap] * SampleDensityDepth;
}
// Build outline from depth
float3 DepthOutline = max(abs(DepthGradX), abs(DepthGradY));
float3 CombineColor;
if( Effects.bDarkOutline )
CombineColor = Color * ( 1.0 - DepthOutline * 0.25 );
else
CombineColor = Color + DepthOutline * 0.25 * Effects.OutlineColor;
return saturate(CombineColor);
}
[numthreads(8, 8, 1)]
void VisualizeCS(uint3 DTID : SV_DispatchThreadID, uint3 GID : SV_GroupID)
{
const uint VisualizeMode = GetVisualizeMode();
const uint2 PickingPos = uint2(PickingPixelPos);
const uint2 PixelPos = DTID.xy + uint2(View.ViewRectMin.xy);
const uint2 TilePos = GID.xy; // 8x8 tile 2D coord
const FNaniteView NaniteView = GetNaniteView( 0 );
if(!IsPixelInRect(PixelPos, NaniteView.ViewRect))
return;
const UlongType VisPixel = VisBuffer64[PixelPos];
uint DepthInt;
uint VisibleClusterIndex;
uint TriIndex;
bool bIsImposter;
UnpackVisPixel(VisPixel, DepthInt, VisibleClusterIndex, TriIndex, bIsImposter);
float4 SvPosition = float4( PixelPos + 0.5, asfloat( DepthInt ), 1 );
FShadingMask UnpackedMask = UnpackShadingMask(ShadingMask[PixelPos]);
float3 Result = float3(0, 0, 0);
float Opacity = 1.0f;
const FVisualizeEffects Effects = GetVisualizeModeEffects(VisualizeMode);
if (VisualizeMode == NANITE_VISUALIZE_OVERDRAW)
{
uint DebugValueAdd = DbgBuffer32[PixelPos];
const float OverdrawScale = GetOverdrawScale();
const float OverdrawCount = DebugValueAdd; // Num of evaluations per pixel
const float OverdrawColor = 1 - exp2(-OverdrawCount * OverdrawScale);
Result = ColorMapInferno(OverdrawColor);
}
else if (VisibleClusterIndex != 0xFFFFFFFF && (!GetCompositeWithSceneDepth() || UnpackedMask.bIsNanitePixel))
{
// Nanite Pixel
UlongType DbgPixel = DbgBuffer64[PixelPos];
uint DebugDepthInt;
uint DebugValueMax;
UnpackDbgPixel(DbgPixel, DebugDepthInt, DebugValueMax);
uint DebugValueAdd = DbgBuffer32[PixelPos];
const uint PackedShadingMaterialFlags = ShadingBinData.Load<FNaniteShadingBinMeta>(UnpackedMask.ShadingBin * NANITE_SHADING_BIN_META_BYTES).MaterialFlags;
FNaniteMaterialFlags ShadingMaterialFlags = UnpackNaniteMaterialFlags(PackedShadingMaterialFlags);
FVisibleCluster VisibleCluster = GetVisibleCluster(VisibleClusterIndex);
FInstanceSceneData InstanceData = GetInstanceSceneDataUnchecked(VisibleCluster);
FInstanceDynamicData InstanceDynamicData = CalculateInstanceDynamicData(NaniteView, InstanceData);
FPrimitiveSceneData PrimitiveData = GetPrimitiveData(InstanceData.PrimitiveId);
FInstanceViewData InstanceViewData = GetInstanceViewData(InstanceData.InstanceId, NaniteView.SceneRendererPrimaryViewId);
FCluster Cluster = GetCluster(VisibleCluster.PageIndex, VisibleCluster.ClusterIndex);
const bool bEvaluateWPO = (VisibleCluster.Flags & NANITE_CULLING_FLAG_ENABLE_WPO) != 0 && !bIsImposter;
const bool bFallbackRaster = (VisibleCluster.Flags & NANITE_CULLING_FLAG_FALLBACK_RASTER) != 0;
uint3 TriIndices = DecodeTriangleIndices(Cluster, TriIndex);
FReconstructedVoxelData VoxelData;
BRANCH
if (Cluster.bVoxel)
{
VoxelData = ReconstructVoxel(NaniteView, PrimitiveData, InstanceData, InstanceViewData, VisibleCluster, Cluster, SvPosition, TriIndex);
TriIndices = VoxelData.VertIndex;
}
FNanitePostDeformVertex Verts[3];
FetchAndDeformLocalNaniteVerts<3>(PrimitiveData, InstanceData, InstanceViewData, Cluster, VisibleCluster, TriIndices, NANITE_MAX_UVS, Verts);
const float3 PointTranslatedWorld0 = mul( float4( Verts[0].Position, 1 ), InstanceDynamicData.LocalToTranslatedWorld ).xyz;
const float3 PointTranslatedWorld1 = mul( float4( Verts[1].Position, 1 ), InstanceDynamicData.LocalToTranslatedWorld ).xyz;
const float3 PointTranslatedWorld2 = mul( float4( Verts[2].Position, 1 ), InstanceDynamicData.LocalToTranslatedWorld ).xyz;
FBarycentrics Barycentrics;
if (!Cluster.bVoxel)
{
const float4 PointClip0 = mul( float4( PointTranslatedWorld0, 1 ), NaniteView.TranslatedWorldToClip );
const float4 PointClip1 = mul( float4( PointTranslatedWorld1, 1 ), NaniteView.TranslatedWorldToClip );
const float4 PointClip2 = mul( float4( PointTranslatedWorld2, 1 ), NaniteView.TranslatedWorldToClip );
const float2 PixelClip = (SvPosition.xy - NaniteView.ViewRect.xy) * NaniteView.ViewSizeAndInvSize.zw * float2(2, -2) + float2(-1, 1);
Barycentrics = CalculateTriangleBarycentrics(PixelClip, PointClip0, PointClip1, PointClip2, NaniteView.ViewSizeAndInvSize.zw);
}
else
{
Barycentrics.Value = float3(1,0,0);
Barycentrics.Value_dx = 0;
Barycentrics.Value_dy = 0;
}
float4 VertexColor = Lerp(Verts[0].Color, Verts[1].Color, Verts[2].Color, Barycentrics).Value;
TDual<float2> TexCoords[NANITE_MAX_UVS];
UNROLL
for (uint i = 0; i < NANITE_MAX_UVS; i++)
{
TexCoords[i] = Lerp(Verts[0].TexCoords[i], Verts[1].TexCoords[i], Verts[2].TexCoords[i], Barycentrics);
}
uint RasterBin = GetMaterialRasterBin(Cluster, InstanceData.PrimitiveId, MeshPassIndex, TriIndex, RegularMaterialRasterBinCount, bFallbackRaster);
const FNaniteMaterialFlags RasterMaterialFlags = UnpackNaniteMaterialFlags(RasterBinMeta[RasterBin].MaterialFlags_DepthBlock & 0xFFFFu);
RasterBin = RemapRasterBin(RasterBin, RenderFlags, RasterMaterialFlags, Cluster.bVoxel, InstanceViewData.bIsDeforming);
const int HierarchyOffset = InstanceData.NaniteHierarchyOffset;
// Note: The mode is no longer a bitmask at this point, just a single visualization mode.
if (VisualizeMode == NANITE_VISUALIZE_TESSELLATION)
{
const uint SubPatch = BitFieldExtractU32(DebugValueMax, 8, 8);
const uint MicroTri = BitFieldExtractU32(DebugValueMax, 8, 16);
const float3 PatchColor = IntToColor(TriIndex).x * 0.5 + 0.5;
float3 SubPatchColor = IntToColor(SubPatch << 8u) * 0.6 + 0.4;
float3 MicroTriColor = IntToColor(MicroTri) * 0.6 + 0.4;
MicroTriColor = lerp(MicroTriColor, dot(MicroTriColor, float3(0.3, 0.6, 0.1)), 0.5);
SubPatchColor /= max3(SubPatchColor.x, SubPatchColor.y, SubPatchColor.z);
Result = MicroTriColor * SubPatchColor * PatchColor;
}
else if (VisualizeMode == NANITE_VISUALIZE_TRIANGLES)
{
const uint SubPatch = BitFieldExtractU32(DebugValueMax, 8, 8);
const uint MicroTri = BitFieldExtractU32(DebugValueMax, 8, 16);
if( MicroTri != 0u )
{
TriIndex = MurmurAdd( TriIndex, SubPatch );
TriIndex = MurmurAdd( TriIndex, MicroTri );
}
Result = IntToColor( TriIndex );
Result = Result * 0.8 + 0.2;
}
else if (VisualizeMode == NANITE_VISUALIZE_PATCHES)
{
Result = IntToColor(TriIndex);
Result = Result * 0.8 + 0.2;
}
else if (VisualizeMode == NANITE_VISUALIZE_VOXELS)
{
Result = IntToColor(TriIndex);
Result = lerp(Result, Cluster.bVoxel ? float3(0, 1, 0) : float3(1, 0, 0), 0.5f);
}
else if (VisualizeMode == NANITE_VISUALIZE_CLUSTERS)
{
Result = IntToColor( MurmurAdd( VisibleCluster.PageIndex, VisibleCluster.ClusterIndex ) );
Result = Result * 0.8 + 0.2;
}
else if (VisualizeMode == NANITE_VISUALIZE_GROUPS)
{
Result = IntToColor(Cluster.GroupIndex);
}
else if (VisualizeMode == NANITE_VISUALIZE_PAGES)
{
Result = IntToColor(VisibleCluster.PageIndex);
}
else if (VisualizeMode == NANITE_VISUALIZE_ASSEMBLIES)
{
if (IsAssemblyPartCluster(VisibleCluster))
{
// Get the "local" assembly transform index from the assembly metadata
const uint HierarchyTransformIndex = AssemblyMeta.Load(VisibleCluster.AssemblyTransformIndex * 4u);
Result = IntToColor(HierarchyTransformIndex) * 0.8 + 0.2;
}
else if (PrimitiveData.NaniteAssemblyTransformOffset != 0xFFFFFFFFu)
{
Result = float3(0, 0, 0.2f);
}
else
{
Result = float3(0.15f, 0.15f, 0.15f);
}
}
else if (VisualizeMode == NANITE_VISUALIZE_SKINNING)
{
if (Cluster.bSkinning && RasterMaterialFlags.bSkinnedMesh)
{
// Green = active, Red = disabled
Result = InstanceViewData.bIsDeforming ?
float3(0.0f, 1.0f, 0.0f) : float3(1.0f, 0.0f, 0.0f);
}
else
{
// Grey = unskinned
Result = float3(0.15f, 0.15f, 0.15f);
}
}
else if (VisualizeMode == NANITE_VISUALIZE_PRIMITIVES)
{
Result = IntToColor(InstanceData.PrimitiveId) * 0.8;
}
else if (VisualizeMode == NANITE_VISUALIZE_INSTANCES)
{
Result = IntToColor(VisibleCluster.InstanceId) * 0.8;
}
else if (VisualizeMode == NANITE_VISUALIZE_RASTER_MODE)
{
const uint RasterMode = BitFieldExtractU32(DebugValueMax, 8, 0);
uint VisValue = 0u;
if (RasterMode == 1u)
{
// Hardware Raster
VisValue = 1u;
}
else if (RasterMode == 2u)
{
// Software Raster
VisValue = 2u;
}
else if (RasterMode == 3u)
{
// Software Tessellation
VisValue = 6000u;
}
Result = (IntToColor(VisValue) * 0.75 + 0.25) * (IntToColor(0).x * 0.5 + 0.5);
}
else if (VisualizeMode == NANITE_VISUALIZE_RASTER_BINS)
{
Result = IntToColor(RasterBin) * 0.8 + 0.2;
}
else if (VisualizeMode == NANITE_VISUALIZE_SHADING_BINS)
{
const uint ShadingBin = GetMaterialShadingBin(Cluster, InstanceData.PrimitiveId, MeshPassIndex, TriIndex);
Result = IntToColor(ShadingBin) * 0.8 + 0.2;
}
else if (VisualizeMode == NANITE_VISUALIZE_HIERARCHY_OFFSET)
{
Result = IntToColor(HierarchyOffset);
}
else if (VisualizeMode == NANITE_VISUALIZE_MATERIAL_COUNT)
{
Result = IntToColor(GetMaterialCount(Cluster));
}
else if (VisualizeMode == NANITE_VISUALIZE_MATERIAL_MODE)
{
Result = IsMaterialFastPath(Cluster) ? float3(0, 1, 0) : float3(1, 0, 0);
}
else if (VisualizeMode == NANITE_VISUALIZE_MATERIAL_INDEX)
{
Result = IntToColor(GetRelativeMaterialIndex(Cluster, TriIndex));
}
else if (VisualizeMode == NANITE_VISUALIZE_SHADING_WRITE_MASK)
{
const uint WriteMask = BitFieldExtractU32(PackedShadingMaterialFlags, 8u, 24u);
Result = IntToColor(WriteMask);
}
else if (VisualizeMode == NANITE_VISUALIZE_SCENE_Z_MIN || VisualizeMode == NANITE_VISUALIZE_SCENE_Z_MAX || VisualizeMode == NANITE_VISUALIZE_SCENE_Z_DELTA)// || VisualizeMode == NANITE_VISUALIZE_SCENE_Z_MASK)
{
const uint4 ZLayout = SceneZLayout[PixelPos];
float2 MinMax = float2(f16tof32(ZLayout.y), f16tof32(ZLayout.y >> 16u));
if (VisualizeMode == NANITE_VISUALIZE_SCENE_Z_MIN)
{
Result = float3(pow(MinMax.x, 0.11f), 0.0f, 0.0f);
}
else if (VisualizeMode == NANITE_VISUALIZE_SCENE_Z_MAX)
{
Result = float3(pow(MinMax.y, 0.11f), 0.0f, 0.0f);
}
else if (VisualizeMode == NANITE_VISUALIZE_SCENE_Z_DELTA)
{
Result = float3(pow(MinMax.y - MinMax.x, 0.11f), 0.0f, 0.0f);
}
//else if (VisualizeMode == NANITE_VISUALIZE_SCENE_Z_MASK)
//{
// Result = IntToColor((uint)ZLayout.z);
//}
}
else if (VisualizeMode == NANITE_VISUALIZE_SCENE_Z_DECODED)
{
const float ZDecoded = SceneZDecoded[PixelPos];
Result = float3(pow(ZDecoded, 0.11f), 0.0f, 0.0f);
}
#if USE_EDITOR_SHADERS
else if (VisualizeMode == NANITE_VISUALIZE_HIT_PROXY_DEPTH)
{
if ((InstanceData.Flags & INSTANCE_SCENE_DATA_FLAG_HAS_EDITOR_DATA) != 0u)
{
Result = IntToColor(InstanceData.EditorData.HitProxyPacked);
}
else
{
Result = IntToColor(GetMaterialHitProxyId(Cluster, InstanceData.PrimitiveId, TriIndex, MaterialHitProxyTable));
}
}
#endif
else if (VisualizeMode == NANITE_VISUALIZE_NO_DERIVATIVE_OPS)
{
const uint ModeValue = select(ShadingMaterialFlags.bNoDerivativeOps, 2, 1);
Result = (IntToColor(ModeValue) * 0.75 + 0.25) * (IntToColor(TriIndex).x * 0.5 + 0.5);
}
else if (VisualizeMode == NANITE_VISUALIZE_FAST_CLEAR_TILES)
{
const uint TileData = FastClearTileVis[PixelPos];
// Tile data has a counter for each MRT that is still marked as "clear" in
// the meta data, indicating that the next fast clear eliminate will fully
// write through this memory (slow tile).
Result = ColorMapInferno(float(TileData) / float(GetShadingExportCount()));
}
else if (VisualizeMode == NANITE_VISUALIZE_NANITE_MASK)
{
Result = float3(0, 1, 0);
Opacity = 0.5f;
}
else if (VisualizeMode == NANITE_VISUALIZE_EVALUATE_WORLD_POSITION_OFFSET)
{
const bool bAlwaysEvaluateWPO = (PrimitiveData.Flags & PRIMITIVE_SCENE_DATA_FLAG_HAS_ALWAYS_EVALUATE_WPO_MATERIALS) != 0u;
if (bAlwaysEvaluateWPO)
{
Result = float3(1, 1, 0);
}
else if (bEvaluateWPO)
{
Result = float3(0, 1, 0);
}
else
{
Result = float3(1, 0, 0);
}
}
else if (VisualizeMode == NANITE_VISUALIZE_PIXEL_PROGRAMMABLE_RASTER)
{
bool bHighlight = false;
switch (GetPixelProgrammableVisMode())
{
case NANITE_PIXEL_PROG_VIS_MODE_DEFAULT:
bHighlight = RasterMaterialFlags.bPixelDiscard || RasterMaterialFlags.bPixelDepthOffset;
break;
case NANITE_PIXEL_PROG_VIS_MODE_MASKED_ONLY:
bHighlight = RasterMaterialFlags.bPixelDiscard;
break;
case NANITE_PIXEL_PROG_VIS_MODE_PDO_ONLY:
bHighlight = RasterMaterialFlags.bPixelDepthOffset;
break;
default:
break;
}
if (bFallbackRaster &&
(PrimitiveData.PixelProgrammableDistanceSquared > 0.0f ||
PrimitiveData.MaterialDisplacementFadeOutSize > 0.0f))
{
// We've disabled pixel programmable for this cluster
bHighlight = false;
}
Result = select(bHighlight, float3(1, 0, 0), float3(0.1, 0, 0.3));
}
else if (VisualizeMode == NANITE_VISUALIZE_LIGHTMAP_UVS)
{
const float2 LightmapUVs = TexCoords[PrimitiveData.LightmapUVIndex].Value;
Result = float3(LightmapUVs.x, LightmapUVs.y, 0);
}
else if (VisualizeMode == NANITE_VISUALIZE_LIGHTMAP_UV_INDEX)
{
Result = IntToColor(PrimitiveData.LightmapUVIndex);
}
else if (VisualizeMode == NANITE_VISUALIZE_LIGHTMAP_DATA_INDEX)
{
Result = IntToColor(PrimitiveData.LightmapDataIndex);
}
else if (VisualizeMode == NANITE_VISUALIZE_POSITION_BITS)
{
const uint NumBits = Cluster.PosBits.x + Cluster.PosBits.y + Cluster.PosBits.z;
if (NumBits <= 30)
{
Result = lerp(float3(0.0f, 1.0f, 0.0f), float3(1.0f, 1.0f, 1.0f), NumBits / 30.0f);
}
else
{
Result = lerp(float3(1.0f, 1.0f, 1.0f), float3(1.0f, 0.0f, 0.0f), (NumBits - 30) / (float)(3 * 16 - 30));
}
}
else if (VisualizeMode == NANITE_VISUALIZE_SHADOW_CASTERS)
{
FShadowCasterDrawParameters ShadowCasterDrawParameters = GetShadowCasterDrawParameters(NaniteView, InstanceData, InstanceViewData, InstanceDynamicData, PrimitiveData);
Result = ShadowCasterDrawParameters.Color;
}
else if (VisualizeMode == NANITE_VISUALIZE_DISPLACEMENT_SCALE)
{
const float DisplacementCenter = RasterBinMeta[RasterBin].MaterialDisplacementParams.Center;
const float DisplacementMagnitude = RasterBinMeta[RasterBin].MaterialDisplacementParams.Magnitude;
const float MinDisplacement = (0.0f - DisplacementCenter) * DisplacementMagnitude;
const float MaxDisplacement = (1.0f - DisplacementCenter) * DisplacementMagnitude;
const float AbsDelta = abs(MaxDisplacement - MinDisplacement);
Result = GreenToRedTurbo(AbsDelta / 30.0f);
}
else if (VisualizeMode == NANITE_VISUALIZE_VERTEX_COLOR)
{
#if USE_EDITOR_SHADERS
if (GetMeshPaintingShowMode() == NANITE_MESH_PAINTING_SHOW_SELECTED && !IsInstanceSelected(InstanceData, VisibleCluster, TriIndex))
{
Opacity = 0;
}
#endif
Result = LinearToPostTonemapSpace(ApplyMeshPaintingChannelMode(GetMeshPaintingChannelMode(), VertexColor));
}
else if (VisualizeMode == NANITE_VISUALIZE_MESH_PAINT_TEXTURE)
{
#if USE_EDITOR_SHADERS
if (GetMeshPaintingShowMode() == NANITE_MESH_PAINTING_SHOW_SELECTED && !IsInstanceSelected(InstanceData, VisibleCluster, TriIndex))
{
Opacity = 0;
}
#endif
if (GetMeshPaintingShowMode() == NANITE_MESH_PAINTING_SHOW_ALL && GetMeshPaintingTextureMode() == NANITE_MESH_PAINTING_TEXTURE_DEFAULT && !GetMeshPaintTextureDescriptorIsValid(PrimitiveData))
{
Opacity = 0;
}
if (Opacity > 0)
{
if (GetMeshPaintingTextureMode() == NANITE_MESH_PAINTING_TEXTURE_DEFAULT)
{
// Sample the shared mesh paint virtual texture atlas.
TDual<float2> UV = TexCoords[GetMeshPaintTextureCoordinateIndex(PrimitiveData)];
float2 SVPositionXY = (float2)PixelPos + 0.5f;
FVirtualTextureFeedbackParams VirtualTextureFeedback;
InitializeVirtualTextureFeedback(VirtualTextureFeedback);
VTPageTableResult PageTableResult = TextureLoadVirtualPageTableGrad(Scene.MeshPaint.PageTableTexture, VTPageTableUniform_Unpack(GetMeshPaintTextureDescriptor(PrimitiveData)), UV.Value, VTADDRESSMODE_CLAMP, VTADDRESSMODE_CLAMP, UV.Value_dx, UV.Value_dy, SVPositionXY, VirtualTextureFeedback);
float4 Color = TextureVirtualSample(Scene.MeshPaint.PhysicalTexture, View.SharedBilinearClampedSampler, PageTableResult, 0, VTUniform_Unpack(Scene.MeshPaint.PackedUniform));
FinalizeVirtualTextureFeedback(VirtualTextureFeedback, float4(SVPositionXY, 0, 0), View.VTFeedbackBuffer);
Result = LinearToPostTonemapSpace(ApplyMeshPaintingChannelMode(GetMeshPaintingChannelMode(), Color));
}
else
{
// Sample a single selected mesh paint texture.
TDual<float2> UV = TexCoords[MeshPaintTextureCoordinate];
float4 Color = MeshPaintTexture.SampleGrad(View.SharedBilinearClampedSampler, UV.Value, UV.Value_dx, UV.Value_dy);
Result = LinearToPostTonemapSpace(ApplyMeshPaintingChannelMode(GetMeshPaintingChannelMode(), Color));
}
}
}
else if (VisualizeMode == NANITE_VISUALIZE_PICKING)
{
const UlongType PickedVisPixel = VisBuffer64[PickingPos];
uint PickedDepthInt;
uint PickedVisibleClusterIndex;
uint PickedTriIndex;
UnpackVisPixel(PickedVisPixel, PickedDepthInt, PickedVisibleClusterIndex, PickedTriIndex);
FVisibleCluster PickedVisibleCluster = GetVisibleCluster(PickedVisibleClusterIndex);
FInstanceSceneData PickedInstanceData = GetInstanceSceneDataUnchecked(PickedVisibleCluster);
bool bPicked = false;
switch (GetPickingDomain())
{
case NANITE_PICKING_DOMAIN_TRIANGLE:
bPicked = (VisibleClusterIndex == PickedVisibleClusterIndex && PickedTriIndex == TriIndex);
Result = IntToColor(TriIndex) * 0.8 + 0.2;
break;
case NANITE_PICKING_DOMAIN_CLUSTER:
bPicked = (VisibleClusterIndex == PickedVisibleClusterIndex);
Result = IntToColor(VisibleCluster.ClusterIndex) * 0.8;
break;
case NANITE_PICKING_DOMAIN_INSTANCE:
bPicked = (VisibleCluster.InstanceId == PickedVisibleCluster.InstanceId);
Result = IntToColor(VisibleCluster.InstanceId) * 0.8;
break;
case NANITE_PICKING_DOMAIN_PRIMITIVE:
bPicked = (InstanceData.PrimitiveId == PickedInstanceData.PrimitiveId);
Result = IntToColor(InstanceData.PrimitiveId) * 0.8;
break;
default:
// Invalid picking domain
break;
}
if (bPicked)
{
Result = float3(1.0f, 0.0f, 1.0f);
}
else
{
Result *= 0.3f;
}
}
if (Effects.bApplyDebugShading && !Cluster.bVoxel)
{
// Apply N dot V shading using face normal
// TODO: Debug shade voxels? N will need to be done differently here
const float3 N = normalize( cross( PointTranslatedWorld2 - PointTranslatedWorld0, PointTranslatedWorld1 - PointTranslatedWorld0 ) );
float3 PixelTranslatedWorld =
PointTranslatedWorld0 * Barycentrics.Value.x +
PointTranslatedWorld1 * Barycentrics.Value.y +
PointTranslatedWorld2 * Barycentrics.Value.z;
float3 V = normalize( PixelTranslatedWorld - View.TranslatedWorldCameraOrigin );
Result *= abs( dot( N, V ) ) * 0.7 + 0.3;
}
}
else
{
// Non-Nanite Pixel
if (GetVisualizeMode() == NANITE_VISUALIZE_NANITE_MASK)
{
if (SceneDepth[PixelPos] > 0.0f) // only visualize written fragments
{
Result = float3(1, 0, 0);
Opacity = 0.5f;
}
}
else if (GetVisualizeMode() == NANITE_VISUALIZE_VERTEX_COLOR
|| GetVisualizeMode() == NANITE_VISUALIZE_MESH_PAINT_TEXTURE
|| GetVisualizeMode() == NANITE_VISUALIZE_SHADOW_CASTERS)
{
Opacity = 0.0f;
}
}
if (Effects.bApplySobel && (!GetCompositeWithSceneDepth() || UnpackedMask.bIsNanitePixel))
{
Result = ApplySobelFilter(PixelPos, DepthInt, Result, Effects);
}
DebugOutput[PixelPos] = float4(Result, Opacity);
}
[numthreads(1, 1, 1)]
void PickingCS(uint3 DTID : SV_DispatchThreadID, uint3 GID : SV_GroupID)
{
FNanitePickingFeedback FeedbackResults = (FNanitePickingFeedback)0;
const uint2 PickingPos = uint2(PickingPixelPos);
FeedbackResults.PixelX = PickingPos.x;
FeedbackResults.PixelY = PickingPos.y;
const UlongType PickedPixel = VisBuffer64[PickingPos];
uint DepthInt;
uint VisibleClusterIndex;
uint TriIndex;
UnpackVisPixel(PickedPixel, DepthInt, VisibleClusterIndex, TriIndex);
FNaniteView NaniteView = GetNaniteView(0);
FShadingMask UnpackedMask = UnpackShadingMask(ShadingMask[PickingPos]);
if (VisibleClusterIndex != 0xFFFFFFFFu && UnpackedMask.bIsNanitePixel)
{
UlongType DbgPixel = DbgBuffer64[PickingPos];
uint DebugDepthInt;
uint DebugValueMax;
UnpackDbgPixel(DbgPixel, DebugDepthInt, DebugValueMax);
uint DebugValueAdd = DbgBuffer32[PickingPos];
FVisibleCluster VisibleCluster = GetVisibleCluster(VisibleClusterIndex);
FInstanceSceneData InstanceData = GetInstanceSceneDataUnchecked(VisibleCluster);
FInstanceDynamicData InstanceDynamicData = CalculateInstanceDynamicData(NaniteView, InstanceData);
FInstanceViewData InstanceViewData = GetInstanceViewData(InstanceData.InstanceId, NaniteView.SceneRendererPrimaryViewId);
FPrimitiveSceneData PrimitiveData = GetPrimitiveData(InstanceData.PrimitiveId);
FCluster Cluster = GetCluster(VisibleCluster.PageIndex, VisibleCluster.ClusterIndex);
const bool bWPOEnabled = (VisibleCluster.Flags & NANITE_CULLING_FLAG_ENABLE_WPO) != 0;
const bool bFallbackRaster = (VisibleCluster.Flags & NANITE_CULLING_FLAG_FALLBACK_RASTER) != 0;
uint RasterBin = GetMaterialRasterBin(Cluster, InstanceData.PrimitiveId, MeshPassIndex, TriIndex, RegularMaterialRasterBinCount, bFallbackRaster);
const FNaniteMaterialFlags RasterMaterialFlags = UnpackNaniteMaterialFlags(RasterBinMeta[RasterBin].MaterialFlags_DepthBlock & 0xFFFFu);
RasterBin = RemapRasterBin(RasterBin, RenderFlags, RasterMaterialFlags, Cluster.bVoxel, InstanceViewData.bIsDeforming);
const uint AssemblyTransformIndex = IsAssemblyPartCluster(VisibleCluster) ? AssemblyMeta.Load(VisibleCluster.AssemblyTransformIndex * 4u) : (uint)-1;
FeedbackResults.PrimitiveId = InstanceData.PrimitiveId;
FeedbackResults.InstanceId = VisibleCluster.InstanceId;
FeedbackResults.PersistentIndex = PrimitiveData.PersistentPrimitiveIndex;
FeedbackResults.ClusterIndex = VisibleCluster.ClusterIndex;
FeedbackResults.GroupIndex = Cluster.GroupIndex;
FeedbackResults.PageIndex = VisibleCluster.PageIndex;
FeedbackResults.TriangleIndex = TriIndex;
FeedbackResults.DepthInt = DepthInt;
FeedbackResults.RasterMode = DebugValueMax;
FeedbackResults.RasterBin = RasterBin;
FeedbackResults.ShadingBin = GetMaterialShadingBin(Cluster, InstanceData.PrimitiveId, MeshPassIndex, TriIndex);
FeedbackResults.MaterialIndex = GetRelativeMaterialIndex(Cluster, TriIndex);
FeedbackResults.MaterialCount = GetMaterialCount(Cluster);
FeedbackResults.MaterialMode = IsMaterialFastPath(Cluster) ? 0u : 1u;
FeedbackResults.HierarchyOffset = InstanceData.NaniteHierarchyOffset;
FeedbackResults.RuntimeResourceID = InstanceData.NaniteRuntimeResourceID;
FeedbackResults.AssemblyTransformOffset = PrimitiveData.NaniteAssemblyTransformOffset;
FeedbackResults.AssemblyTransformIndex = AssemblyTransformIndex;
const float4 ApproximateClusterBoundsColor = ColorCyan;
const float4 SkinnedClusterBoundsColor = ColorLightGreen;
const float4 ClusterBoundsColor = ColorLightRed;
const float4 InstanceBoundsColor = ColorOrange;
const float4 ClusterLODBoundsColor = ColorYellow;
FShaderPrintContext ShaderPrint = InitShaderPrintContext(true, float2(0.15, 0.1));
Print( ShaderPrint, TEXT("LODError "), FontYellow );
Print( ShaderPrint, Cluster.LODError );
Newline( ShaderPrint );
Print( ShaderPrint, TEXT("EdgeLength "), FontYellow );
Print( ShaderPrint, Cluster.EdgeLength );
Newline( ShaderPrint );
const float4x4 LocalToTranslatedWorld = DFFastToTranslatedWorld(InstanceData.LocalToWorld, NaniteView.PreViewTranslation);
// If WPO and/or material displacement is enabled, the cluster bounds will have been dilated to account for max displacement
const float3 WPOExtent = GetLocalMaxWPOExtent(PrimitiveData, InstanceData, bWPOEnabled);
const float DisplacementExtent = GetMaxMaterialDisplacementExtent(PrimitiveData, bFallbackRaster, false /* bIsShadowPass */);
FNodeCullingBounds ClusterCullingBounds = InitNodeCullingBounds(InstanceData, InstanceViewData, VisibleCluster, Cluster);
FNodeCullingBounds OrgClusterCullingBounds = ClusterCullingBounds;
// Pre-Skinned Bounds
AddOBBTWS(
ShaderPrint,
ClusterCullingBounds.BoxCenter - ClusterCullingBounds.BoxExtent,
ClusterCullingBounds.BoxCenter + ClusterCullingBounds.BoxExtent,
ClusterBoundsColor,
LocalToTranslatedWorld
);
const bool bIsSkinned = (PrimitiveData.Flags & PRIMITIVE_SCENE_DATA_FLAG_SKINNED_MESH) != 0 && Cluster.bSkinning;
FSkinningHeader SkinningHeader = (FSkinningHeader)0;
BRANCH
if (bIsSkinned)
{
SkinningHeader = LoadSkinningHeader(InstanceData.PrimitiveId);
FBoneInfluenceHeader BoneInfluenceHeader = GetBoneInfluenceHeader(Cluster);
float3 MinBounds = float( 999999999.0f).xxx;
float3 MaxBounds = float(-999999999.0f).xxx;
for (uint VertexID = 0; VertexID < Cluster.NumVerts; ++VertexID)
{
float3 PointLocal = FetchLocalNaniteVertexPosition(InstanceData, Cluster, VisibleCluster, VertexID);
float3 PointSkinned = float3(0.0f, 0.0f, 0.0f);
LOOP
for (uint InfluenceIndex = 0; InfluenceIndex < BoneInfluenceHeader.NumVertexBoneInfluences; ++InfluenceIndex)
{
uint BoneIndex = 0;
float BoneWeight = 0.0f;
DecodeVertexBoneInfluence(BoneInfluenceHeader, VertexID, InfluenceIndex, BoneIndex, BoneWeight);
float4x3 CurrentBoneTransform = LoadSkinningBoneTransform(SkinningHeader.TransformBufferOffset + (InstanceData.SkinningData * SkinningHeader.MaxTransformCount) + BoneIndex);
PointSkinned += mul(float4(PointLocal, 1.0f), CurrentBoneTransform) * BoneWeight;
}
MinBounds = min(MinBounds, PointSkinned);
MaxBounds = max(MaxBounds, PointSkinned);
}
ClusterCullingBounds.BoxCenter = (MaxBounds + MinBounds) * 0.5f;
ClusterCullingBounds.BoxExtent = (MaxBounds - MinBounds) * 0.5f;
}
BRANCH
if ((PrimitiveData.Flags & PRIMITIVE_SCENE_DATA_FLAG_SPLINE_MESH) != 0 &&
(InstanceData.Flags & INSTANCE_SCENE_DATA_FLAG_HAS_PAYLOAD_EXTENSION) != 0)
{
// Only show the spline mesh debug stuff when picking clusters
ShaderPrint.bIsActive = GetPickingDomain() == NANITE_PICKING_DOMAIN_CLUSTER;
const FSplineMeshShaderParams SplineMeshParams = SplineMeshLoadParamsFromInstancePayload(InstanceData);
const FSplineMeshDeformedLocalBounds NewBounds = SplineMeshDeformLocalBoundsDebug(
SplineMeshParams,
ShaderPrint,
LocalToTranslatedWorld,
ClusterCullingBounds.BoxCenter,
ClusterCullingBounds.BoxExtent
);
ClusterCullingBounds.BoxCenter = NewBounds.BoundsCenter;
ClusterCullingBounds.BoxExtent = NewBounds.BoundsExtent;
ClusterCullingBounds.Sphere = SplineMeshDeformLODSphereBounds(SplineMeshParams, ClusterCullingBounds.Sphere);
ShaderPrint.bIsActive = true;
}
ClusterCullingBounds.BoxExtent += WPOExtent + DisplacementExtent.xxx;
// Skinned Bounds
AddOBBTWS(
ShaderPrint,
ClusterCullingBounds.BoxCenter - ClusterCullingBounds.BoxExtent,
ClusterCullingBounds.BoxCenter + ClusterCullingBounds.BoxExtent,
SkinnedClusterBoundsColor,
LocalToTranslatedWorld
);
// Estimated Skinned Bounds
BRANCH
if (bIsSkinned)
{
SkinningHeader = LoadSkinningHeader(InstanceData.PrimitiveId);
FBoneInfluenceHeader BoneInfluenceHeader = GetBoneInfluenceHeader(Cluster);
ClusterCullingBounds = OrgClusterCullingBounds;
SkinClusterBounds(Cluster, InstanceData, SkinningHeader, ClusterCullingBounds.BoxCenter, ClusterCullingBounds.BoxExtent);
AddOBBTWS(
ShaderPrint,
ClusterCullingBounds.BoxCenter - ClusterCullingBounds.BoxExtent,
ClusterCullingBounds.BoxCenter + ClusterCullingBounds.BoxExtent,
ApproximateClusterBoundsColor,
LocalToTranslatedWorld
);
}
AddReferentialTWS(ShaderPrint, LocalToTranslatedWorld, 50.f);
if (GetPickingDomain() == NANITE_PICKING_DOMAIN_INSTANCE)
{
const float3 InstanceBoxBoundsCenter = InstanceData.LocalBoundsCenter;
const float3 InstanceBoxBoundsExtent = InstanceData.LocalBoundsExtent;
AddOBBTWS(
ShaderPrint,
InstanceBoxBoundsCenter - InstanceBoxBoundsExtent,
InstanceBoxBoundsCenter + InstanceBoxBoundsExtent,
InstanceBoundsColor,
LocalToTranslatedWorld
);
}
// Only show the cluster LOD bounds with cluster picking to reduce visual noise
if (GetPickingDomain() == NANITE_PICKING_DOMAIN_CLUSTER)
{
AddSphereTWS(
ShaderPrint,
mul(float4(ClusterCullingBounds.Sphere.xyz, 1), LocalToTranslatedWorld).xyz,
ClusterCullingBounds.Sphere.w * InstanceData.NonUniformScale.w,
ClusterLODBoundsColor,
32
);
}
}
else
{
FeedbackResults.PrimitiveId = INVALID_PRIMITIVE_ID;
}
FeedbackBuffer[0] = FeedbackResults;
}