2015-08-26 2 views
2

Après plusieurs jours de lutte je suis venu ici. J'essaie de transmettre un attribut personnalisé par vertex vec3 à un shader personnalisé basé sur le tutoriel this. Le tutoriel décrit comment passer un uniforme personnalisé qui fonctionne bien. Cependant quand j'essaye de modifier le code pour passer mon attribut par-vertex personnalisé il semble que rien n'est transféré à vertex shader et je ne peux pas comprendre comment le faire fonctionner.Libgdx attribut shader par-vertex personnalisé

Jusqu'à présent, je l'ai fait ce qui suit:

J'ai créé plusieurs boîtes avec modelBuilder.createBox() (donc je sais que chaque modèle a 24 vertex)

Je » v a généré un FloatBuffer contenant des données d'attribut réelles comme ceci:

int[] data = new int[]{x1, y1, z1, x1, y1, z1, ...} 

    ByteBuffer byteBuffer = ByteBuffer.allocateDirect(data.length * 4); 
    byteBuffer.order(ByteOrder.nativeOrder()); 
    mAttributeBuffer = byteBuffer.asFloatBuffer(); 
    mAttributeBuffer.put(data); 
    mAttributeBuffer.position(0); 

Ensuite, j'initialisation de la variable d'emplacement d'attribut correspondant (avec succès, a_coord> = 0):

a_coord = program.getAttributeLocation("a_coord"); 

Après que le côté libgdx dans la méthode de shader personnalisée render(Renderable) Je passe le tampon à OpenGL comme ceci:

program.setVertexAttribute(a_coord, 3, Gdx.gl20.GL_FLOAT, false, 0, mAttributeBuffer); 

Mon vertex shader personnalisé est comme suit:

attribute vec3 a_position; 
attribute vec3 a_normal; 
attribute vec2 a_texCoord0; 

uniform mat4 u_worldTrans; 
uniform mat4 u_projTrans; 


varying vec2 v_texCoord0; 

//my custom attribute 
attribute vec2 a_coord; 

void main() { 
    v_texCoord0 = a_texCoord0; 
    float posY = a_position.y + a_coord.y; 
    gl_Position = u_projTrans * u_worldTrans * vec4(a_position.x, posY, a_position.z, 1.0); 
} 

Le problème

En ce moment a_coord est 0 pour ev ery vertex. Qu'est-ce qui me manque et comment passer correctement l'attribut personnalisé à vertex shader?

Je suppose que le problème est quelque part dans le champ VBO et la façon dont libGDX transmet les données d'attribut aux vertex, mais je n'arrive toujours pas à comprendre comment le faire fonctionner.

Je serai heureux si quelqu'un peut me diriger dans la bonne direction sur cette question.

Code complet:

principal AplicationListener classe:

public class ProtoGame implements ApplicationListener { 

    public ProtoGame() 
    { 
     super(); 
    } 

    public PerspectiveCamera cam; 
    public CameraInputController camController; 
    public Model model; 
    public Array<ModelInstance> instances = new Array<ModelInstance>(); 
    public ModelBatch modelBatch; 

    @Override 
    public void create() { 
     cam = new PerspectiveCamera(67, Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); 
     cam.position.set(0f, 8f, 8f); 
     cam.lookAt(0,0,0); 
     cam.near = 1f; 
     cam.far = 300f; 
     cam.update(); 

     camController = new CameraInputController(cam); 
     Gdx.input.setInputProcessor(camController); 

     ModelBuilder modelBuilder = new ModelBuilder(); 
     model = modelBuilder.createBox(1f, 1f, 1f, 
       new Material(), 
       VertexAttributes.Usage.Position | VertexAttributes.Usage.Normal | VertexAttributes.Usage.TextureCoordinates); 

     Color colorU = new Color(), colorV = new Color(); 
     for (int x = -5; x <= 5; x+=2) { 
      for (int z = -5; z<=5; z+=2) { 
       ModelInstance instance = new ModelInstance(model, x, 0, z); 
       //this is where I'll put per-vertex attribute data for every instance 
       //but for now it's hardcoded in the Shader class so the data is the same across instances 

       TestShader.DoubleColorAttribute attr = new TestShader.DoubleColorAttribute(TestShader.DoubleColorAttribute.DiffuseUV, 
         colorU.set((x+5f)/10f, 1f - (z+5f)/10f, 0, 1), 
         colorV.set(1f - (x+5f)/10f, 0, (z+5f)/10f, 1)); 
       instance.materials.get(0).set(attr); 
       instances.add(instance); 
      } 
     } 


     modelBatch = new ModelBatch(new BaseShaderProvider() { 

      @Override 
      protected Shader createShader(Renderable renderable) { 
       return new TestShader(); 
      } 

     }); 
    } 

