Files
XCEngine/MVS/3DGS-Unity/Runtime/GaussianSplatRenderer.cs
2026-03-29 01:36:53 +08:00

1070 lines
52 KiB
C#

// SPDX-License-Identifier: MIT
using System;
using System.Collections.Generic;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Mathematics;
using Unity.Profiling;
using Unity.Profiling.LowLevel;
using UnityEngine;
using UnityEngine.Experimental.Rendering;
using UnityEngine.Rendering;
using UnityEngine.XR;
namespace GaussianSplatting.Runtime
{
class GaussianSplatRenderSystem
{
// ReSharper disable MemberCanBePrivate.Global - used by HDRP/URP features that are not always compiled
internal static readonly ProfilerMarker s_ProfDraw = new(ProfilerCategory.Render, "GaussianSplat.Draw", MarkerFlags.SampleGPU);
internal static readonly ProfilerMarker s_ProfCompose = new(ProfilerCategory.Render, "GaussianSplat.Compose", MarkerFlags.SampleGPU);
internal static readonly ProfilerMarker s_ProfCalcView = new(ProfilerCategory.Render, "GaussianSplat.CalcView", MarkerFlags.SampleGPU);
// ReSharper restore MemberCanBePrivate.Global
public static GaussianSplatRenderSystem instance => ms_Instance ??= new GaussianSplatRenderSystem();
static GaussianSplatRenderSystem ms_Instance;
readonly Dictionary<GaussianSplatRenderer, MaterialPropertyBlock> m_Splats = new();
readonly HashSet<Camera> m_CameraCommandBuffersDone = new();
readonly List<(GaussianSplatRenderer, MaterialPropertyBlock)> m_ActiveSplats = new();
CommandBuffer m_CommandBuffer;
public void RegisterSplat(GaussianSplatRenderer r)
{
if (m_Splats.Count == 0)
{
if (GraphicsSettings.currentRenderPipeline == null)
Camera.onPreCull += OnPreCullCamera;
}
m_Splats.Add(r, new MaterialPropertyBlock());
}
public void UnregisterSplat(GaussianSplatRenderer r)
{
if (!m_Splats.ContainsKey(r))
return;
m_Splats.Remove(r);
if (m_Splats.Count == 0)
{
if (m_CameraCommandBuffersDone != null)
{
if (m_CommandBuffer != null)
{
foreach (var cam in m_CameraCommandBuffersDone)
{
if (cam)
cam.RemoveCommandBuffer(CameraEvent.BeforeForwardAlpha, m_CommandBuffer);
}
}
m_CameraCommandBuffersDone.Clear();
}
m_ActiveSplats.Clear();
m_CommandBuffer?.Dispose();
m_CommandBuffer = null;
Camera.onPreCull -= OnPreCullCamera;
}
}
// ReSharper disable once MemberCanBePrivate.Global - used by HDRP/URP features that are not always compiled
public bool GatherSplatsForCamera(Camera cam)
{
if (cam.cameraType == CameraType.Preview)
return false;
// gather all active & valid splat objects
m_ActiveSplats.Clear();
foreach (var kvp in m_Splats)
{
var gs = kvp.Key;
if (gs == null || !gs.isActiveAndEnabled || !gs.HasValidAsset || !gs.HasValidRenderSetup)
continue;
m_ActiveSplats.Add((kvp.Key, kvp.Value));
}
if (m_ActiveSplats.Count == 0)
return false;
// sort them by depth from camera
var camTr = cam.transform;
m_ActiveSplats.Sort((a, b) =>
{
var trA = a.Item1.transform;
var trB = b.Item1.transform;
var posA = camTr.InverseTransformPoint(trA.position);
var posB = camTr.InverseTransformPoint(trB.position);
return posA.z.CompareTo(posB.z);
});
return true;
}
// ReSharper disable once MemberCanBePrivate.Global - used by HDRP/URP features that are not always compiled
public Material SortAndRenderSplats(Camera cam, CommandBuffer cmb)
{
Material matComposite = null;
foreach (var kvp in m_ActiveSplats)
{
var gs = kvp.Item1;
gs.EnsureMaterials();
matComposite = gs.m_MatComposite;
var mpb = kvp.Item2;
// sort
var matrix = gs.transform.localToWorldMatrix;
if (gs.m_FrameCounter % gs.m_SortNthFrame == 0)
gs.SortPoints(cmb, cam, matrix);
++gs.m_FrameCounter;
// cache view
kvp.Item2.Clear();
Material displayMat = gs.m_RenderMode switch
{
GaussianSplatRenderer.RenderMode.DebugPoints => gs.m_MatDebugPoints,
GaussianSplatRenderer.RenderMode.DebugPointIndices => gs.m_MatDebugPoints,
GaussianSplatRenderer.RenderMode.DebugBoxes => gs.m_MatDebugBoxes,
GaussianSplatRenderer.RenderMode.DebugChunkBounds => gs.m_MatDebugBoxes,
_ => gs.m_MatSplats
};
if (displayMat == null)
continue;
gs.SetAssetDataOnMaterial(mpb);
mpb.SetBuffer(GaussianSplatRenderer.Props.SplatChunks, gs.m_GpuChunks);
mpb.SetBuffer(GaussianSplatRenderer.Props.SplatViewData, gs.m_GpuView);
mpb.SetBuffer(GaussianSplatRenderer.Props.OrderBuffer, gs.m_GpuSortKeys);
mpb.SetFloat(GaussianSplatRenderer.Props.SplatScale, gs.m_SplatScale);
mpb.SetFloat(GaussianSplatRenderer.Props.SplatOpacityScale, gs.m_OpacityScale);
mpb.SetFloat(GaussianSplatRenderer.Props.SplatSize, gs.m_PointDisplaySize);
mpb.SetInteger(GaussianSplatRenderer.Props.SHOrder, gs.m_SHOrder);
mpb.SetInteger(GaussianSplatRenderer.Props.SHOnly, gs.m_SHOnly ? 1 : 0);
mpb.SetInteger(GaussianSplatRenderer.Props.DisplayIndex, gs.m_RenderMode == GaussianSplatRenderer.RenderMode.DebugPointIndices ? 1 : 0);
mpb.SetInteger(GaussianSplatRenderer.Props.DisplayChunks, gs.m_RenderMode == GaussianSplatRenderer.RenderMode.DebugChunkBounds ? 1 : 0);
cmb.BeginSample(s_ProfCalcView);
gs.CalcViewData(cmb, cam, matrix);
cmb.EndSample(s_ProfCalcView);
// draw
int indexCount = 6;
int instanceCount = gs.splatCount;
MeshTopology topology = MeshTopology.Triangles;
if (gs.m_RenderMode is GaussianSplatRenderer.RenderMode.DebugBoxes or GaussianSplatRenderer.RenderMode.DebugChunkBounds)
indexCount = 36;
if (gs.m_RenderMode == GaussianSplatRenderer.RenderMode.DebugChunkBounds)
instanceCount = gs.m_GpuChunksValid ? gs.m_GpuChunks.count : 0;
cmb.BeginSample(s_ProfDraw);
cmb.DrawProcedural(gs.m_GpuIndexBuffer, matrix, displayMat, 0, topology, indexCount, instanceCount, mpb);
cmb.EndSample(s_ProfDraw);
}
return matComposite;
}
// ReSharper disable once MemberCanBePrivate.Global - used by HDRP/URP features that are not always compiled
// ReSharper disable once UnusedMethodReturnValue.Global - used by HDRP/URP features that are not always compiled
public CommandBuffer InitialClearCmdBuffer(Camera cam)
{
m_CommandBuffer ??= new CommandBuffer {name = "RenderGaussianSplats"};
if (GraphicsSettings.currentRenderPipeline == null && cam != null && !m_CameraCommandBuffersDone.Contains(cam))
{
cam.AddCommandBuffer(CameraEvent.BeforeForwardAlpha, m_CommandBuffer);
m_CameraCommandBuffersDone.Add(cam);
}
// get render target for all splats
m_CommandBuffer.Clear();
return m_CommandBuffer;
}
void OnPreCullCamera(Camera cam)
{
if (!GatherSplatsForCamera(cam))
return;
InitialClearCmdBuffer(cam);
m_CommandBuffer.GetTemporaryRT(GaussianSplatRenderer.Props.GaussianSplatRT, -1, -1, 0, FilterMode.Point, GraphicsFormat.R16G16B16A16_SFloat);
m_CommandBuffer.SetRenderTarget(GaussianSplatRenderer.Props.GaussianSplatRT, BuiltinRenderTextureType.CurrentActive);
m_CommandBuffer.ClearRenderTarget(RTClearFlags.Color, new Color(0, 0, 0, 0), 0, 0);
// add sorting, view calc and drawing commands for each splat object
Material matComposite = SortAndRenderSplats(cam, m_CommandBuffer);
// compose
m_CommandBuffer.BeginSample(s_ProfCompose);
m_CommandBuffer.SetRenderTarget(BuiltinRenderTextureType.CameraTarget);
m_CommandBuffer.DrawProcedural(Matrix4x4.identity, matComposite, 0, MeshTopology.Triangles, 3, 1);
m_CommandBuffer.EndSample(s_ProfCompose);
m_CommandBuffer.ReleaseTemporaryRT(GaussianSplatRenderer.Props.GaussianSplatRT);
}
}
//[ExecuteInEditMode]
public class GaussianSplatRenderer : MonoBehaviour
{
public enum RenderMode
{
Splats,
DebugPoints,
DebugPointIndices,
DebugBoxes,
DebugChunkBounds,
}
public GaussianSplatAsset m_Asset;
[Range(0.1f, 2.0f)] [Tooltip("Additional scaling factor for the splats")]
public float m_SplatScale = 1.0f;
[Range(0.05f, 20.0f)]
[Tooltip("Additional scaling factor for opacity")]
public float m_OpacityScale = 1.0f;
[Range(0, 3)] [Tooltip("Spherical Harmonics order to use")]
public int m_SHOrder = 3;
[Tooltip("Show only Spherical Harmonics contribution, using gray color")]
public bool m_SHOnly;
[Range(1,30)] [Tooltip("Sort splats only every N frames")]
public int m_SortNthFrame = 1;
public RenderMode m_RenderMode = RenderMode.Splats;
[Range(1.0f,15.0f)] public float m_PointDisplaySize = 3.0f;
public GaussianCutout[] m_Cutouts;
public Shader m_ShaderSplats;
public Shader m_ShaderComposite;
public Shader m_ShaderDebugPoints;
public Shader m_ShaderDebugBoxes;
[Tooltip("Gaussian splatting compute shader")]
public ComputeShader m_CSSplatUtilities;
int m_SplatCount; // initially same as asset splat count, but editing can change this
GraphicsBuffer m_GpuSortDistances;
internal GraphicsBuffer m_GpuSortKeys;
GraphicsBuffer m_GpuPosData;
GraphicsBuffer m_GpuOtherData;
GraphicsBuffer m_GpuSHData;
Texture m_GpuColorData;
internal GraphicsBuffer m_GpuChunks;
internal bool m_GpuChunksValid;
internal GraphicsBuffer m_GpuView;
internal GraphicsBuffer m_GpuIndexBuffer;
// these buffers are only for splat editing, and are lazily created
GraphicsBuffer m_GpuEditCutouts;
GraphicsBuffer m_GpuEditCountsBounds;
GraphicsBuffer m_GpuEditSelected;
GraphicsBuffer m_GpuEditDeleted;
GraphicsBuffer m_GpuEditSelectedMouseDown; // selection state at start of operation
GraphicsBuffer m_GpuEditPosMouseDown; // position state at start of operation
GraphicsBuffer m_GpuEditOtherMouseDown; // rotation/scale state at start of operation
GpuSorting m_Sorter;
GpuSorting.Args m_SorterArgs;
internal Material m_MatSplats;
internal Material m_MatComposite;
internal Material m_MatDebugPoints;
internal Material m_MatDebugBoxes;
internal int m_FrameCounter;
GaussianSplatAsset m_PrevAsset;
Hash128 m_PrevHash;
static readonly ProfilerMarker s_ProfSort = new(ProfilerCategory.Render, "GaussianSplat.Sort", MarkerFlags.SampleGPU);
internal static class Props
{
public static readonly int SplatPos = Shader.PropertyToID("_SplatPos");
public static readonly int SplatOther = Shader.PropertyToID("_SplatOther");
public static readonly int SplatSH = Shader.PropertyToID("_SplatSH");
public static readonly int SplatColor = Shader.PropertyToID("_SplatColor");
public static readonly int SplatSelectedBits = Shader.PropertyToID("_SplatSelectedBits");
public static readonly int SplatDeletedBits = Shader.PropertyToID("_SplatDeletedBits");
public static readonly int SplatBitsValid = Shader.PropertyToID("_SplatBitsValid");
public static readonly int SplatFormat = Shader.PropertyToID("_SplatFormat");
public static readonly int SplatChunks = Shader.PropertyToID("_SplatChunks");
public static readonly int SplatChunkCount = Shader.PropertyToID("_SplatChunkCount");
public static readonly int SplatViewData = Shader.PropertyToID("_SplatViewData");
public static readonly int OrderBuffer = Shader.PropertyToID("_OrderBuffer");
public static readonly int SplatScale = Shader.PropertyToID("_SplatScale");
public static readonly int SplatOpacityScale = Shader.PropertyToID("_SplatOpacityScale");
public static readonly int SplatSize = Shader.PropertyToID("_SplatSize");
public static readonly int SplatCount = Shader.PropertyToID("_SplatCount");
public static readonly int SHOrder = Shader.PropertyToID("_SHOrder");
public static readonly int SHOnly = Shader.PropertyToID("_SHOnly");
public static readonly int DisplayIndex = Shader.PropertyToID("_DisplayIndex");
public static readonly int DisplayChunks = Shader.PropertyToID("_DisplayChunks");
public static readonly int GaussianSplatRT = Shader.PropertyToID("_GaussianSplatRT");
public static readonly int SplatSortKeys = Shader.PropertyToID("_SplatSortKeys");
public static readonly int SplatSortDistances = Shader.PropertyToID("_SplatSortDistances");
public static readonly int SrcBuffer = Shader.PropertyToID("_SrcBuffer");
public static readonly int DstBuffer = Shader.PropertyToID("_DstBuffer");
public static readonly int BufferSize = Shader.PropertyToID("_BufferSize");
public static readonly int MatrixMV = Shader.PropertyToID("_MatrixMV");
public static readonly int MatrixObjectToWorld = Shader.PropertyToID("_MatrixObjectToWorld");
public static readonly int MatrixWorldToObject = Shader.PropertyToID("_MatrixWorldToObject");
public static readonly int VecScreenParams = Shader.PropertyToID("_VecScreenParams");
public static readonly int VecWorldSpaceCameraPos = Shader.PropertyToID("_VecWorldSpaceCameraPos");
public static readonly int SelectionCenter = Shader.PropertyToID("_SelectionCenter");
public static readonly int SelectionDelta = Shader.PropertyToID("_SelectionDelta");
public static readonly int SelectionDeltaRot = Shader.PropertyToID("_SelectionDeltaRot");
public static readonly int SplatCutoutsCount = Shader.PropertyToID("_SplatCutoutsCount");
public static readonly int SplatCutouts = Shader.PropertyToID("_SplatCutouts");
public static readonly int SelectionMode = Shader.PropertyToID("_SelectionMode");
public static readonly int SplatPosMouseDown = Shader.PropertyToID("_SplatPosMouseDown");
public static readonly int SplatOtherMouseDown = Shader.PropertyToID("_SplatOtherMouseDown");
}
[field: NonSerialized] public bool editModified { get; private set; }
[field: NonSerialized] public uint editSelectedSplats { get; private set; }
[field: NonSerialized] public uint editDeletedSplats { get; private set; }
[field: NonSerialized] public uint editCutSplats { get; private set; }
[field: NonSerialized] public Bounds editSelectedBounds { get; private set; }
public GaussianSplatAsset asset => m_Asset;
public int splatCount => m_SplatCount;
enum KernelIndices
{
SetIndices,
CalcDistances,
CalcViewData,
UpdateEditData,
InitEditData,
ClearBuffer,
InvertSelection,
SelectAll,
OrBuffers,
SelectionUpdate,
TranslateSelection,
RotateSelection,
ScaleSelection,
ExportData,
CopySplats,
}
public bool HasValidAsset =>
m_Asset != null &&
m_Asset.splatCount > 0 &&
m_Asset.formatVersion == GaussianSplatAsset.kCurrentVersion &&
m_Asset.posData != null &&
m_Asset.otherData != null &&
m_Asset.shData != null &&
m_Asset.colorData != null;
public bool HasValidRenderSetup => m_GpuPosData != null && m_GpuOtherData != null && m_GpuChunks != null;
const int kGpuViewDataSize = 40;
void CreateResourcesForAsset()
{
if (!HasValidAsset)
return;
m_SplatCount = asset.splatCount;
m_GpuPosData = new GraphicsBuffer(GraphicsBuffer.Target.Raw | GraphicsBuffer.Target.CopySource, (int) (asset.posData.dataSize / 4), 4) { name = "GaussianPosData" };
m_GpuPosData.SetData(asset.posData.GetData<uint>());
m_GpuOtherData = new GraphicsBuffer(GraphicsBuffer.Target.Raw | GraphicsBuffer.Target.CopySource, (int) (asset.otherData.dataSize / 4), 4) { name = "GaussianOtherData" };
m_GpuOtherData.SetData(asset.otherData.GetData<uint>());
m_GpuSHData = new GraphicsBuffer(GraphicsBuffer.Target.Raw, (int) (asset.shData.dataSize / 4), 4) { name = "GaussianSHData" };
m_GpuSHData.SetData(asset.shData.GetData<uint>());
var (texWidth, texHeight) = GaussianSplatAsset.CalcTextureSize(asset.splatCount);
var texFormat = GaussianSplatAsset.ColorFormatToGraphics(asset.colorFormat);
var tex = new Texture2D(texWidth, texHeight, texFormat, TextureCreationFlags.DontInitializePixels | TextureCreationFlags.IgnoreMipmapLimit | TextureCreationFlags.DontUploadUponCreate) { name = "GaussianColorData" };
tex.SetPixelData(asset.colorData.GetData<byte>(), 0);
tex.Apply(false, true);
m_GpuColorData = tex;
if (asset.chunkData != null && asset.chunkData.dataSize != 0)
{
m_GpuChunks = new GraphicsBuffer(GraphicsBuffer.Target.Structured,
(int) (asset.chunkData.dataSize / UnsafeUtility.SizeOf<GaussianSplatAsset.ChunkInfo>()),
UnsafeUtility.SizeOf<GaussianSplatAsset.ChunkInfo>()) {name = "GaussianChunkData"};
m_GpuChunks.SetData(asset.chunkData.GetData<GaussianSplatAsset.ChunkInfo>());
m_GpuChunksValid = true;
}
else
{
// just a dummy chunk buffer
m_GpuChunks = new GraphicsBuffer(GraphicsBuffer.Target.Structured, 1,
UnsafeUtility.SizeOf<GaussianSplatAsset.ChunkInfo>()) {name = "GaussianChunkData"};
m_GpuChunksValid = false;
}
m_GpuView = new GraphicsBuffer(GraphicsBuffer.Target.Structured, m_Asset.splatCount, kGpuViewDataSize);
m_GpuIndexBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Index, 36, 2);
// cube indices, most often we use only the first quad
m_GpuIndexBuffer.SetData(new ushort[]
{
0, 1, 2, 1, 3, 2,
4, 6, 5, 5, 6, 7,
0, 2, 4, 4, 2, 6,
1, 5, 3, 5, 7, 3,
0, 4, 1, 4, 5, 1,
2, 3, 6, 3, 7, 6
});
InitSortBuffers(splatCount);
}
void InitSortBuffers(int count)
{
m_GpuSortDistances?.Dispose();
m_GpuSortKeys?.Dispose();
m_SorterArgs.resources.Dispose();
EnsureSorterAndRegister();
m_GpuSortDistances = new GraphicsBuffer(GraphicsBuffer.Target.Structured, count, 4) { name = "GaussianSplatSortDistances" };
m_GpuSortKeys = new GraphicsBuffer(GraphicsBuffer.Target.Structured, count, 4) { name = "GaussianSplatSortIndices" };
// init keys buffer to splat indices
m_CSSplatUtilities.SetBuffer((int)KernelIndices.SetIndices, Props.SplatSortKeys, m_GpuSortKeys);
m_CSSplatUtilities.SetInt(Props.SplatCount, m_GpuSortDistances.count);
m_CSSplatUtilities.GetKernelThreadGroupSizes((int)KernelIndices.SetIndices, out uint gsX, out _, out _);
m_CSSplatUtilities.Dispatch((int)KernelIndices.SetIndices, (m_GpuSortDistances.count + (int)gsX - 1)/(int)gsX, 1, 1);
m_SorterArgs.inputKeys = m_GpuSortDistances;
m_SorterArgs.inputValues = m_GpuSortKeys;
m_SorterArgs.count = (uint)count;
if (m_Sorter.Valid)
m_SorterArgs.resources = GpuSorting.SupportResources.Load((uint)count);
}
bool resourcesAreSetUp => m_ShaderSplats != null && m_ShaderComposite != null && m_ShaderDebugPoints != null &&
m_ShaderDebugBoxes != null && m_CSSplatUtilities != null && SystemInfo.supportsComputeShaders;
public void EnsureMaterials()
{
if (m_MatSplats == null && resourcesAreSetUp)
{
m_MatSplats = new Material(m_ShaderSplats) {name = "GaussianSplats"};
m_MatComposite = new Material(m_ShaderComposite) {name = "GaussianClearDstAlpha"};
m_MatDebugPoints = new Material(m_ShaderDebugPoints) {name = "GaussianDebugPoints"};
m_MatDebugBoxes = new Material(m_ShaderDebugBoxes) {name = "GaussianDebugBoxes"};
}
}
public void EnsureSorterAndRegister()
{
if (m_Sorter == null && resourcesAreSetUp)
{
m_Sorter = new GpuSorting(m_CSSplatUtilities);
GaussianSplatRenderSystem.instance.RegisterSplat(this);
}
}
public void OnEnable()
{
m_FrameCounter = 0;
if (!resourcesAreSetUp)
return;
EnsureMaterials();
EnsureSorterAndRegister();
CreateResourcesForAsset();
}
void SetAssetDataOnCS(CommandBuffer cmb, KernelIndices kernel)
{
ComputeShader cs = m_CSSplatUtilities;
int kernelIndex = (int) kernel;
cmb.SetComputeBufferParam(cs, kernelIndex, Props.SplatPos, m_GpuPosData);
cmb.SetComputeBufferParam(cs, kernelIndex, Props.SplatChunks, m_GpuChunks);
cmb.SetComputeBufferParam(cs, kernelIndex, Props.SplatOther, m_GpuOtherData);
cmb.SetComputeBufferParam(cs, kernelIndex, Props.SplatSH, m_GpuSHData);
cmb.SetComputeTextureParam(cs, kernelIndex, Props.SplatColor, m_GpuColorData);
cmb.SetComputeBufferParam(cs, kernelIndex, Props.SplatSelectedBits, m_GpuEditSelected ?? m_GpuPosData);
cmb.SetComputeBufferParam(cs, kernelIndex, Props.SplatDeletedBits, m_GpuEditDeleted ?? m_GpuPosData);
cmb.SetComputeBufferParam(cs, kernelIndex, Props.SplatViewData, m_GpuView);
cmb.SetComputeBufferParam(cs, kernelIndex, Props.OrderBuffer, m_GpuSortKeys);
cmb.SetComputeIntParam(cs, Props.SplatBitsValid, m_GpuEditSelected != null && m_GpuEditDeleted != null ? 1 : 0);
uint format = (uint)m_Asset.posFormat | ((uint)m_Asset.scaleFormat << 8) | ((uint)m_Asset.shFormat << 16);
cmb.SetComputeIntParam(cs, Props.SplatFormat, (int)format);
cmb.SetComputeIntParam(cs, Props.SplatCount, m_SplatCount);
cmb.SetComputeIntParam(cs, Props.SplatChunkCount, m_GpuChunksValid ? m_GpuChunks.count : 0);
UpdateCutoutsBuffer();
cmb.SetComputeIntParam(cs, Props.SplatCutoutsCount, m_Cutouts?.Length ?? 0);
cmb.SetComputeBufferParam(cs, kernelIndex, Props.SplatCutouts, m_GpuEditCutouts);
}
internal void SetAssetDataOnMaterial(MaterialPropertyBlock mat)
{
mat.SetBuffer(Props.SplatPos, m_GpuPosData);
mat.SetBuffer(Props.SplatOther, m_GpuOtherData);
mat.SetBuffer(Props.SplatSH, m_GpuSHData);
mat.SetTexture(Props.SplatColor, m_GpuColorData);
mat.SetBuffer(Props.SplatSelectedBits, m_GpuEditSelected ?? m_GpuPosData);
mat.SetBuffer(Props.SplatDeletedBits, m_GpuEditDeleted ?? m_GpuPosData);
mat.SetInt(Props.SplatBitsValid, m_GpuEditSelected != null && m_GpuEditDeleted != null ? 1 : 0);
uint format = (uint)m_Asset.posFormat | ((uint)m_Asset.scaleFormat << 8) | ((uint)m_Asset.shFormat << 16);
mat.SetInteger(Props.SplatFormat, (int)format);
mat.SetInteger(Props.SplatCount, m_SplatCount);
mat.SetInteger(Props.SplatChunkCount, m_GpuChunksValid ? m_GpuChunks.count : 0);
}
static void DisposeBuffer(ref GraphicsBuffer buf)
{
buf?.Dispose();
buf = null;
}
void DisposeResourcesForAsset()
{
DestroyImmediate(m_GpuColorData);
DisposeBuffer(ref m_GpuPosData);
DisposeBuffer(ref m_GpuOtherData);
DisposeBuffer(ref m_GpuSHData);
DisposeBuffer(ref m_GpuChunks);
DisposeBuffer(ref m_GpuView);
DisposeBuffer(ref m_GpuIndexBuffer);
DisposeBuffer(ref m_GpuSortDistances);
DisposeBuffer(ref m_GpuSortKeys);
DisposeBuffer(ref m_GpuEditSelectedMouseDown);
DisposeBuffer(ref m_GpuEditPosMouseDown);
DisposeBuffer(ref m_GpuEditOtherMouseDown);
DisposeBuffer(ref m_GpuEditSelected);
DisposeBuffer(ref m_GpuEditDeleted);
DisposeBuffer(ref m_GpuEditCountsBounds);
DisposeBuffer(ref m_GpuEditCutouts);
m_SorterArgs.resources.Dispose();
m_SplatCount = 0;
m_GpuChunksValid = false;
editSelectedSplats = 0;
editDeletedSplats = 0;
editCutSplats = 0;
editModified = false;
editSelectedBounds = default;
}
public void OnDisable()
{
DisposeResourcesForAsset();
GaussianSplatRenderSystem.instance.UnregisterSplat(this);
DestroyImmediate(m_MatSplats);
DestroyImmediate(m_MatComposite);
DestroyImmediate(m_MatDebugPoints);
DestroyImmediate(m_MatDebugBoxes);
}
internal void CalcViewData(CommandBuffer cmb, Camera cam, Matrix4x4 matrix)
{
if (cam.cameraType == CameraType.Preview)
return;
var tr = transform;
Matrix4x4 matView = cam.worldToCameraMatrix;
Matrix4x4 matProj = GL.GetGPUProjectionMatrix(cam.projectionMatrix, true);
Matrix4x4 matO2W = tr.localToWorldMatrix;
Matrix4x4 matW2O = tr.worldToLocalMatrix;
int screenW = cam.pixelWidth, screenH = cam.pixelHeight;
int eyeW = XRSettings.eyeTextureWidth, eyeH = XRSettings.eyeTextureHeight;
Vector4 screenPar = new Vector4(eyeW != 0 ? eyeW : screenW, eyeH != 0 ? eyeH : screenH, 0, 0);
Vector4 camPos = cam.transform.position;
// calculate view dependent data for each splat
SetAssetDataOnCS(cmb, KernelIndices.CalcViewData);
cmb.SetComputeMatrixParam(m_CSSplatUtilities, Props.MatrixMV, matView * matO2W);
cmb.SetComputeMatrixParam(m_CSSplatUtilities, Props.MatrixObjectToWorld, matO2W);
cmb.SetComputeMatrixParam(m_CSSplatUtilities, Props.MatrixWorldToObject, matW2O);
cmb.SetComputeVectorParam(m_CSSplatUtilities, Props.VecScreenParams, screenPar);
cmb.SetComputeVectorParam(m_CSSplatUtilities, Props.VecWorldSpaceCameraPos, camPos);
cmb.SetComputeFloatParam(m_CSSplatUtilities, Props.SplatScale, m_SplatScale);
cmb.SetComputeFloatParam(m_CSSplatUtilities, Props.SplatOpacityScale, m_OpacityScale);
cmb.SetComputeIntParam(m_CSSplatUtilities, Props.SHOrder, m_SHOrder);
cmb.SetComputeIntParam(m_CSSplatUtilities, Props.SHOnly, m_SHOnly ? 1 : 0);
m_CSSplatUtilities.GetKernelThreadGroupSizes((int)KernelIndices.CalcViewData, out uint gsX, out _, out _);
cmb.DispatchCompute(m_CSSplatUtilities, (int)KernelIndices.CalcViewData, (m_GpuView.count + (int)gsX - 1)/(int)gsX, 1, 1);
}
internal void SortPoints(CommandBuffer cmd, Camera cam, Matrix4x4 matrix)
{
if (cam.cameraType == CameraType.Preview)
return;
Matrix4x4 worldToCamMatrix = cam.worldToCameraMatrix;
worldToCamMatrix.m20 *= -1;
worldToCamMatrix.m21 *= -1;
worldToCamMatrix.m22 *= -1;
// calculate distance to the camera for each splat
cmd.BeginSample(s_ProfSort);
cmd.SetComputeBufferParam(m_CSSplatUtilities, (int)KernelIndices.CalcDistances, Props.SplatSortDistances, m_GpuSortDistances);
cmd.SetComputeBufferParam(m_CSSplatUtilities, (int)KernelIndices.CalcDistances, Props.SplatSortKeys, m_GpuSortKeys);
cmd.SetComputeBufferParam(m_CSSplatUtilities, (int)KernelIndices.CalcDistances, Props.SplatChunks, m_GpuChunks);
cmd.SetComputeBufferParam(m_CSSplatUtilities, (int)KernelIndices.CalcDistances, Props.SplatPos, m_GpuPosData);
cmd.SetComputeIntParam(m_CSSplatUtilities, Props.SplatFormat, (int)m_Asset.posFormat);
cmd.SetComputeMatrixParam(m_CSSplatUtilities, Props.MatrixMV, worldToCamMatrix * matrix);
cmd.SetComputeIntParam(m_CSSplatUtilities, Props.SplatCount, m_SplatCount);
cmd.SetComputeIntParam(m_CSSplatUtilities, Props.SplatChunkCount, m_GpuChunksValid ? m_GpuChunks.count : 0);
m_CSSplatUtilities.GetKernelThreadGroupSizes((int)KernelIndices.CalcDistances, out uint gsX, out _, out _);
cmd.DispatchCompute(m_CSSplatUtilities, (int)KernelIndices.CalcDistances, (m_GpuSortDistances.count + (int)gsX - 1)/(int)gsX, 1, 1);
// sort the splats
EnsureSorterAndRegister();
m_Sorter.Dispatch(cmd, m_SorterArgs);
cmd.EndSample(s_ProfSort);
}
public void Update()
{
var curHash = m_Asset ? m_Asset.dataHash : new Hash128();
if (m_PrevAsset != m_Asset || m_PrevHash != curHash)
{
m_PrevAsset = m_Asset;
m_PrevHash = curHash;
if (resourcesAreSetUp)
{
DisposeResourcesForAsset();
CreateResourcesForAsset();
}
else
{
Debug.LogError($"{nameof(GaussianSplatRenderer)} component is not set up correctly (Resource references are missing), or platform does not support compute shaders");
}
}
}
public void ActivateCamera(int index)
{
Camera mainCam = Camera.main;
if (!mainCam)
return;
if (!m_Asset || m_Asset.cameras == null)
return;
var selfTr = transform;
var camTr = mainCam.transform;
var prevParent = camTr.parent;
var cam = m_Asset.cameras[index];
camTr.parent = selfTr;
camTr.localPosition = cam.pos;
camTr.localRotation = Quaternion.LookRotation(cam.axisZ, cam.axisY);
camTr.parent = prevParent;
camTr.localScale = Vector3.one;
#if UNITY_EDITOR
UnityEditor.EditorUtility.SetDirty(camTr);
#endif
}
void ClearGraphicsBuffer(GraphicsBuffer buf)
{
m_CSSplatUtilities.SetBuffer((int)KernelIndices.ClearBuffer, Props.DstBuffer, buf);
m_CSSplatUtilities.SetInt(Props.BufferSize, buf.count);
m_CSSplatUtilities.GetKernelThreadGroupSizes((int)KernelIndices.ClearBuffer, out uint gsX, out _, out _);
m_CSSplatUtilities.Dispatch((int)KernelIndices.ClearBuffer, (int)((buf.count+gsX-1)/gsX), 1, 1);
}
void UnionGraphicsBuffers(GraphicsBuffer dst, GraphicsBuffer src)
{
m_CSSplatUtilities.SetBuffer((int)KernelIndices.OrBuffers, Props.SrcBuffer, src);
m_CSSplatUtilities.SetBuffer((int)KernelIndices.OrBuffers, Props.DstBuffer, dst);
m_CSSplatUtilities.SetInt(Props.BufferSize, dst.count);
m_CSSplatUtilities.GetKernelThreadGroupSizes((int)KernelIndices.OrBuffers, out uint gsX, out _, out _);
m_CSSplatUtilities.Dispatch((int)KernelIndices.OrBuffers, (int)((dst.count+gsX-1)/gsX), 1, 1);
}
static float SortableUintToFloat(uint v)
{
uint mask = ((v >> 31) - 1) | 0x80000000u;
return math.asfloat(v ^ mask);
}
public void UpdateEditCountsAndBounds()
{
if (m_GpuEditSelected == null)
{
editSelectedSplats = 0;
editDeletedSplats = 0;
editCutSplats = 0;
editModified = false;
editSelectedBounds = default;
return;
}
m_CSSplatUtilities.SetBuffer((int)KernelIndices.InitEditData, Props.DstBuffer, m_GpuEditCountsBounds);
m_CSSplatUtilities.Dispatch((int)KernelIndices.InitEditData, 1, 1, 1);
using CommandBuffer cmb = new CommandBuffer();
SetAssetDataOnCS(cmb, KernelIndices.UpdateEditData);
cmb.SetComputeBufferParam(m_CSSplatUtilities, (int)KernelIndices.UpdateEditData, Props.DstBuffer, m_GpuEditCountsBounds);
cmb.SetComputeIntParam(m_CSSplatUtilities, Props.BufferSize, m_GpuEditSelected.count);
m_CSSplatUtilities.GetKernelThreadGroupSizes((int)KernelIndices.UpdateEditData, out uint gsX, out _, out _);
cmb.DispatchCompute(m_CSSplatUtilities, (int)KernelIndices.UpdateEditData, (int)((m_GpuEditSelected.count+gsX-1)/gsX), 1, 1);
Graphics.ExecuteCommandBuffer(cmb);
uint[] res = new uint[m_GpuEditCountsBounds.count];
m_GpuEditCountsBounds.GetData(res);
editSelectedSplats = res[0];
editDeletedSplats = res[1];
editCutSplats = res[2];
Vector3 min = new Vector3(SortableUintToFloat(res[3]), SortableUintToFloat(res[4]), SortableUintToFloat(res[5]));
Vector3 max = new Vector3(SortableUintToFloat(res[6]), SortableUintToFloat(res[7]), SortableUintToFloat(res[8]));
Bounds bounds = default;
bounds.SetMinMax(min, max);
if (bounds.extents.sqrMagnitude < 0.01)
bounds.extents = new Vector3(0.1f,0.1f,0.1f);
editSelectedBounds = bounds;
}
void UpdateCutoutsBuffer()
{
int bufferSize = m_Cutouts?.Length ?? 0;
if (bufferSize == 0)
bufferSize = 1;
if (m_GpuEditCutouts == null || m_GpuEditCutouts.count != bufferSize)
{
m_GpuEditCutouts?.Dispose();
m_GpuEditCutouts = new GraphicsBuffer(GraphicsBuffer.Target.Structured, bufferSize, UnsafeUtility.SizeOf<GaussianCutout.ShaderData>()) { name = "GaussianCutouts" };
}
NativeArray<GaussianCutout.ShaderData> data = new(bufferSize, Allocator.Temp);
if (m_Cutouts != null)
{
var matrix = transform.localToWorldMatrix;
for (var i = 0; i < m_Cutouts.Length; ++i)
{
data[i] = GaussianCutout.GetShaderData(m_Cutouts[i], matrix);
}
}
m_GpuEditCutouts.SetData(data);
data.Dispose();
}
bool EnsureEditingBuffers()
{
if (!HasValidAsset || !HasValidRenderSetup)
return false;
if (m_GpuEditSelected == null)
{
var target = GraphicsBuffer.Target.Raw | GraphicsBuffer.Target.CopySource |
GraphicsBuffer.Target.CopyDestination;
var size = (m_SplatCount + 31) / 32;
m_GpuEditSelected = new GraphicsBuffer(target, size, 4) {name = "GaussianSplatSelected"};
m_GpuEditSelectedMouseDown = new GraphicsBuffer(target, size, 4) {name = "GaussianSplatSelectedInit"};
m_GpuEditDeleted = new GraphicsBuffer(target, size, 4) {name = "GaussianSplatDeleted"};
m_GpuEditCountsBounds = new GraphicsBuffer(target, 3 + 6, 4) {name = "GaussianSplatEditData"}; // selected count, deleted bound, cut count, float3 min, float3 max
ClearGraphicsBuffer(m_GpuEditSelected);
ClearGraphicsBuffer(m_GpuEditSelectedMouseDown);
ClearGraphicsBuffer(m_GpuEditDeleted);
}
return m_GpuEditSelected != null;
}
public void EditStoreSelectionMouseDown()
{
if (!EnsureEditingBuffers()) return;
Graphics.CopyBuffer(m_GpuEditSelected, m_GpuEditSelectedMouseDown);
}
public void EditStorePosMouseDown()
{
if (m_GpuEditPosMouseDown == null)
{
m_GpuEditPosMouseDown = new GraphicsBuffer(m_GpuPosData.target | GraphicsBuffer.Target.CopyDestination, m_GpuPosData.count, m_GpuPosData.stride) {name = "GaussianSplatEditPosMouseDown"};
}
Graphics.CopyBuffer(m_GpuPosData, m_GpuEditPosMouseDown);
}
public void EditStoreOtherMouseDown()
{
if (m_GpuEditOtherMouseDown == null)
{
m_GpuEditOtherMouseDown = new GraphicsBuffer(m_GpuOtherData.target | GraphicsBuffer.Target.CopyDestination, m_GpuOtherData.count, m_GpuOtherData.stride) {name = "GaussianSplatEditOtherMouseDown"};
}
Graphics.CopyBuffer(m_GpuOtherData, m_GpuEditOtherMouseDown);
}
public void EditUpdateSelection(Vector2 rectMin, Vector2 rectMax, Camera cam, bool subtract)
{
if (!EnsureEditingBuffers()) return;
Graphics.CopyBuffer(m_GpuEditSelectedMouseDown, m_GpuEditSelected);
var tr = transform;
Matrix4x4 matView = cam.worldToCameraMatrix;
Matrix4x4 matProj = GL.GetGPUProjectionMatrix(cam.projectionMatrix, true);
Matrix4x4 matO2W = tr.localToWorldMatrix;
Matrix4x4 matW2O = tr.worldToLocalMatrix;
int screenW = cam.pixelWidth, screenH = cam.pixelHeight;
Vector4 screenPar = new Vector4(screenW, screenH, 0, 0);
Vector4 camPos = cam.transform.position;
using var cmb = new CommandBuffer { name = "SplatSelectionUpdate" };
SetAssetDataOnCS(cmb, KernelIndices.SelectionUpdate);
cmb.SetComputeMatrixParam(m_CSSplatUtilities, Props.MatrixMV, matView * matO2W);
cmb.SetComputeMatrixParam(m_CSSplatUtilities, Props.MatrixObjectToWorld, matO2W);
cmb.SetComputeMatrixParam(m_CSSplatUtilities, Props.MatrixWorldToObject, matW2O);
cmb.SetComputeVectorParam(m_CSSplatUtilities, Props.VecScreenParams, screenPar);
cmb.SetComputeVectorParam(m_CSSplatUtilities, Props.VecWorldSpaceCameraPos, camPos);
cmb.SetComputeVectorParam(m_CSSplatUtilities, "_SelectionRect", new Vector4(rectMin.x, rectMax.y, rectMax.x, rectMin.y));
cmb.SetComputeIntParam(m_CSSplatUtilities, Props.SelectionMode, subtract ? 0 : 1);
DispatchUtilsAndExecute(cmb, KernelIndices.SelectionUpdate, m_SplatCount);
UpdateEditCountsAndBounds();
}
public void EditTranslateSelection(Vector3 localSpacePosDelta)
{
if (!EnsureEditingBuffers()) return;
using var cmb = new CommandBuffer { name = "SplatTranslateSelection" };
SetAssetDataOnCS(cmb, KernelIndices.TranslateSelection);
cmb.SetComputeVectorParam(m_CSSplatUtilities, Props.SelectionDelta, localSpacePosDelta);
DispatchUtilsAndExecute(cmb, KernelIndices.TranslateSelection, m_SplatCount);
UpdateEditCountsAndBounds();
editModified = true;
}
public void EditRotateSelection(Vector3 localSpaceCenter, Matrix4x4 localToWorld, Matrix4x4 worldToLocal, Quaternion rotation)
{
if (!EnsureEditingBuffers()) return;
if (m_GpuEditPosMouseDown == null || m_GpuEditOtherMouseDown == null) return; // should have captured initial state
using var cmb = new CommandBuffer { name = "SplatRotateSelection" };
SetAssetDataOnCS(cmb, KernelIndices.RotateSelection);
cmb.SetComputeBufferParam(m_CSSplatUtilities, (int)KernelIndices.RotateSelection, Props.SplatPosMouseDown, m_GpuEditPosMouseDown);
cmb.SetComputeBufferParam(m_CSSplatUtilities, (int)KernelIndices.RotateSelection, Props.SplatOtherMouseDown, m_GpuEditOtherMouseDown);
cmb.SetComputeVectorParam(m_CSSplatUtilities, Props.SelectionCenter, localSpaceCenter);
cmb.SetComputeMatrixParam(m_CSSplatUtilities, Props.MatrixObjectToWorld, localToWorld);
cmb.SetComputeMatrixParam(m_CSSplatUtilities, Props.MatrixWorldToObject, worldToLocal);
cmb.SetComputeVectorParam(m_CSSplatUtilities, Props.SelectionDeltaRot, new Vector4(rotation.x, rotation.y, rotation.z, rotation.w));
DispatchUtilsAndExecute(cmb, KernelIndices.RotateSelection, m_SplatCount);
UpdateEditCountsAndBounds();
editModified = true;
}
public void EditScaleSelection(Vector3 localSpaceCenter, Matrix4x4 localToWorld, Matrix4x4 worldToLocal, Vector3 scale)
{
if (!EnsureEditingBuffers()) return;
if (m_GpuEditPosMouseDown == null) return; // should have captured initial state
using var cmb = new CommandBuffer { name = "SplatScaleSelection" };
SetAssetDataOnCS(cmb, KernelIndices.ScaleSelection);
cmb.SetComputeBufferParam(m_CSSplatUtilities, (int)KernelIndices.ScaleSelection, Props.SplatPosMouseDown, m_GpuEditPosMouseDown);
cmb.SetComputeVectorParam(m_CSSplatUtilities, Props.SelectionCenter, localSpaceCenter);
cmb.SetComputeMatrixParam(m_CSSplatUtilities, Props.MatrixObjectToWorld, localToWorld);
cmb.SetComputeMatrixParam(m_CSSplatUtilities, Props.MatrixWorldToObject, worldToLocal);
cmb.SetComputeVectorParam(m_CSSplatUtilities, Props.SelectionDelta, scale);
DispatchUtilsAndExecute(cmb, KernelIndices.ScaleSelection, m_SplatCount);
UpdateEditCountsAndBounds();
editModified = true;
}
public void EditDeleteSelected()
{
if (!EnsureEditingBuffers()) return;
UnionGraphicsBuffers(m_GpuEditDeleted, m_GpuEditSelected);
EditDeselectAll();
UpdateEditCountsAndBounds();
if (editDeletedSplats != 0)
editModified = true;
}
public void EditSelectAll()
{
if (!EnsureEditingBuffers()) return;
using var cmb = new CommandBuffer { name = "SplatSelectAll" };
SetAssetDataOnCS(cmb, KernelIndices.SelectAll);
cmb.SetComputeBufferParam(m_CSSplatUtilities, (int)KernelIndices.SelectAll, Props.DstBuffer, m_GpuEditSelected);
cmb.SetComputeIntParam(m_CSSplatUtilities, Props.BufferSize, m_GpuEditSelected.count);
DispatchUtilsAndExecute(cmb, KernelIndices.SelectAll, m_GpuEditSelected.count);
UpdateEditCountsAndBounds();
}
public void EditDeselectAll()
{
if (!EnsureEditingBuffers()) return;
ClearGraphicsBuffer(m_GpuEditSelected);
UpdateEditCountsAndBounds();
}
public void EditInvertSelection()
{
if (!EnsureEditingBuffers()) return;
using var cmb = new CommandBuffer { name = "SplatInvertSelection" };
SetAssetDataOnCS(cmb, KernelIndices.InvertSelection);
cmb.SetComputeBufferParam(m_CSSplatUtilities, (int)KernelIndices.InvertSelection, Props.DstBuffer, m_GpuEditSelected);
cmb.SetComputeIntParam(m_CSSplatUtilities, Props.BufferSize, m_GpuEditSelected.count);
DispatchUtilsAndExecute(cmb, KernelIndices.InvertSelection, m_GpuEditSelected.count);
UpdateEditCountsAndBounds();
}
public bool EditExportData(GraphicsBuffer dstData, bool bakeTransform)
{
if (!EnsureEditingBuffers()) return false;
int flags = 0;
var tr = transform;
Quaternion bakeRot = tr.localRotation;
Vector3 bakeScale = tr.localScale;
if (bakeTransform)
flags = 1;
using var cmb = new CommandBuffer { name = "SplatExportData" };
SetAssetDataOnCS(cmb, KernelIndices.ExportData);
cmb.SetComputeIntParam(m_CSSplatUtilities, "_ExportTransformFlags", flags);
cmb.SetComputeVectorParam(m_CSSplatUtilities, "_ExportTransformRotation", new Vector4(bakeRot.x, bakeRot.y, bakeRot.z, bakeRot.w));
cmb.SetComputeVectorParam(m_CSSplatUtilities, "_ExportTransformScale", bakeScale);
cmb.SetComputeMatrixParam(m_CSSplatUtilities, Props.MatrixObjectToWorld, tr.localToWorldMatrix);
cmb.SetComputeBufferParam(m_CSSplatUtilities, (int)KernelIndices.ExportData, "_ExportBuffer", dstData);
DispatchUtilsAndExecute(cmb, KernelIndices.ExportData, m_SplatCount);
return true;
}
public void EditSetSplatCount(int newSplatCount)
{
if (newSplatCount <= 0 || newSplatCount > GaussianSplatAsset.kMaxSplats)
{
Debug.LogError($"Invalid new splat count: {newSplatCount}");
return;
}
if (asset.chunkData != null)
{
Debug.LogError("Only splats with VeryHigh quality can be resized");
return;
}
if (newSplatCount == splatCount)
return;
int posStride = (int)(asset.posData.dataSize / asset.splatCount);
int otherStride = (int)(asset.otherData.dataSize / asset.splatCount);
int shStride = (int) (asset.shData.dataSize / asset.splatCount);
// create new GPU buffers
var newPosData = new GraphicsBuffer(GraphicsBuffer.Target.Raw | GraphicsBuffer.Target.CopySource, newSplatCount * posStride / 4, 4) { name = "GaussianPosData" };
var newOtherData = new GraphicsBuffer(GraphicsBuffer.Target.Raw | GraphicsBuffer.Target.CopySource, newSplatCount * otherStride / 4, 4) { name = "GaussianOtherData" };
var newSHData = new GraphicsBuffer(GraphicsBuffer.Target.Raw, newSplatCount * shStride / 4, 4) { name = "GaussianSHData" };
// new texture is a RenderTexture so we can write to it from a compute shader
var (texWidth, texHeight) = GaussianSplatAsset.CalcTextureSize(newSplatCount);
var texFormat = GaussianSplatAsset.ColorFormatToGraphics(asset.colorFormat);
var newColorData = new RenderTexture(texWidth, texHeight, texFormat, GraphicsFormat.None) { name = "GaussianColorData", enableRandomWrite = true };
newColorData.Create();
// selected/deleted buffers
var selTarget = GraphicsBuffer.Target.Raw | GraphicsBuffer.Target.CopySource | GraphicsBuffer.Target.CopyDestination;
var selSize = (newSplatCount + 31) / 32;
var newEditSelected = new GraphicsBuffer(selTarget, selSize, 4) {name = "GaussianSplatSelected"};
var newEditSelectedMouseDown = new GraphicsBuffer(selTarget, selSize, 4) {name = "GaussianSplatSelectedInit"};
var newEditDeleted = new GraphicsBuffer(selTarget, selSize, 4) {name = "GaussianSplatDeleted"};
ClearGraphicsBuffer(newEditSelected);
ClearGraphicsBuffer(newEditSelectedMouseDown);
ClearGraphicsBuffer(newEditDeleted);
var newGpuView = new GraphicsBuffer(GraphicsBuffer.Target.Structured, newSplatCount, kGpuViewDataSize);
InitSortBuffers(newSplatCount);
// copy existing data over into new buffers
EditCopySplats(transform, newPosData, newOtherData, newSHData, newColorData, newEditDeleted, newSplatCount, 0, 0, m_SplatCount);
// use the new buffers and the new splat count
m_GpuPosData.Dispose();
m_GpuOtherData.Dispose();
m_GpuSHData.Dispose();
DestroyImmediate(m_GpuColorData);
m_GpuView.Dispose();
m_GpuEditSelected?.Dispose();
m_GpuEditSelectedMouseDown?.Dispose();
m_GpuEditDeleted?.Dispose();
m_GpuPosData = newPosData;
m_GpuOtherData = newOtherData;
m_GpuSHData = newSHData;
m_GpuColorData = newColorData;
m_GpuView = newGpuView;
m_GpuEditSelected = newEditSelected;
m_GpuEditSelectedMouseDown = newEditSelectedMouseDown;
m_GpuEditDeleted = newEditDeleted;
DisposeBuffer(ref m_GpuEditPosMouseDown);
DisposeBuffer(ref m_GpuEditOtherMouseDown);
m_SplatCount = newSplatCount;
editModified = true;
}
public void EditCopySplatsInto(GaussianSplatRenderer dst, int copySrcStartIndex, int copyDstStartIndex, int copyCount)
{
EditCopySplats(
dst.transform,
dst.m_GpuPosData, dst.m_GpuOtherData, dst.m_GpuSHData, dst.m_GpuColorData, dst.m_GpuEditDeleted,
dst.splatCount,
copySrcStartIndex, copyDstStartIndex, copyCount);
dst.editModified = true;
}
public void EditCopySplats(
Transform dstTransform,
GraphicsBuffer dstPos, GraphicsBuffer dstOther, GraphicsBuffer dstSH, Texture dstColor,
GraphicsBuffer dstEditDeleted,
int dstSize,
int copySrcStartIndex, int copyDstStartIndex, int copyCount)
{
if (!EnsureEditingBuffers()) return;
Matrix4x4 copyMatrix = dstTransform.worldToLocalMatrix * transform.localToWorldMatrix;
Quaternion copyRot = copyMatrix.rotation;
Vector3 copyScale = copyMatrix.lossyScale;
using var cmb = new CommandBuffer { name = "SplatCopy" };
SetAssetDataOnCS(cmb, KernelIndices.CopySplats);
cmb.SetComputeBufferParam(m_CSSplatUtilities, (int)KernelIndices.CopySplats, "_CopyDstPos", dstPos);
cmb.SetComputeBufferParam(m_CSSplatUtilities, (int)KernelIndices.CopySplats, "_CopyDstOther", dstOther);
cmb.SetComputeBufferParam(m_CSSplatUtilities, (int)KernelIndices.CopySplats, "_CopyDstSH", dstSH);
cmb.SetComputeTextureParam(m_CSSplatUtilities, (int)KernelIndices.CopySplats, "_CopyDstColor", dstColor);
cmb.SetComputeBufferParam(m_CSSplatUtilities, (int)KernelIndices.CopySplats, "_CopyDstEditDeleted", dstEditDeleted);
cmb.SetComputeIntParam(m_CSSplatUtilities, "_CopyDstSize", dstSize);
cmb.SetComputeIntParam(m_CSSplatUtilities, "_CopySrcStartIndex", copySrcStartIndex);
cmb.SetComputeIntParam(m_CSSplatUtilities, "_CopyDstStartIndex", copyDstStartIndex);
cmb.SetComputeIntParam(m_CSSplatUtilities, "_CopyCount", copyCount);
cmb.SetComputeVectorParam(m_CSSplatUtilities, "_CopyTransformRotation", new Vector4(copyRot.x, copyRot.y, copyRot.z, copyRot.w));
cmb.SetComputeVectorParam(m_CSSplatUtilities, "_CopyTransformScale", copyScale);
cmb.SetComputeMatrixParam(m_CSSplatUtilities, "_CopyTransformMatrix", copyMatrix);
DispatchUtilsAndExecute(cmb, KernelIndices.CopySplats, copyCount);
}
void DispatchUtilsAndExecute(CommandBuffer cmb, KernelIndices kernel, int count)
{
m_CSSplatUtilities.GetKernelThreadGroupSizes((int)kernel, out uint gsX, out _, out _);
cmb.DispatchCompute(m_CSSplatUtilities, (int)kernel, (int)((count + gsX - 1)/gsX), 1, 1);
Graphics.ExecuteCommandBuffer(cmb);
}
public GraphicsBuffer GpuEditDeleted => m_GpuEditDeleted;
}
}