diff --git a/Editor/GsplatSettingsProvider.cs b/Editor/GsplatSettingsProvider.cs index 31cf304..ce82796 100644 --- a/Editor/GsplatSettingsProvider.cs +++ b/Editor/GsplatSettingsProvider.cs @@ -26,6 +26,7 @@ public override void OnGUI(string searchContext) EditorGUILayout.PropertyField(m_gsplatSettings.FindProperty(nameof(GsplatSettings.ComputeShader))); EditorGUILayout.PropertyField(m_gsplatSettings.FindProperty(nameof(GsplatSettings.SplatInstanceSize))); EditorGUILayout.PropertyField(m_gsplatSettings.FindProperty(nameof(GsplatSettings.UploadBatchSize))); + EditorGUILayout.PropertyField(m_gsplatSettings.FindProperty(nameof(GsplatSettings.DepthPrepassAlphaCutoff))); EditorGUILayout.PropertyField(m_gsplatSettings.FindProperty(nameof(GsplatSettings.MaxRenderOrder))); EditorGUILayout.PropertyField( m_gsplatSettings.FindProperty(nameof(GsplatSettings.CameraTranslationRefreshTreshold))); @@ -73,4 +74,4 @@ public static SettingsProvider CreateGsplatSettingsProvider() return provider; } } -} \ No newline at end of file +} diff --git a/Runtime/GsplatGlobalMaterial.cs b/Runtime/GsplatGlobalMaterial.cs index d50f215..a124e27 100644 --- a/Runtime/GsplatGlobalMaterial.cs +++ b/Runtime/GsplatGlobalMaterial.cs @@ -9,10 +9,12 @@ namespace Gsplat public class GsplatGlobalMaterial : ScriptableObject { public Material DefaultMaterial; + public Material DefaultDepthMaterial; public ComputeShader MergeShader; public ComputeShader CopyBufferShader; Material[] m_materials; // indexed by SH band (0–4) + Material[] m_depthMaterials; // indexed by SH band (0–4) /// /// Returns five Material instances — one per SH band level — with the appropriate @@ -41,8 +43,35 @@ public Material[] Materials } } - public void Reset() => m_materials = null; + public Material[] DepthMaterials + { + get + { + if (m_depthMaterials != null && m_depthMaterials[0] != null) + return m_depthMaterials; + + m_depthMaterials = new Material[5]; + for (int i = 0; i < 5; i++) + { + m_depthMaterials[i] = new Material(DefaultDepthMaterial); + m_depthMaterials[i].DisableKeyword("SH_BANDS_0"); + m_depthMaterials[i].DisableKeyword("SH_BANDS_1"); + m_depthMaterials[i].DisableKeyword("SH_BANDS_2"); + m_depthMaterials[i].DisableKeyword("SH_BANDS_3"); + m_depthMaterials[i].DisableKeyword("SH_BANDS_4"); + m_depthMaterials[i].EnableKeyword($"SH_BANDS_{i}"); + } + + return m_depthMaterials; + } + } + + public void Reset() + { + m_materials = null; + m_depthMaterials = null; + } - public bool Valid() => MergeShader && CopyBufferShader && DefaultMaterial; + public bool Valid() => MergeShader && CopyBufferShader && DefaultMaterial && DefaultDepthMaterial; } -} \ No newline at end of file +} diff --git a/Runtime/GsplatGlobalRenderer.cs b/Runtime/GsplatGlobalRenderer.cs index f7375e0..1f2d980 100644 --- a/Runtime/GsplatGlobalRenderer.cs +++ b/Runtime/GsplatGlobalRenderer.cs @@ -5,6 +5,9 @@ using System.Linq; using UnityEngine; using UnityEngine.Rendering; +#if UNITY_6000_0_OR_NEWER +using UnityEngine.Rendering.RenderGraphModule; +#endif namespace Gsplat { @@ -57,6 +60,7 @@ struct RendererParams Matrix4x4[] m_rendererTransformsCache; RendererParams[] m_rendererParamsCache; MaterialPropertyBlock m_globalPropertyBlock; + int m_renderLayer; public bool Valid => m_globalMaterial && m_globalMaterial.Valid() @@ -111,6 +115,7 @@ struct RendererParams static readonly int k_rendererParamsProp = Shader.PropertyToID("_RendererParams"); static readonly int k_totalSplatCount = Shader.PropertyToID("_TotalSplatCount"); static readonly int k_splatInstanceSize = Shader.PropertyToID("_SplatInstanceSize"); + static readonly int k_depthPrepassAlphaCutoff = Shader.PropertyToID("_DepthPrepassAlphaCutoff"); public void InitGlobal(GsplatGlobalMaterial globalMaterial) { @@ -156,9 +161,11 @@ public void Update(List activeGsplats) { EnsureGlobalBuffers(activeGsplats); if (m_totalSplatCount == 0) return; + m_renderLayer = activeGsplats[0].transform.gameObject.layer; UpdateRendererTransforms(activeGsplats); UpdateRendererParams(activeGsplats); - Render(); + if (!GraphicsSettings.currentRenderPipeline) + Render(); } public void DispatchMerge(CommandBuffer cmd, List activeGsplats) @@ -479,6 +486,7 @@ void Render() m_globalPropertyBlock.SetBuffer(k_rendererParamsProp, m_rendererParamsBuffer); m_globalPropertyBlock.SetInteger(k_totalSplatCount, (int)m_totalRemainingCount); m_globalPropertyBlock.SetInteger(k_splatInstanceSize, (int)GsplatSettings.Instance.SplatInstanceSize); + m_globalPropertyBlock.SetFloat(k_depthPrepassAlphaCutoff, GsplatSettings.Instance.DepthPrepassAlphaCutoff); if (m_globalSHBands >= 1) m_globalPropertyBlock.SetBuffer(k_globalSH1Buffer, m_globalSH1Buffer); @@ -492,13 +500,89 @@ 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); Graphics.RenderMeshPrimitives(rp, GsplatSettings.Instance.Mesh, 0, instances); } + public void RenderColor(CommandBuffer cmd) + { + if (m_globalBuffersDirty || m_globalOrderBuffer == null || m_totalRemainingCount == 0) return; + + BindGlobalDrawProperties(); + + int instances = Mathf.CeilToInt(m_totalRemainingCount / (float)GsplatSettings.Instance.SplatInstanceSize); + cmd.DrawMeshInstancedProcedural(GsplatSettings.Instance.Mesh, 0, m_globalMaterial.Materials[m_globalSHBands], + 0, instances, m_globalPropertyBlock); + } + + public void RenderDepthPrepass(CommandBuffer cmd) + { + if (GsplatSettings.Instance.DepthPrepassAlphaCutoff > 1.0f) + return; + + if (m_globalBuffersDirty || m_globalOrderBuffer == null || m_totalRemainingCount == 0) return; + + BindGlobalDrawProperties(); + + int instances = Mathf.CeilToInt(m_totalRemainingCount / (float)GsplatSettings.Instance.SplatInstanceSize); + cmd.DrawMeshInstancedProcedural(GsplatSettings.Instance.Mesh, 0, + m_globalMaterial.DepthMaterials[m_globalSHBands], 0, instances, m_globalPropertyBlock); + } + +#if UNITY_6000_0_OR_NEWER + public void RenderColor(RasterCommandBuffer cmd) + { + if (m_globalBuffersDirty || m_globalOrderBuffer == null || m_totalRemainingCount == 0) return; + + BindGlobalDrawProperties(); + + int instances = Mathf.CeilToInt(m_totalRemainingCount / (float)GsplatSettings.Instance.SplatInstanceSize); + cmd.DrawMeshInstancedProcedural(GsplatSettings.Instance.Mesh, 0, m_globalMaterial.Materials[m_globalSHBands], + 0, instances, m_globalPropertyBlock); + } + + public void RenderDepthPrepass(RasterCommandBuffer cmd) + { + if (GsplatSettings.Instance.DepthPrepassAlphaCutoff > 1.0f) + return; + + if (m_globalBuffersDirty || m_globalOrderBuffer == null || m_totalRemainingCount == 0) return; + + BindGlobalDrawProperties(); + + int instances = Mathf.CeilToInt(m_totalRemainingCount / (float)GsplatSettings.Instance.SplatInstanceSize); + cmd.DrawMeshInstancedProcedural(GsplatSettings.Instance.Mesh, 0, + m_globalMaterial.DepthMaterials[m_globalSHBands], 0, instances, m_globalPropertyBlock); + } +#endif + + void BindGlobalDrawProperties() + { + m_globalPropertyBlock ??= new MaterialPropertyBlock(); + m_globalPropertyBlock.Clear(); + m_globalPropertyBlock.SetBuffer(k_globalOrderBuffer, m_globalOrderBuffer); + m_globalPropertyBlock.SetBuffer(k_globalPackedBuffer, m_globalPackedBuffer); + m_globalPropertyBlock.SetBuffer(k_rendererOffsetsProp, m_rendererOffsetsBuffer); + m_globalPropertyBlock.SetBuffer(k_rendererTransformsProp, m_rendererTransformsBuffer); + m_globalPropertyBlock.SetBuffer(k_rendererParamsProp, m_rendererParamsBuffer); + m_globalPropertyBlock.SetInteger(k_totalSplatCount, (int)m_totalRemainingCount); + m_globalPropertyBlock.SetInteger(k_splatInstanceSize, (int)GsplatSettings.Instance.SplatInstanceSize); + m_globalPropertyBlock.SetFloat(k_depthPrepassAlphaCutoff, GsplatSettings.Instance.DepthPrepassAlphaCutoff); + + if (m_globalSHBands >= 1) + m_globalPropertyBlock.SetBuffer(k_globalSH1Buffer, m_globalSH1Buffer); + if (m_globalSHBands >= 2) + m_globalPropertyBlock.SetBuffer(k_globalSH2Buffer, m_globalSH2Buffer); + if (m_globalSHBands >= 3) + m_globalPropertyBlock.SetBuffer(k_globalSH3Buffer, m_globalSH3Buffer); + if (m_globalSHBands >= 4) + m_globalPropertyBlock.SetBuffer(k_globalSH4Buffer, m_globalSH4Buffer); + } + // ----------------------------------------------------------------------- // Cleanup @@ -537,4 +621,4 @@ public void DisposeGlobalBuffers() m_builtSplatCounts = null; } } -} \ No newline at end of file +} diff --git a/Runtime/GsplatMaterial.cs b/Runtime/GsplatMaterial.cs index da028bc..0d9600a 100644 --- a/Runtime/GsplatMaterial.cs +++ b/Runtime/GsplatMaterial.cs @@ -9,11 +9,14 @@ namespace Gsplat public class GsplatMaterial : ScriptableObject { public Material DefaultMaterial; + public Material DefaultDepthMaterial; private Material[][] m_material; + private Material[][] m_depthMaterial; public void Reset() { m_material = null; + m_depthMaterial = null; } public Material[][] Materials // materials generated with SH bands from 0 to 4 and custom renderOrders @@ -43,6 +46,33 @@ public Material[][] Materials // materials generated with SH bands from 0 to 4 a } } + public Material[][] DepthMaterials + { + get + { + if (m_depthMaterial != null && m_depthMaterial[0][0] != null) + return m_depthMaterial; + + m_depthMaterial = new Material[5][]; + for (var i = 0; i < 5; ++i) + { + m_depthMaterial[i] = new Material[GsplatSettings.Instance.MaxRenderOrder]; + for (var j = 0; j < GsplatSettings.Instance.MaxRenderOrder; ++j) + { + m_depthMaterial[i][j] = new Material(DefaultDepthMaterial); + m_depthMaterial[i][j].DisableKeyword($"SH_BANDS_0"); + m_depthMaterial[i][j].DisableKeyword($"SH_BANDS_1"); + m_depthMaterial[i][j].DisableKeyword($"SH_BANDS_2"); + m_depthMaterial[i][j].DisableKeyword($"SH_BANDS_3"); + m_depthMaterial[i][j].DisableKeyword($"SH_BANDS_4"); + m_depthMaterial[i][j].EnableKeyword($"SH_BANDS_{i}"); + m_depthMaterial[i][j].renderQueue = 3000 + j; + } + } + return m_depthMaterial; + } + } + public ComputeShader CalcDepthShader; public ComputeShader InitOrderShader; } diff --git a/Runtime/GsplatRenderer.cs b/Runtime/GsplatRenderer.cs index a257ce6..6a94bde 100644 --- a/Runtime/GsplatRenderer.cs +++ b/Runtime/GsplatRenderer.cs @@ -5,6 +5,9 @@ using UnityEditor; using UnityEngine; using UnityEngine.Rendering; +#if UNITY_6000_0_OR_NEWER +using UnityEngine.Rendering.RenderGraphModule; +#endif namespace Gsplat { @@ -87,6 +90,30 @@ public GsplatCutout[] Cutouts public void ComputeDepth(CommandBuffer cmd, Matrix4x4 matrixMv) => m_renderer.ComputeDepth(cmd, matrixMv); + public void RenderColor(CommandBuffer cmd, Camera camera) + { + m_renderer.RenderColor(cmd, transform, GammaToLinear, SHDegree, Brightness, + 1.0f - SplatDownscaleFactor, RenderOrder); + } + + public void RenderDepthPrepass(CommandBuffer cmd, Camera camera) + { + m_renderer.RenderDepthPrepass(cmd, transform, 1.0f - SplatDownscaleFactor, RenderOrder); + } + +#if UNITY_6000_0_OR_NEWER + public void RenderColor(RasterCommandBuffer cmd, Camera camera) + { + m_renderer.RenderColor(cmd, transform, GammaToLinear, SHDegree, Brightness, + 1.0f - SplatDownscaleFactor, RenderOrder); + } + + public void RenderDepthPrepass(RasterCommandBuffer cmd, Camera camera) + { + m_renderer.RenderDepthPrepass(cmd, transform, 1.0f - SplatDownscaleFactor, RenderOrder); + } +#endif + void OnEnable() { GsplatSorter.Instance.RegisterGsplat(this); @@ -163,12 +190,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); + } } -} \ No newline at end of file +} diff --git a/Runtime/GsplatRendererImpl.cs b/Runtime/GsplatRendererImpl.cs index efe457d..76272d8 100644 --- a/Runtime/GsplatRendererImpl.cs +++ b/Runtime/GsplatRendererImpl.cs @@ -5,6 +5,9 @@ using System.Collections.Generic; using UnityEngine; using UnityEngine.Rendering; +#if UNITY_6000_0_OR_NEWER +using UnityEngine.Rendering.RenderGraphModule; +#endif using Vector3 = UnityEngine.Vector3; namespace Gsplat @@ -34,6 +37,7 @@ public class GsplatRendererImpl static readonly int k_shDegree = Shader.PropertyToID("_SHDegree"); static readonly int k_brightness = Shader.PropertyToID("_Brightness"); static readonly int k_scaleFactor = Shader.PropertyToID("_ScaleFactor"); + static readonly int k_depthPrepassAlphaCutoff = Shader.PropertyToID("_DepthPrepassAlphaCutoff"); uint m_framesBeforeRecomputeSort = 0; uint m_sortsBeforeRecomputeCutouts = 0; @@ -272,6 +276,7 @@ public void Render(Transform transform, int layer, bool gammaToLinear = false, i m_propertyBlock.SetInteger(k_shDegree, Math.Min(m_gsplatAsset.SHBands, shDegree)); m_propertyBlock.SetFloat(k_brightness, brightness); m_propertyBlock.SetFloat(k_scaleFactor, scaleFactor); + m_propertyBlock.SetFloat(k_depthPrepassAlphaCutoff, GsplatSettings.Instance.DepthPrepassAlphaCutoff); m_propertyBlock.SetMatrix(k_matrixM, transform.localToWorldMatrix); uint order = Math.Clamp(renderOrder, 0, GsplatSettings.Instance.MaxRenderOrder - 1); @@ -285,5 +290,89 @@ public void Render(Transform transform, int layer, bool gammaToLinear = false, i Graphics.RenderMeshPrimitives(rp, GsplatSettings.Instance.Mesh, 0, Mathf.CeilToInt(m_remainingCount / (float)GsplatSettings.Instance.SplatInstanceSize)); } + + public void RenderDepthPrepass(CommandBuffer cmd, Transform transform, float scaleFactor = 1.0f, + uint renderOrder = 0) + { + if (m_remainingCount <= 0 || GsplatSettings.Instance.DepthPrepassAlphaCutoff > 1.0f) + return; + + m_propertyBlock.SetInteger(k_splatCount, (int)m_remainingCount); + m_propertyBlock.SetInteger(k_splatInstanceSize, (int)GsplatSettings.Instance.SplatInstanceSize); + m_propertyBlock.SetFloat(k_scaleFactor, scaleFactor); + m_propertyBlock.SetFloat(k_depthPrepassAlphaCutoff, GsplatSettings.Instance.DepthPrepassAlphaCutoff); + m_propertyBlock.SetMatrix(k_matrixM, transform.localToWorldMatrix); + + uint order = Math.Clamp(renderOrder, 0, GsplatSettings.Instance.MaxRenderOrder - 1); + int instances = Mathf.CeilToInt(m_remainingCount / (float)GsplatSettings.Instance.SplatInstanceSize); + cmd.DrawMeshInstancedProcedural(GsplatSettings.Instance.Mesh, 0, + m_gsplatAsset.GsplatMaterial.DepthMaterials[m_gsplatAsset.SHBands][order], 0, instances, + m_propertyBlock); + } + +#if UNITY_6000_0_OR_NEWER + public void RenderDepthPrepass(RasterCommandBuffer cmd, Transform transform, float scaleFactor = 1.0f, + uint renderOrder = 0) + { + if (m_remainingCount <= 0 || GsplatSettings.Instance.DepthPrepassAlphaCutoff > 1.0f) + return; + + m_propertyBlock.SetInteger(k_splatCount, (int)m_remainingCount); + m_propertyBlock.SetInteger(k_splatInstanceSize, (int)GsplatSettings.Instance.SplatInstanceSize); + m_propertyBlock.SetFloat(k_scaleFactor, scaleFactor); + m_propertyBlock.SetFloat(k_depthPrepassAlphaCutoff, GsplatSettings.Instance.DepthPrepassAlphaCutoff); + m_propertyBlock.SetMatrix(k_matrixM, transform.localToWorldMatrix); + + uint order = Math.Clamp(renderOrder, 0, GsplatSettings.Instance.MaxRenderOrder - 1); + int instances = Mathf.CeilToInt(m_remainingCount / (float)GsplatSettings.Instance.SplatInstanceSize); + cmd.DrawMeshInstancedProcedural(GsplatSettings.Instance.Mesh, 0, + m_gsplatAsset.GsplatMaterial.DepthMaterials[m_gsplatAsset.SHBands][order], 0, instances, + m_propertyBlock); + } +#endif + + public void RenderColor(CommandBuffer cmd, Transform transform, bool gammaToLinear = false, int shDegree = 3, + float brightness = 1.0f, float scaleFactor = 1.0f, uint renderOrder = 0) + { + if (m_remainingCount <= 0) + return; + + m_propertyBlock.SetInteger(k_splatCount, (int)m_remainingCount); + m_propertyBlock.SetInteger(k_gammaToLinear, gammaToLinear ? 1 : 0); + m_propertyBlock.SetInteger(k_splatInstanceSize, (int)GsplatSettings.Instance.SplatInstanceSize); + m_propertyBlock.SetInteger(k_shDegree, Math.Min(m_gsplatAsset.SHBands, shDegree)); + m_propertyBlock.SetFloat(k_brightness, brightness); + m_propertyBlock.SetFloat(k_scaleFactor, scaleFactor); + m_propertyBlock.SetFloat(k_depthPrepassAlphaCutoff, GsplatSettings.Instance.DepthPrepassAlphaCutoff); + m_propertyBlock.SetMatrix(k_matrixM, transform.localToWorldMatrix); + + uint order = Math.Clamp(renderOrder, 0, GsplatSettings.Instance.MaxRenderOrder - 1); + int instances = Mathf.CeilToInt(m_remainingCount / (float)GsplatSettings.Instance.SplatInstanceSize); + cmd.DrawMeshInstancedProcedural(GsplatSettings.Instance.Mesh, 0, m_gsplatAsset.Materials[order], 0, + instances, m_propertyBlock); + } + +#if UNITY_6000_0_OR_NEWER + public void RenderColor(RasterCommandBuffer cmd, Transform transform, bool gammaToLinear = false, + int shDegree = 3, float brightness = 1.0f, float scaleFactor = 1.0f, uint renderOrder = 0) + { + if (m_remainingCount <= 0) + return; + + m_propertyBlock.SetInteger(k_splatCount, (int)m_remainingCount); + m_propertyBlock.SetInteger(k_gammaToLinear, gammaToLinear ? 1 : 0); + m_propertyBlock.SetInteger(k_splatInstanceSize, (int)GsplatSettings.Instance.SplatInstanceSize); + m_propertyBlock.SetInteger(k_shDegree, Math.Min(m_gsplatAsset.SHBands, shDegree)); + m_propertyBlock.SetFloat(k_brightness, brightness); + m_propertyBlock.SetFloat(k_scaleFactor, scaleFactor); + m_propertyBlock.SetFloat(k_depthPrepassAlphaCutoff, GsplatSettings.Instance.DepthPrepassAlphaCutoff); + m_propertyBlock.SetMatrix(k_matrixM, transform.localToWorldMatrix); + + uint order = Math.Clamp(renderOrder, 0, GsplatSettings.Instance.MaxRenderOrder - 1); + int instances = Mathf.CeilToInt(m_remainingCount / (float)GsplatSettings.Instance.SplatInstanceSize); + cmd.DrawMeshInstancedProcedural(GsplatSettings.Instance.Mesh, 0, m_gsplatAsset.Materials[order], 0, + instances, m_propertyBlock); + } +#endif } -} \ No newline at end of file +} diff --git a/Runtime/GsplatSettings.cs b/Runtime/GsplatSettings.cs index db1195c..93e4e9f 100644 --- a/Runtime/GsplatSettings.cs +++ b/Runtime/GsplatSettings.cs @@ -76,6 +76,8 @@ public static GsplatSettings Instance public uint SplatInstanceSize; public uint UploadBatchSize; + [Tooltip("Alpha cutoff used by the optional depth prepass. Values above 1 disable depth writes.")] + [Range(0f, 1.1f)] public float DepthPrepassAlphaCutoff = 1.1f; [Range(1, 20)] public uint MaxRenderOrder; public bool DisplayBoundingBoxes; @@ -136,6 +138,7 @@ public void Reset() Materials = DefaultMaterials; SplatInstanceSize = 128; UploadBatchSize = 100000; + DepthPrepassAlphaCutoff = 1.1f; MaxRenderOrder = 1; DisplayBoundingBoxes = false; CameraTranslationRefreshTreshold = 0.2f; diff --git a/Runtime/GsplatSorter.cs b/Runtime/GsplatSorter.cs index db476eb..410a4e8 100644 --- a/Runtime/GsplatSorter.cs +++ b/Runtime/GsplatSorter.cs @@ -6,6 +6,9 @@ using System.Linq; using UnityEngine; using UnityEngine.Rendering; +#if UNITY_6000_0_OR_NEWER +using UnityEngine.Rendering.RenderGraphModule; +#endif namespace Gsplat { @@ -18,6 +21,12 @@ public interface IGsplat public bool Valid { get; } public bool ComputeSortRequired { get; } public void ComputeDepth(CommandBuffer cmd, Matrix4x4 matrixMv); + public void RenderColor(CommandBuffer cmd, Camera camera); + public void RenderDepthPrepass(CommandBuffer cmd, Camera camera); +#if UNITY_6000_0_OR_NEWER + public void RenderColor(RasterCommandBuffer cmd, Camera camera); + public void RenderDepthPrepass(RasterCommandBuffer cmd, Camera camera); +#endif // Used by GsplatSorter to populate the global packed buffer. public GsplatResource GsplatResource { get; } @@ -165,9 +174,23 @@ bool CanRenderGlobally() return false; } + // One merged draw cannot represent multiple Unity layers. Use per-renderer draws + // for mixed layers so each camera can apply its culling mask independently. + var renderLayer = m_activeGsplats[0].transform.gameObject.layer; + if (m_activeGsplats.Any(gs => gs.transform.gameObject.layer != renderLayer)) + return false; + return true; } + bool CameraRendersGlobalLayer(Camera camera) + { + if (!camera) + return true; + var layer = m_activeGsplats[0].transform.gameObject.layer; + return (camera.cullingMask & (1 << layer)) != 0; + } + public void MarkGlobalBuffersDirty() => m_globalRenderer.MarkGlobalBuffersDirty(); void InitialClearCmdBuffer(Camera cam) @@ -235,18 +258,100 @@ public void DispatchSort(CommandBuffer cmd, Camera camera) m_globalRenderer.DispatchMerge(cmd, m_activeGsplats); } + public void RenderDepthPrepass(CommandBuffer cmd, Camera camera) + { + if (GlobalRenderEnabled) + { + if (CameraRendersGlobalLayer(camera)) + m_globalRenderer.RenderDepthPrepass(cmd); + return; + } + + foreach (var gs in m_activeGsplats) + { + if (gs.RemainingCount <= 0) continue; + var layer = gs.transform.gameObject.layer; + if (camera && (camera.cullingMask & (1 << layer)) == 0) continue; + gs.RenderDepthPrepass(cmd, camera); + } + } + + public void RenderColor(CommandBuffer cmd, Camera camera) + { + if (GlobalRenderEnabled) + { + if (CameraRendersGlobalLayer(camera)) + m_globalRenderer.RenderColor(cmd); + return; + } + + foreach (var gs in m_activeGsplats.OrderBy(gs => (gs as GsplatRenderer)?.RenderOrder ?? 0)) + { + if (gs.RemainingCount <= 0) continue; + var layer = gs.transform.gameObject.layer; + if (camera && (camera.cullingMask & (1 << layer)) == 0) continue; + gs.RenderColor(cmd, camera); + } + } + +#if UNITY_6000_0_OR_NEWER + public void RenderDepthPrepass(RasterCommandBuffer cmd, Camera camera) + { + if (GlobalRenderEnabled) + { + if (CameraRendersGlobalLayer(camera)) + m_globalRenderer.RenderDepthPrepass(cmd); + return; + } + + foreach (var gs in m_activeGsplats) + { + if (gs.RemainingCount <= 0) continue; + var layer = gs.transform.gameObject.layer; + if (camera && (camera.cullingMask & (1 << layer)) == 0) continue; + gs.RenderDepthPrepass(cmd, camera); + } + } + + public void RenderColor(RasterCommandBuffer cmd, Camera camera) + { + if (GlobalRenderEnabled) + { + if (CameraRendersGlobalLayer(camera)) + m_globalRenderer.RenderColor(cmd); + return; + } + + foreach (var gs in m_activeGsplats.OrderBy(gs => (gs as GsplatRenderer)?.RenderOrder ?? 0)) + { + if (gs.RemainingCount <= 0) continue; + var layer = gs.transform.gameObject.layer; + if (camera && (camera.cullingMask & (1 << layer)) == 0) continue; + gs.RenderColor(cmd, camera); + } + } +#endif + // 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); + + GlobalRenderEnabled = m_globalRenderer.Valid && GsplatSettings.Instance.EnableGlobalSort && + m_activeGsplats.Count >= 2 && CanRenderGlobally(); + if (GlobalRenderEnabled) + { + m_globalRenderer.Update(m_activeGsplats); + return; + } + + // The built-in pipeline has no explicit SRP pass to own fallback submission. + // URP and HDRP record their per-renderer draws later in their dedicated passes. + if (!GraphicsSettings.currentRenderPipeline) + foreach (var renderer in m_activeGsplats.OfType()) + renderer.Render(); } public ISorterResource CreateSorterResource(uint count, GraphicsBuffer orderBuffer) diff --git a/Runtime/Materials/GsplatGlobal.asset b/Runtime/Materials/GsplatGlobal.asset index 410a6e5..b2fa2f8 100644 --- a/Runtime/Materials/GsplatGlobal.asset +++ b/Runtime/Materials/GsplatGlobal.asset @@ -13,5 +13,6 @@ MonoBehaviour: m_Name: GsplatGlobal m_EditorClassIdentifier: DefaultMaterial: {fileID: 2100000, guid: e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8, type: 2} + DefaultDepthMaterial: {fileID: 2100000, guid: 4d8e9f104a5b6c7d8e1f0a9b6c5d4e3f, type: 2} MergeShader: {fileID: 7200000, guid: a1e5c8f2b4d7903e6f2a8c1d5e9b3f70, type: 3} CopyBufferShader: {fileID: 7200000, guid: 3f7a1c4e8b2d9f506e1a4c7d3b8f2e90, type: 3} diff --git a/Runtime/Materials/GsplatGlobalDepth.mat b/Runtime/Materials/GsplatGlobalDepth.mat new file mode 100644 index 0000000..0b17742 --- /dev/null +++ b/Runtime/Materials/GsplatGlobalDepth.mat @@ -0,0 +1,31 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!21 &2100000 +Material: + serializedVersion: 8 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: GsplatGlobalDepth + m_Shader: {fileID: 4800000, guid: 9a5c8e1f2b3d4706a4f9c0d1e2b3a456, type: 3} + m_Parent: {fileID: 0} + m_ModifiedSerializedProperties: 0 + m_ValidKeywords: + - SH_BANDS_3 + m_InvalidKeywords: [] + m_LightmapFlags: 4 + m_EnableInstancingVariants: 0 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: -1 + stringTagMap: {} + disabledShaderPasses: [] + m_LockedProperties: + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: [] + m_Ints: [] + m_Floats: [] + m_Colors: [] + m_BuildTextureStacks: [] + m_AllowLocking: 1 diff --git a/Runtime/Materials/GsplatGlobalDepth.mat.meta b/Runtime/Materials/GsplatGlobalDepth.mat.meta new file mode 100644 index 0000000..b1b6fb3 --- /dev/null +++ b/Runtime/Materials/GsplatGlobalDepth.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4d8e9f104a5b6c7d8e1f0a9b6c5d4e3f +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 2100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Materials/GsplatSpark.asset b/Runtime/Materials/GsplatSpark.asset index 2e3acc6..f78cdba 100644 --- a/Runtime/Materials/GsplatSpark.asset +++ b/Runtime/Materials/GsplatSpark.asset @@ -13,5 +13,6 @@ MonoBehaviour: m_Name: GsplatSpark m_EditorClassIdentifier: DefaultMaterial: {fileID: 2100000, guid: 0fd25878460c14a46946f5bffc5ee42f, type: 2} + DefaultDepthMaterial: {fileID: 2100000, guid: 3c7d8e9f104a5b6c8d2e1f0a9b6c5d4e, type: 2} CalcDepthShader: {fileID: 7200000, guid: f234fe86176948548be7fad55f5d6147, type: 3} InitOrderShader: {fileID: 7200000, guid: 9f8e041cb7bfae141b2e6ee80402bf45, type: 3} diff --git a/Runtime/Materials/GsplatSparkDepth.mat b/Runtime/Materials/GsplatSparkDepth.mat new file mode 100644 index 0000000..c77b955 --- /dev/null +++ b/Runtime/Materials/GsplatSparkDepth.mat @@ -0,0 +1,32 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!21 &2100000 +Material: + serializedVersion: 8 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: GsplatSparkDepth + m_Shader: {fileID: 4800000, guid: 8f4d7a3b9c2e4615a0b1c6d8e9f20314, type: 3} + m_Parent: {fileID: 0} + m_ModifiedSerializedProperties: 0 + m_ValidKeywords: + - SH_BANDS_0 + - SPARK + m_InvalidKeywords: [] + m_LightmapFlags: 4 + m_EnableInstancingVariants: 0 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: -1 + stringTagMap: {} + disabledShaderPasses: [] + m_LockedProperties: + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: [] + m_Ints: [] + m_Floats: [] + m_Colors: [] + m_BuildTextureStacks: [] + m_AllowLocking: 1 diff --git a/Runtime/Materials/GsplatSparkDepth.mat.meta b/Runtime/Materials/GsplatSparkDepth.mat.meta new file mode 100644 index 0000000..3cc9523 --- /dev/null +++ b/Runtime/Materials/GsplatSparkDepth.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3c7d8e9f104a5b6c8d2e1f0a9b6c5d4e +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 2100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Materials/GsplatUncompressed.asset b/Runtime/Materials/GsplatUncompressed.asset index 0e6be49..de044d1 100644 --- a/Runtime/Materials/GsplatUncompressed.asset +++ b/Runtime/Materials/GsplatUncompressed.asset @@ -13,5 +13,6 @@ MonoBehaviour: m_Name: GsplatUncompressed m_EditorClassIdentifier: DefaultMaterial: {fileID: 2100000, guid: 0065a348838e5474f802212446cf1ddd, type: 2} + DefaultDepthMaterial: {fileID: 2100000, guid: 2b6c7d8e9f104a5b8c3d2e1f0a9b6c5d, type: 2} CalcDepthShader: {fileID: 7200000, guid: 03396f0af8c54a3093503c0c27511df3, type: 3} InitOrderShader: {fileID: 7200000, guid: 71f302cf0e7c6254e9bae3e5c94b77d6, type: 3} diff --git a/Runtime/Materials/GsplatUncompressedDepth.mat b/Runtime/Materials/GsplatUncompressedDepth.mat new file mode 100644 index 0000000..16ba628 --- /dev/null +++ b/Runtime/Materials/GsplatUncompressedDepth.mat @@ -0,0 +1,32 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!21 &2100000 +Material: + serializedVersion: 8 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: GsplatUncompressedDepth + m_Shader: {fileID: 4800000, guid: 8f4d7a3b9c2e4615a0b1c6d8e9f20314, type: 3} + m_Parent: {fileID: 0} + m_ModifiedSerializedProperties: 0 + m_ValidKeywords: + - SH_BANDS_0 + - UNCOMPRESSED + m_InvalidKeywords: [] + m_LightmapFlags: 4 + m_EnableInstancingVariants: 0 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: -1 + stringTagMap: {} + disabledShaderPasses: [] + m_LockedProperties: + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: [] + m_Ints: [] + m_Floats: [] + m_Colors: [] + m_BuildTextureStacks: [] + m_AllowLocking: 1 diff --git a/Runtime/Materials/GsplatUncompressedDepth.mat.meta b/Runtime/Materials/GsplatUncompressedDepth.mat.meta new file mode 100644 index 0000000..f479b6d --- /dev/null +++ b/Runtime/Materials/GsplatUncompressedDepth.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2b6c7d8e9f104a5b8c3d2e1f0a9b6c5d +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 2100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/SRP/GsplatHDRPPass.cs b/Runtime/SRP/GsplatHDRPPass.cs index e2df33c..fbc4879 100644 --- a/Runtime/SRP/GsplatHDRPPass.cs +++ b/Runtime/SRP/GsplatHDRPPass.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: MIT #if GSPLAT_ENABLE_HDRP +using UnityEngine.Rendering; using UnityEngine.Rendering.HighDefinition; namespace Gsplat @@ -11,7 +12,12 @@ public class GsplatHDRPPass : CustomPass protected override void Execute(CustomPassContext ctx) { if (GsplatSorter.Instance.Valid && GsplatSettings.Instance.Valid && GsplatSorter.Instance.GatherGsplatsForCamera(ctx.hdCamera.camera)) + { GsplatSorter.Instance.DispatchSort(ctx.cmd, ctx.hdCamera.camera); + CoreUtils.SetRenderTarget(ctx.cmd, ctx.cameraColorBuffer, ctx.cameraDepthBuffer); + GsplatSorter.Instance.RenderDepthPrepass(ctx.cmd, ctx.hdCamera.camera); + GsplatSorter.Instance.RenderColor(ctx.cmd, ctx.hdCamera.camera); + } } } } diff --git a/Runtime/SRP/GsplatURPFeature.cs b/Runtime/SRP/GsplatURPFeature.cs index 91fefb2..aebcba5 100644 --- a/Runtime/SRP/GsplatURPFeature.cs +++ b/Runtime/SRP/GsplatURPFeature.cs @@ -46,12 +46,91 @@ public override void Execute(ScriptableRenderContext context, ref RenderingData #endif } + class GsplatDepthPrepass : ScriptableRenderPass + { +#if UNITY_6000_0_OR_NEWER + class PassData + { + public UniversalCameraData CameraData; + } + + public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData) + { + UniversalResourceData resourceData = frameData.Get(); + using var builder = renderGraph.AddRasterRenderPass("Gsplat.DepthPrepass", + out PassData passData); + passData.CameraData = frameData.Get(); + builder.SetRenderAttachmentDepth(resourceData.activeDepthTexture, AccessFlags.ReadWrite); + builder.AllowPassCulling(false); + builder.SetRenderFunc(static (PassData data, RasterGraphContext context) => + { + GsplatSorter.Instance.RenderDepthPrepass(context.cmd, data.CameraData.camera); + }); + } +#else + public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) + { + var cmd = CommandBufferPool.Get("Gsplat.DepthPrepass"); + GsplatSorter.Instance.RenderDepthPrepass(cmd, renderingData.cameraData.camera); + context.ExecuteCommandBuffer(cmd); + CommandBufferPool.Release(cmd); + } +#endif + } + + class GsplatColorPass : ScriptableRenderPass + { +#if UNITY_6000_0_OR_NEWER + class PassData + { + public UniversalCameraData CameraData; + } + + public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData) + { + UniversalResourceData resourceData = frameData.Get(); + using var builder = renderGraph.AddRasterRenderPass("Gsplat.Color", out PassData passData); + passData.CameraData = frameData.Get(); + builder.SetRenderAttachment(resourceData.activeColorTexture, 0, AccessFlags.ReadWrite); + builder.SetRenderAttachmentDepth(resourceData.activeDepthTexture, AccessFlags.Read); + builder.AllowPassCulling(false); + builder.SetRenderFunc(static (PassData data, RasterGraphContext context) => + { + GsplatSorter.Instance.RenderColor(context.cmd, data.CameraData.camera); + }); + } +#else + public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) + { + var cmd = CommandBufferPool.Get("Gsplat.Color"); + GsplatSorter.Instance.RenderColor(cmd, renderingData.cameraData.camera); + context.ExecuteCommandBuffer(cmd); + CommandBufferPool.Release(cmd); + } +#endif + } + GsplatRenderPass m_pass; + GsplatColorPass m_colorPass; + GsplatDepthPrepass m_depthPrepass; bool m_hasGsplats; public override void Create() { - m_pass = new GsplatRenderPass { renderPassEvent = RenderPassEvent.BeforeRenderingTransparents }; + // Record all splat work before URP's transparent draw pass. Render splat color before + // splat depth so overlapping transparent splats can blend before the depth write. + m_pass = new GsplatRenderPass + { + renderPassEvent = (RenderPassEvent)((int)RenderPassEvent.BeforeRenderingTransparents - 3) + }; + m_depthPrepass = new GsplatDepthPrepass + { + renderPassEvent = (RenderPassEvent)((int)RenderPassEvent.BeforeRenderingTransparents - 1) + }; + m_colorPass = new GsplatColorPass + { + renderPassEvent = (RenderPassEvent)((int)RenderPassEvent.BeforeRenderingTransparents - 2) + }; } public override void OnCameraPreCull(ScriptableRenderer renderer, in CameraData cameraData) @@ -66,7 +145,11 @@ public override void OnCameraPreCull(ScriptableRenderer renderer, in CameraData public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) { if (GsplatSorter.Instance.Valid && GsplatSettings.Instance.Valid && m_hasGsplats) + { renderer.EnqueuePass(m_pass); + renderer.EnqueuePass(m_colorPass); + renderer.EnqueuePass(m_depthPrepass); + } } protected override void Dispose(bool disposing) @@ -76,6 +159,8 @@ protected override void Dispose(bool disposing) m_pass.CommandBuffer = null; #endif m_pass = null; + m_colorPass = null; + m_depthPrepass = null; } } } diff --git a/Runtime/Shaders/Gsplat.shader b/Runtime/Shaders/Gsplat.shader index 4c8e655..8c5ffa9 100644 --- a/Runtime/Shaders/Gsplat.shader +++ b/Runtime/Shaders/Gsplat.shader @@ -132,4 +132,4 @@ Shader "Gsplat/Standard" } } -} \ No newline at end of file +} diff --git a/Runtime/Shaders/GsplatDepth.shader b/Runtime/Shaders/GsplatDepth.shader new file mode 100644 index 0000000..d0b108b --- /dev/null +++ b/Runtime/Shaders/GsplatDepth.shader @@ -0,0 +1,121 @@ +// Copyright (c) 2026 Keir Rice +// SPDX-License-Identifier: MIT + +Shader "Gsplat/DepthOnly" +{ + Properties {} + SubShader + { + Tags + { + "RenderType"="Transparent" + "Queue"="Transparent" + } + + Pass + { + ZWrite On + ZTest LEqual + ColorMask 0 + Cull Off + + HLSLPROGRAM + #pragma vertex vert + #pragma fragment frag + #pragma require compute + #pragma multi_compile SH_BANDS_0 SH_BANDS_1 SH_BANDS_2 SH_BANDS_3 SH_BANDS_4 + #pragma multi_compile UNCOMPRESSED SPARK + + #include "UnityCG.cginc" + #include "Gsplat.hlsl" + #ifdef UNCOMPRESSED + #include "GsplatUncompressed.hlsl" + #endif + #ifdef SPARK + #include "GsplatSpark.hlsl" + #endif + + int _SplatCount; + int _SplatInstanceSize; + float4x4 _MATRIX_M; + float _ScaleFactor; + float _DepthPrepassAlphaCutoff; + StructuredBuffer _OrderBuffer; + + struct appdata + { + float4 vertex : POSITION; + #if !defined(UNITY_INSTANCING_ENABLED) && !defined(UNITY_PROCEDURAL_INSTANCING_ENABLED) && !defined(UNITY_STEREO_INSTANCING_ENABLED) + uint instanceID : SV_InstanceID; + #endif + UNITY_VERTEX_INPUT_INSTANCE_ID + }; + + bool InitSource(appdata v, out SplatSource source) + { + #if !defined(UNITY_INSTANCING_ENABLED) && !defined(UNITY_PROCEDURAL_INSTANCING_ENABLED) && !defined(UNITY_STEREO_INSTANCING_ENABLED) + source.order = v.instanceID * _SplatInstanceSize + asuint(v.vertex.z); + #else + source.order = unity_InstanceID * _SplatInstanceSize + asuint(v.vertex.z); + #endif + + if (source.order >= _SplatCount) + return false; + + source.id = _OrderBuffer[source.order]; + source.cornerUV = float2(v.vertex.x, v.vertex.y) * _ScaleFactor; + return true; + } + + struct v2f + { + float2 uv : TEXCOORD0; + float alpha : TEXCOORD1; + float4 vertex : SV_POSITION; + UNITY_VERTEX_OUTPUT_STEREO + }; + + v2f vert(appdata v) + { + v2f o; + UNITY_SETUP_INSTANCE_ID(v); + UNITY_INITIALIZE_OUTPUT(v2f, o); + UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o); + o.vertex = discardVec; + + SplatSource source; + if (!InitSource(v, source)) + return o; + + SplatCenter center; + SplatCorner corner; + float4 color; + if (!InitSplatData(source, mul(UNITY_MATRIX_V, _MATRIX_M), center, corner, color)) + return o; + + ClipCorner(corner, color.w); + + o.vertex = center.proj + float4(corner.offset.x, _ProjectionParams.x * corner.offset.y, 0, 0); + o.alpha = color.a; + o.uv = corner.uv; + return o; + } + + float4 frag(v2f i) : SV_Target + { + float A = dot(i.uv, i.uv); + if (A > 1.0) discard; + + float2 absUV = abs(i.uv); + float maxUV = max(absUV.x, absUV.y); + + float falloff = -exp((maxUV - _ScaleFactor * 1.16) * 25 * _ScaleFactor); + float alpha = (exp(-A * 4.0) + falloff) * i.alpha; + + if (alpha < _DepthPrepassAlphaCutoff) discard; + return 0; + } + ENDHLSL + } + } +} diff --git a/Runtime/Shaders/GsplatDepth.shader.meta b/Runtime/Shaders/GsplatDepth.shader.meta new file mode 100644 index 0000000..a49701a --- /dev/null +++ b/Runtime/Shaders/GsplatDepth.shader.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 8f4d7a3b9c2e4615a0b1c6d8e9f20314 +ShaderImporter: + externalObjects: {} + defaultTextures: [] + nonModifiableTextures: [] + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Shaders/GsplatGlobalDepth.shader b/Runtime/Shaders/GsplatGlobalDepth.shader new file mode 100644 index 0000000..66b7e07 --- /dev/null +++ b/Runtime/Shaders/GsplatGlobalDepth.shader @@ -0,0 +1,105 @@ +// Copyright (c) 2026 Keir Rice +// SPDX-License-Identifier: MIT + +Shader "Gsplat/GlobalDepthOnly" +{ + Properties {} + SubShader + { + Tags + { + "RenderType"="Transparent" + "Queue"="Transparent" + } + + Pass + { + ZWrite On + ZTest LEqual + ColorMask 0 + Cull Off + + HLSLPROGRAM + #pragma vertex vert + #pragma fragment frag + #pragma require compute + #pragma multi_compile SH_BANDS_0 SH_BANDS_1 SH_BANDS_2 SH_BANDS_3 SH_BANDS_4 + + #include "UnityCG.cginc" + + int _SplatInstanceSize; + float _DepthPrepassAlphaCutoff; + + #include "GsplatSparkGlobal.hlsl" + + struct appdata + { + float4 vertex : POSITION; + #if !defined(UNITY_INSTANCING_ENABLED) && !defined(UNITY_PROCEDURAL_INSTANCING_ENABLED) && !defined(UNITY_STEREO_INSTANCING_ENABLED) + uint instanceID : SV_InstanceID; + #endif + UNITY_VERTEX_INPUT_INSTANCE_ID + }; + + struct v2f + { + float2 uv : TEXCOORD0; + nointerpolation uint rendererId : TEXCOORD1; + float alpha : TEXCOORD2; + float4 vertex : SV_POSITION; + UNITY_VERTEX_OUTPUT_STEREO + }; + + v2f vert(appdata v) + { + v2f o; + UNITY_SETUP_INSTANCE_ID(v); + UNITY_INITIALIZE_OUTPUT(v2f, o); + UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o); + o.vertex = discardVec; + + #if !defined(UNITY_INSTANCING_ENABLED) && !defined(UNITY_PROCEDURAL_INSTANCING_ENABLED) && !defined(UNITY_STEREO_INSTANCING_ENABLED) + uint instanceId = v.instanceID; + #else + uint instanceId = unity_InstanceID; + #endif + + GlobalSplatSource source; + if (!InitGlobalSource(instanceId, v.vertex.xyz, source)) + return o; + + SplatCenter center; + SplatCorner corner; + float4 color; + if (!InitGlobalSplatData(source, center, corner, color)) + return o; + + ClipCorner(corner, color.a); + + o.vertex = center.proj + float4(corner.offset.x, _ProjectionParams.x * corner.offset.y, 0, 0); + o.alpha = color.a; + o.uv = corner.uv; + o.rendererId = source.rendererId; + return o; + } + + float4 frag(v2f i) : SV_Target + { + RendererParams p = _RendererParams[i.rendererId]; + + float A = dot(i.uv, i.uv); + if (A > 1.0) discard; + + float2 absUV = abs(i.uv); + float maxUV = max(absUV.x, absUV.y); + + float falloff = -exp((maxUV - p.scaleFactor * 1.16) * 25 * p.scaleFactor); + float alpha = (exp(-A * 4.0) + falloff) * i.alpha; + + if (alpha < _DepthPrepassAlphaCutoff) discard; + return 0; + } + ENDHLSL + } + } +} diff --git a/Runtime/Shaders/GsplatGlobalDepth.shader.meta b/Runtime/Shaders/GsplatGlobalDepth.shader.meta new file mode 100644 index 0000000..94ec8a4 --- /dev/null +++ b/Runtime/Shaders/GsplatGlobalDepth.shader.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 9a5c8e1f2b3d4706a4f9c0d1e2b3a456 +ShaderImporter: + externalObjects: {} + defaultTextures: [] + nonModifiableTextures: [] + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Shaders/GsplatMergeOrderBuffers.compute b/Runtime/Shaders/GsplatMergeOrderBuffers.compute index 840458b..0f85ffe 100644 --- a/Runtime/Shaders/GsplatMergeOrderBuffers.compute +++ b/Runtime/Shaders/GsplatMergeOrderBuffers.compute @@ -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.