Developers Club geek daily blog

1 year, 8 months ago
Article will be useful to those who begin the acquaintance to libgdx and shaders. Shaders are often ignored by beginners, though allow to do a set of beautiful effects, rather simply. I will strongly not go deep into OpenGL and shaders, and I will walk only on tops, but it is quite enough both for use of others shaders, and for writing of the.

It is a little theory


So, what is a shader? Shaders in OpenGL are the small programs written in the C similar GLSL language. These programs are performed directly on GPU. Shaders work in couple: vertex shaders and fragmentary.

Shaders in libgdx for teapots

The vertex shader (vertex shader) is responsible for execution of operations over tops. Each execution of the program affects exactly one top. If to look at triangle drawing, then it has 3 tops, respectively the vertex shader will be executed 3 times. The vertex shader will set final positions of tops taking into account camera provision, and will also prepare and will display some variables demanded for a fragmentary shader. When developing simple shaders, you most likely do not need to change a vertex shader.

The fragmentary shader (fragment shader) processes each visible part of the final image. I will call each such fragment pixel though it is not absolutely right as the pixel in rendering of OpenGL and in the final image which you see on the screen can differ by the size.

In a fragmentary shader we will work with everything that is connected with a surface — lighting, shadows, reflections, textures and any effects which you want. The result of work of a fragmentary shader is a color of pixel in the RGBA format (red, green, blue and the alpha channel). For the majority of effects we will change it.

Let's assume that the triangle occupies the space in 300 pixels. The vertex shader for this triangle will be executed 3 times. The fragmentary shader will be executed 300 times. Therefore have it in a type when writing shaders. Everything that becomes in a fragmentary shader, will be exponential more expensively. It needs to be considered always during the work with shaders.

Standard shaders in libgdx


Before starting standard shaders, it is a little more theory. The GLSL language is the C similar language, and I will not focus attention on basic things, however there are things which I have to explain before we begin to sort a code.

In shaders such concepts as are used: attribute, uniform, varying.

Attributes (attribute) are a property of top. The top can have different attributes. For example, coordinates of position in space, normal vector coordinate, color. Besides, you can transfer any attributes to a vertex shader. It is important to understand that the attribute is a property of top and therefore it has to be set for each top. Attributes are transferred to only a vertex shader. Attributes are available to a vertex shader only to reading and cannot be rewritten.

Yuniforma (uniform) are external data which can be used for calculations, but cannot be rewritten. Uniforms can be transferred both in vertex, and in fragmentary shaders. Uniforms are not connected with specific top in any way and are global constants. For example, as uniforms it is possible to transfer to a shader of coordinate of a light source and coordinate of an eye (camera).

Variable (varying) are data which upon transition from vertex to a fragmentary shader will be calculated for each pixel by data smoothing of tops. I will explain in more detail. In a vertex shader we deal with coordinates of specific top. If to transfer coordinates of this top to a fragmenty shader as varying, then on an input of a fragmentary shader we will receive coordinates in space already for each pixel which will be received by averaging of coordinates of tops. Process of averaging is called interpolation. Coordinates of a vector of a normal and coordinate of a vector of color are similarly interpolated. It is important that varying-variables have to be surely declared equally in vertex and fragmentary shaders.

Vertex shader
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; 
}


Fragmentary shader
//#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);// итоговый цвет пикселя
}


Work with shaders in Libgdx


In libgdx for work with shaders the class ShaderProgram.Ha an input is used it accepts either two files, or two lines of the shaders containing a code.

//Загрузка из файлов
shaderProgram=new ShaderProgram(Gdx.files.internal("shaders/default.vert"),Gdx.files.internal("shaders/default.frag"));
//Загрузка из строк vertexShader и fragmentShader это String в котором хранится код шейдеров
shaderProgram=new ShaderProgram(vertexShader,fragmentShader);

During the work with shaders it is desirable to write:

ShaderProgram.pedantic = false;

Because without it shaders can not be compiled, libgdx swears when in a shader there are yuniforma which are not used. After the shader did not become necessary, it is important not to forget to release resources:

shaderProgram.dispose().

Now, when we dealt with shaders, let's make a simple shader which will change color of pixel for opposite. For this task we need to change only a fragmentary shader. Conversion becomes in two lines:

//как и в стандартном шейдере получаем итоговый цвет пикселя
gl_FragColor = v_color * texture2D(u_texture, v_texCoords);
//после получения итогового цвета, меняем его на противоположный
gl_FragColor.rgb=1.0-gl_FragColor.rgb;

Result which we want to receive
Shaders in libgdx for teapots

Fragmentary shader
#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;
}


Final code
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();
	}
}


This article is a translation of the original post at habrahabr.ru/post/274813/
If you have any questions regarding the material covered in the article above, please, contact the original author of the post.
If you have any complaints about this article or you want this article to be deleted, please, drop an email here: sysmagazine.com@gmail.com.

We believe that the knowledge, which is available at the most popular Russian IT blog habrahabr.ru, should be accessed by everyone, even though it is poorly translated.
Shared knowledge makes the world better.
Best wishes.

comments powered by Disqus