libGDX Android: Disegnare con OpenGL ES – Shader, Vertici e FrameLayout in Java (Parte 1)

In questa guida imparerai a usare libGDX OpenGL ES shader per disegnare grafica 2D su Android con Java, integrando vertici, Mesh e FrameLayout in un’unica applicazione. Questa è la prima parte di una serie dedicata allo sviluppo di applicazioni grafiche con libGDX, in cui esploreremo passo dopo passo come sfruttare le API di basso livello di OpenGL ES per creare rendering personalizzati, scrivere shader GLSL e integrare la surface OpenGL ES in un layout Android con FrameLayout. Se preferisci un approccio più tradizionale alla grafica 2D, consulta anche la guida su Canvas Android.

libGDX OpenGL ES shader su Android con vertici e FrameLayout in Java

Prerequisiti per libGDX OpenGL ES shader su Android

Per seguire questa guida ti servono:

  • Conoscenza base di Java
  • Conoscenza base di Android Studio (creare e avviare un progetto)
  • Android Studio installato (ultima versione stabile)
  • Gradle configurato (normalmente incluso in Android Studio)

1. Introduzione a libGDX e OpenGL ES

Cos’è libGDX?

libGDX è un framework Java multipiattaforma pensato soprattutto per lo sviluppo di videogiochi, ma adatto anche a qualunque applicazione grafica avanzata (visualizzatori, simulazioni, prototipi UI custom). Inoltre, fornisce un layer di astrazione sopra OpenGL/OpenGL ES e gestisce per te molti dettagli complessi:

  • Creazione della finestra / surface di rendering
  • Gestione del ciclo di vita (create, render, resize, pause, resume, dispose)
  • Gestione input (touch, tastiera, gamepad)
  • Caricamento risorse (texture, font, audio, ecc.)
  • Supporto multipiattaforma (Desktop, Android, iOS, HTML5)

Cos’è OpenGL ES e perché usarlo su Android

OpenGL ES (OpenGL for Embedded Systems) è una versione semplificata di OpenGL pensata per dispositivi mobili e embedded (Android, iOS, set-top box, ecc.). Di conseguenza, è lo standard di fatto per la grafica 2D/3D accelerata su Android.

Usare direttamente OpenGL ES richiede molto codice “boilerplate”. Tuttavia, libGDX si appoggia a OpenGL ES su Android, ma ti espone un’API Java più comoda. In altre parole, quando usi Mesh, ShaderProgram e altri componenti libGDX, sotto al cofano vengono eseguite chiamate OpenGL ES.

Perché usare libGDX + OpenGL ES su Android?

  • Controllo totale sulla grafica: linee, triangoli, mesh 2D/3D, shader personalizzati.
  • Prestazioni elevate: rendering GPU altamente ottimizzato.
  • Portabilità: scrivi in Java, esegui su Desktop, Android (e altre piattaforme) con poche modifiche.
  • Strumenti e community: tanti esempi, wiki, forum, progetti open source.

In questa serie useremo i libGDX OpenGL ES shader tramite l’API di basso livello (Mesh + ShaderProgram) per capire bene come dialogare con OpenGL ES.

2. Setup del progetto libGDX su Android Studio

Creare un nuovo progetto libGDX

Il modo consigliato è usare il Setup Application ufficiale di libGDX (un tool desktop che genera il progetto multipiattaforma). Tuttavia, per semplicità, in questa guida ci concentriamo sul modulo Android e sulla parte grafica.

Se utilizzi il setup ufficiale, otterrai una struttura di progetto simile a questa:

my-gdx-project/
├── core/           # Codice principale condiviso (Java)
├── android/        # Launcher Android + integrazione con OS
├── desktop/        # Launcher desktop (facoltativo)
└── gradle/         # Configurazione Gradle condivisa

Noi, in particolare, ci concentreremo su core (dove vivono Mesh e ShaderProgram) e su android (dove useremo FrameLayout).

Dipendenze Gradle

Nel modulo core, innanzitutto assicurati di avere la dipendenza libGDX nel build.gradle (versione indicativa, controlla sempre l’ultima sul sito ufficiale):

dependencies {
    implementation "com.badlogicgames.gdx:gdx:1.12.0" // esempio di versione
}

Nel modulo android avrai qualcosa del genere:

