Как я вскрывал Frostpunk. Часть 3: Почему моё здание было чёрным
Первая часть: Frostpunk Community Modkit. Или разработка модкита для первого фростпанка
Вторая часть: Как я вскрывал Frostpunk. Часть 2: Картинки, сломанное меню и закон-призрак
К этому моменту я уже умел менять игре настройки, тексты и картинки. Законы, технологии, иконки - всё моё!
Но здания всё равно были чужие. Я мог взять палатку и назвать её "Станция тепловых капсул".
Поменять ей стоимость, количество мест, иконку
А выглядела она всё равно как палатка
Хотелось своё. Свою модель
Ну что ж. Лезем в 3D. Что может пойти не так
Где живёт модель
Первый вопрос - где вообще лежат модели зданий. Отдельного склада с моделями у игры нет.
Было бы слишком просто
Нашлись они в складе templates, внутри шаблонов. Шаблон здания - это такая коробка, в которую запихано всё: как здание выглядит, какие у него эффекты, сколько стоит, сколько людей влезает. И модель тоже там, внутри, где-то между стоимостью и списком эффектов
Код, который её читает, я нашёл в игре тем же способом, что и в первой части. Файл движка называется meshtemplaterenderingdataserialization.cpp - разработчики, как обычно, забыли путь к исходнику внутри exe. Спасибо, ребята, без вас я бы ничего не нашёл!
Модель в 3D - это набор точек в пространстве, их называют вершинами, и треугольники, натянутые между ними. Любое здание, персонаж, дерево в любой игре - это просто дохуища треугольников
Каждая вершина во фростпанке весит 36 байт. Вот начало модели обычного уличного фонаря:
Первые четыре байта a4 01 00 00 - это 420. Столько вершин у одного сраного фонаря. Следующие четыре, 24 00 00 00 - это 36, байт на вершину. Дальше служебное описание буфера и сами вершины
Разберём одну:
позиция - три числа: x, y, z. Где точка висит в пространстве. Двенадцать байт
нормаль - куда смотрит поверхность в этой точке. По ней игра считает, как падает свет. Сжата до двух байт на ось, потому что жадные
касательная - ещё одно направление, поперёк нормали. Нужна, чтобы на ровной поверхности нарисовать рельеф: кирпичную кладку, доски, заклёпки. Без неё стена была бы гладкой
цвет - у всех ванильных моделей ff ff ff ff, то есть белый. Разработчикам нахуй не пригодился, но место занимает. В каждой вершине. Четыре байта. Умножьте на все модели игры
координаты текстуры - какая точка картинки натягивается на эту вершину. Записаны числами половинной точности, по два байта. Экономят как могут, а на цвете не сэкономили, логика
Дальше треугольники - просто тройки номеров вершин. А в самом конце шаблона - материалы, пути к картинкам и таблица "что и как рисовать"
Лимит - 65 535 вершин на модель. Потому что номер вершины в треугольнике записан двумя байтами, а больше в два байта не влезает хоть ты тресни. Запомните эту цифру, она ещё вылезет и укусит
Первая модель
Модкит научился превращать обычный файл модели OBJ - такие умеет сохранять любая 3D-программа - в формат фростпанка. Переложить вершины, пересчитать нормали и касательные, упаковать, запихнуть в шаблон
Сначала игра послала меня нахуй в принципе. "Compound template element 'mesh' references an invalid template" - мол, ссылаешься на модель, которой не существует. Хотя вот она, лежит, сука, я её сам туда положил
Оказалось, новую модель мало положить в склад, её надо ещё прописать в реестре шаблонов.
Это отдельная история и отдельная боль в жопе, расскажу когда-нибудь потом
В итоге всё собралось. Установил. Запустил игру. Построил
Здание было чёрным
Не тёмным. Не грязным. Не "ну освещение такое"
Чёрным, блядь, как дыра. Как будто кто-то вырезал кусок экрана ножницами и забыл вставить обратно
Вокруг снег, свет, огни генератора, всё красиво. А посреди - чёрная клякса в форме моей станции
Я смотрел на это и думал: ну пиздец. Приехали
Расследование
Когда здание чёрное, причин может быть миллион. Игра не пишет в журнал "у вас тут чёрное здание, потому что вы долбоёб". Она просто рисует чёрное и молчит. Как партизан
Начал копать. И нашёл не одну причину, а четыре. Одновременно. Каждая сама по себе могла сделать здание чёрным. Это был не баг, это была коллекция
Причина первая. Картинка вверх жопой
В Blender и в формате OBJ координаты картинки считаются снизу вверх. А в DirectX, на котором работает фростпанк, - сверху вниз. Потому что стандарты - это когда каждый делает по-своему
То есть каждая точка модели брала цвет из зеркального места картинки. Доски тянулись с металла, крыша - с камня. Само по себе это не чернота, но каша ещё та
Лечится переворотом одной координаты.
Причина вторая. Чужие таблицы
В хвосте шаблона лежит таблица "с какого треугольника по какой рисовать". И рамка - коробка, в которую влезает модель. По рамке игра решает, видно здание в кадре или нет, и как от него падает тень
Я вставлял свою модель в шаблон палатки, а таблицы и рамка оставались от палатки. Игра честно рисовала столько треугольников, сколько было у палатки. И обрезала по габаритам палатки. А на то, что там теперь совсем другое здание, ей было глубоко все равно
Теперь модкит пересчитывает и таблицы, и рамку сам
Причина третья. Старая знакомая
Помните из второй части историю с 144-м байтом? Как модкит клал пиксели картинки не туда и получалась цветная каша?
Так вот. Эта ошибка жила в коде дольше, чем я думал, и выстрелила здесь второй раз. Текстуры модели съезжали, и игра жрала мусор
Ну охуенно. Одни и те же грабли, только ручка теперь длиннее
Причина четвёртая. Главная
А вот тут начинается самое весёлое
У материала здания во фростпанке две картинки. Первая называется _ALB - это просто цвет. Доски коричневые, металл серый, тут всё понятно даже мне
Вторая называется _NOR. По всем законам 3D-графики это карта нормалей. Та самая картинка, которая рисует рельеф: где выпуклость, где щель между досками. Классическая карта нормалей - сиреневая такая, все её видели
Я сделал обычную карту нормалей. Положил. Чёрное
Сделал заново. Положил. Чёрное
Да ёб твою мать
Тогда я пошёл туда, где правда. В шейдеры
Шейдер - это маленькая программа для видеокарты, которая решает, какого цвета будет каждый пиксель на экране. Как падает свет, как блестит металл, как лежит тень. Все шейдеры игры лежат в складе common, уже скомпилированные
Я вытащил их все и прогнал через дизассемблер от Microsoft. Он превращает скомпилированный шейдер обратно в читаемые команды
Шейдеров оказалось 6962. Шесть тысяч девятьсот шестьдесят два. На одну игру про замерзающих людей
И в них нашлось вот это:
RoughnessSourceMask - "откуда брать шероховатость"
MetalnessSourceMask - "откуда брать металличность"
Маска источника. То есть шейдер берёт шероховатость и металличность не из отдельных картинок, а из какого-то канала какой-то картинки
Какой, блядь? Я полез в ванильные текстуры. Взял карту _NOR обычной палатки и посмотрел, что лежит в каждом цвете:
Если бы это была обычная карта нормалей, красный и зелёный были бы в среднем около 128, а синий - около 255. Синий там всегда почти на максимуме, поэтому карты нормалей такие сиреневые
А тут синий в среднем 32. Почти ноль. Красный 170
Чё за хуйня?
Разложил картинку на четыре канала и посмотрел глазами:
И всё встало на свои места
красный - это вообще не нормаль. Это фактура материала: где дерево, где ткань, где металл. Шероховатость
зелёный - рельеф. Складки ткани, доски. Это половина настоящей нормали
синий - почти весь чёрный. Белые только отдельные детали: крепления, котёл, железяки. Это металличность. Чёрное - не металл, белое - металл
прозрачность - снова рельеф, но под другим углом. Вторая половина нормали
Четыре разные картинки, упакованные в одну. Разработчики сэкономили на текстурах. А я на этой экономии проебал весь день
А теперь смотрим, что я туда пихал. Обычную карту нормалей:
в синем канале 255 - игра читает это как "всё здание сделано из металла. Полированного до блеска"
в прозрачности 255 - игра читает это как "поверхность повёрнута набок"
Здание из полированного металла, у которого все поверхности смотрят вбок. Такое не отражает ничего, кроме пустоты. Поэтому чёрное
Логика железная. Буквально
Весь день я пихал игре сиреневую карту и не понимал, почему здание выглядит как дыра в пространстве
Теперь модкит сам собирает _NOR так, как ждёт игра: шероховатость в красный, наклон по вертикали в зелёный, металличность в синий, наклон по горизонтали в прозрачность. А если у модели нет карты шероховатости - подставляет средние значения, подсмотренные у ванильных зданий. Чтобы новое здание было похоже на остальную игру.
Бонус, который вылез заодно. Прозрачность в первой картинке, _ALB, - это маска свечения. Белым там, где ночью должно светиться. Окна, лампы, раскалённая печь. Про это будет отдельно, потому что окна у меня так и не зажглись. Эта сволочь до сих пор не работает
Ожило
Собрал. Установил. Запустил
Иду строить, а у самого жопа сжимается
Вот оно. Доски деревянные. Металл металлический. Даже снег на крыше лежит
Я минут пять просто крутил камеру вокруг и смотрел. Ну охуенно же, согласитесь
Blender в деле
После этого я охуел от собственной крутости и решил, что пора делать что-то серьёзное. Не коробку, а настоящее большое здание. Спуск - огромную шахту-пропасть, вокруг неё постройки, над ней копёр с тросами, на которых вниз спускают людей и еду
Потом всё это нужно "запечь". Модель состоит из сотни отдельных деталей, у каждой свой материал. А игре нужна одна модель и одна картинка на всё. Капризная, блядь
Поэтому Blender разворачивает все детали на одну большую картинку 4096 на 4096 и "запекает" туда цвет, тени, рельеф, шероховатость и металл. Одна кнопка - и пять карт готовы
Модкит забирает это, упаковывает _NOR как положено, и вставляет в игру
Помните лимит в 65 535 вершин? Вот он и вылез
Модель стройки - полупрозрачные леса вокруг будущего здания - модкит генерирует сам. И у Спуска она получилась на 63 981 вершину. Впритык. Ещё один сраный домик сверху - и не влезло бы
Пришлось научить модкит склеивать одинаковые вершины. Стало 40 653. Выдохнул
Вот он, Спуск. В игре. С лифтом, который ездит вверх и вниз по шахте. Лифт, кстати, я взял у ванильного склада - там есть такой эффект, просто его никто не замечает, и он у меня немного поплыл) Потом исправил координату.
Итого
Что модкит умеет после этой части:
брать модель из Blender или любой 3D-программы и превращать её в здание фростпанка
правильно упаковывать материалы, чтобы здание не было чёрной дырой
сам делать модель стройки и снег на крыше
пересчитывать всё служебное: таблицы, рамку, касательные
Продолжение следует

























