Formen zeichnen

Wenn Sie Formen definiert haben, die mit OpenGL gezeichnet werden, möchten Sie sie wahrscheinlich selbst zeichnen. Zum Zeichnen von Formen mit OpenGL ES 2.0 ist etwas mehr Code erforderlich, als Sie sich vorstellen können, da die API viel Kontrolle über die Grafik-Rendering-Pipeline bietet.

In dieser Lektion wird erläutert, wie Sie die Formen, die Sie in der vorherigen Lektion definiert haben, mit der OpenGL ES 2.0 API zeichnen.

Formen initialisieren

Bevor Sie mit dem Zeichnen beginnen, müssen Sie die Formen, die Sie zeichnen möchten, initialisieren und laden. Sofern sich die Struktur (die ursprünglichen Koordinaten) der in Ihrem Programm verwendeten Formen während der Ausführung nicht ändert, sollten Sie sie zur Speicher- und Verarbeitungseffizienz in der Methode onSurfaceCreated() des Renderers initialisieren.

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

Formen zeichnen

Das Zeichnen einer definierten Form mit OpenGL ES 2.0 erfordert eine erhebliche Menge an Code, da Sie viele Details für die Grafik-Rendering-Pipeline bereitstellen müssen. Insbesondere müssen Sie Folgendes definieren:

  • Vertex Shader: OpenGL ES-Grafikcode zum Rendern der Eckpunkte einer Form.
  • Fragment Shader: OpenGL ES-Code zum Rendern der Fläche einer Form mit Farben oder Texturen.
  • Programm: Ein OpenGL ES-Objekt, das die Shader enthält, mit denen Sie eine oder mehrere Formen zeichnen möchten.

Sie benötigen mindestens einen Vertex-Shader zum Zeichnen einer Form und einen Fragment-Shader zum Einfärben der Form. Diese Shader müssen kompiliert und dann einem OpenGL ES-Programm hinzugefügt werden, mit dem dann die Form gezeichnet wird. Hier ein Beispiel für das Definieren von Basis-Shadern, mit denen Sie eine Form in der Triangle-Klasse zeichnen können:

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

    ...
}

Shader enthalten GLSL-Code (OpenGL Shading Language), der vor der Verwendung in der OpenGL ES-Umgebung kompiliert werden muss. Um diesen Code zu kompilieren, erstellen Sie eine Dienstprogrammmethode in Ihrer Renderer-Klasse:

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

Um eine Form zu zeichnen, müssen Sie den Shader-Code kompilieren, ihn einem OpenGL ES-Programmobjekt hinzufügen und dann das Programm verknüpfen. Sie tun dies im Konstruktor des gezeichneten Objekts, sodass dies nur einmal erfolgt.

Hinweis:Das Kompilieren von OpenGL ES-Shadern und Verknüpfungsprogrammen ist im Hinblick auf CPU-Zyklen und Verarbeitungszeit kostspielig. Daher sollten Sie dies nur einmal tun. Wenn Sie den Inhalt Ihrer Shader zur Laufzeit nicht kennen, sollten Sie Ihren Code so erstellen, dass sie nur einmal erstellt und dann zur späteren Verwendung im Cache gespeichert werden.

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

Jetzt können Sie die tatsächlichen Aufrufe hinzufügen, mit denen Ihre Form gezeichnet wird. Zum Zeichnen von Formen mit OpenGL ES müssen Sie mehrere Parameter angeben, um der Rendering-Pipeline mitzuteilen, was gezeichnet werden soll und wie dies gezeichnet werden soll. Da die Zeichenoptionen je nach Form variieren können, empfiehlt es sich, für Ihre Formklassen eine eigene Zeichenlogik zu haben.

Erstellen Sie eine draw()-Methode zum Zeichnen der Form. Mit diesem Code werden die Positions- und Farbwerte auf den Vertex-Shader und den Fragment-Shader der Form festgelegt und dann die Zeichenfunktion ausgeführt.

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

Sobald Sie den gesamten Code platziert haben, müssen Sie zum Zeichnen dieses Objekts nur noch die Methode draw() innerhalb der Methode onDrawFrame() des Renderers aufrufen:

Kotlin

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

    mTriangle.draw()
}

Java

public void onDrawFrame(GL10 unused) {
    ...

    mTriangle.draw();
}

Wenn Sie die Anwendung ausführen, sollte sie in etwa so aussehen:

Abbildung 1: Dreieck, das ohne Projektion oder Kameraansicht gezeichnet wurde.

Bei diesem Codebeispiel gibt es einige Probleme. Zunächst einmal wird es Ihre Freunde nicht beeindrucken. Zweitens ist das Dreieck etwas zusammengedrückt und ändert seine Form, wenn Sie die Bildschirmausrichtung des Geräts ändern. Der Grund für eine verzerrte Form liegt darin, dass die Eckpunkte des Objekts nicht für die Proportionen des Bildschirmbereichs korrigiert wurden, in denen das GLSurfaceView angezeigt wird. Mit einer Projektion und Kameraansicht können Sie dieses Problem in der nächsten Lektion beheben.

Schließlich ist das Dreieck unbewegt, was ein bisschen langweilig ist. In der Lektion Bewegung hinzufügen sorgen Sie dafür, dass sich diese Form dreht, und nutzen die OpenGL ES-Grafikpipeline interessanter.