После того как мы сгенерировали матрицы [link] пришло время заняться непосредственно рендером.
Рисовать мы будет пучками. Геометрия - знакомая многим "звёздочка". 4 плоскости или 8 полигонов.
Она же после альфа теста:
Увеличим плотность засейки. Вблизи смотриться неплохо:
Но если отодвинуть камеру то вылезут артефакты.
Артефакт 1. wrap
На самой верхушки пучков повылазили артефакты. Их природа довольно проста и лечится всё это дело выставлением clamp'а, вместо wrap в самплере.
Артефакт 2. mipmaps и алиасинг
Если отодвинуть камеру ещё дальше, то можно заметить что картинка поменялась в худшую сторону.
Что произошло? Трава стала менее прозрачной. Почему? Кто работал с альфатестом - знают в чём проблема.
Всё дело в mipmap уровнях( если кто не знает что это - [link] )
Рассмотрим мип-уровни куста( альфа канал ).
Как видно, уже на 5-ом уровне альфа канал теряет все детали и превращается в пятно. В результате дальние кусты травы( использующие 4+ мип уровни ) теряют прозрачность и становятся более "монолитными".
Что первое приходит на ум - отключить сам mipmaping, в результате чего будет использоваться текстура с максимальным разрешением. Но сделав это, мы получим очень неприятный алиасинг.
( на превьюшке плохо видно, лучше смотреть полноразмерный скрин )
Очевидно, что отказаться от мип-уровней мы не можем. Что делать? Можно исхитриться и разбить текстуру 2 части. Первая - диффузная, вторая - альфа маска. Поместив их в разные самплеры и отключив мип уровни только для альфы мы можем побороть алиасинг.
Но есть способ проще - достаточно грузить только первые 3-4 мип-уровня. Для загрузки текстур я использую функцию D3DXCreateTextureFromFileEx, которая позволяет задавать количество загружаемых мип-уровней.
Как видно, алиасинг исчез, проблемы с альфа тестом тоже.
Instancing
Очевидно что рисовать один куст за вызов слишком "дорого". К примеру, у меня в кадре может быть более 4000 кустов. Делать 4000 вызовов на отрисовку( dip ) будет слишком накладно. Посему будем использовать shader constant geometry instancing.
Как оно работает. Известно, что vertex shader 2_0 и выше может работать как минимум с 256 регистрами( каждый регистр - это 4 float ), в которые можно затолкать 64 матрицы 4х4. Оставим 16 регистров для других нужд( видовая матрица, освещение и т.д ) и "забёрём" остальные. Итого 60 матриц в нашем распоряжении.
Чтобы использовать instancing нужна "особая" геометрия. Если кратко - батчинг с индексом на каждый элемент. Если подробно - каждый куст мы описывает 16-ю вершинами и 24-мя индексами. Нам нужно увеличить вершинный и индексный буффер в 60 раз и заполнить их копиями оригинала. Примерно так:
for( unsigned int j = 1 ; j < 60 ; j++ ) { |
_Winnie C++ Colorizer |
С индексами нужно поступить аналогично. В дополнение к сделанному нужно расширить формат вершины. Если раньше она содержала только позицию и текстурные координаты, то теперь она дополнилась индексом батча. Т.е. каждая вершина в "пучке" знает к какому индексу она относится.
for( unsigned int j = 0 ; j < 60 ; j++ ) { |
_Winnie C++ Colorizer |
Буфера "забатчены" и проиндексированы. Осталось отрисовать.
float4x4 g_vp : register( c0 ); |
_Winnie C++ Colorizer |
В итоге количество dip calls сократилось в 60 раз. 100 вызовов вместо 6000. Думаю, разница очевидна. FPS радостно подпрыгнул, что и требовалось.
BTW: в DirectX 9 SDK есть очень хороший пример с instancing.
Итого
Комбинируя разные типы растительность можно получить очень приятную картинку


По моему, вышло неплохо =)
2 комментария:
С удовольствием читаю твои статьи
Продолжай писать!
zabivator
С удовольствием читаю твои статьи
Продолжай писать!
С удовольствием пишу свои статьи. Продолжайте читать!
спасиба, да =)
Отправить комментарий