Rysuj kształty

Po zdefiniowaniu kształtów do rysowania w trybie OpenGL warto je narysować. Rysowanie kształtów za pomocą OpenGL ES 2.0 zajmuje trochę więcej kodu, niż mogłoby się wydawać, ponieważ ten interfejs API zapewnia ogromną kontrolę nad potokiem renderowania grafiki.

Z tej lekcji dowiesz się, jak rysować kształty zdefiniowane w poprzedniej lekcji za pomocą interfejsu OpenGLES 2.0 API.

Inicjuj kształty

Zanim zaczniesz rysować, musisz zainicjować i wczytać kształty, które zamierzasz narysować. Jeśli struktura (pierwotne współrzędne) kształtów używanych w programie nie zmieni się w trakcie wykonywania kodu, zainicjuj je w metodzie onSurfaceCreated() mechanizmu renderowania, aby zwiększyć ilość pamięci i wydajność przetwarzania.

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

Narysuj kształt

Rysowanie zdefiniowanego kształtu za pomocą OpenGL ES 2.0 wymaga dużej ilości kodu, ponieważ proces renderowania grafiki wymaga podania wielu szczegółów. W szczególności musisz określić:

  • Vertex Shader – kod graficzny OpenGL ES do renderowania wierzchołków kształtu.
  • cieniowanie fragmentów – kod OpenGL ES do renderowania powierzchni kształtu z kolorami lub teksturami.
  • Program – obiekt OpenGL ES zawierający narzędzia do cieniowania, których chcesz używać do rysowania jednego lub większej liczby kształtów.

Potrzebujesz co najmniej jednego programu do cieniowania wierzchołków do rysowania kształtu i jednego, by pokolorować ten kształt. Trzeba je skompilować, a potem dodać do programu OpenGL ES, którego używa się do rysowania kształtu. Oto przykład definiowania podstawowych programów do cieniowania, za pomocą których możesz narysować kształt w klasie 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;" +
        "}";

    ...
}

Oprogramowanie do cieniowania zawiera kod OpenGL SHAding Language, który musi zostać skompilowany przed użyciem w środowisku OpenGL ES. Aby skompilować ten kod, utwórz w klasie mechanizmu renderowania metodę narzędzia:

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

Aby narysować kształt, musisz skompilować kod programu do cieniowania, dodać go do obiektu programu OpenGL ES i połączyć program. Zrób to w konstruktorze narysowanego obiektu, dzięki czemu zrobisz to tylko raz.

Uwaga: kompilowanie programów do cieniowania OpenGL ES i łączenie programów jest kosztowne pod względem cykli procesora i czasu przetwarzania, dlatego nie należy robić tego więcej niż raz. Jeśli nie znasz zawartości programów do cieniowania w czasie działania, skompiluj kod w taki sposób, aby uruchamiały się one tylko raz, a następnie znajdowały się w pamięci podręcznej do późniejszego użycia.

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

Teraz możesz zacząć dodawać rzeczywiste wywołania, które rysują kształt. Rysowanie kształtów za pomocą OpenGL ES wymaga podania kilku parametrów informujących potok renderowania, co chcesz narysować i jak to zrobić. Opcje rysowania mogą się różnić w zależności od kształtu, dlatego warto zadbać o to, by klasy kształtu zawierały własną logikę rysowania.

Utwórz metodę draw() do rysowania kształtu. Ten kod ustawia wartości pozycji i koloru dla cieniowania wierzchołków kształtu i programu do cieniowania fragmentów, a następnie wykonuje funkcję rysowania.

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

Gdy masz już wdrożony cały kod, rysunek tego obiektu wymaga wywołania metody draw() z poziomu metody onDrawFrame() mechanizmu renderowania:

Kotlin

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

    mTriangle.draw()
}

Java

public void onDrawFrame(GL10 unused) {
    ...

    mTriangle.draw();
}

Po uruchomieniu aplikacji powinna wyglądać mniej więcej tak:

Rysunek 1. Trójkąt narysowany bez projekcji lub obrazu z kamery.

Wystąpiło kilka problemów z tym przykładowym kodem. Przede wszystkim nie zrobi tego na swoich znajomych. Po drugie trójkąt jest nieco ściśnięty i zmienia kształt przy zmianie orientacji ekranu urządzenia. Kształt jest przekrzywiony, ponieważ wierzchołki obiektu nie zostały skorygowane do proporcji obszaru ekranu, na którym wyświetla się GLSurfaceView. Możesz rozwiązać ten problem, korzystając z widoku z rzutu i kamery.

Trójkąt jest nieruchomy, co jest dość nudne. W lekcji Dodaj ruch obracasz kształt i w bardziej interesujący sposób wykorzystasz potok graficzny OpenGL ES.