Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions Runtime/GsplatGlobalRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ struct RendererParams
Matrix4x4[] m_rendererTransformsCache;
RendererParams[] m_rendererParamsCache;
MaterialPropertyBlock m_globalPropertyBlock;
int m_renderLayer;

public bool Valid => m_globalMaterial
&& m_globalMaterial.Valid()
Expand Down Expand Up @@ -156,6 +157,7 @@ public void Update(List<IGsplat> activeGsplats)
{
EnsureGlobalBuffers(activeGsplats);
if (m_totalSplatCount == 0) return;
m_renderLayer = activeGsplats[0].transform.gameObject.layer;
UpdateRendererTransforms(activeGsplats);
UpdateRendererParams(activeGsplats);
Render();
Expand Down Expand Up @@ -492,7 +494,8 @@ void Render()
var rp = new RenderParams(m_globalMaterial.Materials[m_globalSHBands])
{
worldBounds = new Bounds(Vector3.zero, Vector3.one * 1e6f),
matProps = m_globalPropertyBlock
matProps = m_globalPropertyBlock,
layer = m_renderLayer
};

int instances = Mathf.CeilToInt(m_totalRemainingCount / (float)GsplatSettings.Instance.SplatInstanceSize);
Expand Down Expand Up @@ -537,4 +540,4 @@ public void DisposeGlobalBuffers()
m_builtSplatCounts = null;
}
}
}
}
36 changes: 30 additions & 6 deletions Runtime/GsplatRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,24 @@ public enum GsplatSortMode
public bool AsyncUpload;
public bool RenderBeforeUploadComplete = true;

[SerializeField]
[Tooltip("When enabled, this renderer can be merged with other compatible renderers for global depth sorting. Disable it for isolated previews or other renderers that must retain an independent draw.")]
bool m_participateInGlobalSort = true;

public bool ParticipateInGlobalSort
{
get => m_participateInGlobalSort;
set
{
if (m_participateInGlobalSort == value)
return;

m_participateInGlobalSort = value;
if (isActiveAndEnabled)
GsplatSorter.Instance.MarkGlobalBuffersDirty();
}
}

[Tooltip("Does cutouts update the Gsplat world bounds? (Costly on moving cutouts)")]
public bool CutoutsUpdateBounds = true;

Expand Down Expand Up @@ -123,6 +141,7 @@ public void OnDrawGizmos()
void OnValidate()
{
ForceRefresh();
GsplatSorter.Instance.MarkGlobalBuffersDirty();
#if UNITY_EDITOR
if (GsplatAsset &&
AssetDatabase.TryGetGUIDAndLocalFileIdentifier(GsplatAsset, out var guid, out long localId))
Expand Down Expand Up @@ -163,12 +182,17 @@ public void Update()
{
m_renderer.EvaluateRefreshRequired(SortMode, SortRefreshRate - 1, CutoutsRefreshRate - 1);
m_renderer.DispatchInitOrder(Cutouts, transform.localToWorldMatrix, CutoutsUpdateBounds);
// When the global sorter has merged all renderers into a single draw call,
// skip the per-renderer draw — GsplatSorter.DrawAll handles rendering.
if (!GsplatSorter.Instance.GlobalRenderEnabled)
m_renderer.Render(transform, gameObject.layer, GammaToLinear, SHDegree, Brightness,
1.0f - SplatDownscaleFactor, RenderOrder);
}
}

// Called by GsplatSorter after it has selected one render path for the frame.
internal void Render()
{
if (!Valid || !GsplatSettings.Instance.Valid || !GsplatSorter.Instance.Valid)
return;

m_renderer.Render(transform, gameObject.layer, GammaToLinear, SHDegree, Brightness,
1.0f - SplatDownscaleFactor, RenderOrder);
}
}
}
}
49 changes: 39 additions & 10 deletions Runtime/GsplatSorter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ public void Dispose()
readonly HashSet<IGsplat> m_gsplats = new();
readonly HashSet<Camera> m_camerasInjected = new();
readonly List<IGsplat> m_activeGsplats = new();
readonly List<IGsplat> m_globalGsplats = new();
readonly List<IGsplat> m_fallbackGsplats = new();
readonly HashSet<int> m_warnedUncompressed = new();
GsplatSortPass m_sortPass;
public const string k_passName = "SortGsplats";
Expand Down Expand Up @@ -124,6 +126,8 @@ public void UnregisterGsplat(IGsplat gsplat)
}

