- PVSM.RU - https://www.pvsm.ru -

Тема генерации ландшафтов путем применения разнообразных хитроумных алгоритмов достаточно широко освещалась на хабре (раз [1], два [2], три [3] и продолжать можно до бесконечности). Перечисленные примеры касаются случайной генерации некой абстрактной местности для повышения реализма в конечных игровых продуктах. А как быть, если требуется смоделировать некую реальную местность?
По этой теме тоже довольно много разного рода публикаций в сети. Однако, многие из них опираются на использование платных приложений или расширений для Unity. Существуют описания и «дешевых» методов, но основная масса их ориентирована на получение так называемых heightmap — черно-белых квадратных изображений местности, где градациями серого определяется относительных уровень высот в данной точке. Существует ряд способов генерации подобных карт высот с использованием например инструментария GDAL [4]. И такой подход не лишен недостатков, связанных с достаточной громоздкостью процедуры создания карты высот и последующей привязкой к полученной местности. Поэтому, в данной статье будет изложен некий альтернативный подход и интересующиеся приглашаются под кат.
Тут всё уже давно изобретено до нас, за что следует благодарить NASA, выполнивших в свое время программу SRTM (Shuttle Radar Topography Mission) по радиолокационному картонированию земной поверхности. Данные, полученные в ходе миссии находятся в открытом доступе вот здесь [5]. Идем по этой ссылке и наблюдаем
наш родной «шарик» поделенный на зоны. Для демонстрации описываемой методики выберем район «крыши мира», гору Эверест. Для этого щелкаем по нужному куску карты и жмем кнопку «Download».
Распаковываем скачанный архив, и находим там среди прочего файл srtm_54_07.tif в формате GeoTIFF. GeoTIFF [6] — открытый стандарт хранения картографической информации. Анализируя данный файл можно получить данные по высотам точек земной поверхности. Так как формат открытый, организовать непосредственную работу с ним, скажем из Unity будет несложно, но мы, пока, для упрощения, пойдем более коротким путем и воспользуемся маленькой но полезной программкой 3DEM [7]. Это софтинка бесплатна и легко качается с официального сайта. Качаем её и открываем в ней добытый нами GeoTIFF района Эвереста. При открытии нас попросят выбрать формат геоданных, выбираем GeoTIFF
после чего выбираем вышеупомянутый файл и видим такую картину
Показанный район довольно велик. Прямоугольником по центру показан интересующий нас район горы Эверест. Чтобы вырезать нужный кусок жмем F8, выбираем интересующую нас область и жмем Enter. Нам предложат уточнить границы участка по широте и долготе.
Уточняем если нужно и нажав ОК получим интересующий нас район на экране
Если надо, повторяем эту процедуру. В конце концов мы выберем нужный район, а затем идем в главное меню по пути File -> Save Terrain Matrix. Нам предложат выбрать формат сохраняемых данных
Выбираем бинарный формат float, так как с ним будет меньше возни. Выбираем далее путь и имя сохраняемых файлов. В качестве выхлопа мы получим два файла
3. Описание формата хранения геоданных
Его нетрудно получить из pdf-документа, идущего вместе с 3DEM. Главная информация находится в *.hdr-файле и выглядит она так
file_title = everest
data_format = float32
map_projection = Lat/Lon
left_map_x = 86.859001
lower_map_y = 27.899000
right_map_x = 87.014999
upper_map_y = 28.046000
number_of_rows = 177
number_of_columns = 188
elev_m_unit = meters
elev_m_minimum = 4574
elev_m_maximum = 8794
elev_m_missing_flag = -9999
Опишу самые важные для нашей задачи поля
Таким образом, становится понятно как читать бинарный файл — он просто поток float'ов, записанных в файл построчно. Эта матрица, в нашем случае хранит высоты в 177 х 188 = 33276 точках на выбранном участке, ограничения по долготе и широте для которого нам известны. Так же нам известен и диапазон высот на данном участке.
Класс Terrain в Unity имеет публичное поле terrainData, через которое можно получить доступ к следующим полям
Карта высот — квадратный массив вполне определенного размера. Матрица геоданных — массив прямоугольный. Вычитать её из файла дело техники, и подобный код настолько прост, что специально приводить его не имеет смысла. Главная сложность, преобразовать данные GeoTIFF к карте высот, удовлетворяющей следующим требованиям
Нормировать высоты достаточно просто
где level[i,j] — нормированная высота; h[i,j] — высота в точке (i,j); h_min, h_max — минимальная и максимальная высота на участке. Для приведения размера матрицы к выставленным требованиям нужна аппроксимация высоты в зависимости от координаты. Воспользуемся простейшей линейной аппроксимацией. Пусть высота есть функция координат точки на участке
. Тогда, выберем базовую точку
, совпадающую с одним из узлом заданной координатной сетки и разложим эту функцию в ряд Тейлора
При кажущейся сложности, формула легко реализуется кодом на C#
float getHeight(float x, float z)
{
float height = 0.0f;
// Вычисляем шаг по координатам
float dx = terrain_data.x_range / (terrain_data.numder_of_rows - 1);
float dz = terrain_data.z_range / (terrain_data.number_of_columns - 1);
// Определяем базовую точку
int i = (int) (x / dx);
int j = (int) (z / dz);
// Проверяем индексы на предмет выхода за размер массива данных
if ((i >= terrain_data.numder_of_rows) || (j >= terrain_data.number_of_columns))
return 0.0f;
// Вычисляем частные производные в точке (i, j)
float dydx = (terrain_data.normalize_data[i + 1, j] - terrain_data.normalize_data[i, j]) / dx;
float dydz = (terrain_data.normalize_data[i, j + 1] - terrain_data.normalize_data[i, j]) / dz;
// Вычисляем высоту
height = terrain_data.normalize_data[i, j] + dydx * (x - i * dx) + dydz * (z - j * dz);
return height;
}
Пользуясь этой функцией легко формируем матрицу для карты высот
terrain_data.height_map = new float[resolution, resolution];
float dx = terrain_data.x_range / resolution - 1;
float dz = terrain_data.z_range / resolution - 1;
for (int i = 0; i < resolution; i++)
{
for (int j = 0; j < resolution; j++)
{
terrain_data.height_map[i, j] = getHeight(i * dx, j * dz);
}
}
Чтобы облегчить жизнь мной был написан небольшой плагин для редактора движка UnityGeoDataLoader [8], позволяющий решать поставленную задачу. Этот продукт и его исходный код распространяются по лицензии GNU GPL v2.0. По указанной ссылке можно получить и то и другое. Там же есть и инструкция по использованию данного инструмента.
Плагин встраивается в редактор и добавляем в главное меню пункт «Tools -> Load GeoData». Выбрав террейн в инспекторе, жмем этот пункт меню, указываем путь к файлу *.hdr (рядом с которым лежит и файл *.bin с данными) и вуаля — лицезреем гору Эверест в редакторе Unity
Дальше с этим террейном делаем всё что душе угодно — текстурируем, применяем шейдеры, расставляем другие объекты. Это уж на ваше усмотрение.
Покритикую сам себя. Нехорошо, при открытости стандарта GeoTIFF использовать сторонний инструмент для добычи геоданных. По-хорошему надо весь код поместить в плагин, добавить возможности выбора региона по координатам и много чего ещё. Но предела совершенству нет, главное, что задача загрузки в движок реального ландшафта имеет вполне приемлемое и простое решение.
Благодарю за внимание!
Автор: Дмитрий Притыкин
Источник [9]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/c-2/255968
Ссылки в тексте:
[1] раз: https://habrahabr.ru/post/226635/
[2] два: https://habrahabr.ru/post/111538/
[3] три: https://habrahabr.ru/post/319226/
[4] GDAL: http://www.gdal.org/
[5] здесь: http://dwtkns.com/srtm/
[6] GeoTIFF: https://ru.wikipedia.org/wiki/GeoTIFF
[7] 3DEM: http://www.hangsim.com/3dem/
[8] UnityGeoDataLoader: https://github.com/maisvendoo/UnityGeoDataLoader
[9] Источник: https://habrahabr.ru/post/329246/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best
Нажмите здесь для печати.