Внедряване на динамика на флагове с помощта на Cg

В началото на 2003 г. www.ixbt.com обяви конкурс за шейдъри, написани на новоизсечения език Cg. Тъй като наградите бяха много примамливи, а аз вече имах известен опит в програмирането на шейдъри, беше грехота да не участвам.

И така, идеята ми беше да изобразя знаме, което се люлее от вятъра. Освен това флагът трябва да бъде осветен от точков източник, движещ се в кръг (фиг. 1).

Така задачата автоматично се разделя на две:

Първи- за осигуряване на развяването на знамето и с променлива амплитуда на развяване. Тази задача беше възложена навертекс шейдъра.

Второ- да се осигури осветеност на флага, като трикът беше, че за да се постигне плавно засенчване, трябва да се правят изчисления върху всяка точка. Но това не е всичко. Трябва да накарамепикселния шейдърда проследява определени зони на флага и да ги маркира с бели точки, подобно на това как големите градове обикновено светят от сенчестата страна на Земята.

Ето пълния текст навертекс шейдъра:

Структуратаappdataотговаря за входните данни към шейдъра. Нека го разгледаме по-подробно. С помощта на променливатаpositionпредаваме позицията на върха в световните координати,tex0- координатите на UV текстурата. Но трябва да обърнете специално внимание на променливатаlight_pos! Едно от предимствата на Cg е, че можете да предавате данни от съвместим тип към шейдъра чрез регистри, които имат напълно различна цел. Както в случая с използването наlight_pos. На пръв поглед променливатаlight_posсе използва за представяне на цвета. Но ние всъщност го използваме, за да предадем позицията на източника на светлина. Това е много успешен финт, койтотрябва да се вземат предвид при по-нататъшна употреба.

След това описанието на структуратаvfconnследва в програмата. Тя отговаря за предаването на данни директно до точките вътре в полигона.

С позицията и цвета всичко изглежда ясно (не забравяйте, че с помощта на цвят предаваме позицията на източника на светлина). Но за координатите на текстурата трябваше да разпределим още 2 допълнителни регистъра. Защо това беше необходимо, ще обясня малко по-късно. А сега нека да преминем директно към функциятаmain().

Както можете да видите, към него се предават 3 променливи: 1. Матрица, която е продукт на изгледна и проекционна матрици. 2. Времето, изминало от началото на програмата. С тази променлива ще накараме флага да се разклати. 3. Максималната амплитуда на размахване на флага.

В тялото на функцията първо се дефинира работна променлива, която в резултат на това ще върнем от функцията. Съответно, той трябва да бъде от същия тип (vfconn) като функцията.

Следващите 3 реда са отговорни за изчисляването на позицията на върха в пространството. Както можете да видите, координатитеXиYостават непроменени, докато координататаZсе променя според синусоидалния закон, което създава осцилиращия ефект. Помните ли общия изглед на осцилаторния процес? Ако не, тогава ще ви напомня:Y=A*sin(B*X+T), къдетоAе амплитудата,Bе периодът,Tе фазовото отместване (е, трябва да знаете тази фраза :-)).

Ето как се изчислява координататаZна флага. Единственото нещо, което отличава движението на моето знаме от движението по синусоида, е намаляването на амплитудата на люлеенето на знамето в близост до пилона. Този ефект се постига чрез умножаване на стойността на амплитудата по x-координатата на текстурата. Остава да умножим получените координати по трансформационната матрица.

След това умножаваме координатите на текстурата,предаваме позицията на източника на светлина на пикселния шейдър (отново, чрез цвят) и след това връщаме всички получени данни къмmain. Всичко. Освен това данните ще бъдат обработени вече в пикселния шейдър, чието описание е дадено по-долу. Ето пълния му код:

Първото нещо, което може да привлече вниманието ви, е липсата на описание на структурите за въвеждане и извеждане на данни. Въпросът е, че за вход/изход ще използваме малко по-различен метод от този, използван във вертекс шейдъра. Но на първо място.

В първите редове декларираме самата функция и променливите. Ако описанието на променливата съдържаin, това означава, че в тази променлива предаваме данни към шейдъра, акоout- връщаме се от него. Пикселният шейдър може да върне само цвят (COLOR) или дълбочина (DEPTH). В този случай шейдърът връща цвят. Променливитеtex1иtex2съдържат указатели към плоски текстури.tex1е основната текстура (фиг.1), аtex2е текстурата с маркирани "светещи точки" на картата (фиг.2).

Да преминем към тялото на функцията. В първия ред декларирам 3 променливи:TexColor1,TexColor2иa1. ВTexColor1иTexColor2незабавно задаваме цвета на съответните текстурни точки. Това се прави с помощта на функциятаtex2D(). Той има една особеност - след изпълнението му, променливата, съдържаща координатите на текстурата, се "разваля", тоест не носи стойностите, които е носила преди изпълнението наtex*()(внимавайте, не се хващайте на тази стръв в бъдеще!) Ето защо ние умножихме координатите на текстурата във вертекс шейдъра.

В следващия ред намираме вектора от източника на светлина до точката на многоъгълника (използвайки векторната разлика) и извличаме компонентите x и y от резултата.

След това към променливатаcпишем нещо подобно :-) за разстоянието от източника на светлина до точката. Намира се доста просто. Ако някой е забравил, напомням ви, че самото скаларно произведение на вектор дава квадрат на неговата дължина. Нотациятаdot(a1,a1)говори сама за себе си (между другото, бях посъветван да използвам този метод от самия IronPeter, победител в това състезание, за което съм му особено благодарен). Вярно е, че също умножих получената стойност по 4, за да увелича площта на осветената зона. И за да може размерът на осветената зона да зависи от разстоянието от източника на светлина до флага, добавих z-координатата на позицията на източника на светлина към разстоянието (също в квадрат, за да спазя размерите).

И накрая, време е директно да изчислим цвета на точката на флага. Тук не бива да забравяме трика, който планирахме в самото начало, а именно: необходимо е определени области на флага, които са маркирани на отделна текстура, да светят (за щастие, в пикселните шейдъри можете да създадете прости условия, подобни на C++ оператора " ? : "). В условието проверяваме за наличието на черно върху втората текстура. Ако цветът е черен (произведението на rgb компонентите е сравнимо с 0), тогава шейдърът връща бяло (RGB==1). Ако не е, тогава цветът на текстурата се връща, умножен по стойността на разстоянието („1-s“ означава, че колкото по-голямо е разстоянието, толкова по-тъмно е, по-близо до 0).

Това е всичко за шейдърната част на програмата. Основната програма трябва само да предаде необходимите данни на шейдъра, като текстури, стойности на времето и амплитудата, позицията на източника на светлина и т.н.

Ето такъв проблем. Пълният текст на програмата можете да изтеглите от тук. Ако имате проблеми или въпроси пишете.