dependencies {
    implementation project(":core")
    implementation "com.badlogicgames.gdx:gdx-backend-android:1.12.0"

    // Per supporto audio facoltativo
    implementation "com.badlogicgames.gdx:gdx-platform:1.12.0:natives-armeabi-v7a"
    implementation "com.badlogicgames.gdx:gdx-platform:1.12.0:natives-arm64-v8a"
}

La versione 1.12.0 è solo un esempio: quindi usa la versione corrente indicata nella documentazione libGDX.

Struttura del codice

  • core: contiene la tua classe ApplicationListener (o Game) che rappresenta l’applicazione libGDX. Qui scriveremo il codice dei nostri esempi.
  • android: contiene l’Activity di lancio (tipicamente AndroidLauncher) che crea e configura la view OpenGL ES e la integra con Android.

3. FrameLayout e integrazione con libGDX

Perché usare un FrameLayout con libGDX OpenGL ES?

Di default, il progetto libGDX Android usa una Activity che mostra solo la surface OpenGL (full-screen). Tuttavia, spesso vogliamo integrare il rendering con una UI Android standard: bottoni, Toolbar, overlay, ecc. In questo caso, un FrameLayout è una scelta semplice per ospitare la view di libGDX insieme ad altre view Android.

Esempio di layout XML con FrameLayout

Nel modulo android, per esempio crea un layout activity_main.xml come questo:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/root_frame"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!-- Qui inseriremo la view di libGDX a runtime -->

    <Button
        android:id="@+id/overlay_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Overlay UI"
        android:layout_gravity="top|end"
        android:layout_margin="16dp" />

</FrameLayout>

Integrazione in AndroidLauncher con AndroidApplicationConfiguration

La classe tipica di lancio Android estende AndroidApplication. Invece di usare initialize() (che crea e imposta direttamente la view), useremo initializeForView() per ottenere una view da aggiungere al nostro FrameLayout.

package com.mio.progetto.android;

import android.os.Bundle;
import android.view.View;
import android.widget.FrameLayout;

import com.badlogic.gdx.backends.android.AndroidApplication;
import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration;
import com.mio.progetto.MyGdxApp; // classe nel modulo core

public class AndroidLauncher extends AndroidApplication {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        FrameLayout root = findViewById(R.id.root_frame);

        AndroidApplicationConfiguration config = new AndroidApplicationConfiguration();
        config.useImmersiveMode = true; // facoltativo
        config.useAccelerometer = false;
        config.useCompass = false;

        // Crea la view OpenGL ES di libGDX
        View gameView = initializeForView(new MyGdxApp(), config);

        // Aggiungi la view al FrameLayout
        root.addView(gameView,
                new FrameLayout.LayoutParams(
                        FrameLayout.LayoutParams.MATCH_PARENT,
                        FrameLayout.LayoutParams.MATCH_PARENT));

        // Qui puoi configurare altri widget (es. il bottone overlay)
        findViewById(R.id.overlay_button).setOnClickListener(v -> {
            // Azione sul bottone, ad esempio pausa del gioco o apertura di un menu
        });
    }
}

In questo modo, la surface OpenGL ES di libGDX vive all’interno del tuo layout Android, sotto (o sopra) altri widget a seconda dell’ordine in cui li aggiungi.

4. Concetti base di OpenGL ES in libGDX

Sistema di coordinate normalizzato (NDC)

Quando lavori direttamente con Mesh e ShaderProgram, di solito operi in Normalized Device Coordinates (NDC), dove:

  • L’asse X va da -1.0 a +1.0 orizzontalmente
  • L’asse Y va da -1.0 a +1.0 verticalmente
  • L’asse Z va da -1.0 a +1.0 in profondità (per 3D)

In questo spazio di coordinate:

  • (0, 0) è il centro dello schermo
  • (-1, -1) è l’angolo in basso a sinistra
  • (+1, +1) è l’angolo in alto a destra

Viewport e dimensioni della finestra

La viewport è l’area dello schermo in cui OpenGL ES disegna. Di norma, libGDX la imposta automaticamente quando la finestra viene ridimensionata, ma puoi anche farlo manualmente con Gdx.gl.glViewport(x, y, width, height).

Nel metodo resize(int width, int height) della tua applicazione libGDX, puoi gestire il cambio di orientamento o di dimensioni della view. In questa prima parte, tuttavia, useremo coordinate NDC semplici e non introdurremo ancora le matrici di proiezione/visualizzazione (che tratteremo nella Parte 2).

