Back


Android - OpenGL ES 1 - Tutorial 1 - Progetto Base 2D

Iniziamo questa serie di tutorial su OpenGL ES 1 con un progetto base orientato al 2D e con le sole istruzioni necessarie a renderlo eseguibile.

Apriamo Android Studio, clicchiamo su "Start a new Android Studio project":


Selezioniamo il tipo progetto "No Activity", clicchiamo su "Next":


Inseriamo il nome del progetto e del package, clicchiamo su "Finish":


Ora creiamo 3 classi, "MainActivity", "MainView" e "MainRenderer":






Sostituiamo il codice delle classi appena create con i seguenti:

- per "MainActivity":
package opengles1.tutorial1;

import android.app.Activity;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.view.Window;
import android.view.WindowManager;

public class MainActivity extends Activity {
    // Dedicated surface for displaying OpenGL rendering.
    GLSurfaceView glSurfaceView;

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

        // Hide the activity title.
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        // Set full screen.
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);

        // Create and set the dedicated surface.
        this.glSurfaceView = new MainView(this);
        setContentView(this.glSurfaceView);
    }

    @Override
    protected void onPause() {
        super.onPause();

        // Pause the rendering thread on activity pause.
        this.glSurfaceView.onPause();
    }

    @Override
    protected void onResume() {
        super.onResume();

        // Resume the rendering thread on activity resume.
        this.glSurfaceView.onResume();
    }

}
- per "MainView":
package opengles1.tutorial1;

import android.content.Context;
import android.opengl.GLSurfaceView;

public class MainView extends GLSurfaceView {
    // Interface for drawing graphics.
    MainRenderer glRenderer;

    // Constructor.
    public MainView(Context context) {
        super(context);

        // Create and set the interface for drawing graphics.
        this.glRenderer = new MainRenderer();
        setRenderer(this.glRenderer);
    }

}
- per "MainRenderer":
package opengles1.tutorial1;

import android.opengl.GLSurfaceView;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

public class MainRenderer implements GLSurfaceView.Renderer {
    int width, height;

    // Called when the surface is created or recreated.
    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        // Set color's clear-value to black.
        gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    }

    // Called when the surface changed size.
    // Called after the surface is created and whenever the OpenGL ES surface size changes.
    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        // Save size for future use.
        this.width = width;
        this.height = height;

        // Set the viewport (display area) to cover the entire window.
        gl.glViewport(0, 0, this.width, this.height);
        // Select projection matrix.
        gl.glMatrixMode(GL10.GL_PROJECTION);
        // Resets the matrix to its initial values to erase any previous settings.
        gl.glLoadIdentity();
        // Describes a matrix that produces a parallel projection.
        gl.glOrthof(0.0f, this.width - 1.0f, 0.0f, this.height - 1.0f, 1.0f, -1.0f);
    }

    // Called to draw the current frame.
    @Override
    public void onDrawFrame(GL10 gl) {
        // Clear buffers to preset values.
        gl.glClear(GL10.GL_COLOR_BUFFER_BIT);

        // Your rendering code here.
    }

}
Completiamo il tutto modificando il file "AndroidManifest.xml" nel seguente modo:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="opengles1.tutorial1">

    <application
        android:allowBackup="false"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

        <activity
            android:name="opengles1.tutorial1.MainActivity"
            android:label="@string/app_name"
            android:screenOrientation="portrait" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>
Il nostro progetto farà ben poco, semplicemente cancellerà il frame, ma vediamo cosa accade.

Partiamo dal file "AndroidManifest.xml" nel quale abbiamo specificato che l'activity da eseguire all'avvio è "MainActivity" (notare "extends Activity" nella classe) e che l'orientamento dello schermo è "portrait" ovvero in verticale.
Se volessimo impostare lo schermo in orizzontale, allora la modalità sarebbe "landscape", mentre omettendo l'orientamento (android:screenOrientation) faremo in modo che si generi l'evento della rotazione quando eseguito dall'operatore con la rotazione dello smartphone.

All'avvio dell'app verrà richiamato il metodo "onCreate()" dove con "requestWindowFeature(Window.FEATURE_NO_TITLE);" nascondiamo il titolo nella parte superiore dello schermo e con "getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);" impostiamo la modalità a pieno schermo.

Arriviamo a "GLSurfaceView", ovvero una superficie dedicata per la grafica con OpenGL.

Per poter utilizzare questa superficie abbiamo creato la classe "MainView" (notare "extends GLSurfaceView"), classe che definiamo durante l'esecuzione dell'app con "this.glSurfaceView = new MainView(this);" e che associamo all'activity con "setContentView(this.glSurfaceView);".

Chi si occupa realmente di disegnare è il "Renderer". Per questo abbiamo creato la classe "MainRenderer" (notare "implements GLSurfaceView.Renderer").

Quando abbiamo definito "this.glSurfaceView", abbiamo richiamato "MainView(Context context)" dove, a sua volta, è stato definito il "Renderer" con "this.glRenderer = new MainRenderer();" e che abbiamo associato a "this.glSurfaceView" con "setRenderer(this.glRenderer);".

Prima di proseguire, vediamo brevemente i metodi "onPause()" e "onResume()" presenti nel "MainActivity". Qui troviamo "this.glSurfaceView.onPause();" e "this.glSurfaceView.onResume();", ciò serve a mettere in pausa ed a ripristinare "this.glSurfaceView" quando l'app viene messa in pausa o ripristinata dall'operatore; in assenza "this.glSurfaceView" continuerebbe ad essere attivo.

