суббота, 7 марта 2009 г.

Parallel Split Shadow Map тени.

Наконец Внедрил Parallel Split Shadow Map в движок. Реализация основана на демке PSSM
Разбиения рассчитаны по логарифмической схеме logarithmic split scheme.
Было несколько ошибок: Дикие растяжения теней проблема из-за неправильного построения Crop Matrix, проблемы с глубиной, исчезновение объектов и листьев ландшафта.
Crop Matrix это некая матрица трансформации с масштабированием и смещением. Как раз она корректирует текущую матрицу проекции источника света подгоняя под ограничивающую область текущего сплита, для оптимального использования разрешения текстуры теней, избегая проблем алгоритма стандартных теней Shadow Map Aliasing. Спасибо Арсению Капулкину(Zeux) за подсказку.
Вторая проблема была связана с рисованием объектов через FFP(что я планирую исправить на шейдеры по умолчанию) в Direct3D9/OpenGL при выключении шейдера на большом расстоянии, я просто не учел матрицу проекции для текущего split.
Третья проблема тоже простая, для видимого объекта ставился флаг видимости, и если он виден в текущем разбиении н не виден в предыдущем или последующем флаг сбрасывался и объект не рисовался.
Четвертая проблема еще проще, рисовался повторно ландшафт, затирая то, что уже отрисовано создавая эффект выпадания листьев.
Приведу несколько удачных скриншотов показывающих разницу между Parallel Split Shadowm Map и Standart Shadow Map. Скриншоты сделаны в режиме Direct3D9 рендера.

SSM


PSSM

SSM


PSSM

SSM


PSSM


SSM


PSSM



В процессе внедрения PSSM использовал подход в выставлении отладочного пиксельного шейдера, который выставляется для объектов текущего разбиения подсвечивая их красным цветом игнорируя текстуры и освещение. Спасибо Сергею Милойкову(Zemedelec) за предоставленный совет. Данная функциональность удобно включалась командой с консоли "debug_split i" - где i номер разбиения.
"debug_split -1" - отключал отладку.
Выглядит это вот так:

Split 0



Split 1


Split 2


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

пятница, 20 февраля 2009 г.

Тени в OpenGL рендере. Прогресс и дальнейшие планы.

Сначала я сделал тени для Direct3D 9. Потом переделал для OpenGL. Тени работали, но отличались в OpenGL по сравнению Direct3D в худшую сторону. Эту задачу я отложил до лучших времен. Иногда к ней возвращался но так и не мог понять в чем дело. Реализация у меня была с использованием рендера в текстуру и с использованием расширений GL_ARB_vertex_program и GL_ARB_fragment_program. (Код получен как результат компиляции Cg компилятором.)На данный момент частично используется GLSL полученный как результат компиляции из Cg.Сначала я думал что проблема в проблема была в использовании или инициализации рендера в текстуру с использованием GL_EXT_frame_buffer_object в связке с текстурами с плавающей точкой через расширение GL_ARB_texture_float. Но вскоре выяснилось что я немного неправильно построил матрицу источника света и матрицу текстуры. Теперь тень пиксель в пиксель совпадает с Direct3D.

OpenGL:

Direct3D:


В общем я решил пока не удалять OpenGL рендер из движка. Не так все плохо. Но еще есть проблемы на ATI.

На данный момент добавил рендер всей сцены в глубину. Результат сохраняется в float текстуру. Используя это эту возможность расширил в связи с этим менеджер постропцессинга, который может использовать эту текстуру. К примеру к примеру на основании этого реализовал эффект постпроцессинга Dof - Depth of Filed. Спасибо за переработанный шейдер из различных SDK Глебу Гущину(innuendo), а помощь в исправлении расчета преобразования глубины Арсению Капулкину(Zeux).
На данный момент идет улучшение теней с использованием алгоритма
Parallel Split Shadow Map.

В дальнейших планах исправление ошибок. Сейчас их накопилось много. Есть серьезные, а есть просто много мелочей. Параллельно буду писать редактор. Пусть пока простой - расстановка объектов, cохранение и загрузка.

суббота, 17 января 2009 г.

Передача констант в шейдеры.

