Перейти к содержимому
Strategium.ru

Программирование шейдеров на GLSL


kuzmich774

Рекомендованные сообщения

kuzmich774
(изменено)

Введение

 

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

Практически каждая современная графическая сцена являет собой результат работы некоторого кода, написанного специально для GPU — от реалистичных эффектов освещения в новейших ААА-играх до 2D-эффектов и симуляции жидкости.

 

8iHVJuL.jpeg

Сцена в Minecraft до и после применения нескольких шейдеров

 

Цель этой инструкции


Программирование шейдеров иногда кажется загадочной черной магией. Тут и там можно встретить отдельные куски кода шейдеров, которые обещают вам невероятные эффекты и, возможно, вправду способны их обеспечить — но при этом совершенно не объясняют, что именно они делают и как добиваются столь впечатляющих результатов. Данная статья попробует закрыть этот пробел. Я сфокусируюсь на базовых вещах и терминах, касающихся написания и понимания шейдерного кода, так что впоследствии вы сами сможете менять код шейдеров, комбинировать их или писать свои собственные с нуля.

Что такое шейдер?


Шейдер — это просто программа, которая запускается на одном из графических ядер и говорит видеокарте, как нужно отрисовать каждый пиксель. Программы называются «шейдерами», поскольку они часто используются для контроля эффектов Войдите или зарегистрируйтесь, чтобы увидеть скрытое содержимое.  и Войдите или зарегистрируйтесь, чтобы увидеть скрытое содержимое.  («shading»). Но, конечно, нет никаких причин ограничиваться только этими эффектами.

Шейдеры пишутся на специальном языке программирования. Не беспокойтесь, вам не нужно прямо сейчас идти и изучать с нуля новый язык программирования. Мы будем использовать GLSL (OpenGL Shading Language), (который имеет С-подобный синтаксис),который уже упоминался в статье Войдите или зарегистрируйтесь, чтобы увидеть скрытое содержимое. . Существуют и Войдите или зарегистрируйтесь, чтобы увидеть скрытое содержимое.  языки программирования шейдеров под различные платформы, но, поскольку их конечной целью является всё тот же запуск кода на GPU, они имеют достаточно схожие принципы.

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

Поехали!


Для наших экспериментов мы воспользуемся Войдите или зарегистрируйтесь, чтобы увидеть скрытое содержимое. . Это позволит вам взять и начать писать шейдерный код здесь и сейчас, не откладывая это дело на потом из-за необходимости устанавливать какие-то определённые инструменты или SDK. Единственное, что вам необходимо — это браузер с поддержкой WebGL. Создавать аккаунт на ShaderToy не обязательно (только, если вы захотите сохранить там свой код).

Заметка: ShaderToy сейчас в стадии беты, так что на момент прочтения вами этой статьи некоторые нюансы его UI могут измениться.

Итак, нажимаем кнопку Войдите или зарегистрируйтесь, чтобы увидеть скрытое содержимое.  в правом углу, что приведёт к созданию нового шейдера:

 

OT0L7RU.jpeg

 

Маленькая чёрная стрелка под кодом компилирует и запускает шейдер.
 

Что здесь происходит?


Я сейчас объясню, как работает шейдер, ровно одним предложением. Вы готовы? Вот оно. Единственным предназначением шейдера является вернуть четыре числа: r, g, b и a.
Это всё, что может и должен сделать шейдер.

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

Итак, теперь у нас есть достаточно знаний для того, чтобы, например, залить весь экран чистым красным цветом. Значения каждой из компонент rgba (red, green, blue и «alpha» — то есть «прозрачность») может быть в диапазоне от 0 до 1, так что в нашем случае мы просто вернем r,g,b,a = 1,0,0,1. ShaderToy ожидает финальный цвет пикселя в переменной fragColor.
 

Войдите или зарегистрируйтесь, чтобы увидеть скрытое содержимое.



Мои поздравления! Это ваш первый работающий шейдер!
Мини-задание: сможете залить весь экран серым цветом?

vec4 — это просто тип данных, так что мы можем объявить наш цвет как переменную:
 

