Pull to refresh

u-Nebula: первое свидание

Game development *
Сразу хочется извиниться за задержку с обзорной статьей по движку и поблагодарить за поддержку и комментарии к предыдущему посту. Отклонение от ранее взятых обязательств было вызвано возней с нашим художеством и желанием сделать вводную статью с использованием Lisp’a. К сожалению поддержка Lisp’a у нас пока весьма сырая так, что мы воспользуемся надежным Tcl. В конце пути, у нас должна получится визуализация медитативно-расслабляющего плана (применять в случае зимней депрессии).

Под катом текст, код и картинки.

Часть I. Земля

Для начала нужно скачать и установить последний SDK. Инсталлировать нужно не в Program Files, так как в сценах многое генерируется на лету и могут возникнуть проблемы с правами доступа.

Для желающих самостоятельно собрать движок из исходников, есть памятка , но необходимости в этом нет — все можно сделать в бегущей демонстрации CityGen.
Что может оказаться полезным из памятки так это генерация документации по командам движка, для этого надо запустить autodoc.py из папки bin и открыть index.html в папке doc/autodoc (здесь и далее все папки будут указываться относительно папки nebula определенной во время установки).

Приступим? Пожалуй, самое время.
Запускаем CityGen из стартового меню и видим картинку со сгенерированным на ходу пейзажем. Да-да, пейзаж слегка подкачал, но это студенческая работа и цель её в проверке того, что мы наустонавливали, так что кислое выражения лица, в данном случае, ни к чему.
Сгенерированный городок
Теперь у нас есть 2 способа для работы/экспериментов:
1. Вызвать консоль нажатием Esc. Она появится на фоне картинки.
2. Нажатием на F2 вызвать nbrowser. Вот как он выглядит:
nbrowser - проводник по бегущему движку
Не будем описывать nbrowser в этой статье (у него достаточно простой интерфейс и если будут желающие, то мы набросаем короткий пост с описанием его возможностей), а потому воспользуемся встроенной консолью.

Итак бесстрашно жмем Esc и получаем, после различных логов, командную строку вроде этой:/usr/scene>

Надо отметить, что в nebul’е все объекты движка это экземпляры классов С++, наследники класса nroot, и во время исполнения организованы в иерархическую древовидную систему с путями наподобие файловой. Пути могут быть как абсолютными так и относительными. Узел дерева с которого начинается визуализация (rendering) т.н. корень сцены, в nebul’е традиционно находится в /usr/scene, где мы с вами, судя по промпту, и находимся. Команды (функции), за исключением нескольких базовых, являются вызовами оригинальных методов оригинальных классов и могут отдаваться как текущему узлу так и произвольному объекту с указанным путем.
В качестве примера, гламурные дамы могут сменить цвет буковок консоли на розовый:

/sys/servers/console.seticolor 255 10 100 255


После того, как мы насладились живописным видом текущей сцены, давайте ее быстренько сотрем, только аккуратно, чтоб не удалить лишнего (камеру пользователя например — без нее рендеринг рухнет).

Чтобы узнать, что у нас есть в текущем узле используем команду dir

/usr/scene>dir
cam skybox sun city landscape lights machine 


Камеру, солнце и свет мы пожалуй оставим а остальное уничтожим не дрогнувшей рукой:
delete machine
delete city
delete landscape
delete skybox


И удалили мы небо и землю… и стало пусто… (но светло т.к. свет оставили).

Ну что ж, теперь можно и создать что-нибудь. Только вот начнем мы с музыки — настроение, типа, и всё прочее. Перейдем в папку где лежат заранее подготовленные (т.к. их создание лежит за пределами данной статьи) мешы, текстуры и музыка. Для перехода по настоящей файловой системе в tcl используется обычная команда cd. Нам надо попасть в папку tutorials.

cd ../tutorials


Создаем звуковой узел:
new nsoundnode music
sel music
.setlooping 1
.setfile Souls_of_Gaia_V.2_Sotano_Sellado.ogg
sel ..

Для того чтобы не вводить в консоли сложно-длинное имя звукового файла можно применить техническую хитрость — .setfile [glob *.ogg], т.к. *.ogg файл у нас всего один — проблем не предвидится.

К выше использованным командам требуется небольшое пояснение. Переход по узлам дерева осуществляется с помощью sel (от select) аналогично команде cd, соответственно подняться на уровень вверх будет sel ... Kомандой new nsoundnode music мы создали звуковой узел с именем music. new принимает первым аргументом класс, в данном случае — nsoundnode, вторым путь, он же имя объекта, в данном случае — локальный music и возвращает созданный объект, в случае с tcl, это строка с путем. Текущий узел возвращает команда psel. Командой .setlooping 1 мы попросили музыку не покидать нас пока бежит аппликация. Ну и, наконец, командой .setfile указали какой звуковой файл мы хотим проиграть (поддерживаются ogg/vorbis и wav форматы).

Вот нам и веселее. Поблагодарим автора PeerGynt Lobogris и сайт jamendo за это произведение и приободренные пойдем дальше.
Не мешало бы как-то начать ориентироваться в слепленной нами пустоте и черноте, и поможет нам в этом конечно же грид, для создания которого у нас имеется готовая команда:
::visual_debug::create_grid 30 60

