Android Tutorial Shaders in libGDX

In this tutorial, I'm going to modify the Perf_Flakes demo of the libGDX library to explain how to use shaders with your objects rendered by lgSpriteBatch. I'm NOT going to explain the basics of OpenGL, what are shaders and how to write a GLSL program. There are plenty of tutorials for that on Internet.

All the files can be downloaded at the end of this post.

1) I declare my Shader object in Globals:
B4X:
Dim Shader As lgShaderProgram

2) I add all the code to compile my shader program in LG_Create, and I check that the compilation is OK:
B4X:
'Compiles the vertex and the fragment shaders
Shader.Pedantic = False
Shader.InitializeWithFiles(lGdx.Files.Internal("shaderprog/vertex.txt"), _
                          lGdx.Files.Internal("shaderprog/fragment_color.txt"))

'Is the shader program compiled?
If Not(Shader.IsCompiled) Then
        Log("Could not compile shader: " & Shader.Log)
        Return
End If

'Logs any warning
If Shader.Log.Length <> 0 Then
        Log(Shader.Log)
End If

3) I replace the default shader of my renderer with this new shader:
B4X:
Batch.Shader = Shader

4) For this demo, I prefer to use a more colored image, so I replace the droid PNG by the wheel PNG:
B4X:
texDroid.Initialize("wheel.png")

5) That's all for the B4A part. Let's have a look to the vertex shader:
B4X:
attribute vec4 a_position;
attribute vec4 a_color;
attribute vec2 a_texCoord0;

uniform mat4 u_projTrans;

varying vec4 vColor;
varying vec2 vTexCoord;

void main() {
  vColor = a_color;
  vColor.a = vColor.a * (256.0/255.0);
  vTexCoord = a_texCoord0;
  gl_Position = u_projTrans * a_position;
}
This is the default vertex shader used internally by lgSpriteBatch. I keep it unchanged. LibGDX will pass automatically the color and the texture coordinates of each vertex to this code. The renderer will pass also the combined matrix (projection+transformation).

6) The fragment shader (also called pixel shader):
B4X:
precision mediump float;

varying lowp vec4 vColor;
varying vec2 vTexCoord;

uniform sampler2D u_texture;
uniform float halfwidth;

void main()
{
  vec3 color = texture2D(u_texture, vTexCoord).rgb;
  if (gl_FragCoord.x > halfwidth)
  {
    gl_FragColor = vColor * texture2D(u_texture, vTexCoord);
  }
  else if (color.g > 0.7)
  {
    vec3 colorswapped = vec3(color.g, color.b, color.r);
    gl_FragColor = vec4(colorswapped, texture2D(u_texture, vTexCoord).a);
  }
  else
  {
    float gray = (color.r + color.g + color.b) / 3.0;
    vec3 grayscale = vec3(gray);
    gl_FragColor = vec4(grayscale, texture2D(u_texture, vTexCoord).a);
  }
}
What do I do here ? I swap the three color components of the pixel if the green value is above 0.7, otherwise I turn the colors to gray. So my wheel image will have gray and colored parts, and the colored part will be rendered with altered colors (pink instead of yellow/green).
To show the difference, I add another condition which applies the effect only on a half of the display. For this condition to work, I need to know what's the value of the viewport width / 2. So I add an uniform, "halfwidth", and I set this uniform in my B4A code, in LG_Resize:
B4X:
Shader.Begin
Shader.SetUniform1f("halfwidth", Width / 2)
Shader.End

Easy, isn't it?
 

Attachments

  • Perf_Flakes_shader.zip
    95.9 KB · Views: 943

walterf25

Expert
Licensed User
Longtime User
Hi Informatix, i hadn't tried your water shader example until today, and while running it for a while i noticed something that I came across a while ago, if you let the app run for a long period of time (maybe 3 to 5 minutes) you start seeing that the top part of the water (surface) starts to loose it's uniformity, i don't know if that's the right way to describe it, but i have posted 2 images, the first one shows the water as soon as the app is started, and the 2nd one shows how it looks after you run the app for a while. I followed a tutorial online to do the same effect a while back and i noticed the same behavior, i was wondering if you could maybe figure out why this happens?


normal.jpg

Normal Behavior


Pixelated.jpg

Water surface looks pixelated.

Here is also the link to the apk file of the project I did, following a tutorial online of course, if you install it you will see the same behavior on the water waves after a while of running the app.

https://www.dropbox.com/s/jendbj5x5p55g96/WaterShader.apk?dl=0



Thanks Informatix, i hope you can explain why this happens and how to avoid it.

Walter
 
Last edited:

Informatix

Expert
Licensed User
Longtime User
Hi Informatix, i hadn't tried your water shader example until today, and while running it for a while i noticed something that I came across a while ago, if you let the app run for a long period of time (maybe 3 to 5 minutes) you start seeing that the top part of the water (surface) starts to loose it's uniformity, i don't know if that's the right way to describe it, but i have posted 2 images, the first one shows the water as soon as the app is started, and the 2nd one shows how it looks after you run the app for a while. I followed a tutorial online to do the same effect a while back and i noticed the same behavior, i was wondering if you could maybe figure out why this happens?


View attachment 28930
Normal Behavior


View attachment 28931
Water surface looks pixelated.

Here is also the link to the apk file of the project I did, following a tutorial online of course, if you install it you will see the same behavior on the water waves after a while of running the app.

https://www.dropbox.com/s/jendbj5x5p55g96/WaterShader.apk?dl=0



Thanks Informatix, i hope you can explain why this happens and how to avoid it.

Walter
Thanks for the report. I can reproduce the problem on my device. I have no idea for now about the cause. That looks like the filter goes from Linear to Nearest after some time. And the rendering becomes choppy despite it still runs at 60 fps.
 
Last edited:

Informatix

Expert
Licensed User
Longtime User
OK, I've found what's the cause. Change the precision of floats to highp in the fragment shader. It is a bit strange as mediump is recommended for Android, but in this particular case, you don't have the choice. I will update the examples of libGDX later today.
 

wonder

Expert
Licensed User
Longtime User
Yes, I meant libGDX. Sorry for not clarifying. I'd like to start experimenting with shaders, I saw some pretty interesting demos on youtube.
My question was in the sense of understanding the advantages of your SpecialFX library.

I don't know what ping-pong buffering is, but if you have no time to explain, don't worry, I'll find my way around the web. :)
 

Informatix

Expert
Licensed User
Longtime User
Yes, I meant libGDX. Sorry for not clarifying. I'd like to start experimenting with shaders, I saw some pretty interesting demos on youtube.
My question was in the sense of understanding the advantages of your SpecialFX library.

I don't know what ping-pong buffering is, but if you have no time to explain, don't worry, I'll find my way around the web. :)
The ping-pong buffering technique is used when you need to do several passes of a shader. You copy the result of the initial framebuffer into another framebuffer, then you send back the result in the first framebuffer. That saves memory as you use only two framebuffers.
SpecialFX has a few optimizations that you cannot implement with B4A and is also useful when you want to combine several shaders.
 

walterf25

Expert
Licensed User
Longtime User
OK, I've found what's the cause. Change the precision of floats to highp in the fragment shader. It is a bit strange as mediump is recommended for Android, but in this particular case, you don't have the choice. I will update the examples of libGDX later today.
Cool, sorry i had not seen your response, i now it's been a while since i posted that issue, but I will try your suggestions and let you know how it goes.

Thanks,
Walter
 

wonder

Expert
Licensed User
Longtime User
Hello,

I have a texture (dynamically generated) to which I would like to apply a certain effect.
Is it possible to utilize the shader program in a way where u_texture represents only this texture, instead of the entire framebuffer? If so, how?

Thanks in advance.
 

wonder

Expert
Licensed User
Longtime User
Is it possible to utilize the shader program in a way where u_texture represents only this texture, instead of the entire framebuffer? If so, how?
I found a solution:

LG_Create:
B4X:
Batch.Initialize 
Shader.Pedantic = False
Shader.InitializeWithFiles( _
    lGdx.Files.Internal("shaderprog/vertex.txt"), _
    lGdx.Files.Internal("shaderprog/fragment_color.txt")) 
tex.Initialize("wheel.png")
tex.SetFilter(tex.FILTER_Linear, tex.FILTER_Linear)
FBO.Initialize(fbo.FORMAT_RGBA8888, tex.Width, tex.Height)


LG_Render:
B4X:
'=====================================================
'Step 1:
'=====================================================
'Draw the texture into a framebuffer object of the
'same size (256x256). Custom shader is applied.
'-----------------------------------------------------
FBO.Begin
    Camera.SetToOrtho2(False, tex.Width, tex.Height)
    Camera.Update
    Batch.ProjectionMatrix = Camera.Combined
    Batch.Shader = Shader 
    Batch.Begin
        Batch.DrawTex(tex, 0, 0)
    Batch.End
FBO.End
'=====================================================


'=====================================================
'Step 2:
'=====================================================
'Revert back to the default shader and draw the
'modified texture. Draw other stuff.
'-----------------------------------------------------
Camera.SetToOrtho2( _
    False, lGdx.Graphics.Width, lGdx.Graphics.Height)
Camera.Update
Batch.ProjectionMatrix = Camera.Combined
Batch.Shader = Batch.CreateDefaultShader
Batch.Begin
    Batch.DrawTex(FBO.ColorBufferTexture, 300, 300)
    Batch.DrawTex(SomeOtherTexture, 100, 100)
Batch.End
'=====================================================
 
Last edited:

Informatix

Expert
Licensed User
Longtime User
Hello,

I have a texture (dynamically generated) to which I would like to apply a certain effect.
Is it possible to utilize the shader program in a way where u_texture represents only this texture, instead of the entire framebuffer? If so, how?

Thanks in advance.
u_texture is the texture on which the shader is applied, so I don't understand what you mean. If you look at the shader examples of LibGDX, I don't use any FrameBuffer (and if you use a FrameBuffer, you apply the shader to the texture of this FrameBuffer when rendering). ShaderProgram_BrightnessContrast for example shows how to apply the same shader to different textures displayed on the same screen, with different settings for different results.
 

wonder

Expert
Licensed User
Longtime User
I don't understand what you mean
What I meant was, from what I understand, the u_texture object in the shader program always refers to the "yet to be rendered" contents (framebuffer) of lgBatch.
After looking at you example, as you suggested, I see that after each shader pass you call the Flush method.
I think I start to understand how it works now...
 
Top