НЕСКОЛЬКО СЛОВ ОБ ОПТИМИЗАЦИИ ИГРОВОЙ ГРАФИКИ

(Играем и обсуждаем игры)

Модератор: Buxyr

Аватара пользователя
Buxyr

Коммандо Активист Друг форума Кот Леопольд
Рейнджер
Сообщения: 451
Рег. Вс май 02, 2010 10:07 am
Награды: 4
Репутация: 429
Вклад в проект: 27
Откуда: --
Поблагодарили: 6 раз

НЕСКОЛЬКО СЛОВ ОБ ОПТИМИЗАЦИИ ИГРОВОЙ ГРАФИКИ

Сообщение Buxyr » Пт июн 18, 2010 12:46 pm

ОБОЖАЮ ОПТИМИЗАЦИЮ ПО ТОЙ ПРОСТОЙ ПРИЧИНЕ, ЧТО ВО ВРЕМЯ ПРОФИЛИРОВАНИЯ КОДА ПРИХОДИТСЯ СЕРЬЕЗНО НАПРЯГАТЬ МОЗГИ И ИСКАТЬ ИНТЕРЕСНЫЕ РЕШЕНИЯ ЗНАКОМЫХ ЗАДАЧ. ПРИ ПРОГРАММИРОВАНИИ ИГР МНЕ ДОСТАВЛЯЕТ БОЛЬШЕЕ УДОВОЛЬСТВИЕ НЕ СОЗДАНИЕ ИГРЫ, А СОЗДАНИЕ ЭФФЕКТОВ И ПРОЦЕСС ОПТИМИЗАЦИИ. ИМЕННО ТУТ ДОСТИГАЕТСЯ ИСТИННЫЙ ОРГАЗМ :). МОЖЕТ БЫТЬ, ПОЭТОМУ Я ЕЩЕ НЕ СОЗДАЛ НИ ОДНОЙ ИГРЫ, ХОТЯ С ГРАФИКОЙ ДРУЖУ УЖЕ ДАВНО? ПОГОВОРИМ ОБ ОПТИМИЗАЦИИ ГРАФИЧЕСКИХ ПРОГРАММ, В ТОМ ЧИСЛЕ ИГР

Что будем рассматривать в этой статье? Нас интересует графика, а значит, будем говорить о тех вопросах, которые связаны с графикой и играми. Оптимизировать отображение стандартного окна с настройками игры мы не будем. Опускаться до такой пошлости :), как ассемблер, тоже не будем, потому что если опуститься туда, то можно будет писать целую книгу. Здесь лучше меня расскажет Касперски или другие уважаемые спецы по этому языку. Я знаю ассемблер, но не в такой степени, чтобы заниматься оптимизацией, хотя, даже если просто переписать какую-то функцию C++ на ассемблере, то код может работать быстрее. «Может», но не обязательно, поэтому не будем трогать эту тему.

Компилятор
Не секрет, что львиная доля игр создается на С++, и чаще всего для компиляции используется компилятор от Microsoft. Этот стандарт признанный, и кто оптимизирует программу для работы в Windows лучше, чем сам производитель ОС? Однако нужна ли эта оптимизация в отношении Windows настолько сильно? С полной уверенностью говорю, что не нужна!!! Почему? Об этом читай буквально в следующем абзаце.

Всегда удивляюсь тем паразитам, которые разрабатывают программы (в том числе игры) с минимальными требованиями в Pentium IV, причем компилируют с помощью Visual C++ 6.0 без патчей и обновлений. Что тут удивительного? То, что Visual Studio 6.0 без патчей не знает о существовании Pentium IV, он даже MMX-команды по умолчанию не использует. Почему бы не использовать то, что есть в минимальных требованиях? Одни только команды MMX могут повысить производительность, а если включить и SIMD, то выиграешь в производительности до 20%, не внося ни капли изменений в код.

Так почему же не нужна оптимизация под ОС Windows? Игры используют возможности ОС минимально, то есть больше DirectX и процессора, а кто знает архитектуру процессоров лучше, чем сама Intel? По некоторым тестам, одна смена компилятора может повысить производительность программы на 10-15%, особенно в программах, интенсивно рассчитывающих графику. Действительно, такие показатели видишь не очень часто, а может приключиться и замедление работы, но 10% — вполне реальная цифра, причем абсолютно без каких-либо вмешательств в код. Главное — использовать процессор по максимуму и задействовать все возможности.

