Реалізація динаміки прапора із використанням 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 рядки відповідають за розрахунок позиції вершини у просторі. Як бачимо, координатиХі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-с" означає, що чим більша відстань, тим темніше ближче до 0).
Ось і все, що стосується шейдерної частини програми. Основною програмою залишається лише передати шейдеру необхідні дані, такі як: текстури, значення часу та амплітуди, положення джерела світла тощо.
Ось таке завдання. Повний текст програми можна завантажити звідси. Якщо виникнуть проблеми чи питання, пишіть.