Да будет скрипт. Методическое пособие по сотворению террейна
(Навязчивая мелодия)
Катастрофа со временем! Заказчики жмут, сроки горят, а еще по террейну, как говорится, конь не валялся, даже копыто поставить некуда – земля безвидна и пуста!
- Что-то текст неоригинален. Все это уже было, - вдруг просыпается внутренний голос.
- Где качнуть, ну, хоть почитать? – спрашиваю, разглаживая морщинистое дежа вю на лбу.
- Ветхий Завет, Книга Бытия, - сообщает голос и продолжает: - Эх, ты! Жертва атеизма!
Вздыхаю. На рабочем столе обои с надписью «Моя Родина – СССР». Много в ней лесов, полей и рек… Словно на террейне, однако - огромадный, как Союз Нерушимый, тут тебе и береговая линия, и перепад высот, и реки, и озера, море, острова, болота, горы, город, поселки, заводы, дамбы, электростанции… В каком пакете это Бытие моделилось – непостижимо! Руки опускаются.
- Да-а! Генераторы террейна тут не катят. Давай, начинай с чего-то для вдохновения – бурчит внутренний голос и развивает тему, зануда:
- Террейн большой - 16x16 км2. Для живости ландшафта нужен перепад высот, ну, например, в 1 километр (Это между самым глубоким омутом и самым высоким холмиком). Хорошо бы подробностей побольше, особенно в береговой линии да в местах возможных взрывов (чтоб воронки оставались, а не просто декальки с вывороченной землей). Ограничить бы надо возможности выхода за пределы террейна естественными препятствиями в виде глубокого-глубокого моря и неприступных гордых гор. И добавь пару островов, реку, изрежь пейзаж причудливой дельтой, насади деревьев. Свободное творчество. День первый.
- Уф, нарисовали, вроде. Полюбуйтесь. Деревья, конечно, немасштабные, и вообще, но… Безгрешные могут камней покидать. Может, по пивку?
Но внутренний голос не дает роздыху:
- Представь террейн в виде прямоугольного меша с регулярной сеткой. Тогда достаточно на него спроецировать grayscale bitmap в качестве карты высот и применить displace.
Хорошо. Попробуем определиться с возможностями.
Grayscale bitmap. По картинке можно прорисовать и карту высот. Самые темные места – это дно моря, соответственно чем светлее, тем выше.
Как применить displace. Можно построить шейдер, но проще применить команду меню Edit Poligons/Color/Prelight (Maya Software).
Не забыть бы предварительно установить источник освещения, чтобы команда могла сработать, например ambientLight. Само собой нужна галочка для Displace Geometry. Выставляем 1000 Sample Scale Factor в разделе Storage Options (перепад высот). Зато быстро, раз – и дисплэйс готов.
Шаг регулярной сетки. С одной стороны, хочется, чтобы шаг был помельче. Например, та же воронка от взрыва гранаты на сеточке с шагом в 10 см получится очень живописно. Опять же береговая линия станет непринужденно изрезанной. Как в жизни! Отлично – делаем!
- Разогнался! – усмехается внутренний голос, - Ну-ка, прикинь количество полигонов при размере террейна 16х16 км с шагом в 10 см.
- Упс! – чешу затылок, - 160 000 х 160 000 = 25 600 000 000. Облом, даже без Triangulate. Молчу. И в горле пересохло.
Голос предлагает:
- Зайди со стороны игрового движка.
Вопрос, может быть, спорный, но предположим, что движок будет крутить мешик в 1,5 миллиона полигонов, или 750 000 регулярных фэйсов, то есть получаем сеточку примерно 800х800. Таким образом, шаг сетки 16000 / 800 = 20 метров. Какие уж там воронки! Пойду за пивом.
- А вручную! – никак не угомонится внутреннее я.
Что-ж, можно, засучив рукава, упереться задом в кресло и построить все вручную. Там где надо, полигонов можно и прибавить, а где-то и убрать, чтобы выйти на эти 1,5 миллиона. Но как-то влом. Уж больно нудная работа. Предел. Тупик. Мель.
- А может с мели да на MEL, - бурчит внутренний голос.
- А почему бы и нет! На MEL, так на MEL, - киваю и соглашаюсь.
Опять проблемы. И самая главная, как заставить MEL в нужных местах увеличивать или уменьшать шаг сетки! А что если…
По картинке террейна или по карте высот прорисовываем белым цветом береговую линию и места, где нужны особо подробный ландшафт. Остальное заливаем черным. Получаем «карту подробностей террейна».
Вполне вероятно, что можно заставить MEL «унюхать» различие между светлыми и темными участками. Если спроецировать текстуру «карты подробностей» на меш, то будем иметь четкую взаимосвязь между физическими координатами Vertex Faces меша и соответствующими UV координатами. Останется определить, содержит ли UV пространство текстуры в координатах ограниченных каждым Vertex Face, светлые тона. Если да, то этот фэйс разбивается, если нет – остается неизменным.
- Ну, давай, давай, - подгоняет внутренний голос.
Не гони! Сейчас надо внимательно осмотреться в хелпе. Что может понадобиться?
Команда colorAtPoint
возвратит вес цвета в указанной точке UV пространства от 0 (черный.) до 1 (белый).
Команда polyInfo
может возвратить список вертексов у выделенного фэйса, если использовать флаг -<strong>faceToVertex(-fv)</strong>
.
Команда polyEditUV
с флагом-q
возвращает U и V значения выделенного вертекса отселектированного фэйса.
Команда ls
с флагом–selection (-sl)
выдаст на гора весь список имен отселектированных объектов. Кстати флаг <strong>-flatten (-fl)</strong>
организует поименный список элементов, а не «оптовый через тире от и до». Некстати, если есть желание загнать в переменную нечто, возвращаемое любой командой, пользуйтесь символом обратного апострофа (не путать с прямым), например: string $aw[] = `ls -sl`;
Команда select
позволяет варьировать наборы из активного листа выделенных элементов в зависимости от используемого флага, например флаг –clear (-cl)
снимет выделение со всех объектов, а флаг –replace (-r)
позволяет переопределить заново элементы активного листа. Краткое напоминание о любой команде можно получить, набрав в командной строке help, аргументом которой является запрашиваемая команда. Вот что возвращает Script Editor, например в случае:
help select;<br />// Result: <br />Synopsis: select [flags] [String...]<br />Flags:<br />-add - <br />-adn -allDependencyNodes <br />-ado -allDagObjects <br />-af -addFirst <br />-all - <br />-cl -clear <br />-d -deselect <br />-hi -hierarchy <br />-ne -noExpand <br />-r -replace <br /> -tgl -toggle <br /> -vis -visible<br />//
Очень интересная команда tokenize
, позволяющая разбить строку на список элементов, которые можно занести в массив.
Команда polySubdivideFacet
разбивает выделенный фэйс на части.
Команда sort
сортирует элементы массива по возрастанию, например.
Команда print
с любой переменной или константой в виде аргумента помогает организовать тесты выполнения кода. Строковый аргумент "\n"
означает перевод строки при отображении ряда данных.
Цикл while
дает возможность закольцевать кусок кода до тех пор, пока не выполнится какое-то условие.
Условный оператор if-else
инструктирует Maya на выполнение блока кода, если проверочное условие соблюдается, а в противном случае – на выполнение альтернативного блока кода.
Выстраивается следующая технологическая цепочка:
1. Выделяем фэйс.
2. Определяем имя фэйса с помощью команды ls
с флагом <strong>–sl</strong>
.
3. Получаем список вертексов из 4 элементов, подставив имя фэйса в команду polyInfo
с флагом -fv
.
4. Определяем uv-координаты каждого элемента из полученного выше списка вертексов с помощью команды polyEditUV
с флагом -q
.
5. Сканируем все UV-пространство внутри полученных 4-х пар uv-координат на наличие нечерного цвета, используя команду colorAtPoint
с указанием текущей пары uv-координат.
6. Если сканирование определяет наличие нечерного цвета – разбиваем выделенный фэйс.
- Это все – ля-ля, - бесцеремонно заявляет внутренний голос, - ближе к телу.
Вот же наглая сущность. Но обойдемся без словесной перепалки.
Вот кое-что и поконкретнее.
Тыкаем мышом в меш, переходим в режим выделения компонентов Face и селектируем нужный фэйс. После этого запускаем скрипт, часть кода которого приведена ниже. Код проверяет фэйс на «нечерность», то есть содержит ли фэйс светлые тона текстуры «карты подробностей».
//Заносим отселектированный фэйс в строковый массив <code>$aw[]
. Предварительно, разумеется, необходимо выделить этот фэйс.
string $aw[] = `ls -sl`;
//Получаем строку из имени, номера фэйса и списка номеров образующих вертексов, разделенных пробелами.
string $aw[] = `polyInfo -fv $aw`;
//Разбиваем полученную строку на список и заносим его в этот же массив. Номера всех 4-х вертексов теперь содержатся в массиве под индексами от 2-го до 5-го соответственно.
tokenize $aw[0] $aw;
//Для проверки распечатаем элементы массива
print $aw;
//Получаем uv-координаты 1-го вертекса по индексом 2 в массиве $aw[2]. Заносим в соответствующие массивы.
float $pNum[] = `polyEditUV -q ("pPlane1.map[" + $aw[2] + "]")` ;<br /> $Ucoord[0] = $pNum[0]; $Vcoord[0] = $pNum[1];
//Получаем uv-координаты 2-го вертекса по индексом 3 в массиве $aw[3]. Заносим в соответствующие массивы.
float $pNum[] = `polyEditUV -q ("pPlane1.map[" + $aw[3] + "]")` ;<br /> $Ucoord[1] = $pNum[0]; $Vcoord[1] = $pNum[1];
//Получаем uv-координаты 3-го вертекса по индексом 4 в массиве $aw[4]. Заносим в соответствующие массивы.
float $pNum[] = `polyEditUV -q ("pPlane1.map[" + $aw[4] + "]")` ;<br /> $Ucoord[2] = $pNum[0]; $Vcoord[2] = $pNum[1];
//Получаем uv-координаты 4-го вертекса по индексом 5 в массиве $aw[5]. Заносим в соответствующие массивы.
float $pNum[] = `polyEditUV -q ("pPlane1.map[" + $aw[5] + "]")` ;<br /> $Ucoord[3] = $pNum[0]; $Vcoord[3] = $pNum[1];
//Сортируем массивы с парами u-координат и v-координат по возрастанию. Минимальная имеет индекс 0 в массиве, Максимальная имеет индекс 1 в массиве.
$Ucoord = sort ($Ucoord); $Vcoord = sort ($Vcoord);
//Можно распечатать для контроля.
//print $Ucoord; print $Vcoord; print (" " + "\n");
//Зная информацию об этих парах можно организовать цикл сканирования участка в пределах UV-пространства, ограниченного значениями этих пар.
//Цикл сканирования UV-участка на наличие нечерного цвета
//Некий флаг-индикатор «нечерности». Если содержит 0, то фэйс «черный», если 1 – «нечерный».
$wFl = 0;<br /> clear $cFl;
//Присваиваем цикловому шагу минимальное значение по u-координате.
float $uStep = $Ucoord[0];
//Присваиваем цикловому шагу минимальное значение по v-координате.
float $vStep = $Vcoord[0];
//Цикл по u-координате. Условие прекращения цикла $uStep <= $Ucoord[3].
while ($uStep <= $Ucoord[3])<br /> {
//Цикл по u-координате. Условие прекращения цикла $vStep <= $Vcoord[3].
while ($vStep <= $Vcoord[3])<br /> {<br /> //print ("U= " + $uStep + " V= " + $vStep + "\n");
//Определяем цвет текстуры в указанных uv-координатах
$cFl = `colorAtPoint -u $uStep -v $vStep file1`;<br /> if ($cFl[0] > 0)<br /> {
//Если цвет нечерный, то есть >0, то в содержимое флага вносим 1, а цикл прерываем.
$wFl = 1;<br /> break;<br /> }<br /> $vStep = $vStep + (($Vcoord[3] - $Vcoord[0])/$stepFl);
}
$vStep = $Vcoord[0];
if ($cFl[0] > 0)
{<br /> break;<br /> }<br /> $uStep = $uStep + (($Ucoord[3] - $Ucoord[0])/$stepFl);<br /> }<br /> $uStep = $Ucoord[0];
//Печатаем содержимое флага-индикатора.
print $wFl;
//Примечание: переменная $stepFl – это дробность сканирования UV-участка текстуры. Чем выше дробность, тем мельче шаг сканирования, тем точнее и дольше процедура.
Вот, собственно, и решение. Теперь можно оформить этот кусок кода в виде процедуры, возвращающей содержимое флага-индикатора. Затем эту процедуру включить в цикл перебора фэйсов и отселектировать их по признаку «нечерности». А уж тогда с чувством выполненного долга и гордостью можно разбить все фэйсы селекции. Затем цикл повторять (по необходимости), осуществляя все более тщательный поиск и разбиение фэйсов до получения необходимого результата.
Вот вид террейна во время первого цикла работы скрипта:
Это результат первого цикла:
В течение 10 минут пройдены 5 циклов сканирования и разбиения. В результате получен меш следующего вида:
Структура меша нерегулярна. Отмеченные на «карте подробностей» места автоматически разбиты на более мелкие фэйсы с минимальным размером 31.25х31.25 м2. А вот вид в полутонах после применения displace:
- Ну, да! Еще пару циклов и число полигонов возрастет так, что потребуется очень приличный ящик, чтобы этот меш ворочать, - бурчит внутренний голос.
Что тут ответить! Можно схитрить и разбить меш на несколько кусков и обрабатывать их отдельно каждый. Предположим, что этих кусков 16. Их размер 4х4 км2. Запускаем 7 проходов цикла сканирования-разбиения и получаем минимальную ячейку сетки около 7 метров. При этом число треугольников на каждом куске достигает в среднем 80 000, а на весь террейн соответственно 80 000 х 16 = 1 280 000.
- И в чем прикол? – не унимается внутренний голос.
Что ж, смею надеяться, что это эффективнее, чем моделить все руками. Судите сами!
- Да будет скрипт! – раздается наконец слаженное двухголосие. Неплохая кода для дуэта.
Спасибо Славе Хамурарь за художественное воплощение карты.
С Уважением, Игорь Леводянский, ilee@adv.md.
PS. Если кому интересен и актуален финальный вариант скрипта – мыльте.