    @Override 
    public void render() { 
     camController.update(); 

     Gdx.gl.glViewport(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); 
     Gdx.gl.glClearColor(1, 1, 1, 1); 
     Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT); 

     modelBatch.begin(cam); 
     for (ModelInstance instance : instances) 
      modelBatch.render(instance); 
     modelBatch.end(); 
    } 

    @Override 
    public void dispose() { 
     model.dispose(); 
     modelBatch.dispose(); 
    } 
} 

classe shaders personnalisés libgdx:

public class TestShader implements Shader { 
    private FloatBuffer mAttributeBuffer; 



    ShaderProgram program; 
    Camera camera; 
    RenderContext context; 
    int u_projTrans; 
    int u_worldTrans; 
    int u_colorU; 
    int u_colorV; 

    int a_coord; 

    private static String getCustomVertexShader() { 
     return Gdx.files.internal("shader/test.vertex.glsl").readString(); 
    } 

    private static String getCustomFragmentShader() { 
     return Gdx.files.internal("shader/test.fragment.glsl").readString(); 
    } 


    @Override 
    public void init() { 

     program = new ShaderProgram(getCustomVertexShader(), getCustomFragmentShader()); 
     if (!program.isCompiled()) 
      throw new GdxRuntimeException(program.getLog()); 

     //tutorial's logic to init custom uniform locations 
     u_projTrans = program.getUniformLocation("u_projTrans"); 
     u_worldTrans = program.getUniformLocation("u_worldTrans"); 
     u_colorU = program.getUniformLocation("u_colorU"); 
     u_colorV = program.getUniformLocation("u_colorV"); 

     //initing custom attribute location 
     a_coord = program.getAttributeLocation("a_coord"); 


     //generating data and passing it to nio Buffer 
     float data[] = generateData(); 

     ByteBuffer byteBuffer = ByteBuffer.allocateDirect(data.length * 4); 
     byteBuffer.order(ByteOrder.nativeOrder()); 
     mAttributeBuffer = byteBuffer.asFloatBuffer(); 
     mAttributeBuffer.put(data); 
     mAttributeBuffer.position(0); 
    } 

    private float[] generateData() { 
     Vector3[] dataArray = new Vector3[1]; 
     dataArray[0] = new Vector3(2, 2, 2); 

     int components = 3; 
     int vertexPerModel = 24; 
     float[] data = new float[dataArray.length * components * vertexPerModel]; 
     for(int i = 0; i < dataArray.length; ++i){ 
      int i3 = i * components; 
      for(int j = 0; j < vertexPerModel; ++j) { 
       int j3 = j * components; 
       data[i3 + 0 + j3] = dataArray[i].x; 
       data[i3 + 1 + j3] = dataArray[i].y; 
       data[i3 + 2 + j3] = dataArray[i].z; 
      } 
     } 
     return data; 
    } 

    @Override 
    public void dispose() { 
     program.dispose(); 
    } 

    @Override 
    public void begin(Camera camera, RenderContext context) { 
     this.camera = camera; 
     this.context = context; 
     program.begin(); 
     program.setUniformMatrix(u_projTrans, camera.combined); 
     context.setDepthTest(GL20.GL_LEQUAL); 
     context.setCullFace(GL20.GL_BACK); 
    } 

    @Override 
    public void render(Renderable renderable) { 
     program.setUniformMatrix(u_worldTrans, renderable.worldTransform); 
     //tutorial's logic to pass uniform 
     DoubleColorAttribute attribute = ((DoubleColorAttribute) renderable.material.get(DoubleColorAttribute.DiffuseUV)); 
     program.setUniformf(u_colorU, attribute.color1.r, attribute.color1.g, attribute.color1.b); 
     program.setUniformf(u_colorV, attribute.color2.r, attribute.color2.g, attribute.color2.b); 


     //passing my custom attributes to the vertex shader 
     program.setVertexAttribute(a_coord, 3, Gdx.gl20.GL_FLOAT, false, 0, mAttributeBuffer); 


     renderable.mesh.render(program, renderable.primitiveType, 
       renderable.meshPartOffset, renderable.meshPartSize); 
    } 

