- Версия(и) Minecraft
- любая
Доброго времени суток, уважаемые о/
Данная серия туториалов будет посвящена созданию собственных шейдеров на основе языка openGL Shading Language, или GLSL.
Предисловие:
Освоить создание графических шейдеров — это значит взять под свой контроль всю мощь видепроцессора с его тысячами параллельно работающих ядер. При таком способе программирования требуется другой образ мышления, но раскрытие его потенциала стоит потраченных усилий. " ©
Язык GLSL чем-то похож на Java, чем-то - на Си, так что с синтаксисом проблем возникнуть не должно.
Первая часть будет посвящена Фрагментным Шейдерам. Что это такое объясню ниже.
Теория:
Начнём с теории. Что такое шейдер?
Шейдер - это программа, которая выполняется в цикле рендеринга графики, использующаяся для определения и изменения параметров геометрических объектов или изображений (для создания эффектов сдвига, отражения, преломления, затемнения с учётом заданных параметров поглощения и рассеяния света, для наложения текстур на геометрические объекты)...
Сложно, тёмно и не особо понятно, да? Давайте попробуем упростить вышесказанное и всё таки дать ответ на главный вопрос "что же делают фрагментные шейдеры? ". Готовы?
В двух словах: определяют цвет
Всё. Это действительно всё, что может сделать этот тип шейдера - определить и вернуть цвет одного-единственного полученного пикселя. Но не спешите закрывать урок. Даже с этим, казалось бы, небольшим функционалом можно делать просто умопомрачительные эффекты. Как? Читайте дальше.
На входе фрагментный шейдер действительно получает лишь один пиксель, точнее, его координаты, x и y. А на выходе выдаёт цвет в формате RGBA (красный, зелёный, синий и прозрачность).
Итак, рассмотрим примерный вид программы, которые у вас всех будут в итоге получаться:
Типы переменных:
В GLSL есть практически все стандартные примитивные типы: bool (неboolean!), int, uint (unsigned int, т.е. неотрицательное число), float и double.
В добавок к ним есть контейнеры - векторы: vec2, vec3, vec4 (на 2, 3 и 4 значения соответственно) и матрицы: mat2, mat3, mat4 (2x2, 3x3 и 4x4 соответственно). Стандартый тип переменных в них - float. Если нужен, например, вектор int'ов на три компонента, вначале нужно дописать i - ivec3 (соответственно b - bool, u - uint, d - double).
Грубо говоря, это одно- и двумерные массивы, и доступ к их элементам можно получить через стандартный оператор индексирования - []. Но для нашего удобства был разработан метод swizzling, позволяющий получить доступ к элементам по их идентификаторам (xyzw, rgba или stpq, использующиеся для координат, цветов и точек текстуры соответственно (но не обязательно так)), допустим:
Модификатор uniform - это переменные, передаваемые в шейдер извне (из программы, в которой использовался шейдер). Самый распространённый uniform - time, то есть время. Он используется для анимации, ведь мало кому интересно смотреть на статичные шейдеры, которые проще нарисовать текстурой в фотошопе, чем учить новый язык.
Модификаторы in и out - это входные и выходные данные. Например, переменная gl_FragColor из кода выше как раз таки является переменной с модификатором out, встроенной в GLSL, её тип - vec4. Она используется для извлечения цвета пикселя из шейдера.
"Аннотация" #version указывает версию GLSL, которую вы хотите использовать. Она всегда должна находится на самом верху. Вот небольшая справка соответствия версий OpenGL и GLSL:
Функции:
В GLSL есть множество встроенных функций, очень похожих на их аналоги из библиотеки Math:
sin(a), cos(a), tan(a); min(a, b), max(a, b) - синус, косинус, тангенс углов; минимальное и максимальное значения.
normalize(a) - возвращает нормализованный (с длинной равной 1) вектор a
length(a) - возвращает длину вектора a
distance(a, b) - расстояние между двумя точками (векторами a и b)
И так далее. Полный список можно найти вот тут - *тык* (для OpenGL 4)
Практика:
С теорией покончили, настало время попрактиковаться!
Для этого есть множество удобных и не очень программ и сайтов, лично я при обучении использовал Shader Edit for Android - бесплатная программа из Play Market'а (нет, это не реклама и мне за неё не платили). Для компьютеров можно использовать ShaderToy, но убедитесь, что ваш браузер поддерживает WebGL, иначе ничего не выйдет.
Итак, давайте для начала покрасим весь экран в один цвет, скажем, зелёный.
Для этого нам понадобится всего одна строка в нашей main функции:
Теперь давайте превратим сплошной цвет в градиент. Для этого "нормализуем" координаты нашего пикселя по размеру экрана, то есть поделим его координаты на размер окна:
И используем координату y для выбора цвета:
Сможете перевернуть градиент, чтобы чёрный был сверху? А справа? А слева внизу?
Теперь давайте попробуем вставить изображение. В ShaderToy для этого используются каналы ниже; в Shader Edit нужно нажать на три точки справа вверху и в меню "Add uniform/texture" выбрать и добавить нужный sampler2D, нажав на +, например:
Далее, используя функцию texture[2D] мы получаем цвет в нужной нам точке текстуры и возвращаем его программе:
Вы можете заметить, что экран окрасился в один цвет, совсем не похожий на вашу картинку.
Что же делать? Попробуйте нормализовать координаты (поделить на разрешение экрана):
Теперь давайте попробуем поменять цвета на картинке, скажем, наложив градиент жёлтый -> синий.
Для этого просто сделаем синий цвет зависимым от какой-то координаты:
Теперь фотографируем всё подряд и пропускаем через этот шейдер :D
Попробуйте сделать чёрно-белый фильтр. А сепию? Осветление/затемнение?
Ну вот и всё.
Для закрепления материала попробуйте создать радугу, имитацию водной поверхности (с волнами) или телевизионные помехи. Не забывайте использовать встроенные в Shader Edit и ShaderToy uniform'ы.
Послесловие:
Вершинный шейдер выполняется ДЛЯ КАЖДОГО пикселя на экране, а таких пикселей может быть миллионы и миллиарды, так что вот пара советов по оптимизации:
- Чем меньше условных операторов, тем лучше
- Старайтесь экономить память, т.е. не создавать переменных, без которых можно обойтись
Следующая часть: Шейдеры GLSL [ч.2 - Добавление в игру]
Всего хорошего!
Данная серия туториалов будет посвящена созданию собственных шейдеров на основе языка openGL Shading Language, или GLSL.
Предисловие:
Освоить создание графических шейдеров — это значит взять под свой контроль всю мощь видепроцессора с его тысячами параллельно работающих ядер. При таком способе программирования требуется другой образ мышления, но раскрытие его потенциала стоит потраченных усилий. " ©
Язык GLSL чем-то похож на Java, чем-то - на Си, так что с синтаксисом проблем возникнуть не должно.
Первая часть будет посвящена Фрагментным Шейдерам. Что это такое объясню ниже.
Теория:
Начнём с теории. Что такое шейдер?
Шейдер - это программа, которая выполняется в цикле рендеринга графики, использующаяся для определения и изменения параметров геометрических объектов или изображений (для создания эффектов сдвига, отражения, преломления, затемнения с учётом заданных параметров поглощения и рассеяния света, для наложения текстур на геометрические объекты)...
Сложно, тёмно и не особо понятно, да? Давайте попробуем упростить вышесказанное и всё таки дать ответ на главный вопрос "что же делают фрагментные шейдеры? ". Готовы?
В двух словах: определяют цвет
Всё. Это действительно всё, что может сделать этот тип шейдера - определить и вернуть цвет одного-единственного полученного пикселя. Но не спешите закрывать урок. Даже с этим, казалось бы, небольшим функционалом можно делать просто умопомрачительные эффекты. Как? Читайте дальше.
На входе фрагментный шейдер действительно получает лишь один пиксель, точнее, его координаты, x и y. А на выходе выдаёт цвет в формате RGBA (красный, зелёный, синий и прозрачность).
Итак, рассмотрим примерный вид программы, которые у вас всех будут в итоге получаться:
C-like:
#version N
uniform type globalVar1;
void main() {
// Координата текущего пикселя
vec2 pixelCoord = gl_FragCoord.xy;
// Цвет текущего пикселя
vec4 pixelColor = vec4(0.0);
/**
* Любые вычисления и изменение цвета выходного пикселя
*
*/
// Присвоение выходного результата
gl_FragColor = pixelColor;
}
Типы переменных:
В GLSL есть практически все стандартные примитивные типы: bool (не
В добавок к ним есть контейнеры - векторы: vec2, vec3, vec4 (на 2, 3 и 4 значения соответственно) и матрицы: mat2, mat3, mat4 (2x2, 3x3 и 4x4 соответственно). Стандартый тип переменных в них - float. Если нужен, например, вектор int'ов на три компонента, вначале нужно дописать i - ivec3 (соответственно b - bool, u - uint, d - double).
Грубо говоря, это одно- и двумерные массивы, и доступ к их элементам можно получить через стандартный оператор индексирования - []. Но для нашего удобства был разработан метод swizzling, позволяющий получить доступ к элементам по их идентификаторам (xyzw, rgba или stpq, использующиеся для координат, цветов и точек текстуры соответственно (но не обязательно так)), допустим:
C-like:
// инициализация:
vec4 v = vec4(0.0); // все компоненты равны, т.е. 0.0, 0.0, 0.0, 0.0
vec2 vect = vec2(0.5, 0.7); // два компонента: 0.5 и 0.7
vec4 result = vec4(vect, 0.0, 0.0); // четыре: 0.5, 0.7 (из первого вектора), 0.0, 0.0
vec4 otherResult = vec4(result.xyz, 1.0); // 0.5, 0.7, 0.0, 1.0
// доступ и операции:
vec2 someVec;
vec4 differentVec = someVec.xyxx;
vec3 anotherVec = differentVec.abr;
vec4 otherVec = someVec.xyyx + anotherVec.sttp;
Модификатор uniform - это переменные, передаваемые в шейдер извне (из программы, в которой использовался шейдер). Самый распространённый uniform - time, то есть время. Он используется для анимации, ведь мало кому интересно смотреть на статичные шейдеры, которые проще нарисовать текстурой в фотошопе, чем учить новый язык.
Модификаторы in и out - это входные и выходные данные. Например, переменная gl_FragColor из кода выше как раз таки является переменной с модификатором out, встроенной в GLSL, её тип - vec4. Она используется для извлечения цвета пикселя из шейдера.
"Аннотация" #version указывает версию GLSL, которую вы хотите использовать. Она всегда должна находится на самом верху. Вот небольшая справка соответствия версий OpenGL и GLSL:
(записывать без точки)
2.0 - 1.10
2.1 - 1.20
3.0 - 1.30
3.1 - 1.40
3.2 - 1.50
Начиная с OpenGL 3.3 версии GLSL соответствуют версиям OpenGL, так что, допустим, GL 44 использует GLSL 440
*Источник*
2.0 - 1.10
2.1 - 1.20
3.0 - 1.30
3.1 - 1.40
3.2 - 1.50
Начиная с OpenGL 3.3 версии GLSL соответствуют версиям OpenGL, так что, допустим, GL 44 использует GLSL 440
*Источник*
Функции:
В GLSL есть множество встроенных функций, очень похожих на их аналоги из библиотеки Math:
sin(a), cos(a), tan(a); min(a, b), max(a, b) - синус, косинус, тангенс углов; минимальное и максимальное значения.
normalize(a) - возвращает нормализованный (с длинной равной 1) вектор a
length(a) - возвращает длину вектора a
distance(a, b) - расстояние между двумя точками (векторами a и b)
И так далее. Полный список можно найти вот тут - *тык* (для OpenGL 4)
Практика:
С теорией покончили, настало время попрактиковаться!
Для этого есть множество удобных и не очень программ и сайтов, лично я при обучении использовал Shader Edit for Android - бесплатная программа из Play Market'а (
Итак, давайте для начала покрасим весь экран в один цвет, скажем, зелёный.
Для этого нам понадобится всего одна строка в нашей main функции:
gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);
- мы создаём вектор на 4 компонента, отвечающий за цвет текущего пикселя (RGBA), присваивая зелёному цвету значение 1.0 (это максимально допустимое значение. Если вам сложно рассчитать нужное число, используйте конструкцию "n/255" чтобы превратить числа из диапазона 0-255 в 0.0-1.0), а так же устанавливая насыщенность цвета на максимум:vec2 xy = gl_FragCoord.xy / resolution.xy;
И используем координату y для выбора цвета:
gl_FragColor = vec4(0.0, xy.y, 0.0, 1.0);
uniform sampler2D img;
Далее, используя функцию texture[2D] мы получаем цвет в нужной нам точке текстуры и возвращаем его программе:
gl_FragColor = texture2D(img, gl_FragCoord.xy)
Что же делать? Попробуйте нормализовать координаты (поделить на разрешение экрана):
gl_FragColor = texture2D(img, gl_FragCoord.xy/resolution)
Для этого просто сделаем синий цвет зависимым от какой-то координаты:
Код:
vec2 xy = gl_FragCoord / resolution;
vec4 col = texture(img, xy);
col.b = distance(vec2(0.5), xy);
gl_FragColor = col;
Ну вот и всё.
Для закрепления материала попробуйте создать радугу, имитацию водной поверхности (с волнами) или телевизионные помехи. Не забывайте использовать встроенные в Shader Edit и ShaderToy uniform'ы.
Послесловие:
Вершинный шейдер выполняется ДЛЯ КАЖДОГО пикселя на экране, а таких пикселей может быть миллионы и миллиарды, так что вот пара советов по оптимизации:
- Чем меньше условных операторов, тем лучше
- Старайтесь экономить память, т.е. не создавать переменных, без которых можно обойтись
Следующая часть: Шейдеры GLSL [ч.2 - Добавление в игру]
Всего хорошего!