первый параметр (30) — задает размер плоскости грида;
второй (60) — количество ячеек;
дополнительно можно задать цвет, например так:
::visual_debug::create_grid 30 60 {0.75 0.3 0.0 1.0}

Пространственная навигация происходит с помощью игровых клавиш AWSD, стрелок (при выключенной консоли) и мышки с нажатыми левой, для движения драгом, или правой, для поворотов, кнопками (в распоряжении энтузиастов настройки в файле script/cityviewer/input.tcl). Выключаем консоль (Esc). Осматриваемся…
Консоль с гридом

Пора нам вернуть небо. И снова на помощь приходит готовая команда:
create_skybox stars1

Небо создано — на очереди Земля. Это дело посерьезней, команд будет больше ( ; и следующие за ней комментарии вводить, разумеется, необязательно):
sel [new n3dnode shape] ; #- создаем и сразу выбираем узел типа n3dnode с именем shape.
.tz -10; # -  ставим  наш объект в точку с координатами 0, 0, -10 относительно родительского узла
[new nmeshnode mesh].setfilename sphere.n3d ; # - создаем под узлом shape узел с мешем (геометрическая модель, заданная треугольниками).
sel [new nshadernode shader]; #- шейдер, описывает параметры материала, освещения и отекстуривания 
.setnumstages 1; #- один текстурный проход 
.setcolorop 0 "replace tex"; #- используем только текстуру, без света и материала
.begintunit 0; #- описываем первый текстурный проход
.settexcoordsrc "uv0"; #- тексели берем из готовой карты номер 0 (поставляется в файле с мешем)
.endtunit; #- закончили описание первого текстурного прохода
sel ..; #- выходим из узла 

[new ntexarraynode tex].settexture 0 "globe.jpg" none; #- создаем тектурный узел и назначаем ему файл с текстурой, одной командой

Ну вот, должна быть где-то тут — ищем на гриде, мышью, кнопками, всем ищем. Если не находим, не отчаиваемся, жмем пробел. И вот перед нами Земля (в иллюминаторе, Земля в иллюминаторе видна-а-а-а).
И вот перед нами Земля
Что же мы собственно натворили? Смотрим на код:
sel [new n3dnode shape] — создаем и сразу выбираем узел типа n3dnode с именем shape.
В общем случае, видимый объект в нашем движке представляет собой поддерево, корнем которого является узел содержащий геометрическую трансформацию, относительно родителя. Такой узел относится к классу типа n3dnode или его наследникам. С методами этого класса можно познакомиться на страничке документации которую мы с вами сгенерировали вначале. Сейчас нам понадобилась всего одна команда .tz -10, которая поставит наш объект в точку с координатами 0, 0, -10 относительно родительского узла т.е. сцены.
Следующая команда [new nmeshnode mesh].setfilename sphere.n3d создаст под узлом shape узел с мешем (геометрическая модель, заданная треугольниками). В данном случае мы используем объект класса nmeshnode с именем mesh и немедленно назначаем ему файл с моделью сферы — sphere.n3d. Файл типа .n3d представляет из себя читаемый файл в ASCII формате. Есть также бинарный формат .nvx.
sel [new nshadernode shader] — следующий подузел узла shape, описывает параметры материала, освещения и отекстуривания нашей модели для фиксированного конвеера (наш движок поддерживает и программные шейдеры). Для простоты, тут приведен самый элементарный и «бедненький» шейдер, без параметров освещения и материала.
Ну и наконец текстурный узел [new ntexarraynode tex].settexture 0 «globe.jpg» none, последний подузел нашего shape.
И так повторим еще раз модель видимого объекта в общем виде:
Т — трансформация относительно родителя — n3dnode, с подузлами:
|- М — геометрическая модель, статическая или динамическая — nmeshnode, nmeshcluster, nmeshipol и.т.д.
|- S(опционально) — шейдер, простой или программный — nshadernode, nshaderprogramnode
|- T(опционально) — текстура, ntexarraynode, pdtexarraynode

Стоп! Чего-то явно не хватает. Что там Галилей бубнил на комиссии...?
Убедимся что мы все еще в узле shape
sel /usr/scene/shape
sel [new nipol rot]	
.connect ry		
.addkey1f 0 0
.addkey1f 15 360
sel ..	

Да, вот, теперь “она вертится”!

Давайте сохраним результат нашего труда:
sel /usr/scene
.saveas earth

Теперь в папке data/tutorials можно найти скрипт на Tcl с названием earth.n. Можно его почитать. Чтобы убедиться в том, что все сохранилось, твердой рукой сотрем наше творение:
delete shape

И загрузим его снова, но уже как файл:
.parse earth.n


В принципе, на этом можно было бы остановиться и, поблагодарив терпеливого читателя-экспериментатора за внимание, скромно сойти со сцены, но на носу Новый Год, а небольшое праздничное чудо еще никто не отменял. Так что давайте его сотворим.

Часть II. Не Земля


Стряхнем прах земной, уже привычными командами:
sel /usr/scene
delete shape

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