Всем известно что передавать константы лучше все сразу, а не по отдельности, либо минимизировать эти вызовы.
Достичь это можно несколькими путями и по разному для каждого API. Опишу отдельно для Direct3D9, Direct3D10, OpenGL.
Для Direct3D9 лучше Не использовать ID3DXEffect для шейдеров, не использовать ID3DXConstantTable для установки констант. Они не несомненно удобны но не очень быстрые в работе. Почему сейчас расскажу.
Возьмем любой метод ID3DXEffect/ID3DXConstantTable для установки констант. Все они имеют параметр D3DXHANDLE - это либо строка либо указатель возвращаемый
другими методами. Напрашивается вопрос как значение этого параметра внутри распознается. Внутри это может быть либо свой указатель либо строка с именем константы.
Что конкретно определяется проверкой установленного старшего бита параметра, если установлен то HANDLE, если нет то строка. Таким образом, мы имеем некие потери при установки константы. При чем внутри таких вызовов будут еще и проверки. Если это строка то будет поиск по строке, пусть даже через Hash. В случае с указателем чуть быстрее. Итого, представив много шейдера на сложной сцене, с большим числом констант, которые еще что хуже всего передаются по имени через строку.
В данных интерфейсах есть средства для засылки констант группой, за справкой отсылаю к DirectX SDK. Но нет средств что-бы заслать все константы для всего шейдера разом, что по идее должно быть идеально. Что бы слать все, лучше использовать напрямую интерфейсы IDirect3DPixelShader9/IDirect3DVeretxShader9 и использовать методы:
IDirect3DDevice9::SetPixelShaderConstant*/IDirect3DDevice9::SetVertexShaderConstant*
при таком подходе можно заслать единообразно все константы определенного типа за 1 вызов начиная со стартового регистра. Причем ID3DXConstantTable/ID3DXEffect являются wrapper'ами над этими методами.
Для Direct3D10 все намного проще. Есть константные буферы которые и предназначены для передачи группы констант. Кроме того данная функциональность есть в ID3D10Effect интерфейсе.
К сожалению в OpenGL с этим немного похуже ситуация. В расширениях GL_ARB_fragment_program/GL_ARB_vertex_program есть функция glProgramLocalParameter4fvARB, но она не позволяет слать больше 4 float(1 регистр).
Можно использовать для матриц константы GL_MATRIX0_ARB - GL_MATRIX31_ARB
и передавать через glMatrixMode это чуть улучшает ситуацию. Недавно я использовал новое расшерение GL_EXT_gpu_program_parameters оно позовляет засылать константы пачками. Причем это прямой аналог передачи констант для Direct3D9. Радует поддержка расширения на ATI.
Для GLSL передавать все константы для шейдера за 1 вызов не получится. Потому что нет у порядочности хранения uniform переменных. И функции для передачи параметров могут передавать либо матрицу либо vector4 и т.д. Можно передать матрицы 1 вызовом но их нужно хранить в массиве. Но с появлением расширения GL_EXT_bindable_uniform ситуация улучшилась но оно поддерживается пока только nVidia картами.

четверг, 13 ноября 2008 г.

Lod'ы шейдеров в Direct3D10 и отсутствие FFP

Привел от рисовку объектов при Direct3D10 рендере в порядок. Теперь они не исчезают при большом расстоянии от камеры.
В движке есть понятие Lod для шейдеров. С каким-то шагом можно задавать более сложные шейдеры для объектов. Это дает прирост производительность на старом железе(а может и на новом). К примеру возьмём дерево, которое анимируется на ветру вершинным шейдером, в шейдере есть тригонометрическая функция для нее генерируется достаточно много инструкций, очевидно дерево можно не анимировать с очень большого расстояния, в этом случае включится простой шейдер, просто трансформирующий вершину. Другой пример - нет смысла делать эффект бампа и освещения на большом расстоняии - можно ограничится диффузионным освещением или вообще без него. Конечно переключение возможно будет заметно. Аналогичная ситуация с анимацией травы. Можно сделать лоды для скелетной анимации. Но там более сложные параметры.
В случае OpenGL/Direct3D9, если не будет найден подходящий лод, то будет выставлены NULL шейдеры, т.к. для них есть фиксированный конвейер то можно вызвать IDirect3DDevice9::SetTransform(world) и glLoadMatrixf(worldViewMatrix). Для Direct3D10 в этом случае не будет ничего рисоваться. Я решил эту проблему таким образом. Для каждого формата вершин создал список шейдеров по умолчанию. В случае выставления NULL шейдера, будет браться шейдер по умолчанию, если есть вершинный буфер.
Это простое и удобное решение. Не нужно дополнительных шейдеров, тем более шейдера по умолчанию простые и короткие, поэтому их код хранится прямо в реализации класса рендера движка. Для шейдеров с альфа тестом, есть дополнительный список шейдеров по умолчанию для каждого формата, само собой с параметром - текущий AlphaRef, тут имеется ввиду значение, передаваемое в IDirect3DDevice9::SetRenderState(D3DRS_ALPHAREF, AlphaRef) в Direct3D9 и в glAlphaFunc(func, AlphaRef) в OpenGL. В Direct3D10 убран альфа тест из FFP, поэтому нужен такой параметр по которому делается discard.
Таким образом объекты перестали исчезать.

