В данной статье не будут рассматриваться приемы и инструменты моделирования и анимации для производства игр. Этой информации и так достаточно в интернете. Я же хочу поделиться опытом экономии вычислительных ресурсов и памяти видеосистемы компьютера для обеспечения успешной работы весьма сложных и тяжелых игровых уровней, сделанных в BGE, на не весьма средних компьютерах.
Как-то раз, лет пять назад, руководство моей конторы поставило передо мной задачу – создать за 2 месяца интерактивный 3D план большого предприятия. Предприятие – это порядка десятка квадратных километров местности и около полутора сотен различных зданий, сооружений и конструкций. Причем, все объекты необходимо было выполнить внешне максимально приближенными к оригиналам, а текстура земли должна была иметь достаточное разрешение для высокой детализации с точки зрения пешехода среднего роста. Не буду распространяться, зачем это было нужно, скажу лишь, что требовалась возможность осматривать данную территорию, как с позиции пешехода, так и в режиме полета. Отсюда следовало дополнительное требование – отсутствие ограничения видимости, столь популярное у создателей игр и позволяющее снижать нагрузку на систему. Задача была не тривиальной. Усугублялась она тем, что времени на освоение сторонних игровых движков не было, закупать их про-версии для проекта никто не собирался, да и программирую я на дилетантском уровне, так что написание скриптов для меня было невыносимой пыткой. Посему, единственным приемлемым вариантом в то время мне казалась максимальная оптимизация игрового уровня на основе BGE, которым я более – менее уже владел. Скажу сразу – результат, в конце концов, был получен. Мне удалось выжать в итоге из BGE достаточную производительность для успешной работы проекта даже на довольно слабых не игровых машинах.
Все что будет описано далее, это вымученная многочисленными экспериментами, тупиковыми решениями и обломами информация. Не буду описывать все мои неудачные решения и брошенные в мусор варианты. Только общий путь поиска оптимального решения, которое обеспечило в итоге хорошую производительность. Итак, начнем.
Как известно, любое изображение на экране монитора создается при помощи видеосистемы компьютера. Это может быть интегрированное в процессор или чипсет видео, может быть дискретная видеокарта. Суть от этого не меняется. В любом случае видеосистема – это тандем многоядерного видеопроцессора и видеопамяти. Если первый отвечает за скорость отрисовки треугольников, текстурных блоков, распаковку текстур и их перегрузку в видеопамять, то вторая – за их промежуточное хранение и вывод на экран. Чем больше объем видеопамяти у вашей видеосистемы, тем больше информации она может вместить. И этот фактор на больших по объему игровых уровнях становиться определяющим. Проще говоря, если ваша очередная текстура не уместиться в отведенное пространство видеопамяти, то вы и не увидите ее на экране. Это будет либо дыра в общей картине, либо залитый черным цветом объект. Именно с этим эффектом я столкнулся в первых вариантах своего игрового уровня. Если оптимизация геометрии моделей – дело достаточно наглядное и легко контролируемое, то с текстурами все сложнее. Достаточно сложно узнать – какой объем в видеопамяти реально занимает та или иная текстура. Но в большинстве случаев этот объем будет в разы больше того, что вы видите в колонке размер файлового менеджера. И вот почему.
Все используемые в настоящее время популярные форматы хранения изображений – это сжатые форматы. Они отличаются используемым алгоритмом сжатия и уровнем потерь, но, в любом случае, прежде чем вывести изображение на экран, процессор производит распаковку, на что тратит определенное время и мощность. Для примера, откройте любой снимок в формате JPG в Photoshop и посмотрите на объем памяти, занимаемый файлом после распаковки слева в нижней строке. Вы будете неприятно удивлены. Единственным из популярных форматов, который не использовал никакого сжатия, был давно забытый BMP. Но кто сейчас им пользуется?
Таким образом, для нагруженной сцены в BGE ни jpg, ни png не годились – они не позволяли оптимизировать расход видеопамяти и производительность видеосистемы. Попав в тупик, я решил обратиться к рекомендациям от создателей BGE. Может они подскажут пути оптимизации? Сразу скажу – штудирование имеющихся талмудов никакого прогресса не дало. В литературе настоятельно рекомендовалось использовать текстуры в устаревших ныне форматах tiff или targa. Смысла в этом особого не было, так как эти текстуры точно так же распаковывались в видеопамять. «Воз» был «и ныне там». Срок сдачи проекта близился, а результата не было.
Как обычно, прозрение пришло неожиданно. В конце 90-ых – начале 2000-х годов я был совладельцем игрового салона и магазинчика по продаже пиратских компьютерных дисков. Так вот, делая как-то уборку в кладовке, я наткнулся на завалы старых не реализованных игровых дисков, и, чисто из любопытства, решил попробовать что-нибудь запустить. Конечно, чуда не произошло, диски уже были в основном не читаемые, но файловые системы еще можно было просмотреть. Вот так я и наткнулся на настоящий клад – формат изображения DDS (DirectDraw Surface), знакомый, в основном, лишь игроделам. Данный формат был разработан Microsoft в 1996 году специально для использования в DirectX. Однако, как выяснилось, его поддержка реализована аппаратно и в OpenGL (используется инструкция GL_ARB_texture_compression). Но самое главное не это. Самое главное, что изображения в этом формате не требуют распаковки процессором при загрузке в видеопамять (распаковываются аппаратно на лету при выводе на экран), следовательно, они будут занимать в ней ровно столько же места, сколько на вашем винчестере и не будут излишне нагружать процессор и видеочип. Но и это еще не все.
Данный формат может включать в себя не одно изображение в максимальном разрешении, а несколько изображений, являющихся уменьшенными копиями оригинала с различным разрешением. Для чего это нужно? Представьте, что вы стоите на высоком холме и смотрите на открывающийся перед вами пейзаж. Чем дальше, тем люди, дома и деревья становятся все меньше и меньше. Вы начинаете все хуже различать детали объектов, пока все совсем далекое не сольется в общий размытый фон… Вот так и работает технология, получившая название Mipmap. Суть ее в том, что текстуры на объектах, находящихся от вас на определенном удалении, автоматически замещаются на более простые и легкие. Чем дальше, тем текстуры становятся все проще и проще. Эффект от этого действа заключается в резком сокращении используемой видеопамяти и повышении производительности без ограничения видимости в 3D сцене. Таким образом, формат dds имеет два инструмента повышения производительности и снижения нагрузки на видеосистему, каждый из которых сам по себе дает не плохой результат, а в сумме, это просто находка! Последним аргументом в пользу dds стало то, что данный формат, оказывается, полноценно поддерживается в BGE.
Вот только не один из имеющихся у меня растровых редакторов не поддерживал этот формат по умолчанию. Необходимо было срочно найти способ преобразования имеющихся уже текстур в формат dds. Простой поиск в Google подсказал два основных решения – коммерческий и свободный. Первый, это плагин к Photoshop от NVidia, а второй – плагин к Gimp, который проще первого варианта, имеет совсем мало настроек, однако, его возможностей оказалось достаточно для предстоящей работы. Особенно, если, как у меня, не было времени серьезно разбираться с многочисленными нюансами настроек формата dds. Найти 32 и 64-битные версии этого плагина для Gimp вы можете по адресу – https://code.google.com/p/gimp-dds/downloads/list
Плагин распаковывается и просто переписывается в папку plug-ins редактора Gimp. После чего Gimp необходимо перезапустить. Отныне в меню Экспортировать как… появляется вариант Image dds:
Плагин распаковывается и просто переписывается в папку plug-ins редактора Gimp. После чего Gimp необходимо перезапустить. Отныне в меню Экспортировать как… появляется вариант Image dds:
Но прежде чем сохранять текстуру в новый формат, ее нужно подготовить – привести к разрешенным для формата dds параметрам. А параметры эти весьма жесткие:
- Текстура должна иметь строго квадратный формат.
- Размеры текстуры должны быть кратны двоичному коду.
Что это значит? Во-первых, размер текстуры по горизонтали должен быть равен размеру по вертикали. Во-вторых, размер текстуры в пикселях должен соответствовать двоичной шкале, то есть отвечать одному из следующих размеров: 8х8, 16х16, 32х32, 64х64, 128х128, 256х256, 512х512, 1024х1024, 2048х2048 или 4096х4096. Больший размер текстуры может вызвать проблемы в виде пропадания текстур, а потому не допустим. Таким образом, уже в процессе создания UV-развертки объектов (а я надеюсь, вы в курсе, что UV-развертка обязательна для всех игровых объектов, использующих текстуры) необходимо использовать один из выше перечисленных размеров. Создание самой развертки я упущу, так как этот вопрос был уже множество раз разжеван в различных статьях, в том числе и на этом ресурсе. Перейду сразу к оптимизации. Естественно, чем меньше используемая текстура, тем лучше для производительности системы. По этому, все развертки необходимо максимально оптимизировать с целью наилучшего использования площади квадрата текстуры. Частенько такая оптимизация позволяла без особого ущерба для детализации уменьшить текстуру на одну ступень (к примеру с 2048х2048 до 1024х1024). Основные приемы оптимизации – перепланировка внутри квадрата деталей развертки, масштабирование в сторону уменьшения низко детализированных фрагментов развертки и, соответственно, в сторону увеличения высоко детализированных:
Развертка до оптимизации
И после
Лишь после тщательной оптимизации можно приступать непосредственно к прорисовке текстур поверх UV-развертки. Для этого сохраняем UV-развертку в виде растрового файла:
Имя лучше сразу давать одно всем файлам по данной модели с использованием дополнений к ним, в зависимости от назначения файла. К примеру, файл UV-развертки я назвал 825_uv.png, где 825 название текстуры (использован номер здания), а uv – указатель на назначение. Другие промежуточные рабочие текстуры носили имена, соответственно – 825_diff, 825_spec, 825_bump…Педантичность в отношении имен текстур впоследствии существенно облегчила мне жизнь, особенно когда их скопиться великое множество. Для изготовления текстур я открывал полученный файл развертки в Gimp, создавал новые слои поверх и занимался малярными работами. Основой изображений были фотографии фасадов зданий и сооружений. Правда, использовать их напрямую не представлялось возможным из-за неудачных ракурсов и линзовых искажений фототехники.
Прежде всего рисовалась текстура диффузной карты. Затем карта экспортировалась в формат png (промежуточный формат) и прямо поверх диффузной карты рисовались сначала карта bump (сейчас бы я уже использовал более современную технологию normal – но не забывайте – это было 5 лет назад), затем карта бликов. Впрочем, впоследствии я отказался от использования карт бликов для большинства объектов, так как они практически не влияли на конечный результат. Все карты так же экспортировались в формат png. Зачем нужен этот промежуточный экспорт? Почему сразу не сохранять, к примеру, в формат dds? Дело в том, что плагин dds в Gimp имеет один глюк– он не смешивает слои при экспорте. То есть, если вы вдруг попытаетесь экспортировать ваше изображение, состоящее из нескольких слоев, то плагин экспортирует лишь один слой – тот, что был выделен на момент экспорта. По этому, для экспорта слои предварительно приходиться сводить. Для создания же остальных текстур удобнее иметь слои не сведенными. Кроме того формат dds – это именно конечный формат. Его нельзя использовать для промежуточных работ, так как он может накапливать искажения и артефакты, подобно формату jpg с высокой степенью сжатия. Промежуточные текстуры предназначены лишь для запекания основной текстуры, которая будет использована на модели. Вот ее и необходимо упаковывать в формат dds:
Диффузная текстура и текстура рельефа
Итак, промежуточные текстуры были нарисованы. На это ушло времени больше, чем на моделирование самих объектов. Что поделать – такой уж я художник от слова худо… Теперь необходимо дать пояснения к следующему шагу оптимизации. Как известно, основной объем расчётов при рендере в BGE уходит на трассировку лучей, просчет теней и бликов. Основная идея оптимизации состоит в том, что бы сделать эти расчеты заранее и подставить в сцену уже готовый результат. Как это сделать? При помощи запекания текстуры. При этом создается одна единственная текстура для данной модели, которая заменит не только все промежуточные текстуры, но и просчет освещения и теней. В идеале это позволило вообще убрать источники света из сцены, что дополнительно значительно увеличило производительность системы.
Я запустил Blender и загрузил сцену с моделью. Для начала выставил глобальное освещение, которое используется для просчета диффузного отражения, теней и бликов. Для высвечиваниятекстур в области теней я использовал три основных источника света – один Sun и два Hemi. Sun – это основной источник, создающий тени. Так как в качестве окружения использовалась текстура неба, то этот источник света я установил туда, где на небосводе было расположено солнце. Настроил источник по наклону и силе света, а так же сделал тени мягче настройкой параметров в закладке Sampling. Hemi я использовал для имитации подсветки от неба и отражения света от земли. Один сделал слегка голубым с мощностью не более 0,6 и расположил над сценой. Второй сделал серым с той же мощностью, и установил под плоскостью «земли», направив вверх. Этого вполне достаточно для имитации солнечного летнего дня.
Что бы облегчить работу компьютеру при обсчете насыщенной многочисленными моделями сцены, объекты сцены, не оказывающие влияния на расчетный, я временно перенес на другой слой. Этот слой я отключил, выводя его из рендера. Затем создавал новый материал для текущей модели с тем же именем, что и текстуры, загрузил промежуточные текстуры в текстурный слот материала и настроил их влияние в закладке Influence:
Можно было приступать к финальному просчету текстуры модели методом запекания. Все что было нужно, это войти в режим редактирования модели, создать новую текстуру в окне развертки с размерами промежуточных текстур и нажать кнопку Bake в закладке рендера:
Если все было сделано правильно, то в результате расчета я получал в окне UV-развертки новую текстуру, которую сохранял так же в формате png с именем промежуточных текстур без дополнений:
Теперь было необходимо преобразовать полученную текстуру в конечный формат dds. Данная операция по большому счету механическая и не требует никаких глубоких знаний предмета. Замечу лишь один нюанс. Если вы будете использовать конечную текстуру с альфа каналом, то советую перед экспортом создать новый слой, разместить его под текущим и залить нейтральным цветом (обычно серым). Затем вырезать на нем только те места, которые внутри развертки должны быть прозрачны (окна, водная поверхность). После этого объедините слои. Зачем это нужно? Дело в том, что при создании уменьшенных текстур в инструменте Mipmap крайние пиксели смешиваются с прозрачной частью текстуры вне развертки. В результате, на стыках текстур в сцене появляются неприятные черные линии, заметные при удалении от объекта. Заливая пространство между фрагментами развертки цветом, вы ликвидируете полупрозрачность граничных пикселей, что позволяет избавиться от черных линий при уменьшении текстур. Дам краткую характеристику настроек окна экспорта в dds:
Закладка Compression позволяет сделать выбор из 10 различных пресетов настроек компрессии. Из всех вариантов наиболее часто используемыми для текстурирования моделей являются первые три – DXT1, DXT3, DXT5. Если не вдаваться в технические детали механизма сжатия, то отличаются эти форматы в основном качеством альфа канала, стпенью сжатия и, как следствие, размером итогового файла. Чем больше номер, тем больше файл. Совет простой – если прозрачности на текстуре нет, или она не имеет градиента, то достаточно формата DXT1. Если же текстура имеет градиентную прозрачность, то лучше использовать DXT5. То же относиться и к наличию артефактов при использовании Mipmap. Чем больше номер, тем лучше качество. Лично я для большинства текстур использовал DXT1, для текстур с прозрачностью и текстуры земли – DXT5.
Закладки format и save пропускаем, они не активны. А вот закладка Mipmaps представляет для нас интерес. Открываем меню и выбираем Generate mipmaps. Тем самым мы заставляем плагин сгенерировать полный набор уменьшенных копий нашей текстуры для дальнейшей автоматической подгрузки в видеопамять при удалении от объекта текстурирования.
Если вам нравиться копаться в тонких настройках, можете открыть закладку Advanced Options и поэкспериментировать с выбором механизмов растрирования и размножения текстуры при использовании Mipmap. Если нет, то потеря не велика, так как влияние этих настроек минимально. Остальные настройки в данной вкладке (коррекция гаммы и вырезание из альфа канала пикселей с прозрачностью ниже заданного уровня) мне показались и вовсе бесполезными. Но может у меня просто не возникало ситуаций, где они могли бы пригодиться.
Ну, вот я и добрался до окончательного текстурирования модели. Все, что нужно было сделать, это удалить из текстурного слота в Blender все промежуточные текстуры и создать новую, с нашим изображением в формате dds:
Пришло время совершить то чудо, ради которого была затеяна вся бодяга с запеканием. Заходим в закладку материалов и устанавливаем галочку Shadeless:
Отныне данный объект не будет обрабатываться при просчете трассировки источников света. Это можно легко проверить, временно удалив из сцены все источники света. Вы увидите, что объект по прежнему выглядит так, как будто источники света остались на месте.
С одним объектом я справился, осталось еще 149… Работа по ним была абсолютно аналогична, посему описывать здесь ее я не буду. Лучше расскажу о хождении по мукам при создании текстуры земли.
Собственно, проблема была одна. Я уже писал, что площадь предприятия – это около десятка квадратных километров. Причем текстура при этом должна была быть с достаточно высоким уровнем детализации. Таким, чтобы не слишком безобразно смотреться из глаз пешехода. Самые скромные подсчеты и эксперименты показали, что размер текстуры должен быть никак не меньше 8192х8192. В этом случае один пиксел будет соответствовать примерно 40см реального масштаба. Однако текстура такого размера, даже в формате dds с использованием mipmap, наотрез отказывалась укладываться в видеопамять большинства компьютеров, имеющихся на предприятии. Вместо красочной поверхности получалось сплошное черное поле. Так как я моделировал не просторы чернозема, то срочно нужно было искать выход из ситуации. Сроки были уже совсем впритык. Единственным приемлемым решением была нарезка текстуры на отдельные квадраты допустимого размера. Минимальное количество одинаковых квадратных сегментов, на которые можно разрезать квадратную же текстуру, как известно, равно четырем. Дальнейшие эксперименты показали, что в моем случае этого оказалось достаточно. Хотя никто не мешал разрезать и на большее количество сегментов – 9, или 16. Разрезать сам полигон и присвоить каждому сегменту свою текстуру труда не составило. Как говориться, Ctr + R и кнопка Assign в закладке материалов вам в помощь. Сложнее было порезать нарисованную текстуру земли на одинаковые фрагменты так, что бы стык был не заметен. Для данной работы был так же использован Gimp.
Я загрузил полную текстуру поверхности в Gimp и при помощи инструмента Направляющие(Изображение – Направляющие – Создать направляющую по %…) и разделил ее на 4 равных фрагмента:
Затем инструментом Гильотина (Изображение – Преобразования – Гильотина) я разрезал исходную текстуру на 4 одинаковых квадрата и сохранил их в отдельные файлы. Причем имена им Gimp присваивает автоматически, как производные от имени исходного файла с расширением, обозначающим номер квадранта на разрезе.
Следующим действием было присвоение полученных фрагментов соответствующим квадрантам на плоскости поверхности. Для этого я создал в закладке материалов Blender четыре новых материала с теми же именами, что и нарезанные текстуры, и связал каждый из материалов с соответствующим квадрантом развертки при помощи кнопки Assign:
В текстурный слот каждого из материалов загрузил соответствующую текстуру. Для материала поверхности я не стал создавать карты рельефа, так как такая цель не ставилась. Если вам это необходимо, просто нарисуйте эту карту на основе диффузной карты и геодезической карты высот, и точно так же порежьте на квадранты. После чего разместите полученные нарезки в свободных текстурных слотах соответствующих фрагментов и настройте, как было показано выше для модели.
Пришло время запечь окончательную текстуру поверхности с картой теней. Для этого выделялся по очереди каждый квадрант, и производилось запекание по той же схеме, что и для текстур моделей. Повторно описывать эту процедуру я не буду, кто забыл, почитайте выше. Должен только предупредить о том, что Blender весьма болезненно относиться к таким вещам, как лишние материалы в стеке материалов объекта, а так же совместное использование материала для различных объектов. Почистите слот материалов от балласта и проконтролируйте, что бы каждому материалу строго соответствовал только один объект. В противном случае вы рискуете получить в процессе запекания ошибку. Кроме того необходимо контролировать, что бы UV-развертки не перекрывали друг друга. Если для обычного рендера это не важно, так как расчет проводиться для модели, а не текстуры, то при запекании вы получите наложение одной текстуры на другую.
Мне же осталось сохранить запеченные текстуры с соответствующими именами, преобразовать их в dds и заменить ими текстуры в слотах материалов квадрантов. Здесь еще один маленький секрет. Если не хотите, что бы на текстуре поверхности были четко видны стыки текстур, на развертке каждого фрагмента, чуть–чуть, буквально на пару пикселов, уменьшите развертку относительно текстуры, с каждой стороны. Визуально в сцене это совершенно не будет заметно, но линии пропадут.
Ну и последнее действие. Устанавливаю для материала каждого квадранта поверхности галочку Shadeless и удаляю из сцены все источники света – они более не нужны.
Для окружения я использовал полусферу с натянутой текстурой летнего неба, для материала которой так же был отключен режим трассировки теней (Shadless). Дальность видимости камеры была увеличена (Clipping) до 3 км. Оставалось лишь настроить анимацию, управление камерой, и сохранить проект в виде запускаемого exe – файла, что бы сделать его переносимым на любой компьютер, не имеющий предустановленного Blender. В результате, вся работа над проектом заняла у меня все отведенные для разработки два месяца. За это время были выполнены моделирование полторы сотни объектов, их текстурирование, анимация камеры и различных фишек, создание управления, окончательная оптимизация с сведением в запускаемый файл. И все это в одиночку. Согласитесь, если бы я решился использовать любой другой игровой движок, требующий написание скриптов, то вряд ли бы смог уложиться в столь ограниченный срок. И главное, что сцена работает даже на офисных машинах. Исключение составляют лишь машины, имеющие интегрированное видео от Intel. Но это не проблема движка, это проблема драйверов от Intel, до сих пор не поддерживающих полноценно OpenGL.
Теперь, спустя пять лет, я могу сказать точно, что не так плох BGE как его малюют. Тем, кто его до сих пор считает бесполезным и тяжелым, я отвечу заезженной фразой – «Просто вы не умеете его готовить»… Да, он не поддерживает многих современных технологий. Но существует огромная область применения, где этого и не требуется. От инди–игр, до всевозможных презентаций, интерактивных обучающих тренажеров, рекламы и много еще чего. Главное, что его плохая производительность по большему счету – миф.
Если же BGE для вас – каменный век, то советую обратить внимание на Blend4Web, развивающуюся уже три года технологию, использующую библиотеки HTML5 для запуска 3D-продуктов непосредственно в окне браузера. Правда, без программирования тут уже не обойтись…
Автор урока: Mark Panov
Комментариев нет:
Отправить комментарий