Dessiner des formes

Après avoir défini des formes à dessiner avec OpenGL, vous souhaiterez probablement les dessiner. Dessiner des formes avec OpenGL ES 2.0 nécessite un peu plus de code que vous ne l'imaginez, car l'API offre un contrôle important sur le pipeline de rendu graphique.

Cette leçon explique comment dessiner les formes que vous avez définies dans la leçon précédente à l'aide de l'API OpenGL ES 2.0.

Initialiser des formes

Avant de commencer à dessiner, vous devez initialiser et charger les formes que vous prévoyez de dessiner. À moins que la structure (coordonnées d'origine) des formes que vous utilisez dans votre programme ne change au cours de l'exécution, vous devez les initialiser dans la méthode onSurfaceCreated() de votre moteur de rendu pour améliorer la mémoire et l'efficacité du traitement.

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();
    }
    ...
}

Dessiner une forme

Dessiner une forme définie à l'aide d'OpenGL ES 2.0 nécessite une quantité importante de code, car vous devez fournir de nombreux détails au pipeline de rendu graphique. Plus précisément, vous devez définir les éléments suivants:

  • Vertex Shader : code graphique OpenGL ES permettant d'afficher les sommets d'une forme.
  • Nuanceur de fragments : code OpenGL ES permettant d'afficher le visage d'une forme avec des couleurs ou des textures.
  • Programme : objet OpenGL ES contenant les nuanceurs à utiliser pour dessiner une ou plusieurs formes.

Vous avez besoin d'au moins un nuanceur de sommets pour dessiner une forme, et un nuanceur de fragments pour colorer cette forme. Ces nuanceurs doivent être compilés, puis ajoutés à un programme OpenGL ES qui est ensuite utilisé pour dessiner la forme. Voici un exemple de définition de nuanceurs de base que vous pouvez utiliser pour dessiner une forme dans la 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;" +
        "}";

    ...
}

Les nuanceurs contiennent du code GLSL (OpenGL Shading Language) qui doit être compilé avant de l'utiliser dans l'environnement OpenGL ES. Pour compiler ce code, créez une méthode utilitaire dans votre classe de moteur de rendu:

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;
}

Pour dessiner une forme, vous devez compiler le code du nuanceur, l'ajouter à un objet de programme OpenGL ES, puis associer le programme. Effectuez cette opération dans le constructeur de votre objet dessiné afin de ne le faire qu'une seule fois.

Remarque:La compilation de nuanceurs OpenGL ES et l'association de programmes coûtent cher en termes de cycles de processeur et de temps de traitement. Vous devez donc éviter de le faire plusieurs fois. Si vous ne connaissez pas le contenu de vos nuanceurs au moment de l'exécution, vous devez créer votre code de sorte qu'ils ne soient créés qu'une seule fois, puis mis en cache pour une utilisation ultérieure.

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);
    }
}

À ce stade, vous êtes prêt à ajouter les appels qui dessinent votre forme. Pour dessiner des formes avec OpenGL ES, vous devez spécifier plusieurs paramètres pour indiquer au pipeline de rendu ce que vous souhaitez dessiner et comment. Étant donné que les options de dessin peuvent varier selon la forme, il est recommandé de faire en sorte que vos classes de formes contiennent leur propre logique de dessin.

Créez une méthode draw() pour dessiner la forme. Ce code définit les valeurs de position et de couleur sur le nuanceur de sommets et de fragments de la forme, puis exécute la fonction de dessin.

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);
}

Une fois que vous avez tout ce code en place, pour dessiner cet objet, il vous suffit d'appeler la méthode draw() depuis la méthode onDrawFrame() de votre moteur de rendu:

Kotlin

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

    mTriangle.draw()
}

Java

public void onDrawFrame(GL10 unused) {
    ...

    mTriangle.draw();
}

Lorsque vous exécutez l'application, elle devrait se présenter comme suit:

Figure 1 : Triangle dessiné sans vue de caméra ou de projection.

Cet exemple de code présente quelques problèmes. Tout d'abord, cela ne va pas impressionner vos amis. Deuxièmement, le triangle est un peu écrasé et change de forme lorsque vous modifiez l'orientation de l'écran de l'appareil. La forme est asymétrique, car les sommets de l'objet n'ont pas été corrigés pour les proportions de la zone d'écran où GLSurfaceView est affiché. Vous pouvez résoudre ce problème à l'aide d'une projection et d'une vue de caméra dans la leçon suivante.

Enfin, le triangle est fixe, ce qui est un peu ennuyeux. Dans la leçon Ajouter un mouvement, vous allez faire pivoter cette forme et utiliser le pipeline graphique OpenGL ES de manière plus intéressante.