Ландшафтный дизайн, часть два.
Продолжение первой части http://blog.gamedeff.com/?p=157
Скоро сказка сказывается, QA аппрув значительно более медленная процедура.
Напомню краткое содержание предыдущей части. Был обычный террейн с геоморфингом, умеренно тормозил и доставлял умеренный набор проблем. Проблемы типичные – много DIP вызовов, тяжелый шейдер, много вершинных стримов. Отчасти проблемы росли из-за того, что один и тот же код работал в игре и редакторе. То, что предназначено для редактирования, не работает быстро. То, что работает быстро, не предназначено для редактирования.
Возникло желание сделать вторую версию, оптимизированную. Специальный билдер будет эту версию строить по ночам, будет счастье. В принципе, процесс оказался быстрый, можно и по кнопочке “Save” в редакторе сохранять. Реюзать код не хотелось – было принято волевое решение старый террейн оставить как есть, а новый писать по-новой ( вплоть до шейдеров ), выделяя рефакторингом общие куски по мере необходимости. Апгемахт.
Старый террейн хранится патчами по 256×256 метров, heightmap и карта сплаттинга, на этапе загрузки из нее изготавливались текстурки сплаттинга, 32×32 пикселя ( 32×32 метра ). Решил, что по крайней мере на уровне загрузки гранулярность останется той же самой. Т.е. вся информация сохраняется для куска 256×256 метров.
А вот гранулярность рендеринга решил почикать. Была гранулярность 32×32 метра, стало 8×8 метров. Текстурки сплаттинга в быстром террейне тоже имеют размер 8×8 и запакованы в атлас. В каждом фрагментe 8×8 также хранится протокол binary triangle разбиения. В нем прямоугольные равнобедренные треугольники итеративно делятся точкой на середине гипотенузы, можно взглянуть например на http://www.gamasutra.com/features/20000403/turner_01.htm . Разбиение не доходит до самого “низа”, а останавливается на некотором уровне ошибки, так что плоские куски террейна кодируются минимумом информации. На каждую вершину максимального разбиения надо хранить float и еще несколько служебных байтиков, кодирование получается весьма экономным относительно карты высот.
Это самое бинарное дерево построить – в общем-то несложно. Если бы не два “но”. Первое “но” заключается в том, что надо точно стыковать соседние патчи, а алгоритм не очень локальный. Впрочем, он почти локальный – любые изменения в фрагментике (8×8) задевают только соседей. В результате рабочий алгоритм загружает патч с необходимой “каймой” и работает внутри этого большего множества. Второй момент забавный, связан с тем, что у меня два слоя террейна:
Необходимо согласовывать разбиение на тех фрагментиках из двух слоев, которые пересекаются по высоте. И десогласовывать там, где велика разница высот. Нудно.
Геоморфинг я решил выкинуть. Вместо него - перестройка индексного буфера ( он полностью динамичный, меняется каждый фрейм ). Строится view-dependent по протоколу бинарного разбиения треугольников. Вершинный буфер тоже можно делать полностью динамическим, и я бы обязательно делал его динамическим на своих любимых SPU. Но на PC решил все же не изголяться. Для каждого куска карты ( те самые 256×256 метров ) заранее строится вершинный буфер. В память вроде влезаю, расстояние видимости небольшое ( примерно 600 метров ). Вершинный формат тонкий, что ли 16 байт.
В рантайме я набираю эти самые кусочки 8×8, видимые в кадре, сортирую их по корзинкам-материалам и рисую каждую корзинку за один присест.
Закодал я всю эту красоту ( вместе с шейдерами, загрузчиком и дампилкой ) за неделю. А потом стал приводить в чувство. Отвалилась вода, потому что она намертво слиплась со старым террейном. Пришлось делать новую воду. Она хорошая, тоже оптимизированная, там в специальной текстурке хранится глубина, чтобы рейтрейсом по ней определять прозрачность. Не уверен, что художники видели сей эффект.
Отвалилась трава, потому что она тоже росла только на старом террейне. Случайно кидалась точка, определялась высота, доминирующий тип террейна – рос кустик травки. Тормозило оно и требовало в явном виде информацию о сплаттинге и хейтмап. Этой информации в новом террейне рантайм у меня не было… Я радостно заборол тем, что расставлял кустики травки при билде оптимизованного террейна. И с помощью битовой магии сохранял эту информацию. Травка стала строиться быстрее, подлагивать перестала.
Потом я починил коллизии ( они тоже требовали карту высот! ). Починил весьма забавно – с помощью софтовой растеризации перегоняю свои треугольники в карту высот, но лишь в окрестности пользователя.
Потом я стал статически хранить информацию об окклюдерах, потому что в старой версии она тоже рантайм строилась по карте высот.
Потом я чинил много-много багов, чертыхаясь на то, что полез кодить, старый дурень, как 20 летний пацан.
Вроде, террейн не потерял в качестве. Местами получил overall прирост производительности в полтора раза. Был fps в игре 60, стал 90 ( на 7600 GT ). Ну или был 6, стал 10 ( это на 5200 FX ). Профит.
Недавно пришел менеджер, принес ноутбук от Интела. Выяснилось, что мы должны идти на интегрированной графике. Я взял, запустил игру… И обнаружил, что новый террейн тормозит общий фреймрейт раза в 4. Находясь в состоянии шока, понял ( или вспомнил ), что софтверная обработка вершин ( которая работает в драйверах на железе от Интела ) целиком обрабатывает range, указанный в параметрах DIP. И такое случается на каждый DIP. Динамический же индексный буфер читал из статического вершинного в scattered манере, абсолютно непредсказуемым образом. Я стал материться – в принципе, проблема решалась полностью динамическим буфером вершин… Однако я написал чит, который локализует материалы в вершинном буфере, совершается больше DIP вызовов, но с более предсказуемыми границами. Активизируется чит на софтверной обработке вершин.
Попутно исправил все места в рендере, где границы DIP вызова указывались неточно ( неточно сверху ). Словил перезагрузки, BSOD и просто тривиальные мигания треугольников.
Недавно тестер с каким-то там Radeon 2600 HD заявил, что у него новый террейн мигает. Я хотел было написать works for me. Но потом поглядел в флаги lock индексного буфера. Там был NOOVERWRITE…
Вот такие пирожки с котятками.