The Book of Shaders by Patricio Gonzalez Vivo & Jen Lowe

日本語 - 中文版 - 한국어 - Español - Français - Italiano - Deutsch - Русский - English


Uniform-переменные

В предыдущей главе мы увидели каким образом 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().

Настало время поиграть с кодом выше.

gl_FragCoord

Подобно тому, как GLSL объявляет выходное значение vec4 gl_FragColor по умолчанию, он так же предоставляет вход vec4 gl_FragCoord, содержащий координаты пикселя или фрагмента экрана, над которым работает данный поток. С помощью vec4 gl_FragCoord мы можем узнать где именно поток работает внутри изображения. В данном случае мы не называем входное значение однородным, потому что оно меняется от потока к потоку и называется изменяющимся (varying).

В коде выше мы нормализуем координаты фрагмента, разделяя их на разрешение изображения. В результате значения переходят в диапазон между 0.0 и 1.0, что упрощает отображение значений X и Y на красный и зелёный каналы.

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

Настало время проверить наше понимание приведённого выше кода.

После выполнения этих упражнения у вас скорее всего возникнет вопрос: где ещё можно применить мощь шейдеров? В следующей главе вы научитесь создавать шейдерные инструменты на three.js, Processing, и openFrameworks.