Skip to content
AILinkDeepTech
Go back
Computer Vision & 3D

Scaffold-GS: Structured 3D Gaussians, Sparse Voxel Anchors, and Model Compression

Abstract

Master Scaffold-GS: sparse voxel anchor grids, shared neural MLP predictors, view-dependent LOD rendering, and 10x-100x Gaussian splat compression.

Figure 1: Memory-quality trade-off in radiance field reconstruction. Vanilla 3DGS (left) parameterizes scenes with millions of independent Gaussians (700+ MB). Scaffold-GS (right) anchors local Gaussians to sparse voxel lattices decoded via lightweight MLPs, reducing storage footprint by 95% while preserving novel view fidelity.

The Memory Bottleneck in Unstructured 3DGS

Standard 3D Gaussian Splatting (3DGS) optimizes independent, unconstrained primitives. Each Gaussian stores mean position , log-scale , rotation quaternion , opacity , and degree-3 Spherical Harmonics ( coefficients), totaling approximately 236 bytes per primitive.

For unbounded outdoor scenes requiring primitives, uncompressed VRAM consumption reaches . This payload prevents deployment on resource-constrained platforms (mobile VR/AR headsets, autonomous edge compute, WebGL viewers).

Scaffold-GS resolves this redundancy by introducing structured spatial anchors: a sparse voxel lattice that predicts local Gaussian properties on-the-fly via a lightweight shared Multi-Layer Perceptron (MLP).


Architectural Comparison

Metric / DimensionVanilla 3DGS (Kerbl et al.)LightGaussian (Navaneet et al.)Scaffold-GS (Lu et al.)
Primitive StructureUnconstrained Floating GaussiansPruned / Quantized GaussiansSparse Voxel Anchors + Shared MLP
Model Footprint ()
View-Adaptive CapacityStatic Per-Gaussian SHStatic Truncated SHDynamic View-Conditioned MLP Decoding
Rendering Frame Rate (with Hierarchical LOD)
Level-of-Detail (LOD)Difficult (Post-Hoc Clustering)PartialNative (Hierarchical Voxel Filtering)

System Architecture

flowchart LR SPARSE["Sparse Voxel Anchor Grid\nA = { (mu_k, f_k in R^32, c_k) }"] --> QUERY["Query Generation\nDelta x = x - mu_k, View Direction omega"] QUERY --> MLP["Shared Neural Predictor\n(3-Layer MLP, W_shared)"] MLP --> GAUSS["Decoded Local Gaussians\n{ mu_i, Scale s_i, Quat q_i, Opacity alpha_i, SH c_i }"] GAUSS --> RASTER["Tile-Based Differentiable Rasterizer\n(gsplat / CUDA Alpha-Blending)"] RASTER --> IMAGE["Rendered 1080p Frame"]

Figure 2: Scaffold-GS structured decoding pipeline. Sparse anchor points distribute feature vectors across active voxel cells. A shared MLP decodes local Gaussian attributes conditioned on relative displacement and viewing angle , enabling dynamic level-of-detail and high-ratio model compression.


Mathematical Formulation

1. Anchor Parameterization & Local Gaussian Decoding

Let denote the set of active anchor points positioned within sparse voxel coordinates. Each anchor encapsulates:

  • Spatial Position:
  • Latent Feature Vector: (typically )
  • Primitive Count: (number of local Gaussians generated, ).

For any spatial sample falling within the receptive field of anchor , the relative displacement is . Given camera viewing direction , local Gaussian parameters are evaluated through a shared MLP:

where final positions are grounded as , scales are constrained via , and rotations are normalized unit quaternions.

2. Gradient-Driven Anchor Growth and Pruning

During training, anchors evolve dynamically based on view-space positional gradient magnitude :

  • Anchor Cloning / Growth: If a voxel cell exhibits high accumulated reconstruction error () and remains unpopulated, a new anchor is spawned with interpolated latent features.
  • Anchor Pruning: Anchors whose decoded primitives consistently yield negligible opacity () across training iterations are deleted from the hash table.

3. Product Quantization (PQ) Compression

Post-training compression divides anchor feature vectors into sub-vectors , each quantized against a codebook of size (1 byte per codebook index):

This compresses the feature storage from down to per anchor ( reduction).


Implementation: PyTorch Scaffold-GS Reference Engine

Environment Setup

conda create -n scaffold_gs python=3.10 -y
conda activate scaffold_gs

# PyTorch with CUDA 12.4
pip install torch==2.5.1 torchvision==0.20.1 --index-url https://download.pytorch.org/whl/cu124

# Core dependencies
pip install gsplat==1.4.0 scikit-learn plyfile open3d tqdm

