Desenhar formas

Depois de definir as formas a serem desenhadas com o OpenGL, você provavelmente vai querer desenhá-las. Como desenhar formas com o OpenGL ES 2.0 exige um pouco mais de código do que você imagina, porque a API fornece uma muito controle sobre o pipeline de renderização de gráficos.

Esta lição explica como desenhar as formas que você definiu na lição anterior usando o OpenGL API ES 2.0 do Google.

Inicializar formas

Antes de fazer qualquer desenho, você precisa inicializar e carregar as formas que planeja desenhar. A menos que o a estrutura (as coordenadas originais) das formas usadas no programa mudam durante o curso de execução, você precisa inicializá-los onSurfaceCreated() da sua o renderizador para eficiência de memória e processamento.

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

Desenhar uma forma

Desenhar uma forma definida usando o OpenGL ES 2.0 exige uma quantidade significativa de código, porque é precisa fornecer muitos detalhes para o pipeline de renderização de gráficos. Especificamente, você precisa definir seguinte:

  • Vertex Shader: código de gráficos do OpenGL ES para renderizar os vértices de uma forma.
  • Fragment Shader: código do OpenGL ES para renderizar a face de uma forma com cores ou texturas.
  • Program: um objeto do OpenGL ES que contém os sombreadores que você quer usar para desenhar. uma ou mais formas.

Você precisa de pelo menos um sombreador de vértice para desenhar uma forma e um sombreador de fragmento para colorir essa forma. Esses sombreadores precisam ser compilados e adicionados a um programa OpenGL ES, que é usado para desenhar o formato. Veja um exemplo de como definir sombreadores básicos que podem ser usados para desenhar uma forma no 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;" +
        "}";

    ...
}

Os sombreadores contêm código OpenGL Shading Language (GLSL) que deve ser compilado antes de ser usado em o ambiente OpenGL ES. Para compilar esse código, crie um método utilitário na sua classe de renderizador:

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

Para desenhar sua forma, é necessário compilar o código do shader e adicioná-los a um programa OpenGL ES e vincular o programa. Faça isso no construtor do seu objeto desenhado para que só seja feito uma vez.

Observação:a compilação de sombreadores do OpenGL ES e a vinculação de programas é cara. em termos de ciclos de CPU e tempo de processamento, portanto, evite fazer isso mais de uma vez. Se você conhecer o conteúdo dos sombreadores no momento da execução, crie o código de forma que ele são criados uma vez e armazenados em cache para uso posterior.

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

Neste momento, você está pronto para adicionar as chamadas que desenham sua forma. Como desenhar formas com O OpenGL ES exige que você especifique vários parâmetros para informar ao pipeline de renderização o que você quer e como desenhar. Como as opções de desenho podem variar de acordo com a forma, é uma boa ideia ter As classes shape contêm a própria lógica de desenho.

Crie um método draw() para desenhar a forma. Esse código define a posição e valores de cor ao sombreador de vértice e ao sombreador de fragmento da forma e, em seguida, executa o desenho. função.

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

Depois de ter todo esse código no lugar, desenhar esse objeto requer apenas uma chamada ao draw() no método onDrawFrame() do renderizador:

Kotlin

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

    mTriangle.draw()
}

Java

public void onDrawFrame(GL10 unused) {
    ...

    mTriangle.draw();
}

Quando você executar o aplicativo, ele será parecido com este:

Figura 1. Triângulo desenhado sem uma projeção ou visualização de câmera.

Há alguns problemas nesse exemplo de código. Em primeiro lugar, isso não impressionará seus amigos. Em segundo lugar, o triângulo está um pouco comprimido e muda de forma quando você troca a tela. orientação do dispositivo. A forma é distorcida porque a chave do objeto vértices não foram corrigidos para as proporções da área da tela onde o GLSurfaceView será exibido. É possível corrigir esse problema usando uma projeção e uma câmera na próxima aula.

Por fim, o triângulo é estático, o que é um pouco entediante. Na lição Adicionar movimento, você cria esta forma girar e usar o pipeline de gráficos do OpenGL ES de maneira mais interessante.