Traccia forme

Dopo aver definito le forme da disegnare con OpenGL, è probabile che tu voglia disegnarle. Disegnare forme con OpenGL ES 2.0 richiede un po' più di codice di quanto si possa immaginare, perché l'API offre un grande controllo sulla pipeline di rendering della grafica.

Questa lezione spiega come disegnare le forme definite nella lezione precedente utilizzando l'API OpenGL ES 2.0.

Inizializzare le forme

Prima di creare qualsiasi disegno, devi inizializzare e caricare le forme che intendi disegnare. A meno che la struttura (le coordinate originali) delle forme che utilizzi nel programma non cambi nel corso dell'esecuzione, devi inizializzarle nel metodo onSurfaceCreated() del renderingr per garantire memoria ed efficienza di elaborazione.

Kotlin

class MyGLRenderer : GLSurfaceView.Renderer {
    ...
    private lateinit var mTriangle: Triangle
    private lateinit var mSquare: Square

    override fun onSurfaceCreated(unused: GL10, config: EGLConfig) {
        ...
        // initialize a triangle
        mTriangle = Triangle()
        // initialize a square
        mSquare = Square()
    }
    ...
}

Java

public class MyGLRenderer implements GLSurfaceView.Renderer {

    ...
    private Triangle mTriangle;
    private Square   mSquare;

    public void onSurfaceCreated(GL10 unused, EGLConfig config) {
        ...
        // initialize a triangle
        mTriangle = new Triangle();
        // initialize a square
        mSquare = new Square();
    }
    ...
}

Tracciare una forma

Disegnare una forma definita utilizzando OpenGL ES 2.0 richiede una notevole quantità di codice, perché è necessario fornire molti dettagli alla pipeline di rendering della grafica. In particolare, devi definire quanto segue:

  • Vertex Shader: codice grafico OpenGL ES per il rendering dei vertici di una forma.
  • Fragment Shader: codice OpenGL ES per il rendering di una faccia di una forma con colori o texture.
  • Programma: un oggetto OpenGL ES contenente gli mesh che vuoi utilizzare per disegnare una o più forme.

Sono necessari almeno uno Shaper vertice per disegnare una forma e uno per colorare quella forma. Questi ombreggiatori devono essere compilati e aggiunti a un programma OpenGL ES, che viene poi utilizzato per disegnare la forma. Ecco un esempio di come definire gli mesh di base che puoi utilizzare per disegnare una forma nella classe Triangle:

Kotlin

class Triangle {

    private val vertexShaderCode =
            "attribute vec4 vPosition;" +
            "void main() {" +
            "  gl_Position = vPosition;" +
            "}"

    private val fragmentShaderCode =
            "precision mediump float;" +
            "uniform vec4 vColor;" +
            "void main() {" +
            "  gl_FragColor = vColor;" +
            "}"

    ...
}

Java

public class Triangle {

    private final String vertexShaderCode =
        "attribute vec4 vPosition;" +
        "void main() {" +
        "  gl_Position = vPosition;" +
        "}";

    private final String fragmentShaderCode =
        "precision mediump float;" +
        "uniform vec4 vColor;" +
        "void main() {" +
        "  gl_FragColor = vColor;" +
        "}";

    ...
}

Gli Shader contengono codice OpenGL Shading Language (GLSL) che deve essere compilato prima di utilizzarlo nell'ambiente OpenGL ES. Per compilare questo codice, crea un metodo di utilità nella classe del renderer:

Kotlin

fun loadShader(type: Int, shaderCode: String): Int {

    // create a vertex shader type (GLES20.GL_VERTEX_SHADER)
    // or a fragment shader type (GLES20.GL_FRAGMENT_SHADER)
    return GLES20.glCreateShader(type).also { shader ->

        // add the source code to the shader and compile it
        GLES20.glShaderSource(shader, shaderCode)
        GLES20.glCompileShader(shader)
    }
}

Java

public static int loadShader(int type, String shaderCode){

    // create a vertex shader type (GLES20.GL_VERTEX_SHADER)
    // or a fragment shader type (GLES20.GL_FRAGMENT_SHADER)
    int shader = GLES20.glCreateShader(type);

    // add the source code to the shader and compile it
    GLES20.glShaderSource(shader, shaderCode);
    GLES20.glCompileShader(shader);

    return shader;
}

Per disegnare la forma, devi compilare il codice dello mesh, aggiungerli a un oggetto del programma OpenGL ES e collegare il programma. Esegui questa operazione nel costruttore dell'oggetto disegnato, in modo che l'operazione venga eseguita una sola volta.

Nota: la creazione di mesh e programmi di collegamento OpenGL ES è costosa in termini di cicli della CPU e tempo di elaborazione, quindi dovresti evitare di farlo più di una volta. Se non conosci i contenuti degli mesh in fase di runtime, devi creare il codice in modo che venga creato una sola volta e poi memorizzato nella cache per un utilizzo futuro.

Kotlin

class Triangle {
    ...

    private var mProgram: Int

