Безкраен процедурно генериран град, получен с WFC алгоритъма
Автор на този материал за Unity е Мариан Клейнеберг (Marian Kleineberg), доста известен програмист и гейм дизайнер. Описва се практическото потребление на логаритъма WFC (Wave Function Collapse) – колапс на вълновата функционалност, в който са употребявани някои правила на квантовата механика. Алгоритъмът Wave Function Collapse генерира битови изображения, които са локално сходни на входящите битови иображения.
Нека да се спрем върху хипотетична компютърна игра, в която се разхождате в безконечен град, процедурно генериран по едно и също време с вашето напредване . Градът се построява от избран брой модули благодарение на WFC логаритъма (колапс на вълновата функция).
Готовата демо версия може да се изтегли от уеб страницата itch.io. Сорс кодът е оповестен в съответното вместилище на GitHub. Има и видео, в което е показано напредването в генерирания по този метод град.
Алгоритъмът
Ще назовавам с думата „клетка“ всеки детайл на 3D вокселната мрежа, който може да съдържа блок или да бъде празен. Думата „модул“ значи блокът, който блокът, който може да заема тази клетка.
Алгоритъмът взема решение кои модули да се избират за всяка клетка на игровия свят, Масивът от кафези се смята за вълнова функционалност, която няма по какъв начин да се следи. По този метод, на всяка клетка подхождат редица модули, които могат да се окажат в нея. Ако използваме термините на квантовата механика можем да кажем, че „клетката е в суперпозиция във връзка с всичките модули“. Съществуването на този игрови свят стартира в изцяло ненаблюдаем виртуален тип, в който във всяка клетка би могъл да се намира който и да е от всичките модули. След това всички кафези една след друга колапсират. Това в действителност значи, че за всяка клетка по инцидентен метод се избира един от всичките допустимо модули.
Следва стадия на разпространяване на рестриктивните мерки ( constraint propagation ). За всеки модул се сортират такова подмножество от модули, на които е позволено да бъдат в непосредствена непосредственост до него. Етапът за разпространяване на рестриктивните мерки изисква най-вече запаси от позиция на изчислителната мощ.
Важен аспект на логаритъма е определянето на клетката, която би трябвало да колапсира. Алгоритъмът постоянно намира клетката с най-малка ентропия. Това е клетката, допускаща най-малък брой разновидности на избора – т.е., клетката с най-малка безредица. Ако за всичко модули вероятността за колапс е идентична, то най-малка ентропия ще има клетката, която подхожда на най-малък брой модули.
Вероятността за другите модули да попаднат в дадена клетка е друга. Така да вземем за пример, клетка с два вероятни модула, които имат идентична възможност, дава по-голям избор (по-голяма ентропия) от тази, която може да има два модула, единият от които има доста огромна възможност да попадне в клетката спрямо другия, с доста по-малка възможност.
https://www.kaldata.com/wp-content/uploads/2019/06/wfc.mp4
Ето няколко образеца за работата на логаритъма Колапс на вълновата функционалност. Той в началото бе употребен за генериране на 2D текстури въз основата на единствен пример.
Блоковете, прототипите и модулите
Игровият свят се генерира благодарение на сбирка, съдържаща към 100 блока. Създадох ги благодарение на Blender. Първоначално тези блокове бяха напълно малко, само че последователно добавях нови и нови, когато считаха за нужно да разнообразя играта.
Алгоритъмът би трябвало да знае тъкмо какви модули могат да бъдат ситуирани компактно един до различен. За всеки модул е изработен лист с 6-те вероятни съседи – по един за всяко направление. Но по никакъв начин не мис се искаше да върша този лист ръчно. Освен това, желаех и автоматизирано да се генерират и разновидности, в които модулите са завъртени в една или друга посока.
Тези две задания се вземат решение благодарение на прототипи на модулите. На процедура това са MonoBehaviour, с които е доста комфортно да се работи в редактора на Unity. Модулите дружно със листата с допустимите прилежащи детайли и техните обърнати разновидности автоматизирано се основават благодарение на тези прототипи.
Възникна много комплициран проблем с моделиране на информацията по отношение на това, кои детайли могат да бъдат прилежащи. Ето какво направих:
Всеки един блок има по 6 контакта със прилежащите модули – по един на всяка страна. Всеки контакт си има номер. Осен това, хоризонталните контакти могат да бъдат елементарни, обърнати или симетрични. Вертикалните контакти имат показател на на завъртането от 0 до 3 или се означават като ротационно инвариантни.
Тази информация е задоволителна, с цел да може автоматизирано да се ревизира, кои модули могат да бъдат един до различен – модулите, които са един до различен, имат идентични номера на контактите. Също по този начин, би трябвало да съответствува и тяхната симетричност – идентичен показател на завъртането по вертикала или и двата модула би трябвало да бъдат или симетрични, или инвариантни.
Въведох и правила за изключенията, благодарение на които мога да не разрешавам съседствата, които по дифолт са позволени. Това е належащо, тъй като някои прилежащи блокове не наподобяват красиво един до различен. Ето един образец на карта (горното изображение), в която не са употребявани правила за изключения.
Пътят към безкрайността
Оригиналният логаритъм на колапса на вълновата функционалност генерира карти с стеснен размер. А аз желаех да направя свят, който да се уголемява и уголемява до момента в който се движите в него.
Първоначално пробвах да генерирам фрагменти с краен размер и да употребявам контактите на прилежащите фрагменти като ограничавания. Ако фрагментът е генериран, а прилежащият му откъс също е генериран, то се позволяват единствено модули, които съгласно разпоредбите могат да имат прилежащи модули от другия сегмент. Но при този метод поражда следния проблем: при всеки колапс на клетка, разпространяването на рестриктивните мерки блокира всичко даже и на разстояние от няколко кафези. В изображението по-долу са показани следствията от колапса на единствено една клетка:
Ако на всяка стъпка на този логаритъм се генерира единствено един откъс, то рестриктивните мерки не се популяризират на прилежащите фрагменти. В този случай, вътре във фрагмента се сортират такива модули, които биха се оказали неприемливи, в случай че се обърне внимание на другите фрагменти. В този случай, когато логаритъмът се пробва да генерира идващия откъс, не може да откри нито едно решение.
Сега към този момент не употребявам фрагменти, а протоколирам картата в масив, в който се записват клетките. Всяка клетка се запълва единствено когато е належащо. Необходима е лека промяна на логаритъма, с цел да се регистрира този миг. При избор на клетка, която следва да колапсира, няма по какъв начин да се плануват всички кафези, в случай че техният брой е безконечен. Вместо това генерираме единствено дребен сектор от картата, когато геймърът го доближи. Извън тази област рестриктивните мерки не престават да работят рестриктивните мерки.
Има случаи, при които този метод не работи. Ако се разгледаме нужните модули за принципен сектор на тунел, от време на време се получава този тунел да няма вход. Ако логаритъмът сътвори сходен тунелен модул, то съгласно разпоредбите ще получим безконечен тунел. За сходни случаи подбрах специфична сбирка от модули, които не разрешават възникването на този проблем.
Граничните условия
Трябва да се съобразяваме с две значими гранични условия. Всички граници и ръбове в горния завършек на карата би трябвало да контактуват с въздуха. А всички граници и ръбове в долната част на картата, би трябвало да имат твърди контакти. Ако тези гранични условия не бъдат спазени, стартират да се появяват кратери в земята, а някои здания остават без покриви.
На една карта с краен размер този проблем се взема решение елементарно. За всички кафези от най-високо и най-ниско равнище просто би трябвало да се отстранен всички модули с несъответствуващи контактувания. След това да се започва разпространението на рестриктивните мерки и да се махнат несъответствуващите модули.
Но в една карта с безконечен размер това няма по какъв начин да се направи, тъй като както в горното, по този начин и в долното поле, има безконечен брой кафези. Най-наивното решение е премахването на всички несъответствуващи кафези още с тяхното пораждане. Но при махането на модул от горното равнище се задействат рестриктивните мерки, засягащи околните до него кафези. Възниква лавинообразен резултат, който още веднъж води до безпределно заделяне на клетките.
Реших този проблем създавайки карта с размер 1×n×1, където n е височината. Тази карта употребява пръстенообразни светове за разпространяване на рестриктивните мерки. Механизмът на дейностите е като в играта Pacman – когато излезе отвън десния завършек на картата, персонажът се появява в лявата страна на картата,
Статут на грешките и търсене с връщане обратно
Понякога WFC логаритъма доближава такова положение, в което клетката не подхожда на нито един вероятен модул. В приложенията и игрите, в които имаме работа със свят с краен размер, можем просто да отхвърлим резултата и да стартираме изначало. В безкрайния свят това не работи, тъй като част от виртуалния свят към този момент е показана на геймъра. В началото в местата, където възникваха неточности от сходен жанр, просто запълвах с бели блокове.
Но в този момент употребявам търсене с връщане обратно. Редът за колапса на клетките и част от информацията за разпространяването на рестриктивните мерки се резервират в историята. Ако WFC логаритъмът откаже, то част от историята се анулира. Този метод работи добре, само че от време на време грешките се улавят прекомерно късно и тяхното намиране с връщане обратно изисква прекалено много итерации.
Според мен, поради това ограничаване, потреблението на WFC логаритъма го прави несъответствуващ за комерсиални игри. Но има доста места, където кодът може да бъде доста модернизиран.
Перспективите
Заех се с това, откакто присъствах на лекцията на Оскар Стелбърг, който сподели потреблението на съшия логаритъм за генериране на равнищата в играта Bad North.
Имам редица хрумвания за усъвършенстването на този логаритъм, само че не знам по кое време ще имам време да се заема и да прибавя и геймплей. А в случай че въпреки всичко един ден се заема с това, надали ще основа епичната игра, която вероятно си представяте. Но в случай че желаете да ревизирате по какъв начин работи с този логаритъм вашата обичана игрова механика – просто пробвайте и вие. В края на краищата това е отворен код, който е обществено наличен за всички и се популяризира посредством MIT лиценз.
Алгоритъмът Колапс на вълновата функционалност е осъществен на C++, Python, Kotlin, Rust, Julia, Haxe, JavaScript и е приспособен за Unity. Алгоритъмът генерира равнищата в Bad North, Caves of Qud, в някои по-малки игри, както и в редица прототипи на прототипи на игри. Неговото основаване докара до нови алгоритмични проучвания. Предлагат се огромен брой пояснения, интерактивни демонстрации, управления, образователни материали и доста образци.
Нека да се спрем върху хипотетична компютърна игра, в която се разхождате в безконечен град, процедурно генериран по едно и също време с вашето напредване . Градът се построява от избран брой модули благодарение на WFC логаритъма (колапс на вълновата функция).
Готовата демо версия може да се изтегли от уеб страницата itch.io. Сорс кодът е оповестен в съответното вместилище на GitHub. Има и видео, в което е показано напредването в генерирания по този метод град.
Алгоритъмът
Ще назовавам с думата „клетка“ всеки детайл на 3D вокселната мрежа, който може да съдържа блок или да бъде празен. Думата „модул“ значи блокът, който блокът, който може да заема тази клетка.
Алгоритъмът взема решение кои модули да се избират за всяка клетка на игровия свят, Масивът от кафези се смята за вълнова функционалност, която няма по какъв начин да се следи. По този метод, на всяка клетка подхождат редица модули, които могат да се окажат в нея. Ако използваме термините на квантовата механика можем да кажем, че „клетката е в суперпозиция във връзка с всичките модули“. Съществуването на този игрови свят стартира в изцяло ненаблюдаем виртуален тип, в който във всяка клетка би могъл да се намира който и да е от всичките модули. След това всички кафези една след друга колапсират. Това в действителност значи, че за всяка клетка по инцидентен метод се избира един от всичките допустимо модули.
Следва стадия на разпространяване на рестриктивните мерки ( constraint propagation ). За всеки модул се сортират такова подмножество от модули, на които е позволено да бъдат в непосредствена непосредственост до него. Етапът за разпространяване на рестриктивните мерки изисква най-вече запаси от позиция на изчислителната мощ.
Важен аспект на логаритъма е определянето на клетката, която би трябвало да колапсира. Алгоритъмът постоянно намира клетката с най-малка ентропия. Това е клетката, допускаща най-малък брой разновидности на избора – т.е., клетката с най-малка безредица. Ако за всичко модули вероятността за колапс е идентична, то най-малка ентропия ще има клетката, която подхожда на най-малък брой модули.
Вероятността за другите модули да попаднат в дадена клетка е друга. Така да вземем за пример, клетка с два вероятни модула, които имат идентична възможност, дава по-голям избор (по-голяма ентропия) от тази, която може да има два модула, единият от които има доста огромна възможност да попадне в клетката спрямо другия, с доста по-малка възможност.
https://www.kaldata.com/wp-content/uploads/2019/06/wfc.mp4
Ето няколко образеца за работата на логаритъма Колапс на вълновата функционалност. Той в началото бе употребен за генериране на 2D текстури въз основата на единствен пример.
Блоковете, прототипите и модулите
Игровият свят се генерира благодарение на сбирка, съдържаща към 100 блока. Създадох ги благодарение на Blender. Първоначално тези блокове бяха напълно малко, само че последователно добавях нови и нови, когато считаха за нужно да разнообразя играта.
Алгоритъмът би трябвало да знае тъкмо какви модули могат да бъдат ситуирани компактно един до различен. За всеки модул е изработен лист с 6-те вероятни съседи – по един за всяко направление. Но по никакъв начин не мис се искаше да върша този лист ръчно. Освен това, желаех и автоматизирано да се генерират и разновидности, в които модулите са завъртени в една или друга посока.
Тези две задания се вземат решение благодарение на прототипи на модулите. На процедура това са MonoBehaviour, с които е доста комфортно да се работи в редактора на Unity. Модулите дружно със листата с допустимите прилежащи детайли и техните обърнати разновидности автоматизирано се основават благодарение на тези прототипи.
Възникна много комплициран проблем с моделиране на информацията по отношение на това, кои детайли могат да бъдат прилежащи. Ето какво направих:
Всеки един блок има по 6 контакта със прилежащите модули – по един на всяка страна. Всеки контакт си има номер. Осен това, хоризонталните контакти могат да бъдат елементарни, обърнати или симетрични. Вертикалните контакти имат показател на на завъртането от 0 до 3 или се означават като ротационно инвариантни.
Тази информация е задоволителна, с цел да може автоматизирано да се ревизира, кои модули могат да бъдат един до различен – модулите, които са един до различен, имат идентични номера на контактите. Също по този начин, би трябвало да съответствува и тяхната симетричност – идентичен показател на завъртането по вертикала или и двата модула би трябвало да бъдат или симетрични, или инвариантни.
Въведох и правила за изключенията, благодарение на които мога да не разрешавам съседствата, които по дифолт са позволени. Това е належащо, тъй като някои прилежащи блокове не наподобяват красиво един до различен. Ето един образец на карта (горното изображение), в която не са употребявани правила за изключения.
Пътят към безкрайността
Оригиналният логаритъм на колапса на вълновата функционалност генерира карти с стеснен размер. А аз желаех да направя свят, който да се уголемява и уголемява до момента в който се движите в него.
Първоначално пробвах да генерирам фрагменти с краен размер и да употребявам контактите на прилежащите фрагменти като ограничавания. Ако фрагментът е генериран, а прилежащият му откъс също е генериран, то се позволяват единствено модули, които съгласно разпоредбите могат да имат прилежащи модули от другия сегмент. Но при този метод поражда следния проблем: при всеки колапс на клетка, разпространяването на рестриктивните мерки блокира всичко даже и на разстояние от няколко кафези. В изображението по-долу са показани следствията от колапса на единствено една клетка:
Ако на всяка стъпка на този логаритъм се генерира единствено един откъс, то рестриктивните мерки не се популяризират на прилежащите фрагменти. В този случай, вътре във фрагмента се сортират такива модули, които биха се оказали неприемливи, в случай че се обърне внимание на другите фрагменти. В този случай, когато логаритъмът се пробва да генерира идващия откъс, не може да откри нито едно решение.
Сега към този момент не употребявам фрагменти, а протоколирам картата в масив, в който се записват клетките. Всяка клетка се запълва единствено когато е належащо. Необходима е лека промяна на логаритъма, с цел да се регистрира този миг. При избор на клетка, която следва да колапсира, няма по какъв начин да се плануват всички кафези, в случай че техният брой е безконечен. Вместо това генерираме единствено дребен сектор от картата, когато геймърът го доближи. Извън тази област рестриктивните мерки не престават да работят рестриктивните мерки.
Има случаи, при които този метод не работи. Ако се разгледаме нужните модули за принципен сектор на тунел, от време на време се получава този тунел да няма вход. Ако логаритъмът сътвори сходен тунелен модул, то съгласно разпоредбите ще получим безконечен тунел. За сходни случаи подбрах специфична сбирка от модули, които не разрешават възникването на този проблем.
Граничните условия
Трябва да се съобразяваме с две значими гранични условия. Всички граници и ръбове в горния завършек на карата би трябвало да контактуват с въздуха. А всички граници и ръбове в долната част на картата, би трябвало да имат твърди контакти. Ако тези гранични условия не бъдат спазени, стартират да се появяват кратери в земята, а някои здания остават без покриви.
На една карта с краен размер този проблем се взема решение елементарно. За всички кафези от най-високо и най-ниско равнище просто би трябвало да се отстранен всички модули с несъответствуващи контактувания. След това да се започва разпространението на рестриктивните мерки и да се махнат несъответствуващите модули.
Но в една карта с безконечен размер това няма по какъв начин да се направи, тъй като както в горното, по този начин и в долното поле, има безконечен брой кафези. Най-наивното решение е премахването на всички несъответствуващи кафези още с тяхното пораждане. Но при махането на модул от горното равнище се задействат рестриктивните мерки, засягащи околните до него кафези. Възниква лавинообразен резултат, който още веднъж води до безпределно заделяне на клетките.
Реших този проблем създавайки карта с размер 1×n×1, където n е височината. Тази карта употребява пръстенообразни светове за разпространяване на рестриктивните мерки. Механизмът на дейностите е като в играта Pacman – когато излезе отвън десния завършек на картата, персонажът се появява в лявата страна на картата,
Статут на грешките и търсене с връщане обратно
Понякога WFC логаритъма доближава такова положение, в което клетката не подхожда на нито един вероятен модул. В приложенията и игрите, в които имаме работа със свят с краен размер, можем просто да отхвърлим резултата и да стартираме изначало. В безкрайния свят това не работи, тъй като част от виртуалния свят към този момент е показана на геймъра. В началото в местата, където възникваха неточности от сходен жанр, просто запълвах с бели блокове.
Но в този момент употребявам търсене с връщане обратно. Редът за колапса на клетките и част от информацията за разпространяването на рестриктивните мерки се резервират в историята. Ако WFC логаритъмът откаже, то част от историята се анулира. Този метод работи добре, само че от време на време грешките се улавят прекомерно късно и тяхното намиране с връщане обратно изисква прекалено много итерации.
Според мен, поради това ограничаване, потреблението на WFC логаритъма го прави несъответствуващ за комерсиални игри. Но има доста места, където кодът може да бъде доста модернизиран.
Перспективите
Заех се с това, откакто присъствах на лекцията на Оскар Стелбърг, който сподели потреблението на съшия логаритъм за генериране на равнищата в играта Bad North.
Имам редица хрумвания за усъвършенстването на този логаритъм, само че не знам по кое време ще имам време да се заема и да прибавя и геймплей. А в случай че въпреки всичко един ден се заема с това, надали ще основа епичната игра, която вероятно си представяте. Но в случай че желаете да ревизирате по какъв начин работи с този логаритъм вашата обичана игрова механика – просто пробвайте и вие. В края на краищата това е отворен код, който е обществено наличен за всички и се популяризира посредством MIT лиценз.
Алгоритъмът Колапс на вълновата функционалност е осъществен на C++, Python, Kotlin, Rust, Julia, Haxe, JavaScript и е приспособен за Unity. Алгоритъмът генерира равнищата в Bad North, Caves of Qud, в някои по-малки игри, както и в редица прототипи на прототипи на игри. Неговото основаване докара до нови алгоритмични проучвания. Предлагат се огромен брой пояснения, интерактивни демонстрации, управления, образователни материали и доста образци.
Източник: kaldata.com
КОМЕНТАРИ




