Files
UnrealEngine/Engine/Shaders/Private/Nanite/NaniteVisualize.usf
2025-05-18 13:04:45 +08:00

1030 lines
36 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 "../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;
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;
// 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<FNaniteShadingBinMeta>(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<FNaniteShadingBinMeta>(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<float2> 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<float2> 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<FNaniteShadingBinMeta>(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;
}