Профилятор-турбулятор
Когда игра готова, запускаем ее и ищем самое слабое место. Я всегда искал его на глаз (в принципе, в игровом движке не так уж сложно определить слабое место), но недавно познакомился с программой Intel VTune Performance Analyzer. Эта утилита предназначена для анализа скорости выполнения программы. Настраиваем VTune, запускаем программу и через какое-то время смотрим, какие замеры сделал VTune. Главное, что нас интересует, — какая функция работает дольше всех. Оптимизацией именно такой функции нужно заниматься в дальнейшем.

Только не торопись. Одного времени выполнения мало. Дольше всех может выполняться функция инициализации игрового уровня, где происходит загрузка текстур и т.д., но ее оптимизацией заниматься необязательно. Необходимо учитывать прежде всего те функции, которые участвуют непосредственно в рендеринге.

Кроме того, нужно учитывать количество вызовов. Например, одна функция может выполняться достаточно быстро, но вызываться очень часто. Проанализируй: если вызов происходит из одного или двух мест, то, может, лучше ты сделаешь ее inline, то есть избавишься от нее вовсе и сэкономишь лишний переход, возврат, передачу параметров и т.д. В целом экономия составит примерно три и более команд (в зависимости от количества параметров), но если умножить это значение на количество вызовов функции, то экономия становится существенной. Например, если за секунду произошло 1000 вызовов функции, то избавление от нее даст экономию в 3000 команд в секунду — не так плохо!

Кстати, в играх вообще не должно быть маленьких функций, выполняющих от одного до трех простых действий. Именно они чаще всего вызываются по сотни раз и съедают драгоценные такты.

Секреты от Intel
Корпорация Intel заботится о разработчиках, в том числе о разработчиках игр. На intel.com есть много полезной информации о различных методах оптимизации и эффективных способах по использованию возможностей современных процессоров. Сейчас можно найти множество документов по использованию Hyper-Threading в играх, а двухъядерные процессоры действительно могут повысить производительность на порядок. Если для игры два ядра являются минимумом, то почему не воспользоваться ими для своих нужд?

На сайте есть информация и на русском языке. Да, ее намного меньше, чем на английском, но основные и последние документы переводятся. Советую заглянуть на следующую страничку: http://www.intel.com/cd/ids/developer/e ... /index.htm.

Запомнить этот адрес трудновато, поэтому выжги его в «Избранном».

Аti и nvidia
Компании ATI и NVidia являются законодателями моды на рынке домашних видеоускорителей и так же, как Intel, заботятся о разработчиках. На их сайтах можно найти тонны информации о разработке графических эффектов, игр и использовании максимальных возможностей последних видеочипов.

Чем сильнее ты нагрузишь видеочип, тем быстрее будет работать программа. Почему? Чаще всего видеоускорители выполняют задачи быстрее, чем центральный процессор, потому что они специально заточены для графических вычислений, матриц, векторов и т.д. Некоторые задачи могут выполняться параллельно с вычислениями центральным процессором, что также повысит производительность.

Если ты решил использовать какую-то информацию с сайтов http://www.ati.com или http://www.nvidia.com, то проявляй осторожность. Некоторые функции могут работать на видеочипе одного производителя, но вызвать серьезные проблемы на чипе другого производителя. В этом случае можно потерять большую часть потенциальных пользователей, а если проект коммерческий, то и покупателей. Чтобы решить проблему, попробуй создать две сборки исполняемых файлов: одна для чипа ATI, вторая — для чипа NVidia. Осуществляется не так сложно. Создаешь две функции для разных SDK и подключаешь их в зависимости от сборки, выполняемой в данный момент. Да, усложняется отладка, но прямой вызов функций драйвера видеокарты лучше.