    @Override 
    public void end() { 
     program.end(); 
    } 

    @Override 
    public int compareTo(Shader other) { 
     return 0; 
    } 

    @Override 
    public boolean canRender(Renderable renderable) { 
     return renderable.material.has(DoubleColorAttribute.DiffuseUV); 
    } 
} 
+1

Vous devez intégrer toutes les données que vous voulez dans le maillage. N'appelez pas vous-même 'setVertexAttribute', cela ne marchera pas.'ModelBuilder' (et' MeshBuilder') supporte les attributs personnalisés (fournissez juste le 'VertexAttributes' dans le constructeur, au lieu du masque de bits de commodité) mais dans ce cas, vous devrez modifier le mesh par la suite et définir les valeurs que vous voulez. être. – Xoppa

+0

@Xoppa Merci beaucoup pour le super article et pour le signaler - j'étudie les sources de libGDX dans ce sens maintenant. En attendant peut-être il y a une sorte de tutoriel ou une paix de code dont vous êtes au courant et qui fait la même chose? J'ai creusé des tonnes d'articles les jours précédents sans aucune chance. –

Répondre

1

Enfin j'ai pu passer un attribut personnalisé à vertex shader! Merci beaucoup à @Xoppa pour m'avoir indiqué la bonne direction.

C'est la solution de travail que j'ai jusqu'à présent (je suis ouvert pour des conseils supplémentaires sur la façon de le mettre en œuvre de manière plus élégante):

Tout d'abord, comme Xoppa a déclaré dans le commentaire Il est nécessaire de créer un modèle fournissant une structure de sommet personnalisée lors de sa construction.Ainsi, la création de modèles peut ressembler à ceci:

VertexAttribute posAttr = new VertexAttribute(VertexAttributes.Usage.Position, 3, ShaderProgram.POSITION_ATTRIBUTE); 
    ... 
    VertexAttribute customVertexAttr = new VertexAttribute(512, 3, "a_custom"); 
    VertexAttributes vertexAttributes = new VertexAttributes(
      posAttr, 
      ... 
      customVertexAttr); 

    ModelBuilder modelBuilder = new ModelBuilder(); 
    modelBuilder.begin(); 
    modelBuilder. 
      part("box", GL20.GL_TRIANGLES, vertexAttributes, new Material()). 
      box(1f, 1f, 1f); 
    model = modelBuilder.end(); 

Ou la même chose avec MeshBuilder:

MeshBuilder meshBuilder = new MeshBuilder(); 
    VertexAttributes vertexAttributes = new VertexAttributes(...); 
    meshBuilder.begin(vertexAttributes); 
    meshBuilder.part("box", GL20.GL_TRIANGLES); 
    meshBuilder.setColor(color); 
    meshBuilder.box(1f, 1f, 1f); 
    Mesh mesh = meshBuilder.end(); 

Ce code va créer modèle avec des sommets contenant des données supplémentaires en fonction des attributs fournis. Il est temps de remplir le tableau de vertex correspondant. Vous avez besoin d'un maillage pour cela - il stocke le tableau vertices - un tableau plat d'attributs condensés l'un après l'autre par sommet. Vous avez donc besoin d'un nombre d'attributs par sommet ainsi que d'un décalage pour l'attribut qui doit être modifié. Mesh stocke toutes les données:

Mesh mesh = model.meshes.get(0); 
int numVertices = mesh.getNumVertices(); 
// vertex size and offset are in byte so we need to divide it by 4 
int vertexSize = mesh.getVertexAttributes().vertexSize/4; 
//it's possible to use usage int here passed previously to VertexAttribute constructor. 
VertexAttribute customAttribute = mesh.getVertexAttribute(512) 
int offset = customAttribute.offset/4; 

float[] vertices = new float[numVertices * vertexSize]; 
mesh.getVertices(vertices); 

