Визуализация открытых пространств - штука весьма интересная. Сгенерировать/загрузить саму геометрию ландашфта - это не так сложно. А вот грамотно и красиво его "разукрасить" - это уже поинтереснее.
В настоящий момент б
ольшая часть игр, действие которых происходит на открытых пространствах используют маски + затайленные текстуры. Если кто не совсем понял о чем речь - поясню. Нарисовать и использовать одну текстуру для всего ланшафта - слишком "дорого" для железа. Ибо при обычном подходе( есть ещё "необычный", об этом ниже ) такая текстурка просто отжрёт всю видеопамять( если вообще сможет загрузиться ). Поэтому используют маски. Маска - это обыная ч/б текстура. Диффузный цвет пикселя считается как (
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 ); float2 local_size = float2( local_max.x - local_min.x, local_max.y - local_min.y ); tex.x -= local_min.x / world_size.x; tex.x *= world_size.x / local_size.x; tex.y -= ( world_size.y - local_max.y ) / world_size.y; tex.y *= world_size.y / local_size.y;
|
_Winnie C++ Colorizer |
где
world_min, world_max - aabbox ландшафта
local_min, local_max - aabbox области
Чтобы не считать всё это в шейдере, посчитаем "снаружи" и передадим в качестве матрицы.
const vec2 world_size( world_bbox.width(), world_bbox.depth() ); const vec2 local_size( local_bbox.width(), local_bbox.depth() );
tex_mat_tr = math::matrix_translation( tex_mat_tr, -vec3( local_bbox.min.x / world_size.x, ( world_size.y - local_bbox.max.z ) / world_size.y, 0.f ) );
tex_mat_sc = math::matrix_scaling( tex_mat_sc, vec3( world_size.x / local_size.x, world_size.y / local_size.y, 0.f ) );
tex_mat = tex_mat_tr * tex_mat_sc;
|
_Winnie C++ Colorizer |
Кидаем tex_mat в вершинный шейдер, а там считаем новые текстурыне координаты как
И вот, мы получили маску в нужном нам месте.

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

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

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

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