Качество — производительность
Оптимизация графики — это достаточно интересный процесс, потому что здесь, помимо общих принципов оптимизации программ, применяется множество других приемов. Например, чтобы оптимизировать размер картинки, ее сжимают классическим архиватором. Размер файла уменьшится, но коэффициент сжатия зависит от самой картинки. Например, фотографии с большим количеством цветов сжимаются очень плохо.

Человеческий глаз не способен различить два оттенка цвета, если в них отличается на единицу только одна составляющая. Получается, что если объединить близлежащие точки с небольшим отличием в оттенке и заменить их одним цветом, то файл сожмется больше, а пользователь ничего и не заметит. Такое сжатие называется компрессией с потерей качества. Тут нужно добиваться компромисса качества изображения и качества сжатия, что достигается выбором максимальной разницы между оттенками, которые можно объединить. Если будут объединены два цвета, разница которых незаметна человеческому глазу, то качество пострадает несильно. Однако если отличие заметно, то качество теряется.

Где это используется? Конечно же, в файлах изображений, которые способна загружать программа. Чем меньше файл, тем быстрее он загружается в память с жесткого диска, который является самым слабым звеном компьютера. Далее процессор восстановит изображение, и если алгоритм сжатия несложный, то, может быть, на это потребуется меньше времени, чем на загрузку с диска несжатых данных.

Количество используемых цветов влияет и на потребности в ресурсах. Чем больше цветов мы используем, тем больше памяти требуется для хранения изображений, поверхности и тем больше потребности в процессорном времени, чтобы копировать всю эту память между поверхностями.

Предположим, у нас есть картинка размером 100x100 пикселей. Если используется глубина цвета в один байт, для хранения изображения понадобится 10 000 байт памяти. Такая картинка может содержать максимум 256 цветов (что очень мало), и поэтому желательно использовать минимум два байта, когда количество цветов будет равно 65 535. Получается достаточно для хранения более качественного изображения, но понадобится 20 000 байт памяти, что в два раза больше, соответственно, и копирование данных будет требовать от процессора в два раза больше ресурсов.

Если же выбрать глубину цвета в 24 бита, то тут уже понадобится в три раза больше ресурсов, хотя количество цветов будет исчисляться 16 777 216. Вот и думай после такого, что выбрать: качество или скорость. Наша задача — выбрать оптимальный вариант, который позволит получить лучшее соотношение скорости и качества.

Размер изображений также играет немаловажную роль. Если мы выбрали разрешение экрана в 800x600 пикселей при 16-битном цвете, то для хранения поверхности понадобится 800*600*2 = 960 000 байт памяти. Почти 1 Мб! А если изображение будет 1024x768, то объем необходимой поверхности памяти составит 1 572 864 байт. Снова увеличение ресурсов в два раза, следовательно, мы получаем падение производительности во время копирования содержимого поверхностей.

Оптимизация графики на первоначальном этапе — это борьба качества и скорости, выбор между ними. Современным стандартом стали ЖК-мониторы с диагональю 15 дюймов. Чтобы картинка на них выглядела приемлемо, необходимо использовать разрешение 800x600, а лучше 1024x768.

Однако неплохо было бы предусмотреть возможность выбирать необходимое разрешение. Например, у меня широкоформатный ноутбук, и для него идеально разрешение 1280x800, а разрешения 800x600 и 1024x768 выглядят растянуто, что очень неудобно. Вот почему в данном случае можно предложить пользователю самостоятельно выбирать необходимое разрешение в зависимости от имеющихся ресурсов.

В качестве глубины цвета чаще всего используется 16 бит, потому что так мы добиваемся приемлемого качества при минимуме затрат. Меньше уже нельзя, а больше в основном избыточно. Можно предложить пользователю выбирать и глубину цвета, но тогда придется писать слишком универсальный код, который понизит производительность. Чтобы не терять скорость, лучше все-таки привязаться к определенной глубине экрана.

Не дай себе засохнуть
Двумерная графика может формироваться двумя способами: математическим и спрайтовым. В случае с математическим способом поверхность заполняется через прямой доступ по определенному алгоритму. Некоторые из алгоритмов создания эффектов я рассмотрю в четвертой главе. При использовании «математики» скорость формирования сцены, помимо глубины цвета и разрешения, очень сильно зависит от используемого алгоритма.

