Hiya,
I'm working on incorporating gsplat into Open Brush ( our internal PR was here: icosa-foundation/open-brush#1076 )
As a result of the work I've made some changes (and hopefully improvements) but they are almost entirely AI written (ChatGPT 5.6 Sol) and I didn't want to send you any PRs without checking with you in advance. Happy to put a bit of work into tidying up but I'm pretty maxed out with current Open Brush and related work.
See below.
Pull Request Branch Guide
This document describes the feature branches extracted from
open-brush-unity6-runtime-editor-guards. Each PR branch is based independently on
main at 5502973 (Add runtime PLY loading from byte arrays (#34)). The combined
Open Brush branch remains at ada5fe9 and contains all of the work together.
Branch overview
| Branch |
Suggested PR title |
Scope |
pr/editor-runtime-guards |
Guard editor-only imports in runtime scripts |
Player-build compatibility fix |
pr/depth-prepass |
Add optional depth prepass for Gaussian splats |
Rendering feature and SRP integration |
pr/global-sort-fixes |
Fix global-sort buffer reads and camera layer filtering |
Global rendering correctness fixes |
pr/intersector |
Add GPU sphere intersection for splat renderers |
Runtime query API |
pr/sog-support |
Add PlayCanvas SOG v2 import and runtime loading |
Asset-format support |
All five branches are published to the icosa-mirror/gsplat-unity origin.
pr/editor-runtime-guards
Suggested title
Guard editor-only imports in runtime scripts
Suggested PR description
This PR prevents runtime package code from importing UnityEditor in player builds.
It wraps the editor-only namespace imports in UNITY_EDITOR guards in four runtime
files. The affected code already uses editor-only functionality conditionally; this
change also makes the corresponding namespace references conditional.
The functional behavior in the Unity Editor is unchanged. The purpose is to keep
the runtime assembly compilable when building a player, where UnityEditor is not
available.
Main changes
- Guard
using UnityEditor; in:
GsplatCutouts.cs
GsplatPlayerLoopHook.cs
GsplatRenderer.cs
GsplatSettings.cs
- Normalize missing final newlines in several affected files.
Risk and compatibility
This is a small compile-time compatibility fix. It does not change rendering,
serialization, or editor behavior.
Suggested testing
- Compile the package in the Unity Editor.
- Make a player build with the package installed.
- Confirm there are no runtime-assembly references to
UnityEditor.
pr/depth-prepass
Suggested title
Add optional depth prepass and explicit SRP splat rendering
Suggested PR description
This PR adds an optional alpha-tested depth prepass for Gaussian splats. The prepass
allows opaque scene geometry and sufficiently opaque splat regions to participate in
depth compositing while retaining the normal transparent color pass.
A new Depth Prepass Alpha Cutoff project setting controls which splat fragments
write depth. Its default value is 1.1, which disables depth writes and preserves
the existing rendering behavior until the feature is explicitly enabled.
The change also moves URP color drawing into explicit renderer-feature passes. It
supports both the legacy command-buffer path and the Unity 6 Render Graph path.
HDRP's custom pass explicitly targets the camera buffers and records sorting,
optional depth, and color so HDRP rendering is not suppressed by the SRP-specific
draw path.
Main changes
- Add
GsplatSettings.DepthPrepassAlphaCutoff with a project-settings field.
- Add depth-only shaders and materials for:
- Spark-compressed splats
- Uncompressed splats
- Globally merged splats
- Add cached depth-material variants for SH bands and render orders.
- Add per-renderer and global depth-draw methods.
- Add explicit color and depth passes to the URP renderer feature.
- Support both
CommandBuffer and Unity 6 RasterCommandBuffer rendering.
- Make the HDRP custom pass issue the color draw after sorting.
- Preserve the built-in render-pipeline draw path.
- Apply camera culling masks to per-renderer SRP draws.
Behavior
- A cutoff greater than
1.0 disables the depth prepass.
- Lower cutoff values allow more splat fragments to write depth.
- The normal color pass still performs splat blending.
- Global sorting uses a matching global depth material when enabled.
Risk and compatibility
This PR touches the built-in, URP, and HDRP rendering paths and has the widest
rendering impact of the extracted branches. The default setting is intended to keep
depth writes disabled, but the explicit SRP color path is still a structural change.
This branch currently contains Unity-generated material and .meta lines with
trailing spaces. Those lines came from the original assets and may be normalized
separately if required by the upstream repository's formatting policy.
Suggested testing
- Verify splats render with the default cutoff in built-in, URP, and HDRP projects.
- Test Spark and Uncompressed assets.
- Test global sorting both enabled and disabled.
- Enable the depth prepass and verify occlusion against opaque scene geometry.
- Test several cutoff values, including
0, a mid-range value, 1, and the disabled
default of 1.1.
- Verify Game, Scene, and additional cameras with differing culling masks.
- Test Unity 6 Render Graph and the pre-Unity-6 URP command-buffer path where supported.
pr/global-sort-fixes
Suggested title
Fix invalid global-merge reads and respect camera layer masks
Suggested PR description
This PR fixes two correctness issues in globally sorted rendering.
First, the final merge shader previously used a conditional expression that could
evaluate an out-of-range buffer access before selecting the valid result. Replacing
it with an explicit branch ensures only the selected order buffer is read.
Second, one global draw can carry only one Unity layer. The global path now falls
back to per-renderer rendering when active splat renderers use different layers. If
all renderers share a layer, that layer is assigned to the global RenderParams, so
Unity camera culling masks include or exclude the merged draw correctly.
Main changes
- Replace the final merge shader's conditional buffer read with an explicit branch.
- Detect mixed-layer global renderer sets and disable global merging for that set.
- Store the shared renderer layer on the global renderer.
- Assign the shared layer to the global
RenderParams draw.
Risk and compatibility
Mixed-layer scenes may use more draw calls after this change because correctness
requires falling back to per-renderer rendering. Same-layer scenes retain the global
draw optimization.
Integration note with pr/depth-prepass
This branch is independently correct against current main, whose global draw uses
RenderParams. The depth-prepass branch adds SRP command-buffer global draws, which
do not receive layer filtering automatically. If both PRs are accepted, rebase this
branch after pr/depth-prepass and retain the combined branch's per-camera global
eligibility check in the new color and depth draw methods. This prevents the SRP
global path from reintroducing the camera-mask regression.
Suggested testing
- Render two globally sorted splat objects on the same layer.
- Confirm a camera including that layer renders the global draw.
- Confirm a camera excluding that layer does not render the splats.
- Put the renderers on different layers and confirm the system falls back to
per-renderer rendering.
- Use separate cameras that include different renderer layers.
- Exercise merge inputs where either side of the final merge is exhausted first.
pr/intersector
Suggested title
Add GPU sphere intersection queries for Gaussian splats
Suggested PR description
This PR adds a runtime sphere-intersection query to GsplatRenderer. It is intended
for tools that need to determine whether a world-space sphere overlaps any currently rendered
splat, such as selection widgets or spatial interaction tools.
The query transforms splat centers into world space, dispatches a compute shader
over the surviving render-order entries, and reads back a one-element hit buffer.
Active cutouts and fully transparent splats are excluded. Both Spark and Uncompressed
GPU resource layouts are supported.
Public API
bool TryIntersectSphere(Vector3 centerWorld, float radiusWorld, out float score)
The method returns true when at least one splat intersects the sphere. The current
implementation returns a binary score of 1.0 for a hit and leaves the score at
-1.0 when there is no hit or the renderer cannot be queried.
Main changes
- Add
GsplatRenderer.TryIntersectSphere.
- Add Spark and Uncompressed intersection kernels.
- Account for renderer transforms, non-uniform scale, splat scale, and the renderer's
splat downscale factor.
- Add and dispose a reusable one-element GPU hit buffer.
- Add the intersection compute shader to
GsplatSettings defaults.
Risk and performance
The method performs a synchronous GPU readback through GraphicsBuffer.GetData.
Calling it frequently or once per renderer every frame can stall the CPU waiting for
the GPU. The intended use should therefore be occasional interaction queries unless
the implementation is later converted to asynchronous readback or batching.
The returned score is currently binary rather than a distance or overlap measure.
Suggested testing
- Test hits and misses with Spark and Uncompressed assets.
- Test translated, rotated, uniformly scaled, and non-uniformly scaled renderers.
- Test partially uploaded assets and renderers with no uploaded splats.
- Test the effect of
SplatDownscaleFactor.
- Profile repeated calls to document the synchronous readback cost.
pr/sog-support
Suggested title
Add PlayCanvas SOG v2 import and runtime loading
Suggested PR description
This PR adds support for PlayCanvas SOG v2 bundled .sog files. A supported file is
a ZIP bundle containing meta.json and lossless WebP property images. The loader
decodes the SOG properties into the package's existing Spark or Uncompressed asset
representations, so rendering uses the normal gsplat pipelines after loading.
SOG files can be imported by placing them in a Unity project's Assets directory.
They can also be loaded at runtime by creating GsplatAssetSog or
GsplatAssetSogUncompressed, calling LoadFromSog, and assigning the resulting asset
to a GsplatRenderer.
Main changes
- Register
.sog with GsplatImporter.
- Add Spark and Uncompressed SOG asset implementations.
- Parse SOG v2 metadata and property-image layout.
- Decode lossless WebP images through
unity.webp/libwebp.
- Support SOG SH bands 0 through 3.
- Apply the existing source-coordinate conversion options.
- Add editor caching for packed SOG imports. The cache key includes file metadata,
compression mode, source coordinates, and decoder version.
- Add runtime loading using the same decode path.
- Document editor and runtime usage.
- Add
com.netpyoung.webp version 0.3.22 as a package dependency.
Limitations and compatibility
- Supports bundled SOG v2 ZIP files, not directory-style SOG datasets.
- Runtime loading performs ZIP parsing, JSON parsing, WebP decoding, and splat packing
on the device.
- Runtime support depends on
unity.webp providing a native libwebp plugin for the
target platform. Android is included in the intended supported platforms.
- SH degree 4 is not supported for SOG; supported bands are 0 through 3.
Suggested testing
- Import representative SOG v2 bundles with SH bands 0, 1, 2, and 3.
- Test Spark and Uncompressed import modes.
- Compare bounds, colors, positions, scales, rotations, and SH appearance against a
reference SOG renderer.
- Reimport the same file and verify the editor cache path.
- Change compression mode and source coordinates and verify cache invalidation.
- Load a SOG bundle at runtime and assign it to a renderer.
- Test malformed ZIPs, missing
meta.json, missing property images, invalid metadata,
and unsupported SOG versions.
- Test each intended player platform, especially Android, to verify native WebP
plugin availability.
Combined Open Brush branch
open-brush-unity6-runtime-editor-guards remains the integration branch used by
Open Brush. It contains all five areas above plus the merge of current main:
- Branch tip:
ada5fe9
- Remote:
origin/open-brush-unity6-runtime-editor-guards
The extracted branches should not replace this integration branch until their
upstream PRs have been accepted and the resulting upstream commits have been merged
back into the Open Brush branch. The combined branch at ada5fe9 predates the review
follow-up commits below and must not be treated as containing those fixes.
Recommended PR order
The branches can be reviewed independently, but this order minimizes integration
surprises:
pr/editor-runtime-guards
pr/global-sort-fixes
pr/intersector
pr/sog-support
pr/depth-prepass
If the depth-prepass PR lands before the global-sort fixes, rebase the global-sort
branch and carry its camera-mask logic into the new SRP color and depth paths before
merging it.
Review follow-up audit — 17 July 2026
Four PR branches have now received focused reviews and follow-up fixes.
pr/editor-runtime-guards has not yet received the same review pass.
pr/global-sort-fixes
d0e35bb — read layers through the IGsplat.transform contract.
d5ac39f — choose global or per-renderer rendering atomically so runtime
transitions cannot omit or duplicate a frame.
These changes preserve the intended same-layer global optimization and mixed-layer
fallback.
pr/intersector
cb914be — query surviving order-buffer entries so active cutouts are respected.
b60d58b — ignore fully transparent Uncompressed splats.
1060e59 — compare in world space so non-uniform renderer scale does not inflate
the query sphere along unrelated axes.
The query therefore represents currently rendered splats rather than literally every
uploaded source splat. Synchronous readback and the binary score remain unchanged.
pr/sog-support
19275f6 — use PlayCanvas SOG's RDB coordinate default for fresh editor imports
while preserving explicit overrides and resolved-coordinate cache keys.
86bdb0f — decode smallest-three rotations without per-splat temporary arrays.
A proposed Spark quaternion reorder was intentionally not applied. Git history and
the shader convention confirm that the existing mapping is the established bridge
between Spark packing and the shader's wxyz interpretation.
pr/depth-prepass
08f7dbe — honor RenderOrder in explicit per-renderer color draws.
404cea9 — apply camera masks to globally merged SRP color/depth draws and fall
back to per-renderer draws for mixed layers.
6894a4c — record URP sort, optional depth, and color immediately before URP
transparents, in that order.
aeb81b3 — record the optional depth pass against HDRP's camera depth target.
Commit 404cea9 implements the per-camera global eligibility required by the
integration note above.
Cross-branch integration warning
Do not apply d5ac39f mechanically when combining pr/global-sort-fixes with
pr/depth-prepass. Its deferred fallback submission is correct on the standalone
global-sort branch, but SRP fallback submission must remain owned by the explicit
URP/HDRP passes. In the combined resolution, restrict that deferred fallback draw to
the built-in pipeline; otherwise SRP can submit the same splats twice.
The review fixes otherwise remain consistent with the feature intent and risk notes
documented in this issue.
Hiya,
I'm working on incorporating gsplat into Open Brush ( our internal PR was here: icosa-foundation/open-brush#1076 )
As a result of the work I've made some changes (and hopefully improvements) but they are almost entirely AI written (ChatGPT 5.6 Sol) and I didn't want to send you any PRs without checking with you in advance. Happy to put a bit of work into tidying up but I'm pretty maxed out with current Open Brush and related work.
See below.
Pull Request Branch Guide
This document describes the feature branches extracted from
open-brush-unity6-runtime-editor-guards. Each PR branch is based independently onmainat5502973(Add runtime PLY loading from byte arrays (#34)). The combinedOpen Brush branch remains at
ada5fe9and contains all of the work together.Branch overview
pr/editor-runtime-guardspr/depth-prepasspr/global-sort-fixespr/intersectorpr/sog-supportAll five branches are published to the
icosa-mirror/gsplat-unityorigin.pr/editor-runtime-guardsSuggested title
Guard editor-only imports in runtime scripts
Suggested PR description
This PR prevents runtime package code from importing
UnityEditorin player builds.It wraps the editor-only namespace imports in
UNITY_EDITORguards in four runtimefiles. The affected code already uses editor-only functionality conditionally; this
change also makes the corresponding namespace references conditional.
The functional behavior in the Unity Editor is unchanged. The purpose is to keep
the runtime assembly compilable when building a player, where
UnityEditoris notavailable.
Main changes
using UnityEditor;in:GsplatCutouts.csGsplatPlayerLoopHook.csGsplatRenderer.csGsplatSettings.csRisk and compatibility
This is a small compile-time compatibility fix. It does not change rendering,
serialization, or editor behavior.
Suggested testing
UnityEditor.pr/depth-prepassSuggested title
Add optional depth prepass and explicit SRP splat rendering
Suggested PR description
This PR adds an optional alpha-tested depth prepass for Gaussian splats. The prepass
allows opaque scene geometry and sufficiently opaque splat regions to participate in
depth compositing while retaining the normal transparent color pass.
A new
Depth Prepass Alpha Cutoffproject setting controls which splat fragmentswrite depth. Its default value is
1.1, which disables depth writes and preservesthe existing rendering behavior until the feature is explicitly enabled.
The change also moves URP color drawing into explicit renderer-feature passes. It
supports both the legacy command-buffer path and the Unity 6 Render Graph path.
HDRP's custom pass explicitly targets the camera buffers and records sorting,
optional depth, and color so HDRP rendering is not suppressed by the SRP-specific
draw path.
Main changes
GsplatSettings.DepthPrepassAlphaCutoffwith a project-settings field.CommandBufferand Unity 6RasterCommandBufferrendering.Behavior
1.0disables the depth prepass.Risk and compatibility
This PR touches the built-in, URP, and HDRP rendering paths and has the widest
rendering impact of the extracted branches. The default setting is intended to keep
depth writes disabled, but the explicit SRP color path is still a structural change.
This branch currently contains Unity-generated material and
.metalines withtrailing spaces. Those lines came from the original assets and may be normalized
separately if required by the upstream repository's formatting policy.
Suggested testing
0, a mid-range value,1, and the disableddefault of
1.1.pr/global-sort-fixesSuggested title
Fix invalid global-merge reads and respect camera layer masks
Suggested PR description
This PR fixes two correctness issues in globally sorted rendering.
First, the final merge shader previously used a conditional expression that could
evaluate an out-of-range buffer access before selecting the valid result. Replacing
it with an explicit branch ensures only the selected order buffer is read.
Second, one global draw can carry only one Unity layer. The global path now falls
back to per-renderer rendering when active splat renderers use different layers. If
all renderers share a layer, that layer is assigned to the global
RenderParams, soUnity camera culling masks include or exclude the merged draw correctly.
Main changes
RenderParamsdraw.Risk and compatibility
Mixed-layer scenes may use more draw calls after this change because correctness
requires falling back to per-renderer rendering. Same-layer scenes retain the global
draw optimization.
Integration note with
pr/depth-prepassThis branch is independently correct against current
main, whose global draw usesRenderParams. The depth-prepass branch adds SRP command-buffer global draws, whichdo not receive layer filtering automatically. If both PRs are accepted, rebase this
branch after
pr/depth-prepassand retain the combined branch's per-camera globaleligibility check in the new color and depth draw methods. This prevents the SRP
global path from reintroducing the camera-mask regression.
Suggested testing
per-renderer rendering.
pr/intersectorSuggested title
Add GPU sphere intersection queries for Gaussian splats
Suggested PR description
This PR adds a runtime sphere-intersection query to
GsplatRenderer. It is intendedfor tools that need to determine whether a world-space sphere overlaps any currently rendered
splat, such as selection widgets or spatial interaction tools.
The query transforms splat centers into world space, dispatches a compute shader
over the surviving render-order entries, and reads back a one-element hit buffer.
Active cutouts and fully transparent splats are excluded. Both Spark and Uncompressed
GPU resource layouts are supported.
Public API
The method returns
truewhen at least one splat intersects the sphere. The currentimplementation returns a binary score of
1.0for a hit and leaves the score at-1.0when there is no hit or the renderer cannot be queried.Main changes
GsplatRenderer.TryIntersectSphere.splat downscale factor.
GsplatSettingsdefaults.Risk and performance
The method performs a synchronous GPU readback through
GraphicsBuffer.GetData.Calling it frequently or once per renderer every frame can stall the CPU waiting for
the GPU. The intended use should therefore be occasional interaction queries unless
the implementation is later converted to asynchronous readback or batching.
The returned score is currently binary rather than a distance or overlap measure.
Suggested testing
SplatDownscaleFactor.pr/sog-supportSuggested title
Add PlayCanvas SOG v2 import and runtime loading
Suggested PR description
This PR adds support for PlayCanvas SOG v2 bundled
.sogfiles. A supported file isa ZIP bundle containing
meta.jsonand lossless WebP property images. The loaderdecodes the SOG properties into the package's existing Spark or Uncompressed asset
representations, so rendering uses the normal gsplat pipelines after loading.
SOG files can be imported by placing them in a Unity project's
Assetsdirectory.They can also be loaded at runtime by creating
GsplatAssetSogorGsplatAssetSogUncompressed, callingLoadFromSog, and assigning the resulting assetto a
GsplatRenderer.Main changes
.sogwithGsplatImporter.unity.webp/libwebp.compression mode, source coordinates, and decoder version.
com.netpyoung.webpversion0.3.22as a package dependency.Limitations and compatibility
on the device.
unity.webpproviding a native libwebp plugin for thetarget platform. Android is included in the intended supported platforms.
Suggested testing
reference SOG renderer.
meta.json, missing property images, invalid metadata,and unsupported SOG versions.
plugin availability.
Combined Open Brush branch
open-brush-unity6-runtime-editor-guardsremains the integration branch used byOpen Brush. It contains all five areas above plus the merge of current
main:ada5fe9origin/open-brush-unity6-runtime-editor-guardsThe extracted branches should not replace this integration branch until their
upstream PRs have been accepted and the resulting upstream commits have been merged
back into the Open Brush branch. The combined branch at
ada5fe9predates the reviewfollow-up commits below and must not be treated as containing those fixes.
Recommended PR order
The branches can be reviewed independently, but this order minimizes integration
surprises:
pr/editor-runtime-guardspr/global-sort-fixespr/intersectorpr/sog-supportpr/depth-prepassIf the depth-prepass PR lands before the global-sort fixes, rebase the global-sort
branch and carry its camera-mask logic into the new SRP color and depth paths before
merging it.
Review follow-up audit — 17 July 2026
Four PR branches have now received focused reviews and follow-up fixes.
pr/editor-runtime-guardshas not yet received the same review pass.pr/global-sort-fixesd0e35bb— read layers through theIGsplat.transformcontract.d5ac39f— choose global or per-renderer rendering atomically so runtimetransitions cannot omit or duplicate a frame.
These changes preserve the intended same-layer global optimization and mixed-layer
fallback.
pr/intersectorcb914be— query surviving order-buffer entries so active cutouts are respected.b60d58b— ignore fully transparent Uncompressed splats.1060e59— compare in world space so non-uniform renderer scale does not inflatethe query sphere along unrelated axes.
The query therefore represents currently rendered splats rather than literally every
uploaded source splat. Synchronous readback and the binary score remain unchanged.
pr/sog-support19275f6— use PlayCanvas SOG's RDB coordinate default for fresh editor importswhile preserving explicit overrides and resolved-coordinate cache keys.
86bdb0f— decode smallest-three rotations without per-splat temporary arrays.A proposed Spark quaternion reorder was intentionally not applied. Git history and
the shader convention confirm that the existing mapping is the established bridge
between Spark packing and the shader's wxyz interpretation.
pr/depth-prepass08f7dbe— honorRenderOrderin explicit per-renderer color draws.404cea9— apply camera masks to globally merged SRP color/depth draws and fallback to per-renderer draws for mixed layers.
6894a4c— record URP sort, optional depth, and color immediately before URPtransparents, in that order.
aeb81b3— record the optional depth pass against HDRP's camera depth target.Commit
404cea9implements the per-camera global eligibility required by theintegration note above.
Cross-branch integration warning
Do not apply
d5ac39fmechanically when combiningpr/global-sort-fixeswithpr/depth-prepass. Its deferred fallback submission is correct on the standaloneglobal-sort branch, but SRP fallback submission must remain owned by the explicit
URP/HDRP passes. In the combined resolution, restrict that deferred fallback draw to
the built-in pipeline; otherwise SRP can submit the same splats twice.
The review fixes otherwise remain consistent with the feature intent and risk notes
documented in this issue.