понедельник, 10 ноября 2008 г.

Добавлен встроенный профайл

Добавил в движок встроенный профайл. По очень простой схеме,
некий класс содержащий данные о текущей строке имени файла и имени функции. Этот класс имеет конструктор с такими параметрами. С помощью макроса
#define PROFILE Profile::ProfileData data(__FILE__, __LINE__, __FUNCTION__);
можно в начале любой функции добавлять отсчёт времени. В конструкторе запоминается стартовое время работы. В деструкторе формируется строка в виде "File, line, function, time"
и рассчитывается время работы функции, данные добавляются с список. Все банально и просто. Нужно ещё решить как-то проблему мигания значение цифр, что происходит из-за разброса значений времени.

четверг, 6 ноября 2008 г.

Объединение одинаковых пересекающихся объектов на сцене.

При сильно загруженной сцене бывает очень много вызовов от рисовок геометрии, всем известными под названием DIP (IDirect3DDevice9::DrawIndexedPrimitive для Direct3D9, glDrawElements для OpenGL и ID3D10Device::DrawIndexed для Direct3D10). Большое количество таких вызовов может нанести серьезный удар по быстродействию рендера, особенно это более актуально для Direct3D 9, именно в в момент вызова идет проверка на валидность пиксельного и вершинного шейдера, правильная линовка данных передаваемых из вершинного в пиксельный, проверка вершинных деклараций на соответствие того что использует вершинный шейдер и что находится в вершинном буфере, так-же индексы вершин и остальные параметры. В OpenGL все намного проще, после выполнения каждой функции может возникнуть ошибка. Таким образов время как-бы равномерно распределяется между всеми вызовами функция перед вызовом от рисовки.
Ну в Direct3D 10 DIP cost значительно снижен.
Так-же частые вызовы может серьёзно нагрузить CPU по вызовам функций. Обычно до 1000 DIP на кадр ещё не все так плохо.
Итак при загрузки сцены я сделал некую простейшую оптимизацию, ищем объекты с одинаковы мешами и если они пересекаются своими ограничивающими боксами, собираем их в 1 мешь с учтя матрицу трансформации. Что это дает?
1) уменьшение нагрузки на CPU при обходе Scene Graph и при сортировке объектов;
2) уменьшение DIP cost при отриcовке в Depth текстуру для теней;
3) уменьшение DIP cost при финальной от рисовке объектов;
Минусы в том что будет небольшой перерасход памяти. Но если к примеру у меня на сцене дерево ели имеет 350 полигонов и их несколько сотен, то объедение по 3-4 в 1 мешь будут значительно сокращать DIP cost. Пока тесты особых положительных результатов не дали но число DIP и число объектов в кадре значительно сокращены в грубом приближении на 30% в среднем. Повышение FPS вроде не более 10%. Буду тестировать на более слабом железе. На будущее конечно нужно подумать об использовании Instansing'а. Железо сейчас c такой функциональностью уже достаточно доступно и инстансинг есть уже в 3 API в том числе и поддержка у ATI для OpenGL.

пятница, 31 октября 2008 г.

Подгонка шейдеров под все графические API

Пока Direct3D10 рендер ещё сырой, иногда возвращаюсь к нему и постепенно дописываю.
Cейчас избавился в движке от OpenGL state(передаю матрицы трансформаций напрямую) .
Когда он был заменял макросы на соответствующие параметры при компиляции из Cg в GL_ARB_vertex_program. Код GLSL полученный из Cg не содержит GL state - видно nVidia сразу продумала это на будущее как deprecated функциональность.
При использовании этих макросов компилятор HLSL для Direct3D10 почему-то неверно распределял регистры в отличие от компилятора HLSL Direct3D9. Я пока не стал разбираться с этим, а временно скинул и переписал шейдеры специально для Direct3D10. Немного переписав шейдеры и добавив системный макрос D3D10 я избавился отлишней папки с шейдерами для Direct3D10.Настало время разобраться с использованием Cg для Direct3D10.