При спрайтовом формировании сцены дополнительным фактором торможения является количество спрайтов, поскольку для вывода каждого из них необходимо выполнить операцию Blt или BltFast (я имею в виду функции DirectDraw). Чем больше обращений, тем больше потеря, потому что тут проявляется эффект цикла: приходится много раз вызывать одну и ту же функцию, во время вызова несколько раз параметры поднимаются в стеке (а у Blt их немало) и происходит вызов удаленной функции, а также куча лишних проверок со всеми вытекающими последствиями. Иногда функцию Blt даже лучше заменить копированием данных через прямой доступ к памяти и WinAPI-функцию memcpy.

Потери данных при спрайтовом выводе могут быть и неоправданными. Допустим, у нас в распоряжении находится экран размером в 800x600 пикселей, как показано на рис. 3.1. Все, что закрашено черным цветом, — это оборка, которая статична и не изменяется, а в белой области данные формируются динамически, причем полностью. Эта задача решается вот так: перед формированием кадра вывести на экран изображение 800x600 с оборкой, затем сформировать среднюю часть. Стоп. Зачем делать это, если оборка статична? Не лучше ли один раз вывести оборку, а потом перекрашивать только центральную часть, которая изменяется? Конечно же, лучше. Таким образом мы сэкономим целую операцию Blt, которой приходится копировать большой объем информации.

Если экран программы каждый раз формируется полностью, то и не стоит каждый раз перед формированием сцены очищать его. Все равно каждый пиксель будет закрашен, и от нашей заливки ничего не останется. Другое дело, когда рисуется движение звезд на черном небе. Тут действительно удобнее закрасить экран черным, а потом поверх неба нарисовать звезды. Но если на экране присутствует поверхность земли (например, из 600 пикселей в высоту: нижние 200 — это земля, а верхние 400 — черное небо), то нужно закрашивать только ту часть, где есть небо. Нет смысла очищать весь экран!

Алгоритм
Самое главное, что определяет скорость работы игры, — это алгоритм. Хороший алгоритм не требует дополнительной оптимизации. Не могу предложить однозначное решение, но некоторые рекомендации по оптимизации есть.

Игровые сцены чаще всего характеризуются значительным множеством объектов. Давай вспомним знаменитую игру Command&Conquer (или, проще, C&C). На поле боя одновременно может находиться тысяча объектов. Одних только солдатиков можно наделать пару тысяч, плюс здания, деревья, мосты и т.д. Теперь представим, что произойдет, если цикл отображения сцены будет выглядеть следующим образом:

for (int i=0; i<OBJECT_COUNT; i++)
{
if (объект видим)
Отобразить объект
}

Если значение OBJECT_COUNT равно 2 000, то для отображения сцены придется выполнить 2 000 шагов цикла и на каждом из них произвести проверку видимости, которая может состоять из четырех операций if. Сумасшедшие расходы! Не по силам даже современному компьютеру, если не оптимизировать код. Что же можно сделать здесь? Как сократить количество циклов?

Карта игры C&C большая, и в определенный момент времени видно не более 10% ее. Можно разбить карту на 10-20 квадратов и привязывать объекты именно к определенному квадрату на карте. В результате цикл отображения будет таким:

for (int i=0; i<QUAD_COUNT; i++)
{
if (квадрат видим)
{
Запустить цикл проверки видимости и
отображения объектов данного квадрата на карте.
}
}

Теперь будут проверяться не все объекты, а только те, которые находятся на квадрате, видимом в данный момент. Единственное, что нужно сделать дополнительно, — после перемещения объекта проверить, вышел ли он за пределы своего квадрата, и если вышел, то присоединить объект к новому квадрату, на плоскость которого попал объект. Такие объекты, как деревья и здания, не перемещаются, поэтому для них проводить проверку не нужно.

Сортировка
Следующий способ ускорения отображения — сортировка. Желательно отсортировать все объекты по определенной оси и отображать их в этом же порядке. Замечу, что если очередной объект вышел из зоны видимости по отсортированной оси, то остальные с еще большей вероятностью вышли и дальнейшая проверка объектов бессмысленна — цикл можно прерывать. Соответственно, мы снова экономим цикл, а именно циклы являются самым слабым местом, особенно в играх, где очень много объектов.

