Depois de definir as formas a serem desenhadas com o OpenGL, você provavelmente vai querer desenhá-las. Desenhar formas com o OpenGL ES 2.0 requer um pouco mais de código do que você imagina, porque a API fornece um grande 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 a API OpenGL ES 2.0.
Inicializar formas
Antes de fazer qualquer desenho, você precisa inicializar e carregar as formas que planeja desenhar. A menos que a estrutura (as coordenadas originais) das formas usadas no programa mude durante a execução, é preciso inicializá-las no método onSurfaceCreated()
do seu renderizador para que haja 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 requer uma quantidade significativa de código, porque você precisa fornecer muitos detalhes ao pipeline de renderização de gráficos. Especificamente, você precisa definir o 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 sombreamentos 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 é então usado para desenhar a forma. Veja um exemplo de como definir sombreadores básicos que podem ser usados para desenhar uma forma na 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 o código OpenGL Shading Language (GLSL) que precisa ser compilado antes de ser usado no 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 sombreador, adicioná-lo a um objeto do programa OpenGL ES e vincular o programa. Faça isso no construtor do seu objeto desenhado, para que só precise fazer uma vez.
Observação: a compilação de sombreadores OpenGL ES e a vinculação de programas são dispendiosas, em termos de ciclos de CPU e tempo de processamento. Então, evite fazê-las mais de uma vez. Se você não conhecer o conteúdo dos sombreadores no momento da execução, crie seu código de forma que eles só sejam criados uma vez e depois 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. Para desenhar formas com o OpenGL ES, é necessário especificar vários parâmetros para informar ao pipeline de renderização o que você quer desenhar e de que maneira. Como as opções de desenho podem variar de acordo com a forma, é recomendável fazer com que as classes de forma contenham a própria lógica de desenho.
Crie um método draw()
para desenhar a forma. Esse código define os valores de posição e cor do sombreador de vértice e do sombreador de fragmento da forma e depois executa a função de desenho.
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 aplicar todo esse código, o desenho do objeto requer apenas uma chamada para o método draw()
de dentro do método onDrawFrame()
do renderizador:
Kotlin
override fun onDrawFrame(unused: GL10) { ... triangle.draw() }
Java
public void onDrawFrame(GL10 unused) { ... triangle.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, seus amigos não ficarão impressionados com ele. Em segundo, o triângulo está um pouco comprimido e muda de forma quando você muda a orientação da tela do dispositivo. A forma foi distorcida porque os vértices do objeto não foram corrigidos para as proporções da área da tela em que a GLSurfaceView
é exibida. Você pode corrigir esse problema usando uma projeção e uma visualização de câmera na próxima lição.
Por fim, o triângulo é estático, o que é um pouco entediante. Na lição Adicionar movimento, você faz essa forma girar e usa o pipeline de gráficos do OpenGL ES de maneira mais interessante.