set mesh_files [glob *.n3d];# - берем список файлов с мешами

set meshes {};# - создаем пустой список имен мешей 
foreach m $mesh_files {
lappend meshes [file rootname [file tail $m]] ;# - заполняем его отбрасывая путь и расширение
}

sel [new n3dnode shape];# - создаем и выбираем узел трансформации
.txyz 0 1 -10;# - задаем его позицию
sel [new nspriterender sr];# - создаем и выбираем рендерер спрайтов	
sel [new nstaticmeshemitter me];# - создаем и выбираем статический генератор частиц на базе вершин меша
sel [new nmeshipol mesh];# - создаем и выбираем интерполятор мешей (морфер)
# - проходимся в цикле по найденым мешам и содаем из них узлы для морфинга
#  в командах внутри foreach  необходимо ставить ; - баг консоли движка, исправим
foreach m $meshes { 
new nmeshnode $m;
$m.setfilename $m.n3d;
$m.setreadonly true; # - только чтение, такие узлы не рендерятся, а служат сырьем, не забываем ;
}
.setreadonly true;# - как и наш морфер
set i 0
.beginkeys [expr [llength $meshes] * 2 + 1];# - определяем количество, "ключей" для морфинга 
#  тут тоже  необходимо ставить ; в командах внутри foreach
foreach m $meshes {
.setkey $i [expr $i*3] $m; # - каждый меш будет виден 3 сек, а затем 3 сек морфиться со следующим 
incr i;
.setkey $i [expr $i*3] $m;
incr i;
}	
.setkey $i [expr $i*3] [lindex $meshes 0];# - последним ключем 1й меш, чтобы не было скачка
.endkeys;# - с "ключами" покончено    

.setupdatecoord true;# - морфим координаты меша
.setupdatenorm  true;# - морфим нормали меша
.setupdateuv0   true;# - морфим тексели 0го слоя
sel ..;# - выходим в генератор частиц

.setlifetime 10;# - время жизни частицы
.setmeshnode mesh;# - назначаем "сырьевой" узел для генрации частиц		
sel ..;# - выходим в рендерер спрайтов
set sz 0.075;# - размер спрайта 
set bn 0.025;# - колебания
.beginkeys 4;# - ключи анимации
.setkey 0 $sz $bn 1 1 1 1
.setkey 1 $sz   0 1 1 1 1
.setkey 2 $sz   0 1 1 1 1
.setkey 3 $sz   0 1 1 1 1
.endkeys
.setemitter "me";# - назначаем "сырьевой" узел для спрайтов 
sel ..;# - выходим в shape

sel [new nshadernode shader];# - создаем и выбираем шейдер
.setnumstages 1;# - один тесктурный проход
.setcolorop 0 "mul tex const";# - умножаем тесктуры на константу для цвета
.setalphaop 0 "replace tex";# - прозрачность толко из тесктуры
.setconst 0 1 1 1 1;# - цвет, в дальнейшем будет меняться 
.begintunit 0;# - параметры текстурного прохода
.setminmagfilter "linear_mipmap_nearest" "linear"
.settexcoordsrc "uv0"
.endtunit
.setlightenable false;# - свет в расчет не берем
.setalphaenable true;# - разрешаем использовать прозрачность
.setzwriteenable false;# - не пишем в z-buffer
.setcullmode "none";# - не отсекаем "задние" грани 
sel [new nipol ipol];# - создаем и выбираем аниматор - интерполятор для цвета
.connect setconst0 ;# - подключаемся к команде setconst0 
.addkey4f 0 1 0 0 1; # red
.addkey4f 1 1 1 0 1; # yellow 
.addkey4f 2 0 1 0 1; # green
.addkey4f 3 0 1 1 1; # cyan
.addkey4f 4 0 0 1 1; # blue
.addkey4f 5 1 0 1 1; # magenta
.addkey4f 6 1 1 1 1; # white
sel ..
sel ..

[new ntexarraynode tex].settexture 0 "lib:textures/glow-flat-a.png" "none";# - текстура 
sel [new nipol rot];# - вертим вокруг оси Y	
.connect ry		
.addkey1f 0 0
.addkey1f 15 360
sel ..	
sel ..


Небольшое пояснение к этой части. Морфинг который у нас получился в результате, использует заранее сгенерированные меши. Необходимым условием для морфинга является одинаковое число вершин. Остальной код мы оставляем для самостоятельного изучения. Вопросы разумеется приветствуются!

Для того чтобы загрузить первую часть статьи, достаточно набрать:
cd ../tutorials
source tutorial0.tcl

Затем для запуска 2й части:
part2


Ну вот и все!
Вот что, получилось.

:) Поздравляем, всех читателей Хабра, С Наступающим Новым Годом!

P.S. Если вдруг возникнут проблемы при запуске, то попробуйте один из следующих вариантов:
1. обновить DirectX (мы используем июньский SDK)
2. запустить CityGen-GL чтобы движок использовал OpenGL.
Tags:
Hubs:
Total votes 22: ↑21 and ↓1 +20
Views 1.6K
Comments 15
Comments Comments 15

Posts