Skip to content

AI written PRs #35

Description

@andybak

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:

  1. pr/editor-runtime-guards
  2. pr/global-sort-fixes
  3. pr/intersector
  4. pr/sog-support
  5. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions