Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Libgdx how to use shader in 3D

I reached a point in my wip game, where I want to make it more eye-appealing. Currently I add some Ambientlight and a Directionla-light to an Environment and render my scene with it. But now I want to add a custom Shader to it. So I have been looking for some tutorials and for some reason in almost every tutorial they have used another "version" of using Shader in their game:

  • Giving the ModelBatch a String or FileHandle vertex/fragment-shader
  • Creating a ShaderProgram with vertex and fragment Shader.
  • Creating a new DefaultShader with this vertex and fragment Shader.
  • Creating a class, which implements Shader and use this ShaderClass.

I think there are more possibilitis, because there is also ShaderProviderand other classes as well.
I am a bit confused cause of all this posibilities. so I am looking for someone who can point me in the right direction.
To make it easier for you i tell you what i have and what I need:
I have:

  1. A ModelBatch which should draw everything.
  2. An Array<ModelInstance> instances, to which i wanna apply the Shader.
  3. A Texture and its NormalMap, both stored as Textures.
  4. The position, the direction and the color of my light, given as Vector3 and its strength given as float.
  5. The color of the ambient light and its strength, given as Vector3 and float.
  6. The light "Falloff" given as Vector3.

I need:

  1. To bind the Texture and its Normal Map to the Shader.
  2. Give the light pos, direction, color and strength to the Shader.
  3. Give the ambientlight color and strength to the Shader.
  4. Give the Falloff Vector to the Shader.
  5. Use this Shader for all "instances" in the Array, which are all drawn with my ModelBatch.

Another thing I would like to be able to do is, using different Textures for different models. I have all my ModleInstances with a textured Material. And I have the normal map for all those different Textures. Now i want to be able to bind the right Texture and Normal Map to the Shader, depending on the Texture in the Material of the Modelinstance.

A more "advanced" thing, I would like to if it is possible, is using different Shaders for different ModelTypes in my game (Stonewall uses a Shader without "Specularity" and "Reflection", while Metalwalls use Specularity for example).

What would be the best way to do this things in Libgdx? How can i bind the different variables i have? (With ShaderProgram i can use setUniformi for example).

Thanks a lot. If you need more information or if it is hard to understand let me know and i will try to create a better question.

Edit: I think the best way in my case creating a new Shader class, which implements Shader. But i would like to hear about all the other possibilities, their pros and their cons, regarding design, performance and possible restrictions.

like image 786
Robert P Avatar asked Mar 21 '14 08:03

Robert P


1 Answers

An overall explanation of rendering pipeline of the 3d api can be found here. This tutorial guides you in creating a new Shader from scratch. And this tutorial show how to use custom attributes to pass data to the shader. This wiki page also explains how to use Material Attributes and which Attributes the DefaultShader supports.

In LibGDX there's a difference between a Shader and ShaderProgram. A ShaderProgram is only the GPU implementation (both the vertex and fragment shader program), which is basically only the compiled GLSL files. You can, for example, set uniforms on ShaderProgram and use it to render a mesh. But the ShaderProgram itself does not "know" how it should render a model.

The Shader interface is intended to bridge the gap between the ShaderProgram and the Renderable (the latter being the smallest renderable part of a model). Thus a Shader most commonly encapsulates a ShaderProgram and makes sure it sets the correct uniforms etc. (Note that strictly speaking a Shader doesn't have to encapsulate a ShaderProgram. E.g. before GLES1 support was removed, there also was a GLES1 shader which managed the fixed rendering pipe instead of encapsulating a ShaderProgram)

The BaseShader class is an abstract helper class which implements the Shader interface, encapsulated a ShaderProgram and adds some helper methods to easily set uniforms. If you extend this class, you can easily register and set uniform, e.g. like this:

public class MyShader extends BaseShader {
    public final int u_falloff = register("u_falloff");
    ...
    @Override
    public void render (final Renderable renderable) {
        set(u_falloff, 15f);
        ...
        super.render(renderable);
    }
}

This will set the unifom called "u_falloff" to the specified value 15f (it will call setUniformX). If the ShaderProgram (glsl files) don't implement an uniform called "u_falloff", it will simply ignore the call. You could also check this using: if (has(u_falloff)) { /* calculate falloff and set it */ }. The BaseShader also adds the possibility to use a Validator and Setter for each uniform.

The DefaultShader extends the BaseShader and adds a default implementation for most of the Material Attributes. Have a look at the source if you want to see the naming of each uniform. In practice, if you use the same uniform naming as the DefaultShader does, it is possible to only specify the glsl files and let the DefaultShader take care of setting the uniforms. Of course it is possible to extend the DefaultShader to add additional uniforms.

When you call ModelBatch#render(...), the ModelBatch will query the ShaderProvider for a Shader to use. This is because every possible combination of vertex attributes and material attributes might require a different Shader. For example if you have two ModelInstances (or to be more precise two Renderables), one with a TextureAttribute.Diffuse and one without texture but with a ColorAttribute.Diffuse. Then most commonly, the ShaderProvider needs to create two different Shaders. Note that they can be of the same class (e.g. DefaultShader), but that the underlying GLSL files might be different.

The DefaultShader takes care of this using preprocessor macros (an ubershader). Depending on the vertex attributes and material attributes, it will #define multiple flags, causing the glsl program to be compiled specifically for that combination of vertex and material attributes. Have a look at the glsl files if you want to see how this is done.

So, in practice you will likely need you own ShaderProvider and your own Shader (either by implementing it from scratch, extending BaseShader or extending DefaultShader). You can extends DefaultShaderProvider for this and fall back to the DefaultShader if needed, e.g.:

public class MyShaderProvider extends DefaultShaderProvider {
    ... // implement constructor
    @Override
    protected Shader createShader (final Renderable renderable) {
        if (renderable.material.has(MyCustomAttribute.Type))
            return new MyShader(renderable);
        else
            return super.createShader(renderable);
    }
}

tl;dr If you want to use your own or a modified version of the ubershader, using the same uniforms as the default shader does, then simply provide the glsl files. If you want to use the same uniforms but add an additional uniform or two, then it might be easy to extend DefaultShader. Otherwise (or if you're learning shaders) I would advise to create the shader from scratch as described in This tutorial.

like image 73
Xoppa Avatar answered Nov 15 '22 10:11

Xoppa