5. Vertici e Mesh in libGDX

Cosa sono i vertici negli shader OpenGL ES di libGDX?

Un vertice è un punto nello spazio 2D/3D con una serie di attributi: tipicamente posizione, colore, coordinate di texture (UV) e altro. OpenGL ES, quindi, disegna triangoli a partire da questi vertici.

Per esempio, un vertice 2D con colore RGBA:

  • posizione: (x, y)
  • colore: (r, g, b, a)

Totale: quindi 6 valori float per vertice.

Cosa è una Mesh in libGDX con OpenGL ES shader?

Una Mesh in libGDX è un contenitore di vertici (e opzionalmente indici) pronto per essere inviato alla GPU. In particolare, definisci:

  • Numero massimo di vertici
  • Numero massimo di indici (facoltativo)
  • Formato dei vertici (quali attributi: posizione, colore, UV, ecc.)

Per esempio, ecco un esempio di creazione di una Mesh che rappresenta un triangolo 2D colorato:

package com.mio.progetto;

import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Mesh;
import com.badlogic.gdx.graphics.VertexAttribute;
import com.badlogic.gdx.graphics.VertexAttributes.Usage;

public class MyGdxApp implements ApplicationListener {

    private Mesh triangleMesh;

    @Override
    public void create() {
        // 3 vertici, nessun indice (disegniamo direttamente in ordine)
        triangleMesh = new Mesh(true, 3, 0,
                new VertexAttribute(Usage.Position, 2, "a_position"), // x, y
                new VertexAttribute(Usage.ColorUnpacked, 4, "a_color") // r, g, b, a
        );

        // Definiamo i vertici: (x, y, r, g, b, a)
        float[] vertices = new float[]{
                // Vertice 1: in alto
                0.0f,  0.5f,   1f, 0f, 0f, 1f,   // rosso
                // Vertice 2: in basso a sinistra
               -0.5f, -0.5f,   0f, 1f, 0f, 1f,   // verde
                // Vertice 3: in basso a destra
                0.5f, -0.5f,   0f, 0f, 1f, 1f    // blu
        };

        triangleMesh.setVertices(vertices);
    }

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

    @Override
    public void render() { }

    @Override
    public void pause() { }

    @Override
    public void resume() { }

    @Override
    public void dispose() {
        if (triangleMesh != null) triangleMesh.dispose();
    }
}

In questo esempio abbiamo definito solo la Mesh. Nel prossimo passo, quindi, vedremo come usare gli shader per disegnare effettivamente il triangolo sullo schermo.

6. Shader in libGDX (GLSL)

Vertex shader e fragment shader in libGDX OpenGL ES

In OpenGL ES moderno, non esistono più le “fixed pipeline” (funzioni predefinite) per trasformare e colorare i vertici. Di conseguenza, tutto passa da due piccoli programmi che scrivi tu in GLSL (OpenGL Shading Language): per scrivere un libGDX OpenGL ES shader, devi usare un vertex shader e un fragment shader.

  • Vertex shader: eseguito per ogni vertice, calcola la posizione finale (e passa dati al fragment shader).
  • Fragment shader: eseguito per ogni pixel (frammento) visibile del triangolo, calcola il colore finale da disegnare.

Esempio di vertex shader per un triangolo colorato

Creiamo un vertex shader semplice che, innanzitutto, prende posizione e colore da attributi del vertice e li passa al fragment shader:

// triangle.vert (vertex shader)
#ifdef GL_ES
precision mediump float;
#endif

// Attributi dal vertice (devono corrispondere ai nomi nella Mesh)
attribute vec2 a_position;
attribute vec4 a_color;

// Variazione verso il fragment shader
varying vec4 v_color;

void main() {
    // Passiamo il colore al fragment shader
    v_color = a_color;

    // Convertiamo da vec2 a vec4 per clip space
    gl_Position = vec4(a_position, 0.0, 1.0);
}

Esempio di fragment shader per un triangolo colorato

Il fragment shader, allo stesso modo, riceve il colore interpolato tra i vertici e lo scrive come colore finale del pixel:

// triangle.frag (fragment shader)
#ifdef GL_ES
precision mediump float;
#endif

varying vec4 v_color;

void main() {
    gl_FragColor = v_color;
}

Creare uno ShaderProgram in libGDX

In libGDX, uno ShaderProgram compila e collega i sorgenti di un libGDX OpenGL ES shader in un unico programma utilizzabile per disegnare.