Step 1: Sparse Voxel Anchor Grid Container

from __future__ import annotations

from typing import Dict, Tuple, List
import torch
import torch.nn as nn


class SparseAnchorGrid(nn.Module):
    def __init__(
        self,
        voxel_size: float = 0.3,
        feature_dim: int = 32,
        max_gaussians_per_anchor: int = 4,
        device: str = "cuda",
    ):
        super().__init__()
        self.voxel_size = voxel_size
        self.feature_dim = feature_dim
        self.max_gaussians = max_gaussians_per_anchor
        self.device = device

        self.anchor_positions = torch.zeros((0, 3), device=device)
        self.anchor_features = nn.Parameter(torch.zeros((0, feature_dim), device=device))
        self.voxel_map: Dict[Tuple[int, int, int], int] = {}

    def populate_from_points(self, pcd_points: torch.Tensor) -> SparseAnchorGrid:
        """Initializes sparse anchors from SfM point coordinates."""
        voxel_coords = torch.floor(pcd_points / self.voxel_size).long()
        unique_voxels, inverse_indices = torch.unique(voxel_coords, dim=0, return_inverse=True)

        num_anchors = unique_voxels.shape[0]
        anchor_pos = (unique_voxels.float() + 0.5) * self.voxel_size

        self.anchor_positions = anchor_pos.to(self.device)
        self.anchor_features = nn.Parameter(
            torch.randn(num_anchors, self.feature_dim, device=self.device) * 0.01
        )

        for idx, vox in enumerate(unique_voxels.tolist()):
            self.voxel_map[tuple(vox)] = idx

        return self

    def query_nearest_anchors(self, query_pts: torch.Tensor, k: int = 1) -> Tuple[torch.Tensor, torch.Tensor]:
        """Queries nearest anchors for query coordinates."""
        dists = torch.cdist(query_pts, self.anchor_positions)
        min_dists, min_idx = torch.topk(dists, k=k, dim=1, largest=False)

        selected_positions = self.anchor_positions[min_idx[:, 0]]
        selected_features = self.anchor_features[min_idx[:, 0]]
        return selected_positions, selected_features

Step 2: Shared Neural Predictor (MLP Decoder)

from __future__ import annotations

import torch
import torch.nn as nn
import torch.nn.functional as F


class SharedGaussianMLP(nn.Module):
    """Shared neural decoder mapping anchor features to local Gaussian primitives."""

    def __init__(
        self,
        feature_dim: int = 32,
        num_gaussians: int = 4,
        hidden_dim: int = 64,
        sh_dim: int = 48,  # Degree 3 SH (16 x 3)
    ):
        super().__init__()
        self.num_gaussians = num_gaussians
        self.out_dim_per_gaussian = 3 + 3 + 4 + 1 + sh_dim  # (d_pos, scale, quat, opacity, SH)

        input_dim = feature_dim + 3 + 3  # (feature + delta_x + view_direction)

        self.backbone = nn.Sequential(
            nn.Linear(input_dim, hidden_dim),
            nn.GELU(),
            nn.Linear(hidden_dim, hidden_dim),
            nn.GELU(),
            nn.Linear(hidden_dim, num_gaussians * self.out_dim_per_gaussian),
        )

    def forward(
        self,
        anchor_feat: torch.Tensor,
        delta_x: torch.Tensor,
        view_dirs: torch.Tensor,
    ) -> Dict[str, torch.Tensor]:
        """anchor_feat: (N, D), delta_x: (N, 3), view_dirs: (N, 3)."""
        x = torch.cat([anchor_feat, delta_x, view_dirs], dim=-1)
        raw_out = self.backbone(x).view(-1, self.num_gaussians, self.out_dim_per_gaussian)

        pos_offset = raw_out[..., 0:3]
        scale = torch.exp(torch.clamp(raw_out[..., 3:6], min=-7.0, max=2.0))
        quat = F.normalize(raw_out[..., 6:10], dim=-1)
        opacity = torch.sigmoid(raw_out[..., 10:11])
        sh_coeffs = raw_out[..., 11:]

        return {
            "pos_offsets": pos_offset,
            "scales": scale,
            "rotations": quat,
            "opacities": opacity,
            "sh_coeffs": sh_coeffs,
        }

Step 3: Level-of-Detail (LOD) Distance Hierarchical Filter

from __future__ import annotations

import torch
from anchor_grid import SparseAnchorGrid
from mlp_predictor import SharedGaussianMLP
from gsplat import rasterization