Nel "Renderer" troviamo 3 metodi:
- "onSurfaceCreated()", eseguito quando la superficie viene creata o ricreata;
- "onSurfaceChanged()", eseguito dopo "onSurfaceCreated" e quando la superficie cambia dimensioni (ad esempio dopo la rotazione dello smartphone);
- "onDrawFrame()", eseguito dopo i metodi precedenti, di continuo, per il disegno.

Nel metodo "onSurfaceCreated()":
- impostiamo il colore che utilizzeremo quando cancelleremo il frame, in questo caso, con "gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);", impostiamo il nero senza trasparenza. L'intervallo dei valori va da 0.0f ad 1.0f, in corrispondenza dei valori RGBA da 0 a 255.

Nel metodo "onSurfaceChanged()":
- impostiamo le variabili "width" ed "height" (larghezza ed altezza in pixel del nostro schermo rilevati in base all'orientamento) per un eventuale utilizzo futuro;
- impostiamo il "Viewport" con "gl.glViewport(0, 0, this.width, this.height);". Il "Viewport" è la regione rettangolare dello schermo nel quale verrà visualizzato il frame e, nel nostro caso, stiamo specificando l'intero schermo, dalla posizione 0,0 (angolo in basso a sinistra) per una larghezza this.width ed un'altezza this.height;
- impostiamo il tipo di proiezione, e nel nostro caso, per il 2D, usiamo l'istruzione "glOrthof". Per fare ciò, dobbiamo dapprima puntare alla matrice di proiezione con "gl.glMatrixMode(GL10.GL_PROJECTION);", quindi ci si aspetta, subito dopo, l'istruzione "glOrthof" per impostarla, ma troviamo l'istruzione "gl.glLoadIdentity();". Quando impostiamo una matrice con un'istruzione, stiamo sostanzialmente dicendo: alla matrice metti il suo valore moltiplicato per il nuovo valore. Questo permette di effettuare modifiche in cascata se necessarie. Con "gl.glLoadIdentity();" stiamo resettando la matrice di proiezione ad "1" in modo tale che, con la successiva istruzione, si abbia in sostanza "1" moltiplicato per "glOrthof", evitando inattesi valori.

Nel metodo "onDrawFrame()":
- disegniamo il frame. L'istruzione "glClear" cancella il frame utilizzando solo il colore impostato con "glClearColor" in quanto gli stiamo dicendo di usare solo quello con il parametro "GL10.GL_COLOR_BUFFER_BIT".

Vediamo meglio la proiezione ortogonale che stiamo utilizzando.


In tale sistema, come notate nel pannello frontale, la profondità di un oggetto rispetto ad un altro non influisce sull'aspetto delle dimensioni, li vediamo come se fossero tutti alla stessa distanza, la differenza la fa solo l'ordine in cui sono posti.

Con l'istruzione "glOrthof" abbiamo definito il volume (A-B) della nostra proiezione, specificandone i limiti:
- per l'asse X: sinistra 0.0f, destra this.width - 1.0f
- per l'asse Y: basso 0.0f, alto this.height - 1.0f
- per l'asse Z: vicino 1.0f, lontano -1.0f
La coordinata Z è ininfluente nel 2D, quindi impostata con valore unitario, tutte le operazioni di disegno verranno poste a Z=0.0f ovvero al centro e, come detto pocanzi, conterà l'ordine di sovrapposizione al risultato finale del frame.

Rivediamo la questione "Schermo"-"ViewPort"-"glOrthof" per maggior chiarezza.

1. Il nostro schermo ha una larghezza ed un'altezza fisica (che misuriamo in pixel), dall'angolo in basso a sinistra (coordinata origine x=0 y=0) all'angolo in alto a destra (coordinata x=larghezza-1 y=altezza-1).

2. Con "glOrthof" impostiamo l'area di disegno (in memoria) con i seguenti limiti:
- a sinistra: 0.0f
- a destra: this.width - 1.0f
- in basso: 0.0f
- in alto: this.height - 1.0f

3. Con il "ViewPort" definiamo la posizione e l'area sullo schermo che conterrà il frame presente in memoria.

Quindi, il frame disegnato in memoria viene visualizzato sullo schermo nella posizione e nell'area del viewport ed adattato se necessario, adattato se il sistema di riferimento in memoria non è in proporzione con il viewport.

Nel nostro caso, abbiamo creato in memoria un sistema di coordinate che coincide con la dimensione totale dello schermo ed anche del viewport, abbiamo un corrispondenza di 1:1.

Se impostiamo con "glOrthof" i seguenti limiti (per l'orientamento verticale):
- a sinistra: 0.0f
- a destra: 1.0f
- in basso: 0.0f
- in alto: this.height / this.width
abbiamo in memoria un sistema di coorinate che, per quanto possa sembrare strano, ci permette tranquillamente di disegnare e, rispettando le proporzioni con il viewport, evitiamo anche spiacevoli adattamenti.

Nei prossimi tutorial di base continueremo ad usare il sistema con corrispondenza 1:1, mentre in quelli avanzati verrà mostrato ciò che ora "può sembrare strano".

Download Project:
OpenGL ES 1 Tutorial 1

Per maggiori approfondimenti sulle istruzioni, rimando a: khronos.org/registry/OpenGL-Refpages/es1.1/xhtml/

Pagina creata il 13/07/2020.






  Back


2007-2020 © Incastro.com
All rights reserved