Possiamo caricare gli shader da stringhe (utile per esempi) o da file. Per semplicità, in questo caso useremo stringhe inline:

import com.badlogic.gdx.graphics.glutils.ShaderProgram;

public class MyGdxApp implements ApplicationListener {

    private Mesh triangleMesh;
    private ShaderProgram shader;

    @Override
    public void create() {
        // (Creazione della mesh come visto prima...)

        String vertexShader =
                "#ifdef GL_ES\n" +
                "precision mediump float;\n" +
                "#endif\n" +
                "attribute vec2 a_position;\n" +
                "attribute vec4 a_color;\n" +
                "varying vec4 v_color;\n" +
                "void main() {\n" +
                "    v_color = a_color;\n" +
                "    gl_Position = vec4(a_position, 0.0, 1.0);\n" +
                "}\n";

        String fragmentShader =
                "#ifdef GL_ES\n" +
                "precision mediump float;\n" +
                "#endif\n" +
                "varying vec4 v_color;\n" +
                "void main() {\n" +
                "    gl_FragColor = v_color;\n" +
                "}\n";

        ShaderProgram.pedantic = false; // evita errori troppo severi
        shader = new ShaderProgram(vertexShader, fragmentShader);

        if (!shader.isCompiled()) {
            Gdx.app.error("Shader", "Errore compilazione shader: " + shader.getLog());
        }
    }

    // ... altri metodi
}

Con shader.isCompiled() verifichiamo che la compilazione sia andata a buon fine, altrimenti stampiamo il log di errore. Più avanti, nella sezione “Errori comuni e debug”, vedremo meglio come usare getLog().

7. Disegnare con ShaderProgram e Mesh

Il ciclo render() con shader OpenGL ES in libGDX

libGDX chiama il metodo render() della tua applicazione molte volte al secondo (tipicamente 60 fps). In questo metodo, quindi, devi:

  • Pulire lo schermo (clear)
  • Attivare lo shader (begin())
  • Disegnare la mesh (bind + draw)
  • Disattivare lo shader (end())

Esempio completo di draw con ShaderProgram e Mesh

@Override
public void render() {
    // Puliamo lo schermo con uno sfondo grigio scuro
    Gdx.gl.glClearColor(0.1f, 0.1f, 0.1f, 1f);
    Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

    // Abilitiamo il blending per supportare alpha (se necessario)
    Gdx.gl.glEnable(GL20.GL_BLEND);
    Gdx.gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA);

    // Iniziamo a usare lo shader
    shader.begin();

    // (Se avessimo uniform, qui settiamo i valori con shader.setUniformf / setUniformMatrix, ecc.)

    // Disegniamo la mesh
    triangleMesh.render(shader, GL20.GL_TRIANGLES);

    // Fine utilizzo shader
    shader.end();
}

Il metodo mesh.render(shader, mode) si occupa internamente di fare il bind degli attributi, abilitare gli array di vertici e inviare il draw call a OpenGL ES.

Nota: se usi indici nella mesh, puoi anche passare offset e count per specificare quanti indici disegnare.

8. Esempi pratici completi in Java

Esempio 1: Triangolo con colori interpolati

Questo esempio mostra un triangolo con un colore diverso per ogni vertice. OpenGL ES, di conseguenza, interpola i colori tra un vertice e l’altro, creando un gradiente.

package com.mio.progetto;

import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Mesh;
import com.badlogic.gdx.graphics.VertexAttribute;
import com.badlogic.gdx.graphics.VertexAttributes.Usage;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;

public class ColoredTriangleExample implements ApplicationListener {

    private Mesh mesh;
    private ShaderProgram shader;

