// 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 DebugOutput; Texture2D VisBuffer64; Texture2D DbgBuffer64; Texture2D DbgBuffer32; Texture2D SceneDepth; Texture2D SceneZDecoded; Texture2D SceneZLayout; Texture2D ShadingMask; Texture2D FastClearTileVis; Texture2D MeshPaintTexture; StructuredBuffer 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 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(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 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 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 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; }