Неудавшийся эксперимент по текстурированию ландшафта.
Автор: nsf
В августе/сентябре этого года я занимался небольшим исследованием в области ландшафтов. Это статья о том, что получилось и что не получилось :)
Автор: nsf
В августе/сентябре этого года я занимался небольшим исследованием в области ландшафтов. Это статья о том, что получилось и что не получилось :)
Начало.
Лайтмап генератор.
О структурах данных и Quadtree.
Размышления о текстуринге.
Реализация всего метода.
Начало разочарований.
Дело было в августе/сентябре 2007 года. Делать было нечего, до конца каникул оставалось ещё 2 месяца. Я мучался в попытках придумать какую-нибудь игру. Мысли о том, какая это будет игра не заставили себя долго ждать. Я однозначно решил делать outdoor, основанный на heightmap ландшафтах. Потому что просто, быстро и редактор как бы не нужен. Даже не подумав, я решил, что разбивать пространство нужно quad деревом, и начал работать над реализацией.
Что касается геометрии ландшафта, то я не стал много думать и делать всяческие оптимизации на этом уровне. Просто отсекал quad деревом то, что не входило во фрустум, остальное рендерил. Более детально про организацию квадтри дерева я напишу ниже.
Как и следовало ожидать, наивность и неисчерпаемый оптимизм быстро о себе напомнили. Я не подумал о том о сём. А как делать для ландшафта тени и освещение? А как текстурировать ландшафт? А как освещать объекты находящиеся на ландшафте? А как бросать тени с ландшафта на объекты и с объектов на ландшафт? Ну и много много других вопросов у меня было на тот момент. И это всё проблемы текстуринга. На моей карточке (ti4200) особо не разгуляешься с динамическим освещением, поэтому нужно было использовать лайтмапы. Итак, ещё раз, это всё проблемы текстуринга.
Побродив по просторам всемирной паутины, я нашел несколько интересных заметок по поводу генерации лайтмапов для heightmap ландшафтов. Среди полезных ресурсов по этой теме помню следующие: gamedev.net, gpwiki.org и хотелось бы отдельно выделить проект L3DT (Large 3D Terrain Generator), автор которого любезно придоставил некоторую информацию об алгоритмах, задействованых для работы с ландшафтом: http://www.bundysoft.com/docs/doku.php?id=l3dt:algorithms. Этим список сайтов не ограничивался. Вот еще несколько мест откуда я подчерпнул куски информации:
http://www.vterrain.org
http://www.shamusyoung.com/twentysidedtale/?p=141
Первой из разрешенных проблем стал лайтмап генератор. Как ни странно, он реализуется очень просто. Первые версии алгоритма умещались в 10-20 строк кода. Я думаю можно изложить тут основные принципы алгоритма.
Итак, чтобы построить лайтмап по карте высот, нам нужна, собственно, сама карта высот, а также построенная по ней карта нормалей. Как строить карту нормалей для карты высот, я не буду объяснять, я очень надеюсь, что читатель сам догадается как это сделать. Если же нет, то что ж, поищите об этом информацию в интернете.
Когда готовы все необходимые компоненты, нужно сделать следующее (по пунктам):
1. Сконвертировать лайтмап из 32 битной картинки в double (float) map для большей точности.
2. Увеличить раза в 2-4 heightmap, каким-нибудь bicubic фильтром (у меня было именно так).
3. Создаем пустой lightmap опять же в double (float) формате таких же размеров, как и увеличенный heightmap.
4. Генерируем диффузное освещение по какой-нибудь формуле, и записываем его в lightmap.
5. Дальше нужно сгенерировать непосредственно тени для ландшафта, это немного комплексный процесс, поэтому я выделю его в несколько подэтапов.
[
- Для каждого пикселя из heightmap'а с координатами X,Y берем точку R(X,Y,color(X,Y)). Т.е. это по сути точка нашего ландшафта.
- Запускаем цикл в котором:
- Двигаем точку по направлению истоника света
- Если значение color(R.x,R.y) больше R.z, то тогда прерываем цикл и переходим к следующему пикселю.
- Если же иначе, то записываем в пиксель лайтмапа (R.x,R.y) цвет тени.
]
Т.е. процесс генерации тени сводится к так называемому ray shadow casting'у. Когда проводится луч от каждой точки ландшафта по направлению света и на все точки ниже луча падает тень.
6. Теперь необходимо несколько улучшить полученный в итоге lightmap. Для этого нужно подобрать комбинацию из нескольких действий. Я эксперементировал с размыванием по гауссу и уменьшением размера lightmap'а bilinear фильтром.
7. Собственно, сконвертировать полученный лайтмап в 32 битный формат и загрузить в текстуру.
Как видно всё достаточно просто. Тем не менее, для меня ряд шагов оказался очень непростым. Например, я очень долго не мог понять, как правильно реализовать scale'инг heightmap/lightmap'ов, а также smooth'инг всё этих же картинок. Я думаю для таких же хобби-геймдевелоперов неплохо было бы написать туториал по kernel-based image processing'у. Не сразу также я пришел к выводу, что для нормальной точности нужен формат изображения с плавающей точкой. В целом всё было просто. Позже я поработал над оптимизацией всех частей алгоритма. В итоге получил генерацию лайтмапа для 256х256 heightmap'а примерно за две с половиной секунды. Более чем достаточно, учитывая, что генерация лайтмапа это не run time и даже не load time, оно precomputed.
Теперь чуть поподробней опишу как это всё у меня было устроено. Как я уже писал выше, для разбиения ландшафта на куски я использовал квадтри. И первая реализация опиралась на следующий подход. При загрузке (генерации) ландшафта из heightmap'а я разбивал его на куски указанного размера (nodes) и строил некий node map. Вот такие nodes и были концами веток quadtree дерева. К тому моменту я уже написал frustum culling невидимых nodes и мог любоваться ландшафтом с однообразной текстурой и лайтмапом на втором TMU в multiply режиме. Теперь подходила очередь системы текстуринга в несколько слоев (скалы, трава, камни, песок, etc.).
Думаю стоит напомнить, я и по сей день счастливый обладатель древней железки geforce 4 ti4200 с 64 метрами на борту. Поэтому даже в 2007 году мысли о шейдерах меня как-то не посещают пока (а я ещё на OpenGL пишу). FFP мой друг, и, судя по всему, ещё как минимум на год. Порыскав по инету я конечно же набрел на заметки о некой волшебной технике texture splatting, которая предлагала смешивать две текстуры основываясь на третьей. Ну что ж, 4 TMU nvidia не зря прикрутили на моей карточке — подумал я. Меньше чем через 5 минут я вспомнил о lightmap'е и получилось, что у меня уже забиты все TMU. Ограничивать себя двумя слоями как-то не особо хотелось. Что касается рендеринга в несколько проходов, то да, я рассматривал такой вариант. Тогда моей рабочей ОС был Linux (впрочем, как и сейчас) и среди игрушек у меня водился уже старенький UT2004. Как все знают, там есть режим onslaught, в котором присутствуют открытые пространства и ландшафты. И надо сказать UT2004 почти не тормозит на ti4200. Однако, встроенная инфа по рендерингу показывает, что на отрисовку ландшафта у них уходит от 3 до 10мс. И исходя из этой инфы, мне кажется они используют многопроходный рендеринг. Также замечу, что ландшафт у них рендерится достаточно брутфорсно, особых оптимизаций в виде динамического CLOD меша у них нет. UT2004 служила для меня ориентиром, хуже чем у них делать не хотелось. А если честно — хотелось сделать лучше. Всё это конечно хорошо, но как-то я не мог себя заставить смириться с мыслью о многопроходном рендеринге. Вариантом такую технику я не считал.
Всё началось вершиться, когда я наткнулся в сети на один сайт (http://gameprog.it/hosted/typhoon/terrain.htm). И как там сказано: "Terrain texturing relies on the real-time synthesis of textures." их движок действительно генерирует текстуры в реальном времени для ландшафта. Этот подход чем-то похож на технику Carmack'а megatexture, только тут текстуры не stream'ятся с винта, а генерируются из подручных материалов (маски, лайтмапы, tiled текстуры). Посмотрев внимательно скриншоты на сайте (разглядывал минут 10), я понял — это то, что мне нужно!.
Для начала предстояло узнать, как же я буду генерировать эти текстуры. Вариантов немного, а точнее два. CPU или GPU. Вариант на CPU отпал и этому поспособствовала лень. Нужно было реализовать быстрый блендинг и много всяких мелочей. А из плюсов было только то, что возможно пустить в отдельном потоке. А вот вариант на GPU меня очень даже устраивал. Железка быстрая, да и рендеринг в текстуры придумали не дураки. И тут первое «превед!». Товарищи из NVIDIA уже давно забили на поддержку драйверов для ti4200 в линуксе, поэтому в линуксе нету FBO на этой карте. Желанием возиться с pbuffer'ом (а я хотел чтобы мое чудо можно было портировать потом под винду) я не горел. Ну, и фиг со всем этим — подумал я тогда и реализовал рендеринг в текстуру простым glCopyTexImage, тем самым ограничил себя 512х512 текстурами. Вроде бы все проблемы были решены. Я приступил к реализации.
2 ноября 2007 (Обновление: 5 ноя 2007)
Комментарии [1]