В составе DirectX SDK есть очень хороший пример рисования деревьев на поверхности ландшафта (DXSDK\Samples\C++\Direct3D\Billboard). Деревьев очень много, и чтобы не перебирать их все, во время отображения используется сортировка. Попробуй убрать сортировку и производить проверку всех деревьев при каждом формировании сцены — производительность примера заметно упадет.

Оптимизация 3d
При создании фигуры с помощью Direct3D используй минимально необходимые размеры. Например, при создании буфера индексов IDirect3DIndexBuffer9 каждый элемент массива может быть 16- или 32-битным. Если количество индексов не превышает 65 535, то следует использовать 16-битный массив. В данном случае переход на 32 бита будет неоправданным и принесет лишние расходы памяти, которой никогда не бывает много.

В Direct3D мы формируем сцену с помощью вершин и треугольников. Чем их больше, тем больше времени движок и видеокарта тратят на формирование сцены. И вновь мы выбираем между количеством и качеством! Посмотри на рисунок, где показана сфера, созданная из 32-х сегментов. Сфера получается гладкой, но количество необходимых треугольников слишком велико.

Можно сократить количество сегментов до 16-ти, то есть уменьшить его в два раза. В результате сокращается количество ресурсов, необходимых для отработки фигуры, но сфера получается угловатой. Я думаю, такое положение дел устроит далеко не многих.

Если же нам нужны сглаженные поверхности, то приходится выбирать между гладкостью и скоростью. В большинстве игр разработчики стараются использовать прямоугольные поверхности — для создания четырехугольной плоскости достаточно всего лишь двух треугольников.

Получается, что скорость создания сцены зависит не только от количества объектов на экране (объектов интерьера, освещений, существования теней). На сцену влияют и количество вершин, из которых состоит объект. Здесь необходимо подходить к решению задачи с деликатностью и осторожностью.

С другой стороны, если создать сферу из ста сегментов, то мы получим явную избыточность, особенно если эта сфера в сцене будет находиться очень далеко от зрителя и выглядеть мелкой. Пользователь просто не сможет увидеть мелкие детали, которые ты захочешь передать. Если у тебя есть 3D-редактор, позволяющий контролировать количество сегментов (например 3D Studio Max), то попробуй сейчас создать сферу размером во весь экран. Для нормального отображения и получения гладкой поверхности необходимо не менее 30-ти сегментов. Если же отдалить сферу как можно дальше (чтобы в диаметре она казалась не больше десятикопеечной монеты), то будет достаточно и 12-ти сегментов.

Получается, что при формировании сцены мы можем использовать разное количество сегментов для объектов с разной удаленностью и значительно сэкономить жизнь процессору и видеокарте, а следовательно, увеличить скорость создания сцены.

Посмотри на рисунок, где показаны сферы различного размера. Самая большая сфера создана из 32-х сегментов, а остальные — из 12-ти. Обрати внимание на то, что самая маленькая сфера выглядит вполне гладкой, хотя на сферах побольше хорошо видны угловатости. Использовать множество сегментов на маленьких объектах — лишняя растрата ресурсов.

То же касается и текстур. Чем ближе объект, тем лучше видна каждая деталь на текстуре. На самой большой сфере можно увидеть, что текстура есть, но на самой дальней сфере обычно не разглядишь ничего. Она настолько мала, что кажется окрашенной в один цвет. Тогда зачем использовать текстуру? Не лучше ли просто окрасить сферу в нужный цвет? Пользователь ничего не заметит, особенно если сфера будет находиться в движении.

Чтобы повысить скорость работы с текстурами, предлагаю действовать следующим образом:

СОВСЕМ НЕ ИСПОЛЬЗОВАТЬ ТЕКСТУРЫ ДЛЯ САМЫХ ДАЛЬНИХ (ОТ ЗРИТЕЛЯ) ОБЪЕКТОВ, ТОЛЬКО ОКРАШИВАТЬ ИХ ЦВЕТОМ, МАКСИМАЛЬНО СООТВЕТСТВУЮЩИМ ЦВЕТУ ТЕКСТУРЫ.
ПО МЕРЕ ПРИБЛИЖЕНИЯ ОБЪЕКТА НАТЯНУТЬ НА НЕГО ТЕКСТУРУ НЕБОЛЬШОГО РАЗМЕРА, НАПРИМЕР 16X16. ВОТ ТАК МЫ ЗАРАНЕЕ МАСШТАБИРУЕМ БИТОВЫЙ ФАЙЛ, ОБЛЕГЧАЯ ЖИЗНЬ ВИДЕОКАРТЕ.
ДЛЯ БОЛЬШИХ ОБЪЕКТОВ, НАХОДЯЩИХСЯ ПОБЛИЗОСТИ, ИСПОЛЬЗОВАТЬ БОЛЕЕ КАЧЕСТВЕННЫЕ ТЕКСТУРЫ.
Если масштабировать текстуры заранее, то качество изображения будет лучше, чем если бы оно было сделано DirectX, так как профессиональный художник сможет сделать необходимые сглаживания, чтобы вид уменьшенного изображения был приемлемым. Конечно, невозможно подготовить все возможные варианты текстур, но хотя бы несколько вариантов должны быть сделаны. Меньшие по размеру текстуры, которые будут использоваться для дальних объектов, обработаются быстрее благодаря меньшему размеру и меньшему коэффициенту программного масштабирования. Текстуры, масштабированные заранее, — это один из немногих трюков, благодаря которому мы повышаем и скорость, и качество сцены.

Итого
Я мог бы еще долго рассказывать об оптимизации в играх, но эта тема слишком широкая и требует отдельной книги, над чем я сейчас и работаю. Прошу ко мне на сайт http://www.vr-online.ru — уже к моменту выхода этого номера я постараюсь выложить что-нибудь новое по оптимизации и графике.

Удачи! И не бойся экспериментировать и обманывать пользователя (они это любят)

ПРИ ИСПОЛЬЗОВАНИИ DIRECTDRAW НИКОГДА НЕ ПРИМЕНЯЙ ФУНКЦИИ GDI: ОНИ ТОРМОЗЯТ ПРОГРАММУ. ПОЛЬЗУЙСЯ ТОЛЬКО ПРЯМЫМ ДОСТУПОМ К ПОВЕРХНОСТИ И КОПИРОВАНИЕМ ПОВЕРХНОСТЕЙ

ДЛЯ ЗАЛИВКИ DIRECTDRAW ПОВЕРХНОСТИ МОЖНО ИСПОЛЬЗОВАТЬ ФУНКЦИЮ BLT, НО НАМНОГО БЫСТРЕЕ БУДЕТ ЧЕРЕЗ ПРЯМОЙ ДОСТУП ЗАЛИТЬ ПОВЕРХНОСТЬ ОПРЕДЕЛЕННЫМ ЦВЕТОМ ФУНКЦИЕЙ FILLMEMORY

ИНТЕРФЕЙС IDIRECTDRAWCLIPPER ДОСТАТОЧНО ПРОЖОРЛИВ. НЕМНОГО СТАРАНИЙ — И ТЫ СМОЖЕШЬ НАПИСАТЬ ПРОВЕРКУ ВЫХОДА ЗА ПРЕДЕЛЫ ПОВЕРХНОСТИ САМОСТОЯТЕЛЬНО, СООТВЕТСТВЕННО, МАКСИМАЛЬНО ОПТИМИЗИРУЕШЬ ЭТОТ КОД

ОПТИМАЛЬНАЯ ГЛУБИНА ЦВЕТА ДЛЯ СОВРЕМЕННОЙ ИГРЫ — 16 БИТ. ЕСЛИ СДЕЛАТЬ 24, ТО РАСХОДЫ ОПЕРАТИВНОЙ И ВИДЕОПАМЯТИ НЕОПРАВДАННО ВЫРАСТУТ, А РАЗНИЦУ В КАЧЕСТВЕ В ДИНАМИЧНЫХ СЦЕНАХ НЕ ЗАМЕТИТ ДАЖЕ ЗОРКИЙ ГЛАЗ ОРЛА


По материалам: xakep.ru


Искусство это жизнь

Вернуться в «Игромир»

 

 

cron