Позвольте мне рассказать вам историю о моём друге Бобе. Недавно окончив университет, он решил создать MMO-игру с собственной физикой, глобальным освещением в реальном времени и процедурно генерируемыми ламами в шляпах. Три года спустя его «движок» едва может отобразить вращающийся куб, не перегружая GPU. Не будьте как Боб.

Обольщение собственного движка

Мы все были в такой ситуации — смотрели на 12 миллионов строк кода Unreal Engine на C++ и думали:

// Их способ
GetWorld()->SpawnActor<AAwesomeCharacter>(SpawnLocation);
// Мой гипотетически лучший способ
SpawnCoolDude(x, y, z, swagLevel);

Но прежде чем начать переписывать mathematics.lib, потому что «вы можете сделать это лучше», давайте поговорим о реальности.

Проверка реальности (сейчас уже не 1999 год)

Современное рендеринг включает в себя больше слоёв, чем луковица в тренче:

graph TD A[Данные вершин] --> B[Вершинный шейдер] B --> C[Фрагментный шейдер] C --> D[Буфер кадра] D --> E[Постобработка] E --> F[Сглаживание] F --> G[Цветокоррекция] G --> H[Фактические пиксели] H -->|"Подождите, а где мои 240FPS?"| I[Терморегулирование]

Теперь представьте, что вы поддерживаете это, одновременно внедряя:

  • 14 различных техник теневого картирования
  • 3 системы пространственного разбиения
  • Тот странный баг, который появляется только на мобильных GPU NVIDIA 2018 года

Время: тихий убийца

Средний современный игровой движок состоит из:

  • более 1,2 млн строк графического кода
  • более 300 вариаций шейдеров
  • 4,7 метрических тонн обходных путей для драйверов Вот как выглядит рендеринг треугольника в сыром OpenGL:
// Всего 57 шагов для отрисовки треугольника!
GLuint VBO, VAO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);

Сравните с Unity:

// Шаг 1: Создать треугольник
// Шаг 2: Посмотреть на треугольник
// Шаг 3: Получить прибыль

Ловушка экспертизы

Вам нужно будет освоить:

  • Линейную алгебру (веселье никогда не заканчивается!)
  • Оптимизацию SIMD
  • Системы материалов PBR
  • Управление памятью в Vulkan/DirectX12
  • Примерно 37 различных координатных пространств Однажды я потратил три недели на отладку проблем с z-буфером, только чтобы обнаружить, что перепутал нормали пространства просмотра и мирового пространства. Это реальная история.

Когда это может иметь смысл

pie title "Стоит ли вам создавать свой движок?" "Просто хотите научиться" : 45 "Специфические требования к оборудованию" : 25 "Академические исследования" : 15 "Практическая причина" : 15

Допустимые причины напоминают мои привычки по стирке белья — редкие, но иногда необходимые:

  1. Вы разрабатываете экспериментальное оборудование (квантовый игровой компьютер?)
  2. Вам нужен идеальный контроль для архивных проектов
  3. Вы пишете кандидатскую диссертацию о временном апсэмплинге
  4. Вы наслаждаетесь болью как стилем жизни

Кошмар обслуживания

Каждое обновление движка ощущается как:

  1. Обновление драйверов графики
  2. Всё ломается
  3. Проклинаете существование
  4. Откатываете драйверы
  5. Повторяете до выхода на пенсию Современные движки уже обрабатывают:
  • Кроссплатформенную компиляцию
  • Обходные пути для драйверов
  • Пиплайны компиляции шейдеров
  • Колдовство выравнивания памяти Как сказала разработчик Unity Джессика: «Я бы скорее научила своего кота C++, чем снова занималась поддержкой нашего внутреннего стека рендеринга».

Парадокс продуктивности

Давайте подсчитаем:

ЗадачаСобственный движокUnreal Engine
Базовый PBR материал2 недели2 минуты
Отложенный рендеринг1 месяц1 флажок
Отладка сбоев GPU3 недели3 эспрессо

Пока вы заняты реализацией очередного метода теней, кто-то, использующий существующий движок, занимается:

  • Оттачиванием игровой механики
  • Созданием контента
  • Реальным выпуском своей игры
  • Имеет жизнь

Лучший путь вперёд

Вместо полноценной разработки движка рассмотрите:

// Настраивайте, а не переделывайте
void MyAwesomeRenderer::ApplyBlackMagic(RenderPass pass) {
    if (Engine->GetSettings()->UseDarkSorcery) {
        // Ваш особый соус здесь
        CastVoodooSpell(pass);
        InvokeCthulhu();
    } else {
        Engine->DefaultRender(pass); 
    }
}

Современные движки более расширяемы, чем мои оправдания пропущенных сроков. Доступ к исходному коду Unreal Engine от Epic стоит примерно столько же, сколько зарплата одного разработчика за месяц.

Заключение: будьте шеф-поваром, а не фермером

Если вы не занимаетесь разработкой движков (в противном случае, зачем вы читаете это?), сосредоточьтесь на том, что действительно важно — создании удивительных впечатлений. Миру не нужен ещё один недоделанный обёртки OpenGL — ему нужны ваши уникальные игровые идеи.

Теперь, если вы меня извините, мне нужно пойти извиниться перед моим GPU за то, что я с ним делал в 2017 году. Он до сих пор мигает предупреждающими знаками, когда я открываю редакторы шейдеров.