Фаза 04 · урок 22

3D Gaussian Splatting с нуля

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

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

Курс
AI Engineering
Фаза
Компьютерное зрение
Чтение
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 · Полный каталог