
В настоящий момент большая часть игр, действие которых происходит на открытых пространствах используют маски + затайленные текстуры. Если кто не совсем понял о чем речь - поясню. Нарисовать и использовать одну текстуру для всего ланшафта - слишком "дорого" для железа. Ибо при обычном подходе( есть ещё "необычный", об этом ниже ) такая текстурка просто отжрёт всю видеопамять( если вообще сможет загрузиться ). Поэтому используют маски. Маска - это обыная ч/б текстура. Диффузный цвет пикселя считается как ( diffuse_texture_0 * mask_0 + diffuse_texture_1 * mask_1 ... и т.д. ). Думаю, мысль ясна.
Существуют две основные техники рисования масками( их на самом деле больше, я остановлюсь на самых популярных )
1) Маска в геометрии ландшафта. Как сделано в Oblivion. Каждая вершина содержит 4-х байтный цвет, который раскладывается на 4 маски. Основной минус - увеличить детализацию можно только увеличив плотность сетки.
2) Маски в текстуре. Одна на всю локацию. Как сделано в Neverwinter Nights 2. На самом деле, в одной текстуре 4 маски, просто они лежат в разных RGB каналах. Как видно - предел 4 маски ( 4-ая счиатется как 1.0 - R - G - B ). Основной минус - невозможно увеличить детализацию какой-либо области. Только всю текстуру целиком.
Есть ещё относительно новый подход - "Мегатекстура" от дядюшки Кармака. Вот тут очень популярно о ней написно [link]. Техника довольно проста в реализации. Основной труд - написание инструментария для хранения и генерации этой "мегатекстуры". Я ещё коснусь этой темы в конце поста.
В своё время я реализовывал 1-ый вариант. Причём очень хитрый и заковыристый. С разбивкой на субматериалы, с неограниченным количеством масок и т.д. Но главной проблемы избежать так и не удалось - без увеличения плотности сетки нельзя было увеличить делализацию маски. К примеру - есть ландшафт. На нём хочеться нарисовать тропинку. Нарисовать-то мы её нарисуем, но в итоге увидим что края этой дорожки сильно размыты. Почему? Предположим, шаг сетки у нас 1 метр. В одной веришине значение маски 1.0 в другой 0.0. Что случиться? Правильно, инторполяция. В итоге имеем метровый градиент, дающий малопривлекательную "размытость". Регионы травы, песка и камней рисовать пойдёт, но не более.
Нужен другой подход, сочетающий в себе произвольную точность и низкую ресурсоёмкость. Как этого можно добиться? Нужно "освободить" маски. Чтобы они не были привязаны ни к геометрии, ни размеру текстуры. Как? Разбивать на произвольные регионы. Сейчас объясню как.
Определям регион маски. Вот он - красный aabbox в центре.

Теперь нам нужно в этом месте нарисовать маску. Что для этого нужно сделать? Правильно, пересчитать текстурные координаты.
float2 world_size = float2( world_max.x - world_min.x, world_max.y - world_min.y ); |
_Winnie C++ Colorizer |
world_min, world_max - aabbox ландшафта
local_min, local_max - aabbox области
Чтобы не считать всё это в шейдере, посчитаем "снаружи" и передадим в качестве матрицы.
const vec2 world_size( world_bbox.width(), world_bbox.depth() ); |
_Winnie C++ Colorizer |
Кидаем tex_mat в вершинный шейдер, а там считаем новые текстурыне координаты как
tex = mul( float4( tex.x, tex.y, 1.f, 1.f ), g_tex_mat ).xy; |
_Winnie C++ Colorizer |
И вот, мы получили маску в нужном нам месте.

Важное замечание: нужно обязательното включить "clamp" для текстуры маски. Иначе она пойдёт тайлиться на соседние области, что недопустимо. Тайлиться он перестал, но этого мало, нужно чтобы она ( маска ) не затронула соседние регионы. Есть 2 решения.
1) При рисовании маски закрашивать пиксели на границе в черный цвет.
2) Использовать clip planes. 4 штуки по сторонам бокса материала.
Маска отображается корректно? Самое время добавить диффузную текстуру.
float mask = tex2D( s_mask, tex_default ).r; |
_Winnie C++ Colorizer |
Получаем:

Как нетрудно заметить, маску мы ложим в альфу. Посему предварительно нужно включить alphablending( src_blend - blend_src_alpha, dest_blend - blend_inv_src_alpha ).
При желании диффузную текстуру можно затайлить, домножив трансформированные текстурные координаты на scale factor.

И всё =)
В итоге, варьируя регионы маски и её разрешение можно получить абсолютно любую точность.

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

Про оптимизацию
Ландшафт у меня разбит на патчи. В среденем на 1 патч приходиться 2-3 материала. Рисовать каждую маску за отдельный проход - конечно же, неоптимально. Но лечиться это очень просто. Можно( и нужно ) рисовать несколько масок за раз. Подход такой же, как и при отрисовки одного объекта сразу с несколькоми источниками света. Создаётся 3 шейдера на каждый тип материала( отрисовка одной маски, 2-х и 3-х ). На этапе загрузки определяем сколько материалов на патче, биндим соответсвующий шейдер и всё. Если на патче более 3-х масок, рисуем в несколько проходов.
Про мегатекстуру
Данный подход можно использовать совместно с "мегатекстурой". Для её генерации конечно же, а не отрисовки =) Мысль довольно интересная, может я её разовью в дальнейшем.
Итого
Получилась очень хорошая техника. По крайней мере, мне нравится как она работает.


4 комментария:
Есть 2 решения.
1) При рисовании маски закрашивать пиксели на границе в черный цвет.
2) Использовать clip planes. 4 штуки по сторонам бокста материала.
Какая разница между решениями?
Какая разница между решениями?
При 1-ом варианте нужно не забывать закрашивать границы в чёрный цвет. Если генерация масок идёт инструментарием - никаких проблем. Если маски же рисуются/корректируются в фотошопе - артистам нужно не забывать об этой фиче
При 2-ом подоходе теоритически должен чуть проседать фпс. Но не замерял, так что могу ошибаться( может даже будет быстрее, ибо не происходит растеризация "ненужных" пикселей ).
хм.. а затраты на DIP не жалко?
Нет, учитывая что в боевых условиях можно рисовать по 2-3 маски за патч (и один dip). Этого должно хватить на бОльшую часть террейна.
Отправить комментарий