    init {
        ...

        val vertexShader: Int = loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode)
        val fragmentShader: Int = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode)

        // create empty OpenGL ES Program
        mProgram = GLES20.glCreateProgram().also {

            // add the vertex shader to program
            GLES20.glAttachShader(it, vertexShader)

            // add the fragment shader to program
            GLES20.glAttachShader(it, fragmentShader)

            // creates OpenGL ES program executables
            GLES20.glLinkProgram(it)
        }
    }
}

Java

public class Triangle() {
    ...

    private final int mProgram;

    public Triangle() {
        ...

        int vertexShader = MyGLRenderer.loadShader(GLES20.GL_VERTEX_SHADER,
                                        vertexShaderCode);
        int fragmentShader = MyGLRenderer.loadShader(GLES20.GL_FRAGMENT_SHADER,
                                        fragmentShaderCode);

        // create empty OpenGL ES Program
        mProgram = GLES20.glCreateProgram();

        // add the vertex shader to program
        GLES20.glAttachShader(mProgram, vertexShader);

        // add the fragment shader to program
        GLES20.glAttachShader(mProgram, fragmentShader);

        // creates OpenGL ES program executables
        GLES20.glLinkProgram(mProgram);
    }
}

A questo punto, sei pronto per aggiungere le chiamate effettive che tracciano la forma. Per disegnare le forme con OpenID ES è necessario specificare diversi parametri per indicare alla pipeline di rendering cosa vuoi disegnare e come. Poiché le opzioni di disegno possono variare in base alla forma, è consigliabile che le classi di forma contengano una logica di disegno propria.

Crea un metodo draw() per disegnare la forma. Questo codice imposta i valori di posizione e colore su Vertex Shader e Snippet Shaper della forma, quindi esegue la funzione di disegno.

Kotlin

private var positionHandle: Int = 0
private var mColorHandle: Int = 0

private val vertexCount: Int = triangleCoords.size / COORDS_PER_VERTEX
private val vertexStride: Int = COORDS_PER_VERTEX * 4 // 4 bytes per vertex

fun draw() {
    // Add program to OpenGL ES environment
    GLES20.glUseProgram(mProgram)

    // get handle to vertex shader's vPosition member
    positionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition").also {

        // Enable a handle to the triangle vertices
        GLES20.glEnableVertexAttribArray(it)

        // Prepare the triangle coordinate data
        GLES20.glVertexAttribPointer(
                it,
                COORDS_PER_VERTEX,
                GLES20.GL_FLOAT,
                false,
                vertexStride,
                vertexBuffer
        )

        // get handle to fragment shader's vColor member
        mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor").also { colorHandle ->

            // Set color for drawing the triangle
            GLES20.glUniform4fv(colorHandle, 1, color, 0)
        }

        // Draw the triangle
        GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount)

        // Disable vertex array
        GLES20.glDisableVertexAttribArray(it)
    }
}

Java

private int positionHandle;
private int colorHandle;

private final int vertexCount = triangleCoords.length / COORDS_PER_VERTEX;
private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex

public void draw() {
    // Add program to OpenGL ES environment
    GLES20.glUseProgram(mProgram);

    // get handle to vertex shader's vPosition member
    positionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");

    // Enable a handle to the triangle vertices
    GLES20.glEnableVertexAttribArray(positionHandle);

    // Prepare the triangle coordinate data
    GLES20.glVertexAttribPointer(positionHandle, COORDS_PER_VERTEX,
                                 GLES20.GL_FLOAT, false,
                                 vertexStride, vertexBuffer);

    // get handle to fragment shader's vColor member
    colorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");

    // Set color for drawing the triangle
    GLES20.glUniform4fv(colorHandle, 1, color, 0);

    // Draw the triangle
    GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);

    // Disable vertex array
    GLES20.glDisableVertexAttribArray(positionHandle);
}

Una volta che hai a disposizione tutto questo codice, per disegnare questo oggetto è sufficiente una chiamata al metodo draw() dall'interno del metodo onDrawFrame() del renderer:

Kotlin

override fun onDrawFrame(unused: GL10) {
    ...

    mTriangle.draw()
}

Java

public void onDrawFrame(GL10 unused) {
    ...

    mTriangle.draw();
}

Quando esegui l'applicazione, dovrebbe avere un aspetto simile al seguente:

Figura 1. Triangolo disegnato senza proiezione o telecamera.

Questo esempio di codice presenta alcuni problemi. Prima di tutto, non farà impressionare i tuoi amici. In secondo luogo, il triangolo è un po' schiacciato e cambia forma quando cambi l'orientamento dello schermo del dispositivo. Il motivo per cui la forma è inclinata è dovuto al fatto che i vertici dell'oggetto non sono stati corretti per le proporzioni dell'area dello schermo in cui viene visualizzato GLSurfaceView. Puoi risolvere il problema utilizzando una proiezione e una visualizzazione da videocamera nella prossima lezione.

Infine, il triangolo è stazionario, il che è un po' noioso. Nella lezione Aggiungi movimento, apporterai questa forma alla rotazione e farai un uso più interessante della pipeline grafica OpenGL ES.