В предыдущей главе мы увидели каким образом GPU управляет большим количеством параллельных потоков, каждый из которых отвечает за назначение цвета небольшой части изображения. Каждый из параллельных потоков не знает о состоянии остальных, но всё же нам бывает нужно передавать входные данные от CPU ко всем потокам одновременно. Из-за особенностей архитектуры графических карт, все такие входные данные будут одинаковыми для всех потоков (однородными, uniform) и доступными только для чтения. Другими словами, каждый поток принимает на вход одни и те же данные, которые он может прочитать и не может перезаписать.
Эти входы называются однородными (uniform
) и могут иметь практически любой из поддерживаемых типов: float
, vec2
, vec3
, vec4
, mat2
, mat3
, mat4
, sampler2D
и samplerCube
. Uniform-переменные с указанием своих типов объявляются вначале шейдера сразу после указания точности по умолчанию.
#ifdef GL_ES
precision mediump float;
#endif
uniform vec2 u_resolution; // Размер изображения (ширина, высота)
uniform vec2 u_mouse; // Положение курсора мыши в пикселях
uniform float u_time; // Время в секундах с момента загрузки
Uniform-переменные можно представлять себе как маленькие мостики между CPU и GPU. Имена могут изменяться от примера к примеру, но в этой серии примеров я всегда передаю следующее: u_time
(время в секундах с момента запуска шейдера), u_resolution
(размер изображения) и u_mouse
(положение мыши на изображении, выраженное в пикселях). Я буду писать u_
вначале имён uniform-переменных, чтобы их происхождение было обозначено явно, но на практике вы столкнётесь с самыми разными именами uniform-переменных. Например, ShaderToy.com объявляет эти же переменные со следующими именами:
uniform vec3 iResolution; // разрешение области изображения (в пикселях)
uniform vec4 iMouse; // координаты мыши в пикселях. xy - текущие, zw - клик
uniform float iGlobalTime; // время работы шейдера (секунды)
Хватит слов, давайте посмотрим на юниформы в действии. В следующем коде мы используем u_time
(количество секунд с момента запуска шейдера) в комбинации с функцией синуса чтобы анимировать изменение количества красного цвета на экране.
Как видите, GLSL содержит ещё много сюрпризов. GPU аппаратно ускоряет угловые, тригонометрические и экспоненциальные функции. Вот некоторые из них: sin()
, cos()
, tan()
, asin()
, acos()
, atan()
, pow()
, exp()
, log()
, sqrt()
, abs()
, sign()
, floor()
, ceil()
, fract()
, mod()
, min()
, max()
и clamp()
.
Настало время поиграть с кодом выше.
-
Уменьшите частоту так, чтобы изменение цвета стало почти незаметным.
-
Увеличивайте её до тех пор, пока не увидите сплошной цвет без мерцания.
-
Поиграйтесь с каналами RGB на различных частотах, чтобы добиться какого-нибудь интересного поведения.
Подобно тому, как GLSL объявляет выходное значение vec4 gl_FragColor
по умолчанию, он так же предоставляет вход vec4 gl_FragCoord
, содержащий координаты пикселя или фрагмента экрана, над которым работает данный поток. С помощью vec4 gl_FragCoord
мы можем узнать где именно поток работает внутри изображения. В данном случае мы не называем входное значение однородным, потому что оно меняется от потока к потоку и называется изменяющимся (varying).
В коде выше мы нормализуем координаты фрагмента, разделяя их на разрешение изображения. В результате значения переходят в диапазон между 0.0
и 1.0
, что упрощает отображение значений X и Y на красный и зелёный каналы.
В мире шейдеров у нас нет нормальных отладочных инструментов, поэтому приходится задавать переменным яркие цвета и пытаться извлечь из них смысл. Вы увидите, что иногда программирование на GLSL похоже на засовывание корабля в бутылку. Оно столь же сложно, сколь красиво и захватывающе.
Настало время проверить наше понимание приведённого выше кода.
-
Укажите где находятся координаты
(0.0, 0.0)
на изображении. -
Как насчёт
(1.0, 0.0)
,(0.0, 1.0)
,(0.5, 0.5)
и(1.0, 1.0)
? -
Догадайтесь как использовать
u_mouse
, зная, что координаты даны в пикселях и НЕ нормализованы. Можете ли вы изменять цвета с помощью этой переменной? -
Придумайте какой-нибудь интересный способ изменения цветов с помощью
u_time
иu_mouse
.
После выполнения этих упражнения у вас скорее всего возникнет вопрос: где ещё можно применить мощь шейдеров? В следующей главе вы научитесь создавать шейдерные инструменты на three.js, Processing, и openFrameworks.