Фаза 04 · урок 22
3D Gaussian Splatting с нуля
Цель урока: Сцена — это облако миллионов 3D-гауссиан. У каждой есть положение, ориентация, масштаб, непрозрачность и цвет, зависящий от направления обзора. Растеризуйте их, выполните обратное распространение через растеризацию — готово.
Текущий релиз AlexBred.com: первые 100 уроков русскоязычной программы.
Содержание урока
- Цели обучения
- Задача
- Концепция
- Что несёт гауссиана
- Растеризация, а не марширование луча
- Шаг проецирования
- Правило alpha-композитинга
- Почему это дифференцируемо
- Уплотнение и отсечение
- Сферические гармоники в одном абзаце
- Производственный стек 2026 года
- 4D- и генеративные варианты
- Соберите решение
- Шаг 1: 2D-гауссиана
- Шаг 2: растеризатор 2D splatting
- Шаг 3: обучаемая 2D-сцена из сплатов
- Шаг 4: подгонка 2D-гауссиан к целевому изображению
- Шаг 5: от 2D к 3D
- Шаг 6: вычисление сферических гармоник
- Используйте решение
- Подготовьте к поставке
- Упражнения
- Ключевые термины
- Дополнительные материалы
Сцена — это облако миллионов 3D-гауссиан. У каждой есть положение, ориентация, масштаб, непрозрачность и цвет, зависящий от направления обзора. Растеризуйте их, выполните обратное распространение через растеризацию — готово.
Тип: Соберите Языки: Python Предварительные требования: фаза 4, урок 13 (3D-зрение и NeRF); фаза 1, урок 12 (операции с тензорами); фаза 4, урок 10 (основы diffusion, необязательно) Время: ~90 минут
Цели обучения
- Объяснить, почему 3D Gaussian Splatting заменил NeRF в качестве производственного стандарта для фотореалистичной 3D-реконструкции в 2026 году
- Перечислить шесть параметров каждой гауссианы (положение, кватернион поворота, масштаб, непрозрачность, цвет в сферических гармониках, необязательный признак) и число float-значений, вносимых каждым из них
- Реализовать с нуля растеризатор 2D Gaussian splatting с использованием
alpha-композитинга, а затем показать, как 3D-случай проецируется в тот же цикл - Использовать
nerfstudio,gsplatилиSuperSplatдля реконструкции сцены по 20–50 фотографиям и экспорта в расширение glTFKHR_gaussian_splattingлибо схему OpenUSD 26.03UsdVolParticleField3DGaussianSplat
Задача
NeRF хранит сцену в весах MLP. Каждый отрисованный пиксель требует сотен запросов MLP вдоль луча. Обучение занимает часы, рендеринг — секунды, а веса нельзя редактировать: если вы хотите передвинуть стул внутри сцены, придётся переобучать модель.
3D Gaussian Splatting (Kerbl, Kopanas, Leimkühler, Drettakis, SIGGRAPH 2023) заменил всё это. Сцена представляет собой явный набор 3D-гауссиан. Рендеринг — это растеризация на GPU со скоростью 100+ fps. Обучение занимает минуты. Редактирование прямое: переместите подмножество гауссиан — и вы передвинули стул. К 2026 году Khronos Group ратифицировала расширение glTF для гауссовых сплатов, OpenUSD 26.03 поставляется со схемой гауссовых сплатов, Zillow и Apartments.com рендерят с их помощью недвижимость, а большинство новых исследовательских статей о 3D-реконструкции являются вариантами базовой идеи 3DGS.
Ментальная модель проста, однако в математике достаточно подвижных частей, поэтому большинство введений начинает с растеризации и пропускает проекции и сферические гармоники. В этом уроке мы соберём всё целиком: сначала 2D-версию, затем 3D-расширение.
Концепция
Что несёт гауссиана
Одна 3D-гауссиана — это параметрическое пятно в пространстве со следующими атрибутами:
position mu (3,) centre in world coordinates
rotation q (4,) unit quaternion encoding orientation
scale s (3,) log-scales per axis (exponentiated at render time)
opacity alpha (1,) post-sigmoid opacity [0, 1]
SH coefficients c_lm (3 * (L+1)^2,) view-dependent colour
Поворот вместе с масштабом строят ковариацию 3x3: Sigma = R S S^T R^T. Это форма гауссианы в 3D. Сферические гармоники позволяют цвету меняться с направлением обзора — блики, тонкий глянец, зависящее от обзора свечение — без хранения текстур для каждого вида. При степени SH 3 вы получаете 16 коэффициентов на цветовой канал, то есть 48 float-значений на одну гауссиану только для цвета.
Обычно сцена содержит 1–5 миллионов гауссиан. Каждая хранит примерно 60 float-значений (3 + 4 + 3 + 1 + 48 + прочее). Это 240 MB для сцены с пятью миллионами гауссиан — значительно меньше эквивалентного облака точек с текстурой на точку и на порядок меньше весов MLP NeRF, повторно отрисованных в высоком разрешении.
Растеризация, а не марширование луча
Пять шагов — все дружественные к GPU. Никакого запроса MLP для каждого пикселя. Одна RTX 3080 Ti отрисовывает 6 миллионов сплатов со скоростью 147 fps.
Шаг проецирования
3D-гауссиана в мировой позиции mu с 3D-ковариацией Sigma проецируется в 2D-гауссиану в экранной позиции mu' с 2D-ковариацией Sigma':
mu' = project(mu)
Sigma' = J W Sigma W^T J^T (2 x 2)
W = viewing transform (rotation + translation of camera)
J = Jacobian of the perspective projection at mu'
След 2D-гауссианы — это эллипс, оси которого являются собственными векторами Sigma'. Каждый пиксель внутри эллипса получает вклад гауссианы, взвешенный как exp(-0.5 * (p - mu')^T Sigma'^-1 (p - mu')).
Правило alpha-композитинга
Для одного пикселя гауссианы, покрывающие его, сортируются от заднего плана к переднему (или, эквивалентно, от переднего к заднему с инвертированной формулой). Цвет компонуется тем же уравнением, что и в любом полупрозрачном растеризаторе с 1980-х:
C_pixel = sum_i alpha_i * T_i * c_i
T_i = prod_{j < i} (1 - alpha_j) transmittance up to i
alpha_i = opacity_i * exp(-0.5 * d^T Sigma'^-1 d) local contribution
c_i = eval_SH(SH_i, view_direction) view-dependent colour
Это то же уравнение, что и для объёмного рендеринга NeRF, только применённое к явному разреженному набору гауссиан вместо плотных отсчётов вдоль луча. Именно поэтому качество рендеринга сопоставимо с NeRF: оба метода интегрируют одно и то же уравнение поля излучения.
Почему это дифференцируемо
Каждый шаг — проецирование, назначение плиткам, alpha-композитинг, вычисление SH — дифференцируем по параметрам гауссиан. Имея изображение ground truth, вычислите потерю от отрисованных пикселей, выполните обратное распространение через растеризатор и обновляйте все (mu, q, s, alpha, c_lm) градиентным спуском. Примерно за ~30 000 итераций гауссианы находят правильные положения, масштабы и цвета.
Уплотнение и отсечение
Фиксированный набор гауссиан не может покрыть сложную сцену. Обучение включает два адаптивных механизма:
- Клонируйте гауссиану в её текущем положении, когда величина её градиента велика, а масштаб мал: реконструкции здесь требуется больше деталей.
- Разделяйте крупномасштабную гауссиану на две меньшие, когда её градиент велик: одна большая гауссиана слишком сглажена, чтобы описать этот участок.
- Отсекайте гауссианы, непрозрачность которых опускается ниже порога: они не вносят вклад.
Уплотнение выполняется каждые N итераций. Обычно сцена вырастает примерно со 100k начальных гауссиан (посеянных из точек SfM) до 1–5M к концу обучения.
Сферические гармоники в одном абзаце
Зависящий от обзора цвет — это функция c(direction) на единичной сфере. Сферические гармоники — базис Фурье для сферы. Усеките его на степени L — и получите (L+1)^2 базисных функций на канал. Вычисление цвета для нового вида — это скалярное произведение выученных коэффициентов SH и базиса, вычисленного в направлении просмотра. Степень 0 = один коэффициент = постоянный цвет. Степень 3 = 16 коэффициентов = достаточно, чтобы передать ламбертово освещение, блики и слабые отражения. В статьях о SD Gaussian Splatting по умолчанию используется степень 3.
Производственный стек 2026 года
1. Capture smartphone / DJI drone / handheld scanner
2. SfM / MVS COLMAP or GLOMAP derives camera poses + sparse points
3. Train 3DGS nerfstudio / gsplat / inria official / PostShot (~10-30 min on RTX 4090)
4. Edit SuperSplat / SplatForge (clean floaters, segment)
5. Export .ply -> glTF KHR_gaussian_splatting or .usd (OpenUSD 26.03)
6. View Cesium / Unreal / Babylon.js / Three.js / Vision Pro
4D- и генеративные варианты
- 4D Gaussian Splatting — гауссианы являются функциями времени; применяется для объёмного видео (Superman 2026, «Helicopter» A$AP Rocky).
- Generative splats — модели text-to-splat (Marble от World Labs), которые галлюцинируют целые сцены.
- 3D Gaussian Unscented Transform — вариант NVIDIA NuRec для моделирования автономного вождения.
Соберите решение
Шаг 1: 2D-гауссиана
Сначала соберём 2D-растеризатор. После проецирования 3D-случай сводится к нему.
import torch
import torch.nn as nn
import torch.nn.functional as F
def eval_2d_gaussian(means, covs, points):
"""
means: (G, 2) centres
covs: (G, 2, 2) covariance matrices
points: (H, W, 2) pixel coordinates
returns: (G, H, W) density at every pixel for every Gaussian
"""
G = means.size(0)
H, W, _ = points.shape
flat = points.view(-1, 2)
inv = torch.linalg.inv(covs)
diff = flat[None, :, :] - means[:, None, :]
d = torch.einsum("gpi,gij,gpj->gp", diff, inv, diff)
density = torch.exp(-0.5 * d)
return density.view(G, H, W)
einsum вычисляет квадратичную форму diff^T Sigma^-1 diff для каждой пары (гауссиана, пиксель).
Шаг 2: растеризатор 2D splatting
Alpha-композитинг от переднего плана к заднему. Глубина в 2D не имеет смысла, поэтому для порядка мы используем обучаемый скаляр для каждой гауссианы.
def rasterise_2d(means, covs, colours, opacities, depths, image_size):
"""
means: (G, 2)
covs: (G, 2, 2)
colours: (G, 3)
opacities: (G,) in [0, 1]
depths: (G,) per-Gaussian scalar used for ordering
image_size: (H, W)
returns: (H, W, 3) rendered image
"""
H, W = image_size
yy, xx = torch.meshgrid(
torch.arange(H, dtype=torch.float32, device=means.device),
torch.arange(W, dtype=torch.float32, device=means.device),
indexing="ij",
)
points = torch.stack([xx, yy], dim=-1)
densities = eval_2d_gaussian(means, covs, points)
alphas = opacities[:, None, None] * densities
alphas = alphas.clamp(0.0, 0.99)
order = torch.argsort(depths)
alphas = alphas[order]
colours_sorted = colours[order]
T = torch.ones(H, W, device=means.device)
out = torch.zeros(H, W, 3, device=means.device)
for i in range(means.size(0)):
a = alphas[i]
out += (T * a)[..., None] * colours_sorted[i][None, None, :]
T = T * (1.0 - a)
return out
Это не быстро — в реальной реализации используются CUDA-ядра на основе плиток, — но математика в точности правильная, и всё полностью дифференцируемо.
Шаг 3: обучаемая 2D-сцена из сплатов
class Splats2D(nn.Module):
def __init__(self, num_splats=128, image_size=64, seed=0):
super().__init__()
g = torch.Generator().manual_seed(seed)
H, W = image_size, image_size
self.means = nn.Parameter(torch.rand(num_splats, 2, generator=g) * torch.tensor([W, H]))
self.log_scale = nn.Parameter(torch.ones(num_splats, 2) * math.log(2.0))
self.rot = nn.Parameter(torch.zeros(num_splats)) # single angle in 2D
self.colour_logits = nn.Parameter(torch.randn(num_splats, 3, generator=g) * 0.5)
self.opacity_logit = nn.Parameter(torch.zeros(num_splats))
self.depth = nn.Parameter(torch.rand(num_splats, generator=g))
def covs(self):
s = torch.exp(self.log_scale)
c, si = torch.cos(self.rot), torch.sin(self.rot)
R = torch.stack([
torch.stack([c, -si], dim=-1),
torch.stack([si, c], dim=-1),
], dim=-2)
S = torch.diag_embed(s ** 2)
return R @ S @ R.transpose(-1, -2)
def forward(self, image_size):
covs = self.covs()
colours = torch.sigmoid(self.colour_logits)
opacities = torch.sigmoid(self.opacity_logit)
return rasterise_2d(self.means, covs, colours, opacities, self.depth, image_size)
log_scale, opacity_logit и colour_logits — неограниченные параметры, отображаемые через нужную функцию активации во время рендеринга. Это стандартный паттерн для каждой реализации 3DGS.
Шаг 4: подгонка 2D-гауссиан к целевому изображению
import math
import numpy as np
def make_target(size=64):
yy, xx = np.meshgrid(np.arange(size), np.arange(size), indexing="ij")
img = np.zeros((size, size, 3), dtype=np.float32)
# Red circle
mask = (xx - 20) ** 2 + (yy - 20) ** 2 < 10 ** 2
img[mask] = [1.0, 0.2, 0.2]
# Blue square
mask = (np.abs(xx - 45) < 8) & (np.abs(yy - 40) < 8)
img[mask] = [0.2, 0.3, 1.0]
return torch.from_numpy(img)
target = make_target(64)
model = Splats2D(num_splats=64, image_size=64)
opt = torch.optim.Adam(model.parameters(), lr=0.05)
for step in range(200):
pred = model((64, 64))
loss = F.mse_loss(pred, target)
opt.zero_grad(); loss.backward(); opt.step()
if step % 40 == 0:
print(f"step {step:3d} mse {loss.item():.4f}")
За 200 шагов 64 гауссианы укладываются в две фигуры. В этом вся идея: градиентный спуск по явным геометрическим примитивам.
Шаг 5: от 2D к 3D
3D-расширение сохраняет тот же цикл. Добавляются следующие элементы:
- Поворот каждой гауссианы задаётся кватернионом, а не одним углом.
- Ковариация равна
R S S^T R^T, гдеRпостроена из кватерниона, аS = diag(exp(log_scale)). - Проекция
(mu, Sigma) -> (mu', Sigma')использует внешние параметры камеры и якобиан перспективной проекции в точкеmu. - Цвет становится разложением по сферическим гармоникам; вычисляйте его в направлении обзора.
- Сортировка по глубине использует реальную z-координату в системе камеры, а не обучаемый скаляр.
Каждая производственная реализация (gsplat, inria/gaussian-splatting, nerfstudio) делает именно это на GPU с CUDA-ядрами на основе плиток.
Шаг 6: вычисление сферических гармоник
Базис SH до степени 3 содержит 16 членов на канал. Вычисление:
def eval_sh_degree_3(sh_coeffs, dirs):
"""
sh_coeffs: (..., 16, 3) last dim is RGB channels
dirs: (..., 3) unit vectors
returns: (..., 3)
"""
C0 = 0.282094791773878
C1 = 0.488602511902920
C2 = [1.092548430592079, 1.092548430592079,
0.315391565252520, 1.092548430592079,
0.546274215296039]
x, y, z = dirs[..., 0], dirs[..., 1], dirs[..., 2]
x2, y2, z2 = x * x, y * y, z * z
xy, yz, xz = x * y, y * z, x * z
result = C0 * sh_coeffs[..., 0, :]
result = result - C1 * y[..., None] * sh_coeffs[..., 1, :]
result = result + C1 * z[..., None] * sh_coeffs[..., 2, :]
result = result - C1 * x[..., None] * sh_coeffs[..., 3, :]
result = result + C2[0] * xy[..., None] * sh_coeffs[..., 4, :]
result = result + C2[1] * yz[..., None] * sh_coeffs[..., 5, :]
result = result + C2[2] * (2.0 * z2 - x2 - y2)[..., None] * sh_coeffs[..., 6, :]
result = result + C2[3] * xz[..., None] * sh_coeffs[..., 7, :]
result = result + C2[4] * (x2 - y2)[..., None] * sh_coeffs[..., 8, :]
# degree 3 terms omitted here for brevity; full 16-coefficient version in the code file
return result
Выученные sh_coeffs хранят «цвет во всех направлениях» этой гауссианы. Во время рендеринга вы вычисляете их для текущего направления обзора и получаете 3-вектор RGB.
Используйте решение
Для реальной работы с 3DGS используйте gsplat (Meta) или nerfstudio:
pip install nerfstudio gsplat
ns-download-data example
ns-train splatfacto --data path/to/data
splatfacto — тренер 3DGS из nerfstudio. Для типичной сцены запуск занимает 10–30 минут на RTX 4090.
Варианты экспорта, важные в 2026 году:
.ply— необработанное облако гауссиан (переносимое, самый большой файл)..splat— квантованный формат PlayCanvas / SuperSplat.- glTF
KHR_gaussian_splatting— стандарт Khronos, переносимый между просмотрщиками (RC, февраль 2026). - OpenUSD
UsdVolParticleField3DGaussianSplat— встроенный в USD формат для конвейеров NVIDIA Omniverse и Vision Pro.
Для 4D / динамических сцен 4DGS и Deformable-3DGS расширяют ту же механику средними значениями и непрозрачностями, изменяющимися во времени.
Подготовьте к поставке
Этот урок создаёт:
outputs/prompt-3dgs-capture-planner.md— промпт, который планирует сессию съёмки (число фотографий, путь камеры, освещение) для заданного типа сцены.outputs/skill-3dgs-export-router.md— навык, выбирающий подходящий формат экспорта (.ply/.splat/ glTF / USD) с учётом целевого просмотрщика или движка.
Упражнения
- (Легко) Запустите приведённый выше 2D-тренер сплатов на другом синтетическом изображении. Меняйте
num_splatsв[16, 64, 256]и постройте график MSE по шагам для каждого значения. Найдите точку убывающей отдачи. - (Средне) Расширьте 2D-растеризатор так, чтобы он поддерживал RGB-цвета каждой гауссианы, зависящие от скалярного «угла обзора» через гармонику степени 2. Обучите на паре целевых изображений и убедитесь, что модель реконструирует оба.
- (Сложно) Клонируйте
nerfstudioи обучитеsplatfactoна съёмке из 20 фотографий любой вашей сцены (стол, растение, лицо, комната). Экспортируйте в glTFKHR_gaussian_splattingи откройте в просмотрщике (Three.jsGaussianSplats3D, SuperSplat, Babylon.js V9). Сообщите время обучения, количество гауссиан и fps при рендеринге.
Ключевые термины
| Термин | Как обычно говорят | Что это на самом деле означает |
|---|---|---|
| 3DGS | «Gaussian splats» | Явное представление сцены как миллионов 3D-гауссиан с положением, поворотом, масштабом, непрозрачностью и цветом SH для каждой гауссианы |
| Ковариация | «Форма гауссианы» | Sigma = R S S^T R^T; ориентация и анизотропный масштаб одной гауссианы |
| Alpha-композитинг | «Смешивание от заднего плана к переднему» | То же уравнение, что и в объёмном рендеринге NeRF, но над явным разреженным набором |
| Уплотнение | «Клонировать и разделить» | Адаптивное добавление новых гауссиан там, где реконструкция недообучена |
| Отсечение | «Удалить малонепрозрачные» | Удаление гауссиан, которые во время обучения схлопнулись до непрозрачности, близкой к нулю |
| Сферические гармоники | «Зависящий от обзора цвет» | Базис Фурье на сфере; хранит цвет как функцию направления обзора |
| Splatfacto | «3DGS из nerfstudio» | Самый простой путь к обучению 3DGS в 2026 году |
KHR_gaussian_splatting |
«Стандарт glTF» | Расширение Khronos 2026 года, делающее 3DGS переносимым между просмотрщиками и движками |
Дополнительные материалы
- 3D Gaussian Splatting for Real-Time Radiance Field Rendering (Kerbl et al., SIGGRAPH 2023) — оригинальная статья
- gsplat (Meta/nerfstudio) — производственный CUDA-растеризатор
- nerfstudio Splatfacto — эталонный рецепт обучения
- Расширение Khronos KHR_gaussian_splatting — переносимый формат 2026 года
- Примечания к выпуску OpenUSD 26.03 — схема
UsdVolParticleField3DGaussianSplat - THE FUTURE 3D State of Gaussian Splatting 2026 — обзор отрасли
Источник: 3D Gaussian Splatting from Scratch 04.21 — Обнаружение ключевых точек и оценка позы · Фаза 04 — Компьютерное зрение · 04.23 — Diffusion Transformers и Rectified Flow · Полный каталог