Фаза 04 · урок 22

3D Gaussian Splatting с нуля

Цель урока: Сцена — это облако миллионов 3D-гауссиан. У каждой есть положение, ориентация, масштаб, непрозрачность и цвет, зависящий от направления обзора. Растеризуйте их, выполните обратное распространение через растеризацию — готово.

Текущий релиз AlexBred.com: первые 100 уроков русскоязычной программы.

Курс
AI Engineering from Scratch
Фаза
Компьютерное зрение
Чтение
15 мин.
Проверено
Содержание урока
  1. Цели обучения
  2. Задача
  3. Концепция
  4. Что несёт гауссиана
  5. Растеризация, а не марширование луча
  6. Шаг проецирования
  7. Правило alpha-композитинга
  8. Почему это дифференцируемо
  9. Уплотнение и отсечение
  10. Сферические гармоники в одном абзаце
  11. Производственный стек 2026 года
  12. 4D- и генеративные варианты
  13. Соберите решение
  14. Шаг 1: 2D-гауссиана
  15. Шаг 2: растеризатор 2D splatting
  16. Шаг 3: обучаемая 2D-сцена из сплатов
  17. Шаг 4: подгонка 2D-гауссиан к целевому изображению
  18. Шаг 5: от 2D к 3D
  19. Шаг 6: вычисление сферических гармоник
  20. Используйте решение
  21. Подготовьте к поставке
  22. Упражнения
  23. Ключевые термины
  24. Дополнительные материалы

Сцена — это облако миллионов 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 фотографиям и экспорта в расширение glTF KHR_gaussian_splatting либо схему OpenUSD 26.03 UsdVolParticleField3DGaussianSplat

Задача

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, повторно отрисованных в высоком разрешении.

Растеризация, а не марширование луча

Диаграмма к уроку «3D Gaussian Splatting с нуля»

Пять шагов — все дружественные к 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-расширение сохраняет тот же цикл. Добавляются следующие элементы:

  1. Поворот каждой гауссианы задаётся кватернионом, а не одним углом.
  2. Ковариация равна R S S^T R^T, где R построена из кватерниона, а S = diag(exp(log_scale)).
  3. Проекция (mu, Sigma) -> (mu', Sigma') использует внешние параметры камеры и якобиан перспективной проекции в точке mu.
  4. Цвет становится разложением по сферическим гармоникам; вычисляйте его в направлении обзора.
  5. Сортировка по глубине использует реальную 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) с учётом целевого просмотрщика или движка.

Упражнения

  1. (Легко) Запустите приведённый выше 2D-тренер сплатов на другом синтетическом изображении. Меняйте num_splats в [16, 64, 256] и постройте график MSE по шагам для каждого значения. Найдите точку убывающей отдачи.
  2. (Средне) Расширьте 2D-растеризатор так, чтобы он поддерживал RGB-цвета каждой гауссианы, зависящие от скалярного «угла обзора» через гармонику степени 2. Обучите на паре целевых изображений и убедитесь, что модель реконструирует оба.
  3. (Сложно) Клонируйте nerfstudio и обучите splatfacto на съёмке из 20 фотографий любой вашей сцены (стол, растение, лицо, комната). Экспортируйте в glTF KHR_gaussian_splatting и откройте в просмотрщике (Three.js GaussianSplats3D, 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 from Scratch 04.21 — Обнаружение ключевых точек и оценка позы · Фаза 04 — Компьютерное зрение · 04.23 — Diffusion Transformers и Rectified Flow · Полный каталог