Статья будет полезна тем, кто начинает свое знакомство с libgdx и шейдерами. Шейдеры часто игнорируются новичками, хотя и позволяют делать множество красивых эффектов, достаточно просто. Я не буду сильно углубляться в OpenGL и шейдеры, а пройдусь лишь по верхам, но этого вполне достаточно как для использования чужих шейдеров, так и для написания своих.
Немного теории
Итак, что такое шейдер? Шейдеры в OpenGL — это небольшие программы, написанные на C подобном языке GLSL. Эти программы исполняются напрямую на GPU. Шейдеры работают в паре: вершинные шейдеры и фрагментные.
Вершинный шейдер (vertex shader) отвечает за выполнение операций над вершинами. Каждое выполнение программы действует ровно на одну вершину. Если посмотреть на рисунок треугольника, то у него 3 вершины, соответственно вершинный шейдер выполнится 3 раза. Вершинный шейдер задаст конечные позиции вершин с учетом положения камеры, а так же подготовит и выведет некоторые переменные, требуемые для фрагментного шейдера. При разработке простых шейдеров, вам скорее всего не понадобится изменять вершинный шейдер.
Фрагментный шейдер (fragment shader) обрабатывает каждую видимую часть конечного изображения. Я буду называть каждый такой фрагмент пикселем, хотя это не совсем верно, так как пиксель в рендеринге OpenGL и в итоговом изображении, которое вы видите на экране, может различаться по размеру.
Внутри фрагментного шейдера мы будем работать со всем, что связано с поверхностью — освещение, тени, отражения, текстуры и любые эффекты, которые вы захотите. Результат работы фрагментного шейдера — это цвет пикселя в формате RGBA (красный, зеленый, синий и альфа-канал). Для большинства эффектов мы будем изменять именно его.
Давайте предположим, что треугольник занимает площадь в 300 пикселей. Вершинный шейдер для этого треугольника будет выполнен 3 раза. Фрагментный шейдер будет выполнен 300 раз. Поэтому имейте это в виду при написании шейдеров. Все, что делается в фрагментном шейдере, будет экспоненциально дороже. Это нужно всегда учитывать при работе с шейдерами.
Стандартные шейдеры в libgdx
Прежде чем приступить к стандартным шейдерам, еще немного теории. Язык GLSL — это C подобный язык, и я не буду заострять внимание на базовых вещах, однако есть вещи, которые я должен пояснить, прежде чем мы начнем разбирать код.
В шейдерах используются такие понятия как: attribute, uniform, varying.
Атрибуты (attribute) — это свойство вершины. У вершины могут быть различные атрибуты. Например, координаты положения в пространстве, координаты вектора нормали, цвет. Кроме того, вы можете передавать в вершинный шейдер какие-либо свои атрибуты. Важно понять, что атрибут — это свойство вершины, и поэтому он должен быть задан для каждой вершины. Атрибуты передаются в только вершинный шейдер. Атрибуты доступны вершинному шейдеру только для чтения и не могут быть перезаписаны.
Юниформы (uniform) — это внешние данные, которые могут быть использованы для расчетов, но не могут быть перезаписаны. Униформы могут быть переданы как в вершинный, так и во фрагментный шейдеры. Униформы никак не связаны с конкретной вершиной и являются глобальными константами. Например, в качестве униформ можно передать в шейдер координаты источника света и координаты глаза (камеры).
Переменные (varying) — это данные, которые при переходе из вершинного во фрагментный шейдер будут вычислены для каждого пикселя путем усреднения данных вершин. Поясню подробнее. В вершинном шейдере мы имеем дело с координатами конкретной вершины. Если передать координаты этой вершины в фрагментый шейдер как varying, то на входе фрагментного шейдера получим координаты в пространстве уже для каждого пикселя, которые будут получены путем усреднения координат вершин. Процесс усреднения называют интерполяцией. Аналогично интерполируются координаты вектора нормали и координаты вектора цвета. Важно, что varying-переменные должны быть обязательно объявлены одинаково в вершинном и фрагментном шейдерах.
attribute vec4 a_position; //позиция вершины attribute vec4 a_color; //цвет вершины attribute vec2 a_texCoord0; //координаты текстуры uniform mat4 u_projTrans; //матрица, которая содержим данные для преобразования проекции и вида varying vec4 v_color; //цвет который будет передан в фрагментный шейдер varying vec2 v_texCoords; //координаты текстуры void main(){ v_color=a_color; // При передаче цвет из SpriteBatch в шейдер, происходит преобразование из ABGR int цвета в float. // что-бы избежать NAN при преобразование, доступен не весь диапазон для альфы, а только значения от (0-254) //чтобы полностью передать непрозрачность цвета, когда альфа во float равна 1, то всю альфу приходится умножать. //это специфика libgdx и о ней надо помнить при переопределение вершинного шейдера. v_color.a = v_color.a * (255.0/254.0); v_texCoords = a_texCoord0; //применяем преобразование вида и проекции, можно не забивать себе этим голову // тут происходят математические преобразование что-бы правильно учесть параметры камеры // gl_Position это окончательная позиция вершины gl_Position = u_projTrans * a_position; }
//#ifdef позволяет коду работать на слабых телефонах, и мощных пк.Если шейдер используется на телефоне(GL_ES) то //используется низкая разрядность (точность) данных.(highp – высокая точность; mediump – средняя точность; lowp – низкая точность) #ifdef GL_ES #define LOWP lowp precision mediump float; #else #define LOWP #endif varying LOWP vec4 v_color; varying vec2 v_texCoords; // sampler2D это специальный формат данных в glsl для доступа к текстуре uniform sampler2D u_texture; void main(){ gl_FragColor = v_color * texture2D(u_texture, v_texCoords);// итоговый цвет пикселя }
Работа с шейдерами в Libgdx
В libgdx для работы с шейдерами используется класс ShaderProgram.На вход он принимает либо два файла, либо две строки содержащих код шейдеров.
//Загрузка из файлов shaderProgram=new ShaderProgram(Gdx.files.internal("shaders/default.vert"),Gdx.files.internal("shaders/default.frag")); //Загрузка из строк vertexShader и fragmentShader это String в котором хранится код шейдеров shaderProgram=new ShaderProgram(vertexShader,fragmentShader);
При работе с шейдерами желательно написать:
ShaderProgram.pedantic = false;
Потому что без этого шейдеры могут не скомпилироваться, libgdx ругается когда в шейдере есть юниформы которые не используются. После того как шейдер стал не нужен, важно не забыть освободить ресурсы:
shaderProgram.dispose().
Теперь, когда мы разобрались с шейдерами, давайте сделаем простой шейдер, который будет менять цвет пикселя на противоположный. Для этой задачи нам необходимо поменять только фрагментный шейдер. Само преобразование делается в две строки:
//как и в стандартном шейдере получаем итоговый цвет пикселя gl_FragColor = v_color * texture2D(u_texture, v_texCoords); //после получения итогового цвета, меняем его на противоположный gl_FragColor.rgb=1.0-gl_FragColor.rgb;
#ifdef GL_ES #define LOWP lowp precision mediump float; #else #define LOWP #endif varying LOWP vec4 v_color; varying vec2 v_texCoords; uniform sampler2D u_texture; void main(){ //как и в стандартном шейдере получаем итоговый цвет пикселя gl_FragColor = v_color * texture2D(u_texture, v_texCoords); //после получения итогового цвета, меняем его на противоположный gl_FragColor.rgb=1.0-gl_FragColor.rgb; }
import com.badlogic.gdx.ApplicationAdapter; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.GL20; import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.graphics.glutils.ShaderProgram; import com.badlogic.gdx.scenes.scene2d.Stage; public class ShaderDemo extends ApplicationAdapter { SpriteBatch batch; Texture img; ShaderProgram shader; @Override public void create() { batch = new SpriteBatch(); img = new Texture("badlogic.jpg"); //желательно использовать, тк если мы используем не все юниформы, то шейдер не скомпилируется ShaderProgram.pedantic = false; shader = new ShaderProgram(Gdx.files.internal("shaders/default.vert"), (Gdx.files.internal("shaders/invertColors.frag"))); if (!shader.isCompiled()) { System.err.println(shader.getLog()); System.exit(0); } batch = new SpriteBatch(1000); batch.setShader(shader); } @Override public void render() { Gdx.gl.glClearColor(1, 0, 0, 1); Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); batch.begin(); batch.draw(img, 0, 0,Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); batch.end(); } @Override public void dispose() { //важно не забыть освободить память от шейдера,когда он больше не нужен batch.dispose(); shader.dispose(); img.dispose(); } }
ссылка на оригинал статьи http://habrahabr.ru/post/274813/
Добавить комментарий