Войдите или зарегистрируйтесь, чтобы увидеть скрытое содержимое.



Данный пример не слишком захватывающий. У нас есть мощь сотен или тысяч вычислительных ядер, способных работать эффективно и параллельно, а мы из это пушки стреляем по воробьям, заливая весь экран одним цветом.

Давайте хотя бы нарисуем градиент. Для этого, как вы можете догадаться, нам нужно знать позицию текущего пикселя на экране.
 

Входные параметры шейдера


Каждый пиксельный шейдер имеет в своём распоряжении несколько полезных Войдите или зарегистрируйтесь, чтобы увидеть скрытое содержимое. . В нашем случае наиболее полезной будет fragCoord, которая содержит координаты x и y (а также z, если нужно будет работать в 3D) текущего пикселя. Для начала попробуем закрасить все пиксели в левой половине экрана в черный цвет, а в правой — в красный:
 

Войдите или зарегистрируйтесь, чтобы увидеть скрытое содержимое.



Заметка: для доступа к компонентам переменных типа vec4 вы можете использовать obj.x, obj.y, obj.z, obj.w или obj.r, obj.g, obj.b, obj.a. Это эквивалентные записи. Таким способом мы получаем возможность именовать компоненты vec4 в зависимости от того, чем они являются в каждом конкретном случае.

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

Если что-то не является встроенной переменной, вы можете переслать эту информацию от CPU (основного кода вашего приложения) к GPU (вашему шейдеру). ShaderToy делает это за вас. Вы можете просмотреть все доступные шейдеру переменные во вкладке Shader Inputs. В GLSL они называются uniform-переменными.W27Iro3.jpeg

Давайте исправим наш код таким образом, чтобы он корректно определял середину экрана. Для этого нам понадобится uniform-переменная iResolution:
 

Войдите или зарегистрируйтесь, чтобы увидеть скрытое содержимое.



Теперь даже при увеличении окна предпросмотра (или переходе в полноэкранный режим) мы получим поделенный ровно пополам черно-красный прямоугольник.
 

От разделения экрана к градиенту


Изменить наш код для получения градиентной заливки достаточно просто. Компоненты цветов могут быть в пределах от 0 до 1, и наши координаты тоже теперь представлены в том же диапазоне.
 

Войдите или зарегистрируйтесь, чтобы увидеть скрытое содержимое.



Вуаля!

Мини-задание: попробуете сами сделать вертикальный градиент? Диагональный? Как на счёт перехода между более чем двумя цветами?

Если вы не пропустили вышеуказанное задание с вертикальным градиентом, то уже знаете, что верхний левый угол имеет координаты (0;1), а не (0;0), как можно было бы предположить. Это важно, запомните это.
 

Рисование изображений


Развлекаться с заливкой цветом, конечно, забавно, но, если мы хотим реализовать какой-нибудь по-настоящему захватывающий эффект, наш шейдер должен быть способен принимать на вход картинку и изменять её. Таким образом мы можем написать шейдер, который может влиять, например, на отрисовку всего кадра в игре (реализовать эффекты движения жидкостей или выполнять цветокоррекцию) или наоборот, выполнять лишь отдельные операции для некоторых объектов сцены (например, реализовать часть системы освещения).

Если бы мы писали шейдеры на какой-нибудь обычной платформе, то должны были бы передать изображение шейдеру как uniform-переменную (таким же образом, как передавалось разрешение экрана). ShaderToy делает это за нас. Есть четыре входных канала внизу:b8BYtZp.jpeg

Кликните на канале iChannel0 и выберите любую текстуру (изображение). Теперь у вас есть картинка, которая будет передана вашему шейдеру. Но есть одна проблема: функции DrawImage() у нас нет. Вы ведь помните — всё, что может сделать шейдер, это вернуть значение rgba для одного пикселя.

Итак, если мы можем лишь вернуть значение цвета, то как же нам отрисовать картинку на экране? Мы должны как-то соотнести пиксель в картинке с пикселем, для которого был вызван шейдер:
5wEK5Mh.jpeg