    @Override
    public void create() {
        // 1) Creazione Mesh
        mesh = new Mesh(true, 3, 0,
                new VertexAttribute(Usage.Position, 2, "a_position"),
                new VertexAttribute(Usage.ColorUnpacked, 4, "a_color"));

        float[] vertices = new float[]{
                // x,    y,      r, g, b, a
                 0.0f,  0.5f,   1f, 0f, 0f, 1f,  // Rosso (alto)
                -0.5f, -0.5f,   0f, 1f, 0f, 1f,  // Verde (basso sx)
                 0.5f, -0.5f,   0f, 0f, 1f, 1f   // Blu (basso dx)
        };
        mesh.setVertices(vertices);

        // 2) Creazione Shader
        String vertexShader =
                "#ifdef GL_ES\n" +
                "precision mediump float;\n" +
                "#endif\n" +
                "attribute vec2 a_position;\n" +
                "attribute vec4 a_color;\n" +
                "varying vec4 v_color;\n" +
                "void main() {\n" +
                "    v_color = a_color;\n" +
                "    gl_Position = vec4(a_position, 0.0, 1.0);\n" +
                "}\n";

        String fragmentShader =
                "#ifdef GL_ES\n" +
                "precision mediump float;\n" +
                "#endif\n" +
                "varying vec4 v_color;\n" +
                "void main() {\n" +
                "    gl_FragColor = v_color;\n" +
                "}\n";

        ShaderProgram.pedantic = false;
        shader = new ShaderProgram(vertexShader, fragmentShader);

        if (!shader.isCompiled()) {
            throw new IllegalStateException("Errore compilazione shader: " + shader.getLog());
        }
    }

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

    @Override
    public void render() {
        // Pulizia schermo
        Gdx.gl.glClearColor(0.0f, 0.0f, 0.0f, 1f);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

        // Blending opzionale
        Gdx.gl.glEnable(GL20.GL_BLEND);
        Gdx.gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA);

        // Disegno
        shader.begin();
        mesh.render(shader, GL20.GL_TRIANGLES);
        shader.end();
    }

    @Override
    public void pause() {}

    @Override
    public void resume() {}

    @Override
    public void dispose() {
        if (mesh != null) mesh.dispose();
        if (shader != null) shader.dispose();
    }
}

Cosa osservare:

  • Ogni vertice ha il proprio colore RGBA.
  • Il triangolo risultante mostrerà transizioni morbide di colore tra rosso, verde e blu.
  • Non stiamo usando matrici: le coordinate NDC sono passate direttamente a gl_Position.

Esempio 2: Quadrato con libGDX OpenGL ES shader personalizzato

In questo esempio disegneremo un quadrato (composto da due triangoli) e useremo uno shader che prende il colore da una uniform (stessa tinta per tutto il quadrato), invece che da un attributo per vertice.

Per costruire un quadrato, innanzitutto definiamo 4 vertici in un rettangolo centrato e due triangoli tramite indici.

package com.mio.progetto;

import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Mesh;
import com.badlogic.gdx.graphics.VertexAttribute;
import com.badlogic.gdx.graphics.VertexAttributes.Usage;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;

public class ColoredSquareExample implements ApplicationListener {

    private Mesh mesh;
    private ShaderProgram shader;
    private float time;

    @Override
    public void create() {
        // 1) Mesh con 4 vertici (posizione 2D), 6 indici (2 triangoli)
        mesh = new Mesh(true, 4, 6,
                new VertexAttribute(Usage.Position, 2, "a_position"));

        float size = 0.5f;

        float[] vertices = new float[]{
                // x, y
                -size,  size,   // 0: alto sx
                -size, -size,   // 1: basso sx
                 size, -size,   // 2: basso dx
                 size,  size    // 3: alto dx
        };

        short[] indices = new short[]{
                0, 1, 2,    // primo triangolo
                2, 3, 0     // secondo triangolo
        };

        mesh.setVertices(vertices);
        mesh.setIndices(indices);

        // 2) Shader con uniform per il colore
        String vertexShader =
                "#ifdef GL_ES\n" +
                "precision mediump float;\n" +
                "#endif\n" +
                "attribute vec2 a_position;\n" +
                "void main() {\n" +
                "    gl_Position = vec4(a_position, 0.0, 1.0);\n" +
                "}\n";

        String fragmentShader =
                "#ifdef GL_ES\n" +
                "precision mediump float;\n" +
                "#endif\n" +
                "uniform vec4 u_color;\n" +
                "void main() {\n" +
                "    gl_FragColor = u_color;\n" +
                "}\n";

        ShaderProgram.pedantic = false;
        shader = new ShaderProgram(vertexShader, fragmentShader);
        if (!shader.isCompiled()) {
            throw new IllegalStateException("Errore compilazione shader: " + shader.getLog());
        }
    }

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

    @Override
    public void render() {
        // Aggiorniamo il tempo
        time += Gdx.graphics.getDeltaTime();

        // Pulizia schermo
        Gdx.gl.glClearColor(0.0f, 0.0f, 0.0f, 1f);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);

