Paper · 2024-06-17 · cs.CV, cs.GR
A Hierarchical 3D Gaussian Representation for Real-Time Rendering of Very Large Datasets
Builds a hierarchy of 3D Gaussians for very large street-level scenes: merged interior nodes, cuts chosen by projected size, and smooth level switching. Chunks are trained independently, then consolidated. Author-reported: real-time rendering of multi-km scenes, and 26.62 dB PSNR on one SmallCity chunk against 25.34 dB for 3DGS.
Gaussian splattingNovel view synthesisLevel-of-detail renderingnovel-view-synthesisgaussian-splattinglevel-of-detaillarge-sceneshierarchyreal-time-renderingchunked-training
arXivPDFCodeMarkdown15 min read
AI-compiled by claude-haiku-5-5 on 2026-11-01, not yet reviewed by a human. Every claim cites its source; check it when a detail matters.
In plain words
Gaussian splatting draws a scene as a cloud of soft blobs and renders it fast. A street-level capture of several kilometres needs far more blobs than a computer can hold or draw at interactive speed.
This paper does two things. First, it cuts the city into blocks and trains each block on its own, so no single training run has to hold everything. Second, it stacks the blobs into a tree. Each parent blob stands in for its children. When the camera is far away, the renderer draws the parents; when it is close, it draws the children. The parent and child blobs are blended over a short transition so the switch does not pop. The parents are also tuned afterwards, so they look right when they stand in for many children.
For an engineer the useful parts are the tree construction, the rule that picks which level to draw, the transition blend, and the chunk pipeline. The card lists the values the paper gives and marks the places where the paper leaves a value out or where its equations and text disagree.
TL;DR
The paper builds a Gaussian-splatting level-of-detail hierarchy for very large outdoor captures (up to several kilometres and tens of thousands of images). Each chunk is trained independently, its leaves are merged into a binary tree, the tree is pruned, and interior nodes are optimised. A cut through the tree is chosen per view from projected size, and levels blend smoothly. The authors report real-time rendering of their captured scenes on consumer-grade GPUs. Official code exists under a non-commercial licence, so this card does not use it (see Paper vs code).
Problem
Radiance fields and 3D Gaussian splatting (3DGS) keep the whole scene in memory and draw every primitive. For large captures this exceeds GPU memory and is too slow to render §1. Sparse capture from a vehicle or a helmet rig also makes the original 3DGS densification rule and its training recipe unreliable §6.2, app. C. Existing large-scene NeRF methods either do not reach real-time rates or do not scale to this size §2.
Method
The pipeline has three stages §1, fig. 1:
- Train a coarse scaffold on the full data with no densification and fixed positions §6.1.
- Train each chunk independently with the 3DGS optimiser, with depth supervision, maximum-gradient densification and exposure compensation §6.2, app. C.
- Build a tree per chunk, compact it, optimise its interior nodes, and consolidate all chunks into one scene-level structure §4.1, §5, §6.3.
Rendering then picks a cut through the tree per view and blends across level boundaries §4.2.
Interior nodes: merging Gaussians
Each node is a 3D Gaussian with mean, covariance, SH coefficients and a value called falloff (opacity for leaves) §3.1, §4.1.
Given children with means \mu_i and covariances \Sigma_i, the parent's mean and covariance are weighted moments with weights w_i normalised to sum to 1 eq. 3, eq. 4, §4.1. The unnormalised weight is w'_i = o_i S_i, the opacity times the ellipsoid surface of the child eq. 8, §4.1. The paper derives it from matching the parent's projected contribution to the sum of the children's, under near-isotropy and low perspective distortion eq. 5–eq. 8, §4.1.
SH coefficients are merged with the same normalised weights eq. 9, §4.1. Falloff is \sum_i w'_i / S_p, which can exceed 1 §4.1, fig. 2. The paper keeps this value as the parent's falloff and clamps the blending weight to 1 §4.1.
Building the tree
Build an axis-aligned bounding-box (AABB) tree top-down. The root box encloses all leaves, each enlarged to three times its size §4.1. Split each node by a binary median cut on the longest axis of the box, using the projected means §4.1. Then merge the children bottom-up §4.1.
After merging, orientation matching rotates each child's axes to minimise the rotation relative to its parent. The paper uses an exhaustive search for this §4.1.
Cut selection
For a view and a target granularity \tau_\epsilon (in pixels), the granularity \epsilon(n) of a node is the projected size of the largest side of its leaf-bounding box §4.2, fig. 3. A node is in the cut when it meets \tau_\epsilon and its parent does not §4.2. Because a parent's box is never smaller than a child's, each branch is cut either at one node or not at all §4.2.
Smooth level switching
Each cut node n with parent p gets a weight from its own and its parent's granularity eq. 10, §4.2. Position, colour and SH are interpolated linearly with that weight §4.2. Scale and rotation are interpolated separately; rotation is linear, not slerp §4.2. Opacity needs a start value so that, at the start of a transition, the children blend to the same colour as the parent §4.2, fig. 4.
For two children, that start value is \alpha' = 1 - (1 - \alpha_p)^{1/2} eq. 12, §4.2. The blend is \alpha(t) = t\,\alpha_i + (1 - t)\,\alpha' eq. 13, §4.2. For K children the paper gives \alpha' = 1 - (1 - \alpha_p)^{1/K} §5.2.
# Cut selection for one view [§4.2] [eq. 10]
# Input: tree (nodes with parent links), camera, tau (pixels)
# Output: list of nodes whose blend the renderer draws
def select_cut(tree, camera, tau):
cut = []
for node in tree.nodes:
# Projected size of the node's leaf-bounding box [§4.2]
fine_enough = granularity(node, camera) <= tau
# A root has no parent; treat it as coarse [§4.2]
parent_coarse = (
node.parent is None
or granularity(node.parent, camera) > tau
)
if fine_enough and parent_coarse:
cut.append(node)
return cut# Interpolation weight and child opacity start [§4.2] [eq. 10] [eq. 12]
# Input: node n, its parent p, tau, parent falloff alpha_p, K children
# Output: weight t in the transition, and the start value alpha'
def transition_weight(node, tau, camera):
# t_n = (tau - eps(n)) / (eps(p) - eps(n)) [eq. 10]
eps_n = granularity(node, camera)
eps_p = granularity(node.parent, camera)
return (tau - eps_n) / (eps_p - eps_n)
def child_opacity_start(alpha_p, num_children):
# alpha' = 1 - (1 - alpha_p)^(1/K) [eq. 12] [§5.2]
return 1 - (1 - alpha_p) ** (1 / num_children)# Merge child Gaussians into a parent [§4.1] [eq. 3, eq. 4, eq. 8, eq. 9]
# Input: children (Gaussians with mean, cov, sh, opacity)
# Output: parent Gaussian (mean, cov, sh, falloff)
def merge(children):
# Unnormalised weights w'_i = o_i * S_i [eq. 8] [§4.1]
raw = [c.opacity * ellipsoid_surface(c.cov) for c in children]
total = sum(raw)
# Normalised weights w_i [§4.1]
weights = [r / total for r in raw]
# Parent mean: weighted average of child means [eq. 3]
mean = sum(w * c.mean for c, w in zip(children, weights))
# Parent covariance: weighted spread plus child covariances [eq. 4]
cov = sum(
w * (c.cov + outer(c.mean - mean, c.mean - mean))
for c, w in zip(children, weights)
)
# Parent SH: weighted average, same weights [eq. 9]
sh = sum(w * c.sh for c, w in zip(children, weights))
# Parent falloff: unnormalised sum over parent surface [§4.1]
parent_surface = ellipsoid_surface(cov) # formula not stated
falloff = total / parent_surface
return Gaussian(mean, cov, sh, falloff)Hierarchy optimisation
Interior nodes are rendered like leaves, so they can be optimised §5.1. Each step draws a random training view and a random granularity. The granularity is \tau = \tau_{max}^{\xi}\,\tau_{min}^{1-\xi} with \xi drawn from [0, 1) §5.1. The render is at full resolution, with cut selection and interpolation active §5.1. Leaves are frozen with a stop-gradient §5.1, §7.3. Only the positions, shapes, colours and opacities of interior nodes change §5.1, §7.3.
Compaction
Before optimisation, the tree is pruned to avoid nodes whose parent is barely larger than its children §5.2:
# Sparsify the tree so each kept parent covers enough detail [§5.2]
# Input: tree with leaves, training views, tau_min, tau_max (pixels)
# Output: pruned tree
def compact(tree, views, tau_min, tau_max):
# Leaves are always kept [§5.2]
mark_relevant(tree.leaves)
tau = tau_min # 3 px, minimum primitive extent [§5.2]
while tau < tau_max: # tau_max: half the image resolution [§5.2]
# Union of cuts over all training views at this tau [§5.2]
cut = union(select_cut(tree, v, tau) for v in views)
# Bottom-most nodes of that union are the highest-detail kept [§5.2]
keep = bottom_most(cut)
# Remove nodes between those and already marked nodes [§5.2]
remove_between(tree, keep, mark_relevant)
tau = 2 * tau # double the granularity each round [§5.2]
return treeChunks, depth and densification
Chunks are 50 × 50 m for walking captures and 100 × 100 m for vehicle captures §6. A camera belongs to a chunk if it is inside the chunk bounds, or within twice the bounds and sees more than 50 SfM points inside the bounds §6.1.
The scaffold has a skybox of 100,000 primitives on a sphere ten times the scene diameter §6.2. Outside a chunk, only opacity and SH of the scaffold are optimised §6.2.
Densification uses the maximum of the screen-space positional gradient rather than the mean §6.2.
Depth regularisation uses a monocular depth model rescaled to the COLMAP inverse depth in each image app. C.1. The loss is an absolute difference between rendered and prior depth app. C.1. Its weight decays exponentially from 1 to 0.01 over the per-chunk optimisation app. C.1.
Exposure is a per-image 3 × 4 affine transform, initialised to identity and optimised with Adam app. C.2.
Consolidation deletes a primitive that belongs to chunk i but lies outside it when it is closer to another chunk j §6.3. It then builds one scene root over all chunk trees §6.3.
Hyperparameters
| Name | Value | Where | Notes |
|---|---|---|---|
| Chunk size, walking capture | 50 × 50 m | §6 | |
| Chunk size, vehicle capture | 100 × 100 m | §6 | |
| Camera selection for a chunk | inside bounds, or within 2× bounds with > 50 SfM points inside | §6.1 | |
| Scaffold skybox | 100,000 primitives on a sphere, 10× scene diameter | §6.2 | |
| AABB enlargement for tree build | 3× stored Gaussian size | §4.1 | Same cutoff as 3DGS |
| \tau_{min} (compaction start) | 3 px | §5.2 | Minimum primitive extent after low-pass filtering |
| \tau_{max} (compaction end, hierarchy optimisation) | half the image resolution | §5.2 | Which dimension is not stated |
| Compaction step | \tau doubled each round | §5.2 | |
| Cut target \tau_1 | 3 px | tab. 2 | Finest setting |
| Cut target \tau_2 | 6 px | tab. 2 | Medium setting; main real-time setting |
| Cut target \tau_3 | 15 px | tab. 2 | Coarsest setting |
| Granularity sampling | \tau = \tau_{max}^{\xi}\tau_{min}^{1-\xi}, \xi \in [0,1) | §5.1 | Distribution of \xi taken as uniform |
| Optimisation render resolution | full resolution | §5.1 | |
| Leaf frozen during hierarchy optimisation | yes, stop-gradient | §5.1 §7.3 | |
| Opacity activation in hierarchy optimisation | absolute value | §7.3 | Replaces 3DGS exponential |
| Blend clamp in rasteriser | 0.99 | §7.3 | Gradient zeroed when clamped |
| Falloff clamp in text | 1 | §4.1 | Conflicts with 0.99 above |
| Depth loss weight | not stated | app. C.1 | |
| Depth loss decay | exponential, 1 to 0.01 | app. C.1 | Rate not stated |
| Exposure transform | 3 × 4 affine, identity init | app. C.2 | |
| Exposure learning rate | 1e-3 start, 1e-4 final | app. C.2 | |
| Exposure delay | 5,000 steps, delay multiplier 1e-3 | app. C.2 | Warm-up length not stated |
| Blur filter | variance of Laplacian, drop > 1.5 standard deviations below mean | app. A | |
| Motion check | SfM error < 1.5 px | app. A | |
| Matcher | captures i to i + 2^k, k \in [0, 10] | app. B | |
| Loop-closure matcher | i \pm 2^l to j \pm 2^m, (l,m) \in [0,5]^2 | app. B | Manual step |
| GPS neighbours | 25 nearest frames | app. B | When EXIF GPS exists |
| Camera model | OPENCV per camera, then undistorted to PINHOLE | app. B | |
| Training hardware (chunk, coarse) | NVIDIA V100 32 GB | §8.1 | |
| Rendering hardware | NVIDIA A6000 | §8.2 | Stated for all FPS |
| Per-chunk Gaussians | 2–8 M leaves | §8.1 | |
| Hierarchy memory | 284 B theoretical, 400 B implemented per Gaussian | §8.2 | 69% more than 3DGS |
Not stated: the training loss used for hierarchy optimisation §5.1; number of iterations for each stage; learning rates for the hierarchy stage; densification thresholds and interval §6.2; the depth-loss weight and decay rate app. C.1; the mask used for pixels in the depth prior app. C.1; and the ellipsoid surface formula §4.1.
Results
Author-reported. Setup: one chunk per scene for competitor comparisons, one V100 for training, one A6000 for rendering. Test images are every 50th image alphabetically from each rig camera §8.2. Exposure is disabled in the single-chunk comparison §8.2.
Single chunk, leaves and \tau levels tab. 2:
| Scene | Method | PSNR ↑ | LPIPS ↓ | SSIM ↑ | FPS ↑ |
|---|---|---|---|---|---|
| SmallCity | 3DGS | 25.34 | 0.337 | 0.776 | 99 |
| SmallCity | Ours (leaves) | 26.62 | 0.259 | 0.820 | 58 |
| SmallCity | Ours opt (\tau_2) | 26.29 | 0.275 | 0.810 | 110 |
| Wayve | 3DGS | 25.13 | 0.299 | 0.797 | 125 |
| Wayve | Ours (leaves) | 25.35 | 0.256 | 0.813 | 70 |
| Wayve | Ours opt (\tau_2) | 25.03 | 0.270 | 0.803 | 110 |
| Campus | 3DGS | 23.87 | 0.378 | 0.785 | 82 |
| Campus | Ours (leaves) | 24.61 | 0.331 | 0.807 | 51 |
| Campus | Ours opt (\tau_2) | 24.50 | 0.340 | 0.801 | 80 |
| BigCity | 3DGS | 21.48 | 0.445 | 0.721 | 72 |
| BigCity | Ours (leaves) | 23.10 | 0.348 | 0.769 | 39 |
| BigCity | Ours opt (\tau_2) | 23.05 | 0.359 | 0.762 | 102 |
The full table in tab. 2 also gives \tau_1 and \tau_3 rows and the Mip-NeRF 360, Instant-NGP and F2-NeRF baselines.
Mega-NeRF Mill 19 tab. 3: on Building, Mega-NeRF 20.93 / 0.504 / 0.547 (PSNR / LPIPS / SSIM) against 21.52 / 0.297 / 0.723 for Ours opt (\tau_2). On Rubble: 24.06 / 0.516 / 0.553 against 24.64 / 0.284 / 0.755. The authors report 3 hours on two (Building) or four (Rubble) V100s against 27–30 hours on eight V100s for Mega-NeRF §8.2.
Mip-NeRF 360, single chunk §8.2: 3DGS averages 29.11 dB and Ours 28.96 dB over the dataset. The \tau_1, \tau_2 and \tau_3 hierarchies average 28.86 / 28.87, 28.05 / 28.52 and 24.82 / 27.25 dB (basic / optimised).
Full scene runtime tab. 5 (average per frame; "#Render" is Gaussians on chip):
| Scene | \tau_1 = 3 px #Render (%) / FPS | \tau_2 = 6 px #Render (%) / FPS | \tau_3 = 15 px #Render (%) / FPS |
|---|---|---|---|
| SmallCity | 9.44M (66%) / 46 | 5.64M (39%) / 78 | 2.26M (16%) / 150 |
| Wayve | 12.4M (34%) / 40 | 6.39M (17%) / 65 | 2.10M (6%) / 125 |
| Campus | 21.6M (33%) / 32 | 10.2M (16%) / 62 | 3.01M (5%) / 133 |
| BigCity | 17.6M (19%) / 31 | 8.21M (8%) / 56 | 2.68M (3%) / 103 |
The text reports 30+ FPS on average at \tau_1 and about 60 FPS at \tau_2 across the full paths §8.2.
Storage: after consolidation, scene sizes are 6, 17, 27 and 88 GB, about 68% larger than non-hierarchical 3DGS files §8.1.
Ablation, averages over four scenes tab. 6:
| Method | PSNR ↑ | LPIPS ↓ | SSIM ↑ |
|---|---|---|---|
| 3DGS | 23.96 | 0.365 | 0.770 |
| Ours without modified densification | 24.43 | 0.371 | 0.766 |
| Ours without depth regularisation | 25.10 | 0.298 | 0.804 |
| Ours | 24.98 | 0.303 | 0.798 |
The text says depth supervision does not improve per-image metrics. Its benefit is qualitative on roads and in view extrapolation §8.3, fig. 11.
Paper vs code
Official code exists: the project page links graphdeco-inria/hierarchical-3d-gaussians, an Inria organisation, and the author block lists Inria affiliations. The repository is at commit 596dc7081ee74e57a66502f4746328911f763b34. Its LICENSE.md is an Inria licence that grants research use only and forbids commercial use without consent. It also says the original 3DGS licence applies to the parts built on 3DGS. wikillm publishes with advertising, so this card does not use the repository's code. Only the LICENSE.md was read. The card is written from the paper alone, as for other non-commercial repositories in this wiki.
Because the code was not read, no paper-code comparison has been made. A reader with a research licence should check at least the hierarchy-optimisation loss, the densification thresholds and the depth-loss weight, which the paper leaves unstated.
Pitfalls
- The falloff clamp has two values. The text says blended values are clamped to 1 §4.1. The implementation section says the rasteriser clamps at 0.99 and zeroes the opacity gradient when that happens §7.3. Pick one and test it on a parent that stands in for many children.
- Ellipsoid surface is not defined. The weight w'_i uses the ellipsoid surface S_i eq. 8, §4.1. The paper gives no formula. Its general statement, that the surface is proportional to the square root of the projected covariance determinant, does not fix the constant or the 3D definition.
- Orientation matching is under-specified. The paper says an exhaustive search minimises relative rotation §4.1, but not what is searched: which axis permutations and signs, or whether scale is kept fixed.
- Interpolation direction is not stated. Eq. 10 gives t_n and eq. 13 gives \alpha(t), but the paper does not say which end of the transition is t = 0 or t = 1 §4.2. Check at the start of a transition that the blended colour equals the parent's colour, as the paper requires §4.2, fig. 4.
- Rotation interpolation is linear, not slerp. Linear interpolation of quaternions gives non-unit rotations unless the result is renormalised. The paper does not say whether it renormalises §4.2.
- Opacity start value uses two different rules. For two children it is 1 - (1-\alpha_p)^{1/2} eq. 12. For K children it is 1 - (1-\alpha_p)^{1/K} §5.2. Both are stated; implement the general one.
- \tau_{max} is ambiguous. "Half the image's resolution" does not say which dimension, or whether it is measured before or after any downsampling §5.2.
- "Bottom-most" nodes in compaction is loosely defined. The paper calls the lowest nodes in the union of cuts a cut, but does not say how ties and partial branches are handled §5.2.
- The hierarchy-optimisation loss is not stated. Chunk training follows the 3DGS optimiser with depth and exposure changes §6.2, app. C. The paper does not give the loss used when optimising interior nodes §5.1.
- Depth prior definitions are ambiguous. The median t(\mathbf{D}) is written over \mathbf{D}, but the scale s(\mathbf{D}) sums over SfM indices only app. C.1. It is not clear whether the median is also taken over SfM pixels.
- Depth loss weight and decay rate are missing. The paper gives the start and end values of the weight, not its rate or the exact schedule app. C.1.
- Densification uses the maximum gradient, but no threshold is given §6.2. The interval and threshold must be taken from the 3DGS defaults or tuned.
- Image counts disagree across the paper. BigCity has 38,235 images in tab. 1 but 28,145 in the caption of fig. 8. The introduction says 5,800 to 28,000 images §1, while §7.2 says 5,800 to 40,000.
- Chunk counts are not mapped to scenes. The text gives 4, 11, 22 and 46 chunks in an order that matches the scene order only loosely §8.1. Check against the dataset before using them as a benchmark.
- Table 2 has a likely copy error. The Mip-NeRF 360 row has identical values (20.95 / 0.442 / 0.731) for Campus and BigCity tab. 2. Treat that row with caution.
- Optimisation gains at \tau_2 are small. The optimised \tau_2 PSNR gains over the unoptimised hierarchy are +0.57, +0.40, +0.17 and +0.08 dB on the four scenes tab. 2. The larger gains are at \tau_3, which the text also emphasises §8.2.
- Rendering can be slower than 3DGS. At \tau_1 or leaves, the extra weight computation and the \texttt{pow} in the blend cost time §8.2, tab. 4. The real speed-up appears only at coarser cuts such as \tau_2.
- Cut update and cleanup are asynchronous. The cut is updated every two frames and cleaned up every 100 frames §6.3. These intervals are the paper's and are not justified.
- Memory cost. Each Gaussian takes 400 bytes in the implementation, 69% more than 3DGS, and the theoretical minimum is 284 bytes §8.2.
- Pose estimation is expensive. The hierarchical COLMAP mapper gives poses that are not good enough alone. The paper adds exhaustive matching for the chunk cameras, retriangulation, bundle adjustment and Procrustes alignment, which takes 30 minutes to 5 hours on a V100 16 GB per chunk app. B.
- Training time is per stage, but the scope is unclear. The text says coarse, chunk and hierarchy optimisation each take about one hour on one V100 §8.1. It does not say whether this is per chunk or per scene.
- Data and code are not yet public. The paper says the source and capture data will be released §1. A footnote says dataset release is pending a Data Protection Officer's approval §2.
- The EWA reference key is misleading. The text attributes the anti-aliasing filter to Yu et al., but the bibliography key in the source is
geiger§5.1. - Mega-NeRF comparison uses different inputs. It uses pixSFM poses from the Mega-NeRF release, with points triangulated by COLMAP and metric scale applied afterwards §8.2. It is not the same pipeline as the paper's own.
- Masking. Humans and licence plates are removed, and moving vehicles are found by an SfM-point density test. The test is described as effective but not accurate app. A.
Lineage
- Builds on: 3D Gaussian Splatting, arXiv:2308.04079 §3.1. The rasteriser, densification and SH parameters are kept §6.2, §7.1.
- Anti-aliasing: EWA filtering, as cited in §5.1.
- Depth prior: DPT, Ranftl et al. app. C.1.
- Compared against: Mip-NeRF 360, Instant-NGP, F2-NeRF and 3DGS on single chunks §8.2, and Mega-NeRF on Mill 19 §8.2.
- Related large-scene work: Block-NeRF, Mega-NeRF and BungeeNeRF, which the paper says cannot handle street-level data at this scale §2.
- Superseded by: not stated in this wiki.
Source and license
Adapted from “A Hierarchical 3D Gaussian Representation for Real-Time Rendering of Very Large Datasets” by Bernhard Kerbl, Andréas Meuleman, Georgios Kopanas, Michael Wimmer, Alexandre Lanvin, George Drettakis (arXiv:2406.12080), used under CC BY 4.0. Changes: condensed and restructured for implementation, with tables, equations and algorithms taken from the paper and its code, each cited to its source. Not affiliated with or endorsed by the authors. Code citations refer to https://github.com/graphdeco-inria/hierarchical-3d-gaussians at commit 596dc7081ee7 (Inria Hierarchical-3d-Gaussians License (non-commercial research only)). Any quoted code is shown under that license.