Мы можем сделать это с помощью функции texture(textureData,coordinates), которая принимает на вход текстуру и координаты (x, y), а возвращает цвет текстуры в данной точке в виде переменной типа vec4.

Вы можете соотнести пиксели текстуры и экрана как-угодно. Можно, например, растянуть текстуру на четверть экрана или нарисовать лишь её часть. В нашем случае мы всего лишь хотим увидеть оригинальное изображение:

 

Войдите или зарегистрируйтесь, чтобы увидеть скрытое содержимое.

И вот она, наша картинка!

11T40XH.jpeg

Теперь, когда вы умеете вытягивать данные из текстуры, вы можете манипулировать ими как захотите. Вы можете растянуть или сжать изображение, поиграть с его цветами.
Давайте добавим сюда уже известный нам градиент:
 

Войдите или зарегистрируйтесь, чтобы увидеть скрытое содержимое.

Rtue0o8.jpeg

Поздравляю, вы только что написали свой первый пост-процессинг эффект!

Мини-задание: сможете ли вы написать шейдер, который преобразует входную картинку в черно-белое изображение?

Заметьте, хотя мы используем статическую картинку, то, что вы видите на экране рендерится в реальном времени, много раз в секунду. Вы можете убедиться в этом, заменив во входном канале статическую картинку на видео (просто кликните на канале iChannel0 и выберите видео).
 

Добавляем немного движения


До этого момента все наши эффекты были статические. Мы можем делать намного более интересные вещи, используя входные параметры, предоставляемые нам разработчиками ShaderToy. iGlobalTime это постоянно увеличивающаяся переменная — мы можем использовать её в качестве основы для переодических эффектов. Давайте попробуем поиграть с цветами:
 

Войдите или зарегистрируйтесь, чтобы увидеть скрытое содержимое.



В GLSL есть встроенные функции синуса и косинуса (да и много других полезных). Компоненты цвета не должны быть негативными, так что мы используем функцию abs.

Мини-задание: можете ли вы сделать шейдер, который будет периодически плавно делаеть картинку черно-белой, а потом снова полноцветной?
 

Отладка шейдеров


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

Заключение


Мы рассмотрели лишь базовые средства разработки шейдеров, но вы уже можете экспериментировать с ними и пробовать делать что-то своё. Просмотрите доступные на ShaderToy Войдите или зарегистрируйтесь, чтобы увидеть скрытое содержимое.  и попробуйте понять (или самостоятельно воспроизвести) какие-то из них.

Одна из (многих) вещей, которые я не упомянул в данной статье, это вершинные шейдеры (Vertex Shaders). Они пишутся на том же языке, но запускаются не для пикселей, а для вершин, возвращая, соответственно, новую позицию вершины и её цвет. Вершинные шейдеры занимаются, например, отображением 3D-сцены на экран.

Последнее мини-задание: сможете ли вы написать шейдер, который заменит зелёный фон (есть в некоторых видео на ShaderToy) на другую картинку или видео?

Изменено пользователем kuzmich774

Присоединиться к обсуждению

Вы можете оставить комментарий уже сейчас, а зарегистрироваться позже! Если у вас уже есть аккаунт, войдите, чтобы оставить сообщение через него.

Гость
Ответить в тему...

×   Вы вставили отформатированное содержимое.   Удалить форматирование

  Only 75 emoji are allowed.

×   Ваша ссылка автоматически преображена.   Отображать как простую ссылку

×   Предыдущее содержимое было восстановлено..   Очистить текст в редакторе

×   Вы не можете вставлять картинки напрямую. Загрузите или вставьте их через URL.

  • Ответы 0
  • Создано
  • Последний ответ
  • Просмотры 211

Лучшие авторы в этой теме

  • kuzmich774

    1

Популярные дни

Лучшие авторы в этой теме

Популярные дни

  • Сейчас на странице   0 пользователей

    • Нет пользователей, просматривающих эту страницу
  • Модераторы онлайн

    • alexis
    • Blackfyre Kreis
×
×
  • Создать...