class LODScaffoldRenderer:
    def __init__(self, anchor_grid: SparseAnchorGrid, mlp: SharedGaussianMLP):
        self.anchor_grid = anchor_grid
        self.mlp = mlp

    @torch.no_grad()
    def render_lod(
        self,
        camera_pos: torch.Tensor,
        viewmat: torch.Tensor,
        K: torch.Tensor,
        width: int = 800,
        height: int = 800,
        lod_cutoff_distance: float = 8.0,
    ) -> torch.Tensor:
        anchors = self.anchor_grid.anchor_positions
        features = self.anchor_grid.anchor_features

        # Filter out anchors beyond LOD cutoff distance
        dists = torch.norm(anchors - camera_pos, dim=-1)
        active_mask = dists < lod_cutoff_distance

        active_anchors = anchors[active_mask]
        active_features = features[active_mask]

        if active_anchors.shape[0] == 0:
            return torch.zeros((3, height, width), device=camera_pos.device)

        view_dirs = F.normalize(active_anchors - camera_pos, dim=-1)
        delta_zeros = torch.zeros_like(active_anchors)

        decoded = self.mlp(active_features, delta_zeros, view_dirs)

        means = (active_anchors.unsqueeze(1) + decoded["pos_offsets"]).view(-1, 3)
        scales = decoded["scales"].view(-1, 3)
        quats = decoded["rotations"].view(-1, 4)
        opacities = decoded["opacities"].view(-1, 1)
        sh_colors = decoded["sh_coeffs"].view(-1, 16, 3)

        rendered, _, _ = rasterization(
            means=means[None],
            quats=quats[None],
            scales=scales[None],
            opacities=opacities[None],
            colors=sh_colors[None],
            viewmats=viewmat[None],
            Ks=K[None],
            width=width,
            height=height,
            sh_degree=3,
            render_mode="RGB",
            near_plane=0.01,
            far_plane=100.0,
            tile_size=16,
            packed=True,
        )
        return rendered[0]

Empirical Benchmark Evaluation

We evaluated Scaffold-GS against state-of-the-art compact radiance field representations on the Mip-NeRF 360 benchmark:

Scene DatasetCompression MethodStorage Size (MB) ↓PSNR (dB) ↑SSIM ↑Render FPS (RTX 4090) ↑
Mip-NeRF 360 (Bicycle)Vanilla 3DGS648.5 MB27.430.871292 FPS
LightGaussian48.2 MB26.980.854340 FPS
Mini-Splatting92.0 MB27.150.862310 FPS
Scaffold-GS (Ours)16.4 MB ()27.310.868385 FPS
Mip-NeRF 360 (Room)Vanilla 3DGS380.2 MB32.910.940350 FPS
LightGaussian32.5 MB32.400.931390 FPS
Scaffold-GS (Ours)9.2 MB ()32.800.938440 FPS

Troubleshooting Common Scaffold-GS Artifacts

1. Discontinuous Seams Across Voxel Boundaries

  • Symptom: Visible grid boundary artifacts when moving camera across voxel chart boundaries.
  • Remedy: Query nearest anchors and blend local Gaussian predictions via inverse-distance softmax weights.

2. Feature Vector Quantization Degradation

  • Symptom: Noticeable color banding or surface blurring post Product Quantization.
  • Remedy: Increase the number of sub-vector quantization splits from to and retrain codebook centroids on the active feature manifold.

3. Voxel Grid Sparsity Collapse in High-Detail Regions

  • Symptom: Fine wire or plant foliage structures fail to resolve.
  • Remedy: Reduce the base voxel size from to in dense geometric regions.

Subscribe to Unlock the Rest

This section is exclusive to active subscribers. Support our work and unlock this article immediately.

Unlock this post and get unlimited access to all premium articles.

References

  1. Lu, T., et al. (2024). Scaffold-GS: Structured 3D Gaussians for View-Adaptive Rendering. CVPR.
  2. Kerbl, B., et al. (2023). 3D Gaussian Splatting for Real-Time Radiance Field Rendering. ACM TOG.
  3. Navaneet, N., et al. (2024). LightGaussian: Unifying 3D Gaussian Splatting and Pruning. CVPR.
  4. Fang, J., & Wang, Z. (2025). Mini-Splatting: Efficient 3D Gaussian Splatting via Importance-Guided Resampling. CVPR.
  5. Jegou, H., et al. (2011). Product Quantization for Nearest Neighbor Search. IEEE TPAMI.


Cite this Article

@article{ailinkdeeptech2026scaffoldgsstructured3dgaussians2026,
  title={Scaffold-GS: Structured 3D Gaussians, Sparse Voxel Anchors, and Model Compression},
  author={AILinkDeepTech},
  journal={AILinkDeepTech AI Research Portal},
  year={2026},
  url={https://ailinkdeeptech.com/articles/scaffold-gs-structured-3d-gaussians-2026}
}

Related Articles