// 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 "../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; 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; // TODO: Try N.V to improve shading look float3 ApplySobelFilter(uint2 PixelPosXY, uint DepthInt, float3 Color, float3 OutlineColor, bool bDarkOutline) { // Sobel edge detect depth static int SobelX[] = { 1, 0, -1, 2, 0, -2, 1, 0, -1 }; static int SobelY[] = { 1, 2, 1, 0, 0, 0, -1, -2, -1 }; static uint2 UVSample[] = { {-1, 1}, {0, 1}, {1, 1}, {-1, 0}, {0, 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; for (uint Tap = 0; Tap < 9; ++Tap) { const UlongType VisPixelCurrent = VisBuffer64[PixelPosXY + UVSample[Tap]]; UnpackVisPixel(VisPixelCurrent, DepthIntCurrent, VisibleClusterIndexCurrent, TriIndexCurrent); float SampleDensityDepth = log2( ConvertFromDeviceZ(asfloat(DepthIntCurrent)) + 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( bDarkOutline ) CombineColor = Color * ( 1.0 - DepthOutline * 0.25 ); else CombineColor = Color + DepthOutline * 0.25 * OutlineColor; return saturate(CombineColor); } 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 != 0; } [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 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 ); FNaniteView NaniteView = GetNaniteView( 0 ); FShadingMask UnpackedMask = UnpackShadingMask(ShadingMask[PixelPos]); float3 Result = float3(0, 0, 0); float3 OutlineColor = 1; float Opacity = 1.0f; bool bDarkOutline = false; bool bApplySobel = true; 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 PackedMaterialFlags = ShadingBinData.Load(UnpackedMask.ShadingBin * NANITE_SHADING_BIN_META_BYTES).MaterialFlags; FNaniteMaterialFlags MaterialFlags = UnpackNaniteMaterialFlags(PackedMaterialFlags); FVisibleCluster VisibleCluster = GetVisibleCluster(VisibleClusterIndex); FInstanceSceneData InstanceData = GetInstanceSceneDataUnchecked(VisibleCluster); FInstanceDynamicData InstanceDynamicData = CalculateInstanceDynamicData(NaniteView, InstanceData); FPrimitiveSceneData PrimitiveData = GetPrimitiveData(InstanceData.PrimitiveId); FCluster Cluster = GetCluster(VisibleCluster.PageIndex, VisibleCluster.ClusterIndex); const bool bIsSkinned = (PrimitiveData.Flags & PRIMITIVE_SCENE_DATA_FLAG_SKINNED_MESH) != 0 && Cluster.bSkinning; FNaniteSkinningHeader SkinningHeader = (FNaniteSkinningHeader)0; FBoneInfluenceHeader BoneInfluenceHeader = GetBoneInfluenceHeader(Cluster); BRANCH if (bIsSkinned) { SkinningHeader = LoadNaniteSkinningHeader(InstanceData.PrimitiveId); } uint3 TriIndices = DecodeTriangleIndices(Cluster, TriIndex); if( Cluster.bVoxel ) { float4x4 TranslatedWorldToLocal = DFFastToTranslatedWorld( InstanceData.WorldToLocal, NaniteView.PreViewTranslation ); float4 PixelTranslatedWorld = mul( SvPosition, NaniteView.SVPositionToTranslatedWorld ); float4 PixelLocal = mul( PixelTranslatedWorld, TranslatedWorldToLocal ); float3 VoxelPos = PixelLocal.xyz / ( PixelLocal.w * Cluster.LODError ); FBrick Brick = DecodeBrick( Cluster, TriIndex ); VoxelPos -= Brick.StartPos.xyz; uint3 Voxel = (uint3)floor( VoxelPos ); uint VoxelIndex = Voxel.x + Voxel.y * 4 + Voxel.z * 16; uint Mask = 1u << ( VoxelIndex & 31 ); Mask -= 1; const uint2 BrickBits = reversebits( Brick.ReverseBrickBits.yx ); //TODO: Fix up logic to work on reversed bits directly uint VertIndex = Brick.VertOffset; VertIndex += countbits( BrickBits.x & ( VoxelIndex < 32 ? Mask : ~0u ) ); VertIndex += countbits( BrickBits.y & ( VoxelIndex < 32 ? 0 : Mask ) ); TriIndices = VertIndex; } FNaniteLocalVertex LocalVerts[3]; FetchLocalNaniteTriangle(InstanceData, Cluster, VisibleCluster, TriIndices, NANITE_MAX_UVS, LocalVerts); FNaniteTangentBasis TangentBasis0 = MakeTangentBasis(LocalVerts[0].RawAttributeData); FNaniteTangentBasis TangentBasis1 = MakeTangentBasis(LocalVerts[1].RawAttributeData); FNaniteTangentBasis TangentBasis2 = MakeTangentBasis(LocalVerts[2].RawAttributeData); BRANCH if (bIsSkinned) { float3 SkinnedPosition[3]; SkinnedPosition[0] = float3(0.0f, 0.0f, 0.0f); SkinnedPosition[1] = float3(0.0f, 0.0f, 0.0f); SkinnedPosition[2] = float3(0.0f, 0.0f, 0.0f); float3 SkinnedNormal[3]; SkinnedNormal[0] = float3(0.0f, 0.0f, 0.0f); SkinnedNormal[1] = float3(0.0f, 0.0f, 0.0f); SkinnedNormal[2] = float3(0.0f, 0.0f, 0.0f); float3 SkinnedTangent[3]; SkinnedTangent[0] = float3(0.0f, 0.0f, 0.0f); SkinnedTangent[1] = float3(0.0f, 0.0f, 0.0f); SkinnedTangent[2] = float3(0.0f, 0.0f, 0.0f); LOOP for (uint InfluenceIndex = 0; InfluenceIndex < BoneInfluenceHeader.NumVertexBoneInfluences; ++InfluenceIndex) { uint BoneIndex0 = 0; float BoneWeight0 = 0.0f; DecodeVertexBoneInfluence(BoneInfluenceHeader, TriIndices.x, InfluenceIndex, BoneIndex0, BoneWeight0); uint BoneIndex1 = 0; float BoneWeight1 = 0.0f; DecodeVertexBoneInfluence(BoneInfluenceHeader, TriIndices.y, InfluenceIndex, BoneIndex1, BoneWeight1); uint BoneIndex2 = 0; float BoneWeight2 = 0.0f; DecodeVertexBoneInfluence(BoneInfluenceHeader, TriIndices.z, InfluenceIndex, BoneIndex2, BoneWeight2); float4x3 CurrentBoneTransform[3]; CurrentBoneTransform[0] = LoadNaniteBoneTransform(SkinningHeader.TransformBufferOffset + InstanceData.SkinningData + BoneIndex0); CurrentBoneTransform[1] = LoadNaniteBoneTransform(SkinningHeader.TransformBufferOffset + InstanceData.SkinningData + BoneIndex1); CurrentBoneTransform[2] = LoadNaniteBoneTransform(SkinningHeader.TransformBufferOffset + InstanceData.SkinningData + BoneIndex2); SkinnedPosition[0] += mul(float4(LocalVerts[0].Position, 1.0f), CurrentBoneTransform[0]) * BoneWeight0; SkinnedPosition[1] += mul(float4(LocalVerts[1].Position, 1.0f), CurrentBoneTransform[1]) * BoneWeight1; SkinnedPosition[2] += mul(float4(LocalVerts[2].Position, 1.0f), CurrentBoneTransform[2]) * BoneWeight2; SkinnedNormal[0] += mul((float3x3)CurrentBoneTransform[0], TangentBasis0.TangentZ) * BoneWeight0; SkinnedNormal[1] += mul((float3x3)CurrentBoneTransform[1], TangentBasis1.TangentZ) * BoneWeight1; SkinnedNormal[2] += mul((float3x3)CurrentBoneTransform[2], TangentBasis2.TangentZ) * BoneWeight2; SkinnedTangent[0] += mul((float3x3)CurrentBoneTransform[0], TangentBasis0.TangentXAndSign.xyz) * BoneWeight0; SkinnedTangent[1] += mul((float3x3)CurrentBoneTransform[1], TangentBasis1.TangentXAndSign.xyz) * BoneWeight1; SkinnedTangent[2] += mul((float3x3)CurrentBoneTransform[2], TangentBasis2.TangentXAndSign.xyz) * BoneWeight2; } TangentBasis0.TangentZ = SkinnedNormal[0]; TangentBasis1.TangentZ = SkinnedNormal[1]; TangentBasis2.TangentZ = SkinnedNormal[2]; TangentBasis0.TangentXAndSign.xyz = SkinnedTangent[0]; TangentBasis1.TangentXAndSign.xyz = SkinnedTangent[1]; TangentBasis2.TangentXAndSign.xyz = SkinnedTangent[2]; LocalVerts[0].Position = SkinnedPosition[0]; LocalVerts[1].Position = SkinnedPosition[1]; LocalVerts[2].Position = SkinnedPosition[2]; } const float3 PointWorld0 = mul(float4(LocalVerts[0].Position, 1), InstanceDynamicData.LocalToTranslatedWorld).xyz; const float3 PointWorld1 = mul(float4(LocalVerts[1].Position, 1), InstanceDynamicData.LocalToTranslatedWorld).xyz; const float3 PointWorld2 = mul(float4(LocalVerts[2].Position, 1), InstanceDynamicData.LocalToTranslatedWorld).xyz; const float4 PointClip0 = mul(float4(PointWorld0, 1), NaniteView.TranslatedWorldToClip); const float4 PointClip1 = mul(float4(PointWorld1, 1), NaniteView.TranslatedWorldToClip); const float4 PointClip2 = mul(float4(PointWorld2, 1), NaniteView.TranslatedWorldToClip); const float2 PixelClip = (PixelPos + 0.5 - View.ViewRectMin.xy) * View.ViewSizeAndInvSize.zw * float2(2, -2) + float2(-1, 1); // Calculate perspective correct barycentric coordinates with screen derivatives FBarycentrics Barycentrics = CalculateTriangleBarycentrics(PixelClip, PointClip0, PointClip1, PointClip2, View.ViewSizeAndInvSize.zw); if( Cluster.bVoxel ) { Barycentrics.Value = float3(1,0,0); Barycentrics.Value_dx = 0; Barycentrics.Value_dy = 0; } const FNaniteAttributeData AttributeData = GetAttributeData( Cluster, LocalVerts[0].Position, LocalVerts[1].Position, LocalVerts[2].Position, LocalVerts[0].RawAttributeData, LocalVerts[1].RawAttributeData, LocalVerts[2].RawAttributeData, TangentBasis0, TangentBasis1, TangentBasis2, Barycentrics, InstanceData, NANITE_MAX_UVS ); 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); RasterBin = RemapRasterBin(RasterBin, RenderFlags, MaterialFlags, Cluster.bVoxel); 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; bDarkOutline = true; } else if (VisualizeMode == NANITE_VISUALIZE_PATCHES) { Result = IntToColor(TriIndex); Result = Result * 0.8 + 0.2; bDarkOutline = true; } 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; bDarkOutline = true; } 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 (IsValidAssemblyTransformIndex(VisibleCluster.AssemblyTransformIndex)) { Result = IntToColor(VisibleCluster.AssemblyTransformIndex) * 0.8 + 0.2; } else if (InstanceData.NaniteAssemblyTransformOffset != 0xFFFFFFFFu) { Result = float3(0.2f, 0, 0); } else { 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 PackedFlags = ShadingBinData.Load(UnpackedMask.ShadingBin * NANITE_SHADING_BIN_META_BYTES).MaterialFlags; const uint WriteMask = BitFieldExtractU32(PackedFlags, 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); //} bApplySobel = false; } else if (VisualizeMode == NANITE_VISUALIZE_SCENE_Z_DECODED) { const float ZDecoded = SceneZDecoded[PixelPos]; Result = float3(pow(ZDecoded, 0.11f), 0.0f, 0.0f); bApplySobel = false; } #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(MaterialFlags.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; const bool bEvaluateWPO = !bIsImposter && bWPOEnabled; 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 = MaterialFlags.bPixelDiscard || MaterialFlags.bPixelDepthOffset; break; case NANITE_PIXEL_PROG_VIS_MODE_MASKED_ONLY: bHighlight = MaterialFlags.bPixelDiscard; break; case NANITE_PIXEL_PROG_VIS_MODE_PDO_ONLY: bHighlight = MaterialFlags.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 = AttributeData.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_VSM_STATIC_CACHING) { // TODO: We need to figure out a way to try and better visualize shadow clipmap WPO distances bool bAllowWPO = VirtualShadowMapIsWPOAllowed(PrimitiveData, -1); if (ShouldCacheInstanceAsStatic(VisibleCluster.InstanceId, false, bAllowWPO, NaniteView.SceneRendererPrimaryViewId)) { Result = float3(1, 0, 0); } else { Result = float3(0, 0, 1); } } 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(), AttributeData.VertexColor.Value)); } 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 = AttributeData.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, 0, 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 = AttributeData.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) { bApplySobel = false; 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; } } } 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) { Opacity = 0.0f; } } if (bApplySobel && ShouldApplySobelFilter() && (!GetCompositeWithSceneDepth() || UnpackedMask.bIsNanitePixel)) { Result = ApplySobelFilter(PixelPos, DepthInt, Result, OutlineColor, bDarkOutline); } 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]; const uint PackedMaterialFlags = ShadingBinData.Load(UnpackedMask.ShadingBin * NANITE_SHADING_BIN_META_BYTES).MaterialFlags; FNaniteMaterialFlags MaterialFlags = UnpackNaniteMaterialFlags(PackedMaterialFlags); FVisibleCluster VisibleCluster = GetVisibleCluster(VisibleClusterIndex); FInstanceSceneData InstanceData = GetInstanceSceneDataUnchecked(VisibleCluster); FInstanceDynamicData InstanceDynamicData = CalculateInstanceDynamicData(NaniteView, InstanceData); 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); RasterBin = RemapRasterBin(RasterBin, RenderFlags, MaterialFlags, Cluster.bVoxel); 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 = InstanceData.NaniteAssemblyTransformOffset; FeedbackResults.AssemblyTransformIndex = VisibleCluster.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)); 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, 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; FNaniteSkinningHeader SkinningHeader = (FNaniteSkinningHeader)0; BRANCH if (bIsSkinned) { SkinningHeader = LoadNaniteSkinningHeader(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 = LoadNaniteBoneTransform(SkinningHeader.TransformBufferOffset + InstanceData.SkinningData + 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 = LoadNaniteSkinningHeader(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; }