        // Calcoliamo un colore che cambia nel tempo
        float r = (float) ((Math.sin(time) + 1.0) / 2.0); // 0..1
        float g = (float) ((Math.cos(time) + 1.0) / 2.0);
        float b = 0.5f;

        shader.begin();
        // Passiamo la uniform u_color allo shader
        shader.setUniformf("u_color", r, g, b, 1.0f);

        mesh.render(shader, GL20.GL_TRIANGLES);

        shader.end();
    }

    @Override
    public void pause() {}

    @Override
    public void resume() {}

    @Override
    public void dispose() {
        if (mesh != null) mesh.dispose();
        if (shader != null) shader.dispose();
    }
}

Osserva come u_color viene calcolato in base a time per creare un effetto di pulsazione del colore del quadrato.

9. Errori comuni e debug degli shader

Verificare la compilazione degli shader GLSL in libGDX

Gli errori negli shader GLSL sono tra i problemi più frequenti quando si inizia con OpenGL ES. Per questo, ogni volta che crei uno ShaderProgram, controlla isCompiled() e stampa il log:

shader = new ShaderProgram(vertexShader, fragmentShader);
if (!shader.isCompiled()) {
    Gdx.app.error("Shader", "Errore compilazione: " + shader.getLog());
}

Se isCompiled() restituisce false, il testo di getLog() ti dirà quasi sempre:

  • La riga esatta dove c’è un errore di sintassi (es. punto e virgola mancante).
  • Eventuali variabili usate ma non dichiarate.
  • Tipi incompatibili nelle operazioni (es. sommare un vec3 a un vec4).

Errori di mismatch tra Mesh e shader OpenGL ES in libGDX

Un altro errore classico è la mancata corrispondenza tra:

  • Il nome dell’attributo nella Mesh (es. new VertexAttribute(Usage.Position, 2, "a_position"))
  • Il nome dell’attributo nello shader (es. attribute vec2 a_position;)

Devono essere identici (rispettando maiuscole/minuscole). Se sbagli nome, di conseguenza, quell’attributo potrebbe essere ignorato o ottimizzato via, portando a risultati inattesi (es. tutto nero o tutto in un angolo).

Versione di OpenGL ES e precision qualifier

Su Android è quasi sempre necessario specificare la precisione nei fragment shader:

#ifdef GL_ES
precision mediump float;
#endif

Se la ometti, potresti avere errori di compilazione su alcuni dispositivi (“precision qualifier is not specified” o simili).

Schermo nero con libGDX OpenGL ES shader: checklist veloce

  • Shader compilato? Controlla isCompiled() e getLog().
  • Clear color nero? Potresti star disegnando, ma il triangolo è nero su sfondo nero.
  • Coordinate fuori dallo schermo? Verifica che i vertici siano compresi tra -1 e +1 in NDC, se non usi matrici.
  • Mesh vuota? Controlla che setVertices() e setIndices() siano chiamati correttamente.
  • Uniform non settate? Se lo shader usa una uniform (es. u_color), assicurati di chiamare setUniformf() prima del draw.

10. Conclusione e anticipazione Parte 2

In questa prima parte abbiamo visto come:

  • Impostare un progetto libGDX su Android e integrarlo in un FrameLayout con AndroidApplicationConfiguration.
  • Capire il sistema di coordinate NDC e il ruolo della viewport.
  • Definire vertici e Mesh in libGDX, con attributi di posizione e colore.
  • Scrivere vertex shader e fragment shader in GLSL e creare uno ShaderProgram.
  • Disegnare un triangolo e un quadrato colorati usando Mesh.render() e ShaderProgram.begin()/end().
  • Debuggare errori comuni di compilazione shader.

Nella Parte 2 entreremo, infine, più in profondità in temi fondamentali per la grafica 2D/3D. Inoltre, se desideri vedere subito come utilizzare questi concetti in un progetto pratico, puoi consultare la guida su come creare un Gioco Pong su Android con OpenGL ES:

  • Matrici di proiezione e vista (ortografica e prospettica).
  • Trasformazioni (traslazione, rotazione, scala) applicate alla mesh.
  • Gestione di più oggetti sulla scena.
  • Introduzione alle texture (UV mapping) e sampling nei fragment shader.

Con queste basi, quindi, sei pronto a sperimentare cambiando vertici, colori e shader per creare i tuoi primi effetti grafici personalizzati con libGDX e OpenGL ES su Android.