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:
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.