m_activeGsplats.Clear();
m_globalGsplats.Clear();
m_fallbackGsplats.Clear();
m_commandBuffer?.Dispose();
m_commandBuffer = null;
Camera.onPreCull -= OnPreCullCamera;
Expand All @@ -146,25 +150,31 @@ public bool GatherGsplatsForCamera(Camera cam)
bool CanRenderGlobally()
{
// renderer_id is packed into 8 bits ([31:24]); max 255 renderers.
if (m_activeGsplats.Count > 255)
if (m_globalGsplats.Count > 255)
{
Debug.LogError(
"[GsplatSorter] Global merge supports at most 255 renderers. Falling back to per-renderer rendering.");
return false;
}

// Global merge requires every active renderer to use SPARK compression.
foreach (var gs in m_activeGsplats)
foreach (var gs in m_globalGsplats)
{
if (gs.GsplatResource is GsplatResourceSpark) continue;
var obj = gs as UnityEngine.Object;
var id = obj ? obj.GetInstanceID() : 0;
if (m_warnedUncompressed.Add(id))
Debug.LogWarning(
$"[GsplatSorter] '{obj?.name}' uses an uncompressed asset; global sort requires every active renderer to use SPARK compression. Disabling global sort for this scene — all renderers fall back to per-renderer rendering.");
$"[GsplatSorter] '{obj?.name}' uses an uncompressed asset; global sort requires every participating renderer to use SPARK compression. Participating renderers will fall back to per-renderer rendering.");
return false;
}

// A single global draw can only have one Unity layer. Mixed-layer sets must retain
// per-renderer draws so each camera's culling mask is respected.
var renderLayer = m_globalGsplats[0].transform.gameObject.layer;
if (m_globalGsplats.Any(gs => gs.transform.gameObject.layer != renderLayer))
return false;

return true;
}

Expand Down Expand Up @@ -232,21 +242,40 @@ public void DispatchSort(CommandBuffer cmd, Camera camera)

// --- Global K-way merge ---
if (GlobalRenderEnabled)
m_globalRenderer.DispatchMerge(cmd, m_activeGsplats);
m_globalRenderer.DispatchMerge(cmd, m_globalGsplats);
}

// Called by GsplatPlayerLoopHook once per frame, before Unity's PostLateUpdate phase
public void Update()
{
GlobalRenderEnabled = m_globalRenderer.Valid && GsplatSettings.Instance.EnableGlobalSort &&
m_activeGsplats.Count >= 2;
if (!GlobalRenderEnabled) return;
m_activeGsplats.Clear();
foreach (var gs in m_gsplats.Where(gs => gs is { isActiveAndEnabled: true, Valid: true }))
m_activeGsplats.Add(gs);
GlobalRenderEnabled = GlobalRenderEnabled && CanRenderGlobally();
if (!GlobalRenderEnabled) return;
m_globalRenderer.Update(m_activeGsplats);

m_globalGsplats.Clear();
m_fallbackGsplats.Clear();
foreach (var gs in m_activeGsplats)
{
if (gs is GsplatRenderer { ParticipateInGlobalSort: false })
m_fallbackGsplats.Add(gs);
else
m_globalGsplats.Add(gs);
}

GlobalRenderEnabled = m_globalRenderer.Valid && GsplatSettings.Instance.EnableGlobalSort &&
m_globalGsplats.Count >= 2 && CanRenderGlobally();
if (GlobalRenderEnabled)
{
m_globalRenderer.Update(m_globalGsplats);
foreach (var renderer in m_fallbackGsplats.OfType<GsplatRenderer>())
renderer.Render();
return;
}

// Select and submit the fallback path here as well, so a runtime transition cannot
// make GsplatRenderer.Update observe the previous frame's global-render state.
foreach (var renderer in m_activeGsplats.OfType<GsplatRenderer>())
renderer.Render();
}

public ISorterResource CreateSorterResource(uint count, GraphicsBuffer orderBuffer)
Expand Down
5 changes: 4 additions & 1 deletion Runtime/Shaders/GsplatMergeOrderBuffers.compute
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,10 @@ void MergeTwo(uint3 id : SV_DispatchThreadID)
float dA = (i < _CountA) ? _DepthsA[_OffsetA + i] : FLOAT_MAX;
float dB = (j < _CountB) ? _DepthsB[_OffsetB + j] : FLOAT_MAX;

_OutputOrders[_OutputOffset + p] = (dA <= dB) ? _OrdersA[_OffsetA + i] : _OrdersB[_OffsetB + j];
if (dA <= dB)
_OutputOrders[_OutputOffset + p] = _OrdersA[_OffsetA + i];
else
_OutputOrders[_OutputOffset + p] = _OrdersB[_OffsetB + j];
}

// Intermediate merge pass — writes merged orders AND depths to scratch.
Expand Down