📜 ⬆️ ⬇️

Shaders in libgdx for dummies

The article will be useful to those who begin their acquaintance with libgdx and shaders. Shaders are often ignored by beginners, although they allow you to make many beautiful effects, quite simply. I will not go deep into OpenGL and shaders, and I will go only to the tops, but this is quite enough both for using foreign shaders and for writing my own.

Some theory


So what is a shader? OpenGL shaders are small programs written in C like GLSL. These programs are executed directly on the GPU. Shaders work in pairs: vertex and fragment shaders.



The vertex shader (vertex shader) is responsible for performing operations on vertices. Each program execution acts on exactly one vertex. If you look at the drawing of a triangle, then it has 3 vertices, respectively, the vertex shader will be executed 3 times. The vertex shader will set the end positions of the vertices taking into account the camera position, as well as prepare and display some variables required for the fragment shader. When developing simple shaders, you probably don’t need to modify the vertex shader.
')
The fragment shader (fragment shader) processes each visible part of the final image. I will call each such fragment a pixel, although this is not quite true, since the pixel in the OpenGL rendering and in the final image that you see on the screen may vary in size.

Inside the fragment shader we will work with everything related to the surface - lighting, shadows, reflections, textures, and any effects you want. The result of the fragment shader is the color of the pixel in the RGBA format (red, green, blue and alpha channel). For most effects, we will change it.

Let's assume that a triangle covers an area of ​​300 pixels. The vertex shader for this triangle will be executed 3 times. The fragment shader will be executed 300 times. So keep this in mind when writing shaders. Everything that is done in the fragment shader will be exponentially more expensive. This should always be considered when working with shaders.

Standard shaders in libgdx


Before proceeding with the standard shaders, some more theory. GLSL is a C-like language, and I will not focus on the basic things, but there are things that I need to clarify before we begin to parse the code.

The shaders use such concepts as: attribute, uniform, varying.

Attributes (attribute) is a property of the vertex. Vertices may have different attributes. For example, position coordinates in space, coordinates of the normal vector, color. In addition, you can pass any attributes to the vertex shader. It is important to understand that an attribute is a property of a vertex, and therefore it must be defined for each vertex. Attributes are passed to only the vertex shader. Attributes are accessible only by the vertex shader and cannot be overwritten.

Uniforms (uniform) are external data that can be used for calculations, but cannot be overwritten. Uniforms can be transferred to both vertex and fragment shaders. Uniforms have nothing to do with a particular vertex and are global constants. For example, as a uniform, you can transfer to the shader the coordinates of the light source and the coordinates of the eye (camera).

Variables (varying) are data that, when moving from the vertex to the fragment shader, will be calculated for each pixel by averaging the vertex data. I will explain in more detail. In the vertex shader, we deal with the coordinates of a particular vertex. If we transfer the coordinates of this vertex to the fragment shader as varying, then at the input of the fragment shader we will receive coordinates in space already for each pixel, which will be obtained by averaging the coordinates of the vertices. The averaging process is called interpolation. Similarly, the coordinates of the normal vector and the coordinates of the color vector are interpolated. It is important that varying variables must be declared the same in the vertex and fragment 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; } 


Fragment 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);//    } 


Working with shaders in libgdx


In libgdx, the ShaderProgram class is used for working with shaders. It accepts either two files or two lines containing shaders for input.

 //   shaderProgram=new ShaderProgram(Gdx.files.internal("shaders/default.vert"),Gdx.files.internal("shaders/default.frag")); //   vertexShader  fragmentShader  String      shaderProgram=new ShaderProgram(vertexShader,fragmentShader); 

When working with shaders, it is advisable to write:

 ShaderProgram.pedantic = false; 

Because without this, shaders may not compile, libgdx swears when there are uniforms in the shader that are not used. After the shader is no longer needed, it is important not to forget to free up resources:

 shaderProgram.dispose(). 

Now that we have dealt with the shaders, let's make a simple shader that will change the color of the pixel to the opposite. For this task, we only need to change the fragment shader. The conversion itself is done in two lines:

 //         gl_FragColor = v_color * texture2D(u_texture, v_texCoords); //   ,     gl_FragColor.rgb=1.0-gl_FragColor.rgb; 

The result we want to get


Fragment 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; } 


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

Source: https://habr.com/ru/post/274813/


All Articles