Nous sommes prêts à transmettre les données:

List<Vector3> customData ... 

for(int i = 0; i < numVertices; ++i){ 
    int index = i * vertexSize + offset; 
    vertices[index + 0] = customData.get(i).x; 
    vertices[index + 1] = customData.get(i).y; 
    vertices[index + 2] = customData.get(i).z; 
} 

Et ne pas oublier de passer le tableau de sommets mis à jour en arrière au maillage:

mesh.updateVertices(0, vertices); 

C'est tout.

est ici aussi une mise en œuvre d'une méthode d'aide pour créer un mélange d'attributs par défaut en utilisant Usage drapeaux aux côtés des attributs personnalisés:

private VertexAttributes createMixedVertexAttribute(int defaultAtributes, List<VertexAttribute> customAttributes){ 
    VertexAttributes defaultAttributes = MeshBuilder.createAttributes(defaultAtributes); 
    List<VertexAttribute> attributeList = new ArrayList<VertexAttribute>(); 
    for(VertexAttribute attribute: defaultAttributes){ 
     attributeList.add(attribute); 
    } 
    attributeList.addAll(customAttributes); 
    VertexAttribute[] typeArray = new VertexAttribute[0]; 
    VertexAttributes mixedVertexAttributes = new VertexAttributes(attributeList.toArray(typeArray)); 
    return mixedVertexAttributes; 
} 

La source complète:

public class ProtoGame implements ApplicationListener { 

private static final int CUSTOM_ATTRIBUTE_USAGE = 512; 

public ProtoGame() 
{ 
    super(); 
} 

public PerspectiveCamera cam; 
public CameraInputController camController; 
public Model model; 
public Array<ModelInstance> instances = new Array<ModelInstance>(); 
public ModelBatch modelBatch; 

@Override 
public void create() { 
    cam = new PerspectiveCamera(67, Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); 
    cam.position.set(0f, 8f, 8f); 
    cam.lookAt(0, 0, 0); 
    cam.near = 1f; 
    cam.far = 300f; 
    cam.update(); 

    camController = new CameraInputController(cam); 
    Gdx.input.setInputProcessor(camController); 


    Model model = createModelWithCustomAttributes(); 
    Mesh mesh = model.meshes.get(0); 
    setCustomAttributeData(mesh); 


    Color colorU = new Color(), colorV = new Color(); 
    for (int x = -5; x <= 5; x+=2) { 
     for (int z = -5; z<=5; z+=2) { 
      ModelInstance instance = new ModelInstance(model, x, 0, z); 
      TestShader.DoubleColorAttribute attr = new TestShader.DoubleColorAttribute(TestShader.DoubleColorAttribute.DiffuseUV, 
        colorU.set((x+5f)/10f, 1f - (z+5f)/10f, 0, 1), 
        colorV.set(1f - (x+5f)/10f, 0, (z+5f)/10f, 1)); 
      instance.materials.get(0).set(attr); 
      instances.add(instance); 
     } 
    } 


    modelBatch = new ModelBatch(new BaseShaderProvider() { 

     @Override 
     protected Shader createShader(Renderable renderable) { 
      return new TestShader(); 
     } 

    }); 
} 




@Override 
public void render() { 
    camController.update(); 

    Gdx.gl.glViewport(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); 
    Gdx.gl.glClearColor(1, 1, 1, 1); 
    Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT); 

    modelBatch.begin(cam); 
    for (ModelInstance instance : instances) 
     modelBatch.render(instance); 
    modelBatch.end(); 
} 

private Model createModelWithCustomAttributes() { 
    int defaultAttributes = VertexAttributes.Usage.Position | VertexAttributes.Usage.Normal | VertexAttributes.Usage.TextureCoordinates; 
    VertexAttribute customVertexAttr = new VertexAttribute(CUSTOM_ATTRIBUTE_USAGE, 3, "a_custom"); 

    List<VertexAttribute> customAttributeList = new ArrayList<VertexAttribute>(); 
    customAttributeList.add(customVertexAttr); 

    VertexAttributes vertexAttributes = createMixedVertexAttribute(defaultAttributes, customAttributeList); 

    ModelBuilder modelBuilder = new ModelBuilder(); 
    modelBuilder.begin(); 
    modelBuilder. 
      part("box", GL20.GL_TRIANGLES, vertexAttributes, new Material()). 
      box(1f, 1f, 1f); 
    return modelBuilder.end(); 
} 

private void setCustomAttributeData(Mesh mesh) { 
    int numVertices = mesh.getNumVertices(); 

    int vertexSize = mesh.getVertexAttributes().vertexSize/4; 
    int offset = mesh.getVertexAttribute(CUSTOM_ATTRIBUTE_USAGE).offset/4; 

    float[] vertices = new float[numVertices * vertexSize]; 
    mesh.getVertices(vertices); 

    for(int i = 0; i < numVertices; ++i){ 
     int index = i * vertexSize + offset; 
     vertices[index + 0] = i; 
     vertices[index + 1] = i; 
     vertices[index + 2] = i; 
    } 
    mesh.updateVertices(0, vertices); 
}  

@Override 
public void dispose() { 
    model.dispose(); 
    modelBatch.dispose(); 
} 

private VertexAttributes createMixedVertexAttribute(int defaultAtributes, List<VertexAttribute> customAttributes){ 
    VertexAttributes defaultAttributes = MeshBuilder.createAttributes(defaultAtributes); 
    List<VertexAttribute> attributeList = new ArrayList<VertexAttribute>(); 
    for(VertexAttribute attribute: defaultAttributes){ 
     attributeList.add(attribute); 
    } 
    attributeList.addAll(customAttributes); 
    VertexAttribute[] typeArray = new VertexAttribute[0]; 
    VertexAttributes mixedVertexAttributes = new VertexAttributes(attributeList.toArray(typeArray)); 
    return mixedVertexAttributes; 
} 


@Override 
public void resize(int width, int height) { 
} 

@Override 
public void pause() { 
} 

@Override 
public void resume() { 
} 
} 

Vertex shader:

attribute vec3 a_position; 
attribute vec3 a_normal; 
attribute vec2 a_texCoord0; 

uniform mat4 u_worldTrans; 
uniform mat4 u_projTrans; 


varying vec2 v_texCoord0; 

attribute vec3 a_custom; 

void main() { 
    v_texCoord0 = a_texCoord0; 
    float posX = a_position.x + a_custom.x; 
    float posY = a_position.y + a_custom.y; 
    float posZ = a_position.z + a_custom.z; 
    gl_Position = u_projTrans * u_worldTrans * vec4(posX, posY, posZ, 1.0); 
} 

Fragment shaders

#ifdef GL_ES 
precision mediump float; 
#endif 

uniform vec3 u_colorU; 
uniform vec3 u_colorV; 

varying vec2 v_texCoord0; 

void main() { 
    gl_FragColor = vec4(v_texCoord0.x * u_colorU + v_texCoord0.y * u_colorV, 1.0); 
} 
0

il est en fait possible d'appeler directement setVertexAttributes et le faire fonctionner sans avoir besoin d'un maillage. Juste besoin d'étendre ShaderProgram comme si

public class ShaderProgramExtended extends ShaderProgram { 

public ShaderProgramExtended(String v, String f){ 
    super(v,f); 
} 

/* 
This is VERY NAUGHTY. Mario and Nathan probably made it private for a reason 
*/ 
public int getProgram(){ 

    int result; 
    try{ 
     Field field = ShaderProgram.class.getDeclaredField("program"); 
     field.setAccessible(true); 
     Object value = field.get(this); 
     field.setAccessible(false); 
     if (value == null) { 
      result = 0; 
     }else{ 
      result = (Integer) value; 
     } 
    } catch (NoSuchFieldException e) { 
     throw new RuntimeException(e); 
    } catch (IllegalAccessException e) { 
     throw new RuntimeException(e); 
    } 
    return result; 
} 

public void begin(int program){ 
    Gdx.gl20.glUseProgram(program); 
} 

public void draw(int mode, int first, int count){ 
    Gdx.gl20.glDrawArrays(mode,first,count); 
} 

Et puis appelez comme vous le feriez normalement avec le changement « mineur » d'envoyer l'entier objet shader à la nouvelle méthode commencer

public class TestlibGDXv2 extends ApplicationAdapter { 

private final String tag = (this).getClass().getSimpleName(); 
String message = ""; 

private static final int FLOAT_BYTES = 4; 
FloatBuffer vertexData; 
FloatBuffer colorData; 
ShaderProgramExtended shader; 

private static final String COLOR_ATTRIBUTE = ShaderProgram.COLOR_ATTRIBUTE; 
private int aColourLocation; 

private static final String POSITION_ATTRIBUTE = ShaderProgram.POSITION_ATTRIBUTE; 
private int aPositionLocation; 


/* 
Anti-clockwise winding order. Note, we could share two of the vertices. Haven't for clarity. 
*/ 
float[] vertices = { 
     -0.5f, -0.5f, 
     0.5f, -0.5f, 
     -0.5f, 0.5f, 
     -0.5f, 0.5f, 
     0.5f, -0.5f, 
     0.5f, 0.5f 
}; 

/* 
Need to colour each vertex, so need 6. 
*/ 

float[] colors = {1.0f, 0.0f, 0.0f, 1.0f, 
     0.0f,1.0f,0.0f,1.0f, 
     0.0f,0.0f,1.0f,1.0f, 
     0.0f,0.0f,1.0f,1.0f, 
     0.0f,1.0f,0.0f,1.0f, 
     1.0f,0.0f,0.0f,1.0f 
}; 


@Override 
public void create() { 

    /* 
    Convert from Dalvik VM to OpenGL native 
    */ 
    vertexData = ByteBuffer.allocateDirect(vertices.length * FLOAT_BYTES) 
      .order(ByteOrder.nativeOrder()) 
      .asFloatBuffer(); 

    colorData = ByteBuffer.allocateDirect(colors.length * FLOAT_BYTES) 
      .order(ByteOrder.nativeOrder()) 
      .asFloatBuffer(); 

    vertexData.put(vertices).position(0); 
    colorData.put(colors).position(0); 

    initialiseShaders(); 

} 

@Override 
public void render() { 

    Gdx.gl.glClearColor(0, 0, 0, 1); 
    Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); 

    shader.begin(shader.getProgram()); 
    shader.setVertexAttribute(aPositionLocation, 2, GL20.GL_FLOAT, false, 0, vertexData); 
    shader.enableVertexAttribute(aPositionLocation); 
    shader.setVertexAttribute(aColourLocation, 4, GL20.GL_FLOAT, false, 0, colorData); 
    shader.enableVertexAttribute(aColourLocation); 

    shader.draw(GL20.GL_TRIANGLES, 0, 6); 
    shader.end(); 
} 

private void initialiseShaders() { 


    String vertexShaderSource = 
        "#version 130\n" + 
        "attribute vec4 " + POSITION_ATTRIBUTE + ";\n" + // x,y,z and w 
        "attribute vec4 " + COLOR_ATTRIBUTE + ";\n" + // r,g,b and a 
        "varying vec4 v_color;\n" + // pass to fragment shader 
        "void main(){\n" + 
        " v_color = "+ COLOR_ATTRIBUTE + ";\n" + 
        " gl_Position = " + POSITION_ATTRIBUTE + ";\n" + 
        "}"; 

    String fragmentShaderSource = 
        "#version 130\n" + 
        "#ifdef GL_ES\n" + 
        " precision mediump float;\n" + // medium a good balance between speed and quality 
        "#endif\n" + 
        "varying vec4 v_color;\n" + // incoming from vertex shader 
        "void main(){\n" + 
        " gl_FragColor = v_color;\n" + 
        "}"; 

    shader = new ShaderProgramExtended(vertexShaderSource,fragmentShaderSource); 
    aPositionLocation = shader.getAttributeLocation(POSITION_ATTRIBUTE); 
    aColourLocation = shader.getAttributeLocation(COLOR_ATTRIBUTE); 

} 

}

L'attribut "a_color" (de ShaderProgram.COLOR_ATTRIBUTE) aurait tout aussi bien pu être "a_custom".

J'ai utilisé cette méthode lorsque j'essayais d'apprendre OpenGL ES à partir de livres WebGL - il y en a beaucoup plus que les livres libGDX OpenGL. Cependant, n'utilisez que ce qui précède pour apprendre, le shader n'est plus géré par libGDX et ne fonctionnera donc pas bien sur Android lorsque le contexte est perdu. OK pour le bureau, cependant.

Vive

John