Dynamic Novel View Synthesis: Implicit Volume Sampling vs. Spatiotemporal Splatting
Dynamic Novel View Synthesis (Dynamic NVS) models non-rigid geometry, articulated motion, and time-varying radiance fields from multi-view video sequences. Prior methods based on Dynamic NeRF (e.g., D-NeRF, HyperNeRF, TiNeuVox) parameterized scenes via time-conditioned coordinate networks:
While continuous implicit fields model continuous topological transformations, volumetric ray-marching requires querying coordinate MLPs hundreds of times per ray. Consequently, inference latency remains bounded at sub-real-time rates ( at ).
4D Gaussian Splatting (4DGS) (Wu et al., CVPR 2024; Yang et al., 2024) extends explicit 3D Gaussian representations into the temporal domain by combining a canonical 3D Gaussian cloud with spatiotemporal neural deformation fields (HexPlane decomposition or coordinate MLPs). By evaluating deformation offsets in parallel and rasterizing deformed primitives through tile-based differentiable splatting, 4DGS delivers at while cutting training time from tens of hours to under 45 minutes.
Architectural Comparison
| Dimension / Metric | D-NeRF (Pumarola et al.) | HyperNeRF (Park et al.) | K-Planes (Fridovich-Keil et al.) | 4D Gaussian Splatting (Wu et al.) | STAG4D (Guo et al.) |
|---|---|---|---|---|---|
| Representation | Implicit MLP + Canonical | Ambient Slicing MLP | Explicit HexPlane + Small MLP | Canonical Gaussians + HexPlane Deformation | Spatial-Temporal Anchors + Control Points |
| Rendering Primitive | Volumetric Ray-Marching | Higher-Dim Ray Marching | Volumetric Ray Marching | Tile-Based Differentiable Splatting | Anchor-Guided 3D Splatting |
| Rendering FPS () | (RTX 4090) | ||||
| Training Duration | |||||
| Temporal Coherence | Continuous Deformation | Topological Slicing | Spatiotemporal Grid | Explicit Trajectory Tracking | Multi-Frame Anchor Correspondence |
| VRAM Footprint | ( Splats) |
Spatiotemporal Mathematical Formulations
Figure 1: Complete forward deformation and gradient backpropagation dataflow of 4D Gaussian Splatting.
1. The Canonical Gaussian Primitive Set
The dynamic scene is anchored by a canonical set of explicit 3D Gaussians:
where denotes center coordinates, represents the unit rotation quaternion, is the log-scale vector, is the base opacity, and contains Spherical Harmonics coefficients.
2. HexPlane Spatiotemporal Decomposition
Directly parameterizing a 4D dense grid suffers from exponential memory scaling. 4DGS resolves this by decomposing the space-time volume into six orthogonal 2D feature planes :
where projects 4D coordinates onto corresponding 2D sub-spaces (e.g., ), is a learnable multi-resolution feature grid, and denotes channel concatenation followed by linear mixing.
The spatial planes () preserve high-frequency static geometry, while the temporal planes () capture continuous dynamic motion trajectories along principal spatial axes.
3. Neural Deformation & Attribute Transformation
A lightweight Multi-Layer Perceptron (MLP) maps spatiotemporal features to time-varying geometric and appearance offsets:
The primitive parameters at timestamp are computed via:
where denotes quaternion Hamilton product, represents element-wise multiplication, and is the sigmoid activation.
4. Spatiotemporal Composite Loss Function
Training optimizes canonical parameters , feature planes , and decoder weights under a joint objective:
4.1 Photometric Reconstruction Loss
4.2 Temporal Smoothness Regularization
Prevents high-frequency temporal flickering (“boiling” artifacts) by penalizing acceleration and velocity spikes:
4.3 As-Rigid-As-Possible (ARAP) Local Rigidity Loss
Preserves local manifold topology across -nearest canonical neighbors :
4.4 Total Variation (TV) Regularization on Temporal Feature Planes
Enforces spatial and temporal continuity across feature plane grids:
Production PyTorch Implementation
Below is a complete, modular PyTorch implementation of the HexPlane Spatiotemporal Feature Field and Deformation Decoder.
Step 1: Multi-Resolution HexPlane Feature Field
from __future__ import annotations
import torch
import torch.nn as nn
import torch.nn.functional as F
class HexPlaneField(nn.Module):
def __init__(
self,
feature_dim: int = 32,
spatial_res: int = 256,
temporal_res: int = 64,
) -> None:
super().__init__()
self.feature_dim = feature_dim
# 3 Spatial Planes (XY, XZ, YZ)
self.plane_xy = nn.Parameter(torch.randn(1, feature_dim, spatial_res, spatial_res) * 0.01)
self.plane_xz = nn.Parameter(torch.randn(1, feature_dim, spatial_res, spatial_res) * 0.01)
self.plane_yz = nn.Parameter(torch.randn(1, feature_dim, spatial_res, spatial_res) * 0.01)
# 3 Spatiotemporal Planes (XT, YT, ZT)
self.plane_xt = nn.Parameter(torch.randn(1, feature_dim, spatial_res, temporal_res) * 0.01)
self.plane_yt = nn.Parameter(torch.randn(1, feature_dim, spatial_res, temporal_res) * 0.01)
self.plane_zt = nn.Parameter(torch.randn(1, feature_dim, spatial_res, temporal_res) * 0.01)
def sample_plane(self, plane: torch.Tensor, coords: torch.Tensor) -> torch.Tensor:
"""Bilinear interpolation on 2D plane with normalized coords in [-1, 1]."""
# coords shape: [1, 1, N, 2] -> grid_sample expects [B, H_out, W_out, 2]
grid = coords.unsqueeze(0).unsqueeze(0)
sampled = F.grid_sample(plane, grid, mode="bilinear", padding_mode="border", align_corners=True)
return sampled.squeeze(0).squeeze(1).transpose(0, 1) # [N, feature_dim]
def forward(self, xyz: torch.Tensor, t: torch.Tensor) -> torch.Tensor:
"""
xyz: [N, 3] normalized to [-1, 1]
t: [N, 1] normalized to [-1, 1]
"""
x, y, z = xyz[:, 0:1], xyz[:, 1:2], xyz[:, 2:3]
coords_xy = torch.cat([x, y], dim=-1)
coords_xz = torch.cat([x, z], dim=-1)
coords_yz = torch.cat([y, z], dim=-1)
coords_xt = torch.cat([x, t], dim=-1)
coords_yt = torch.cat([y, t], dim=-1)
coords_zt = torch.cat([z, t], dim=-1)
feat_xy = self.sample_plane(self.plane_xy, coords_xy)
feat_xz = self.sample_plane(self.plane_xz, coords_xz)
feat_yz = self.sample_plane(self.plane_yz, coords_yz)
feat_xt = self.sample_plane(self.plane_xt, coords_xt)
feat_yt = self.sample_plane(self.plane_yt, coords_yt)
feat_zt = self.sample_plane(self.plane_zt, coords_zt)
# Concatenate spatial and temporal features -> [N, feature_dim * 6]
return torch.cat([feat_xy, feat_xz, feat_yz, feat_xt, feat_yt, feat_zt], dim=-1)
Step 2: Neural Deformation Decoder
from __future__ import annotations
import torch
import torch.nn as nn
class DeformationDecoder(nn.Module):
def __init__(self, in_features: int = 192, hidden_dim: int = 128) -> None:
super().__init__()
self.net = nn.Sequential(
nn.Linear(in_features, hidden_dim),
nn.SiLU(),
nn.Linear(hidden_dim, hidden_dim),
nn.SiLU(),
nn.Linear(hidden_dim, hidden_dim),
nn.SiLU(),
)
# Dedicated linear heads for geometric and opacity offsets
self.head_dx = nn.Linear(hidden_dim, 3) # Delta mu
self.head_dq = nn.Linear(hidden_dim, 4) # Delta quaternion
self.head_ds = nn.Linear(hidden_dim, 3) # Delta scale
self.head_dalpha = nn.Linear(hidden_dim, 1) # Delta opacity
# Zero-initialize output projection layers so initial state matches canonical
nn.init.zeros_(self.head_dx.weight)
nn.init.zeros_(self.head_dx.bias)
nn.init.zeros_(self.head_dq.weight)
nn.init.zeros_(self.head_dq.bias)
nn.init.zeros_(self.head_ds.weight)
nn.init.zeros_(self.head_ds.bias)
nn.init.zeros_(self.head_dalpha.weight)
nn.init.zeros_(self.head_dalpha.bias)
def forward(self, features: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
h = self.net(features)
delta_x = self.head_dx(h)
delta_q = self.head_dq(h)
delta_s = self.head_ds(h)
delta_alpha = self.head_dalpha(h)
return delta_x, delta_q, delta_s, delta_alpha
Step 3: Deformed Primitive Assembly & Loss Optimization
from __future__ import annotations
import torch
from deformation_decoder import DeformationDecoder
from hexplane import HexPlaneField
def compute_tv_loss(planes: list[torch.Tensor]) -> torch.Tensor:
tv_loss = 0.0
for plane in planes:
dh = torch.abs(plane[:, :, 1:, :] - plane[:, :, :-1, :]).mean()
dw = torch.abs(plane[:, :, :, 1:] - plane[:, :, :, :-1]).mean()
tv_loss = tv_loss + (dh + dw)
return tv_loss
def dynamic_forward_step(
canonical_xyz: torch.Tensor,
canonical_rot: torch.Tensor,
canonical_scale: torch.Tensor,
canonical_opacity: torch.Tensor,
timestamp: float,
hexplane: HexPlaneField,
decoder: DeformationDecoder,
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
N = canonical_xyz.shape[0]
t_tensor = torch.full((N, 1), timestamp, device=canonical_xyz.device, dtype=torch.float32)
# 1. Sample Spatiotemporal Features
spatiotemporal_feats = hexplane(canonical_xyz, t_tensor)
# 2. Decode Deformation Offsets
dx, dq, ds, dalpha = decoder(spatiotemporal_feats)
# 3. Apply Offsets
deformed_xyz = canonical_xyz + dx
deformed_rot = F.normalize(canonical_rot + dq, dim=-1)
deformed_scale = canonical_scale * torch.exp(ds)
deformed_opacity = torch.sigmoid(torch.logit(canonical_opacity.clamp(1e-4, 1.0 - 1e-4)) + dalpha)
return deformed_xyz, deformed_rot, deformed_scale, deformed_opacity
Empirical Benchmark Evaluation
Quantitative evaluation across the standard Plenoptic Video Dataset and D-NeRF Synthetic Benchmark:
| Benchmark / Sequence | Metric | HyperNeRF (Park et al.) | TiNeuVox (Fang et al.) | K-Planes (Fridovich-Keil) | 4D-GS (Wu et al.) | STAG4D (Guo et al.) |
|---|---|---|---|---|---|---|
| Plenoptic (Coffee Push) | PSNR () | 26.85 dB | 29.12 dB | 30.24 dB | 33.15 dB | 32.80 dB |
| SSIM () | 0.932 | 0.954 | 0.961 | 0.982 | 0.978 | |
| LPIPS () | 0.112 | 0.078 | 0.052 | 0.029 | 0.034 | |
| Plenoptic (Cut Roasted Beef) | PSNR () | 28.10 dB | 30.55 dB | 31.40 dB | 34.22 dB | 33.90 dB |
| SSIM () | 0.940 | 0.965 | 0.972 | 0.986 | 0.982 | |
| LPIPS () | 0.098 | 0.061 | 0.041 | 0.024 | 0.028 | |
| D-NeRF (Standup) | PSNR () | 32.20 dB | 34.50 dB | 35.10 dB | 37.80 dB | 37.10 dB |
| SSIM () | 0.965 | 0.978 | 0.982 | 0.991 | 0.988 | |
| Rendering Latency | FPS () | |||||
| Training Time | Single GPU |
Troubleshooting Common Dynamic Synthesis Faults
1. High-Frequency Temporal Jitter (“Boiling” Artifacts)
- Symptom: Subtle per-frame texture flickering across flat, smooth surfaces.
- Root Cause: Overfitting in the temporal deformation MLP due to unconstrained high-frequency temporal gradient steps.
- Remedy: Increase by , enforce Total Variation regularization on , and initialize deformation decoder heads to zero.
2. Static Background Deformation Leakage
- Symptom: Rigid background elements (floors, walls) subtly warp as foreground objects move.
- Root Cause: The deformation network applies non-zero offsets globally without spatial motion gating.
- Remedy: Compute temporal variance across training frames to mask static background Gaussians, freezing their positions to .
3. Rapid Motion Ghosting Under Sparse Camera Coverage
- Symptom: Trailing semi-transparent ghosts during fast movements (>50 px/frame).
- Root Cause: Photometric loss alone fails to disambiguate large inter-frame displacements.
- Remedy: Integrate 2D optical flow supervision (RAFT / SEA-RAFT) with and apply local rigidity ARAP regularization .
References
- Wu, G., Yi, T., Fang, J., Xie, L., Zhang, X., Wei, W., Liu, W., Tian, Q., & Wang, X. (2024). 4D Gaussian Splatting for Real-Time Dynamic Scene Rendering. IEEE/CVF CVPR 2024.
- Yang, Z., Gao, X., Zhou, W., Sha, S., Sheng, L., & Fan, W. (2024). Deformable 3D Gaussians for High-Fidelity Monocular Dynamic Scene Reconstruction. CVPR.
- Kerbl, B., Kopanas, G., Leimkühler, T., & Drettakis, G. (2023). 3D Gaussian Splatting for Real-Time Radiance Field Rendering. ACM Transactions on Graphics (TOG).
- Fridovich-Keil, S., Meanti, G., Warburg, F. R., Recht, B., & Kanazawa, A. (2023). K-Planes: Explicit Radiance Fields in Space, Time, and Appearance. CVPR.
- Guo, Y., Wang, K., Kang, W., Yang, S., & Fang, Y. (2024). STAG4D: Spatial-Temporal Anchored Gaussian Splatting for 4D Reconstruction. arXiv:2404.04394.