СОВЕТЫ РАЗРАБОТЧИКАМ Советы по оптимизации приложения под Intel GMA X3000X3100 Последнии видеоадаптеры Интел ограничены по скорости отрисовки пикселей. Поэтому необходимо обязательно предоставить пользователю возможность изменить настройки игры, например разрешение, поддержку и качество теней. Еще одним следствием является преимущество однопроходных сложных шейдеров перед многопроходными. То есть по возможности рисовать несколько источников света в одном проходе, выделять наиболее важные источники света и отключать не влияющие на объект. Использовать раннее отсечение по Z (Early Z). Для многопроходных алгоритмов желательно вначале отрисовать все видимые объекты в Z-буфер, при этом отключив отрисовку во фрейм буфер, после этого изменить Z-тест на equal, и рисовать освещенность только для действительно видимых пикселей. Это особенно необходимо, так как в GMA X3000 есть функция Early Z, которая может отбраковать не прошедший Z-теста пиксель перед отправкой его в шейдер или в Render Target. Также для большей производительности можно отсортировать объекты, и отрисовывать их «спереди-назад» GMA X3000 поддерживать Occlusion Query, то есть можно запросить видеоадаптер на видимость объекта. Необходимо отрисовать коробку объекта, при этом отключив заполнение буфера отрисовки и Z-буфера, и обработать результаты запроса. Сама сетка объекта должна быть достоточно сложной, чтобы оправдать дополнительную отрисовку для теста. Для ранней отбраковки неосвещенных пикселей можно использовать отрисовку в стенсиль буфер модели источника света. Например для omni света это будет сфера, для spot источника – сектор сферы. Пометить освещаемые пиксели можно при помощи Z-fail алгоритма Кармака для отрисовки теней. Особенно пригодится двустороний стенсиль, поддерживаемый сейчас почти всеми видеокартами. Так как встроенные видеоадаптеры Интел используют основную память, необходимо уменьшить нагрузку на памяти. Для этого нужно по возможности использовать сжатые текстуры, при создании объектов в памяти выставлять флажок D3DPOOL_MANAGED или D3DPOOL_DEFAULT, предоставлять пользователю возможность изменить качество текстур, обязательно использовать уровень детализации текстур и объектов. Использовать эффективный способ отсечения невидимых пространств, такой как Порталы, либо Binary Separation Planes (BSP) Deferred Shading До недавнего времени все графические движки использовали Forward отрисовку Для отрисовки объекта, освещаемого несколькими источника света, объект рисовался по разу для каждого источника, с включенным режимом блендинга ADD. Также для каждого источника света рассчитывались тени и свет не рисовался в затемненной области. Обычно использовались карты неровностей (bump map) , а также specular карты (карты отражаемого света). Ярким примером такого движка является idTech 4, ранее называвшийся Doom3 engine. На примере этой игры видны минусы такого подхода. Если отключить bump mapping, то можно увидеть очень упрощенную геометрию уровней. Мало того, в движке существует ограничение на количество источников света на объект. Все этого из-за большой нагрузки на вертексные преобразования и растеризацию. Решением данной проблемы является Отложенная Отрисовка. (Deferred Shader). Суть алгоритма в занесении геометрических данных объекта в несколько специальных текстур, которые затем используются для освещения. То есть объект рисуется только один раз. Теперь по шагам: 1) Создать 3 float32 текстуры (поддерживаются начиная с поколения GeForce 6xxx) 2) В итерации цикла отрисовки, установить текстуры как текущие отрисовываемые используя MRT (Multiple Render Targets) 3) Отрисовать все видимые объекты в установленные текстуры. Рассмотрим детально, что куда отрисовывается. В первую текстуру пойдет позиция точки во view space (позже объясню почему view space). Во вторую текстуру пойдет нормаль в точке, тоже во view space. В третью текстуру – диффузная текстура объекта. Остаются еще w координаты текстур, которые можно использовать под карты отражений (gloss map) и дополнительных вещей. 4) Шаг освещения. Для каждого видимого источника света на экране отрисовывается прямоугольник, покрывающий этот источник с установленным специальным шейдером. Также должны быть установлены текстуры, заполненные на предыдущем шаге. В шейдере извлекаются позиции, нормали и альбедо (диффузная текстура). Далее используются стандартные операции попиксельного освещения – рассчет диффузного освещения, specular составляющей, расчет затухания света. Советы по оптимизации и детали: Использование view space – позиция сохраняются во view space для большей точности и оптимизации рассчетов. Отрисовка модели источника света в стенсильный буффер перед рассчетом освещения для маркирования пикселей, освещаемых данным источником. Пиксельный шейдер простой, работает наподобие стенсильных теней (Carmack Z- fail алгоритм). Например для сферического источника света, в стенсиль отрисовывается сфера. Shadow maps для мягких теней. При использовании теневых карт необходимо решить проблему omni (сферических) источников света. Я решил эту проблему при помощи замены Omni источника на 6 spot источников. Для эффекта мягкости испольуется пока простой PCF – смешивание соседних пикселей теневой карты. Удобная многопоточность удобной и безопасной многопоточности можно добиться при разбиении программы на несколько модулей, общающихся между собой при помощи сообщений. Windows использует эту модель, но она достаточно не удобна. Необходимо писать обработчик сообщений, создавать идентификаторы итд итп. Я написал базовый класс подсистемы, в которой сообщениями являются функторы boost. Функторы позволяют сохранить и отложить вызов функции с параметрами. Это своего рода RPC (remote procedure call), основанный на С++. Теперь чтобы вызвать функцию из другого потока необходимо написать например: physics.post_msg(&CPhysics::SetPlayerPosition, position), вызов функции будет помещен в очередь, и очередь сообщений будет исполнена в следующей итерации потока. Параметр функции будет автоматически скопирован, поэтому не будет проблем с синхронизацией доступа к памяти. Также можно усовершенствовать систему для передачи вызовов функций по сети. Советы по оптимизации и детали: Если не требуется многопоточность, данную систему можно использовать в однопоточном приложении. В моем движке это делается статически, то есть для переключения режимов необходима перекомпиляция. Сделано это из-за соображений производительности. В этом случае компилятор оптимизирует код, и почти нет накладных расходов по вызову функций. Подсистема работает в отдельном потоке в зависимости от специального ключа компиляции. Выглядит это так: void AddMessage(boost::function0<bool> func) { // try to get access to recource Lock(); m_queue.push_back(func); // free resource Unlock(); } Исполнение очереди сообщений bool execute() { // try to get access to recource Lock(); // copy messages to reserve array std::vector< boost::function0<bool> > queue = m_queue; m_queue.clear(); // free resource Unlock(); // execute messages bool b = true; foreach(boost::function0<bool>& msg, queue) b &= msg(); return b; } Система основанна полностью на С++ и boost, и поэтому переносима на другие платформы. Поддержка широкой линейки видеокарточек. Для поддержки разного hardware необходимо работать с отрисовкой через интерфейс абстрактный базовый класс. Разработать разные потомки базового интерфейса для разных по возможностям карточек.В движке поддерживается 3 способа освещения: Deffered Shading для современных адаптеров с поддержкой Shader Model 3.0 таких как Intel X3500 (G35), Doom3 аддиативное освещение для предыдущего поколения карточек и освещение при помощи прерасчитанных карт освещения. Попиксельное освещение без пиксельных шейдеров. Современное освещение состоит из следующих компонентов: попиксельное затухание, попиксельный бамп маппинг, тени. Всех этих вещей можно добиться даже при отсутствии пиксельных шейдеров на карточке. Затухание можно сделать при помощи отрисовки двух текстур затухания в альфа буфер, стенсильные тени не требуют пиксельных шейдеров, попиксельный бамп маппинг можно реализовать при помощи встроенного Dot3 текстурирования DirectX(флажок D3DTOP_DOTPRODUCT3). Все это поддерживаются встроенными графическими адаптерами Интел ранних поколений, таких как Intel® 82810, 82815, 82830M, 82845G. Поддержка «мобильной» игры. Для поддержки игры на мобильном устройстве очень полезна библиотека Intel Laptop Gaming TDK. В ней собраны в удобном интерфейсе функции контроля уровня питания, уровня связи и нагрузки на процессор. Я дописал простой менеджер, который еще более упрощает работу с данными функциями. Вот внешний интерфейс: // wrapper for Intel Mobile TDK class CManagerMobile: public CSingletonAccessor<CManagerMobile> { public: CManagerMobile(); public: bool Init(); bool Update(sys_time time); float GetPower(); float GetProcessorUtil(); CONNECTIVITY_INFO& GetConnectivityInfo(); protected: IntelLaptopGamingTDKInterface* m_interface; void CManagerMobile::UpdatePower() { // Get the power source m_power_source = m_interface->GetPowerSrc(); if (m_power_source == AC_Power) { m_battery_mode = false; } else if (m_power_source == Battery_Power) { m_battery_mode = true; } else { return; } if ( m_battery_mode ) { m_battery_life_time = m_interface->GetSecBatteryLifeTimeRemaining(); m_battery_remaining = m_interface->GetPercentBatteryLife(); } if ( m_power_source == AC_Power ) { // Max physics // Max effects } else if (m_power_source == Battery_Power) { m_battery_life_time = m_interface->GetSecBatteryLifeTimeRemaining(); // Min physics // Min effects // check connection if ( !m_bConnected || m_bWiredConnection ) { if (m_interface->IsWirelessAdapterEnabled() && !m_bDisableWirelessQnAsked) { // Suggest to turn off wireless adapter. . . . // // Never ask the question again. bDisableWirelessQnAsked = true; } } } } При инициализации менеджер вычисляет количество процессоров, при обновлении вычисляет уровень питания и качество соединения. Когда питания опускается до предельного уровня, менеджер отображает окно сообщения. Также можно уменьшать нагрузку на видеодаптер и процессор, если пользователь отключил ноутбук от сети. Например понижать качество теней, отключать некоторые эффекты. Советы по оптимизации: получение информации по загруженности процессора очень медленно. Количество кадров в секунду падает примерно в 3 раза, поэтому необходимо вызывать эту функция не чаще чем раз в 3 секунды. Технология передвижения ботов по тени. В проекте Deadly Light необходимо было реализовать передвижение персонажей, не переносящих свет. Для этого я решил рассчитывать освещенность персонажа в каждой точке пути. При загрузке уровня идет расчет освещенности way point’а. Освещенность рассчитывается следующим образом: Для каждой точки рассчитываются все источники света, освещающие ее. Для каждого иточника света делается ray casting от точки пути до света, и если этот луч пересекает что-то, то точка в тени. Освещенность от каждого источника суммируется. Для статических источником освещенность запоминается, для динамических – перерасчитывается каждый раз. Освещенность рассчитывается в точках коробки персонажа. При рассчете пути для зомби (A*), для перехода в освещенные точки выставляется большие веса, чтобы зомби избегал освещенных путей. Теперь зомби будет избегать фонарика и освещенных областей, но при необходимости сможет перескочить эти области, ценой своего здоровья.