Home | History | Annotate | Download | only in opengl
      1 page.title=Drawing Shapes
      2 parent.title=Displaying Graphics with OpenGL ES
      3 parent.link=index.html
      4 
      5 trainingnavtop=true
      6 previous.title=Defining Shapes
      7 previous.link=environment.html
      8 next.title=Applying Projection and Camera Views
      9 next.link=projection.html
     10 
     11 @jd:body
     12 
     13 <div id="tb-wrapper">
     14 <div id="tb">
     15 
     16 <h2>This lesson teaches you to</h2>
     17 <ol>
     18   <li><a href="#initialize">Initialize Shapes</a></li>
     19   <li><a href="#draw">Draw a Shape</a></li>
     20 </ol>
     21 
     22 <h2>You should also read</h2>
     23 <ul>
     24   <li><a href="{@docRoot}guide/topics/graphics/opengl.html">OpenGL</a></li>
     25 </ul>
     26 
     27 <div class="download-box">
     28  <a href="{@docRoot}shareables/training/OpenGLES.zip"
     29 class="button">Download the sample</a>
     30  <p class="filename">OpenGLES.zip</p>
     31 </div>
     32 
     33 </div>
     34 </div>
     35 
     36 <p>After you define shapes to be drawn with OpenGL, you probably want to draw them. Drawing shapes
     37 with the OpenGL ES 2.0 takes a bit more code than you might imagine, because the API provides a
     38 great deal of control over the graphics rendering pipeline.</p>
     39 
     40 <p>This lesson explains how to draw the shapes you defined in the previous lesson using the OpenGL
     41 ES 2.0 API.</p>
     42 
     43 
     44 <h2 id="initialize">Initialize Shapes</h2>
     45 
     46 <p>Before you do any drawing, you must initialize and load the shapes you plan to draw. Unless the
     47 structure (the original coordinates) of the shapes you use in your program change during the course
     48 of execution, you should initialize them in the {@link
     49 android.opengl.GLSurfaceView.Renderer#onSurfaceCreated onSurfaceCreated()} method of your renderer
     50 for memory and processing efficiency.</p>
     51 
     52 <pre>
     53 public class MyGLRenderer implements GLSurfaceView.Renderer {
     54 
     55     ...
     56     private Triangle mTriangle;
     57     private Square   mSquare;
     58 
     59     public void onSurfaceCreated(GL10 unused, EGLConfig config) {
     60         ...
     61 
     62         // initialize a triangle
     63         mTriangle = new Triangle();
     64         // initialize a square
     65         mSquare = new Square();
     66     }
     67     ...
     68 }
     69 </pre>
     70 
     71 
     72 <h2 id="draw">Draw a Shape</h2>
     73 
     74 <p>Drawing a defined shape using OpenGL ES 2.0 requires a significant amount of code, because you
     75 must provide a lot of details to the graphics rendering pipeline. Specifically, you must define the
     76 following:</p>
     77 
     78 <ul>
     79   <li><em>Vertex Shader</em> - OpenGL ES graphics code for rendering the vertices of a shape.</li>
     80   <li><em>Fragment Shader</em> - OpenGL ES code for rendering the face of a shape with colors or
     81 textures.</li>
     82   <li><em>Program</em> - An OpenGL ES object that contains the shaders you want to use for drawing
     83 one or more shapes.</li>
     84 </ul>
     85 
     86 <p>You need at least one vertex shader to draw a shape and one fragment shader to color that shape.
     87 These shaders must be complied and then added to an OpenGL ES program, which is then used to draw
     88 the shape. Here is an example of how to define basic shaders you can use to draw a shape in the
     89 <code>Triangle</code> class:</p>
     90 
     91 <pre>
     92 public class Triangle {
     93 
     94     private final String vertexShaderCode =
     95         "attribute vec4 vPosition;" +
     96         "void main() {" +
     97         "  gl_Position = vPosition;" +
     98         "}";
     99 
    100     private final String fragmentShaderCode =
    101         "precision mediump float;" +
    102         "uniform vec4 vColor;" +
    103         "void main() {" +
    104         "  gl_FragColor = vColor;" +
    105         "}";
    106 
    107     ...
    108 }
    109 </pre>
    110 
    111 <p>Shaders contain OpenGL Shading Language (GLSL) code that must be compiled prior to using it in
    112 the OpenGL ES environment. To compile this code, create a utility method in your renderer class:</p>
    113 
    114 <pre>
    115 public static int loadShader(int type, String shaderCode){
    116 
    117     // create a vertex shader type (GLES20.GL_VERTEX_SHADER)
    118     // or a fragment shader type (GLES20.GL_FRAGMENT_SHADER)
    119     int shader = GLES20.glCreateShader(type);
    120 
    121     // add the source code to the shader and compile it
    122     GLES20.glShaderSource(shader, shaderCode);
    123     GLES20.glCompileShader(shader);
    124 
    125     return shader;
    126 }
    127 </pre>
    128 
    129 <p>In order to draw your shape, you must compile the shader code, add them to a OpenGL ES program
    130 object and then link the program. Do this in your drawn objects constructor, so it is only done
    131 once.</p>
    132 
    133 <p class="note"><strong>Note:</strong> Compiling OpenGL ES shaders and linking programs is expensive
    134 in terms of CPU cycles and processing time, so you should avoid doing this more than once. If you do
    135 not know the content of your shaders at runtime, you should build your code such that they only
    136 get created once and then cached for later use.</p>
    137 
    138 <pre>
    139 public class Triangle() {
    140     ...
    141 
    142     private final int mProgram;
    143 
    144     public Triangle() {
    145         ...
    146 
    147         int vertexShader = MyGLRenderer.loadShader(GLES20.GL_VERTEX_SHADER,
    148                                         vertexShaderCode);
    149         int fragmentShader = MyGLRenderer.loadShader(GLES20.GL_FRAGMENT_SHADER,
    150                                         fragmentShaderCode);
    151 
    152         // create empty OpenGL ES Program
    153         mProgram = GLES20.glCreateProgram();
    154 
    155         // add the vertex shader to program
    156         GLES20.glAttachShader(mProgram, vertexShader);
    157 
    158         // add the fragment shader to program
    159         GLES20.glAttachShader(mProgram, fragmentShader);
    160 
    161         // creates OpenGL ES program executables
    162         GLES20.glLinkProgram(mProgram);
    163     }
    164 }
    165 </pre>
    166 
    167 <p>At this point, you are ready to add the actual calls that draw your shape. Drawing shapes with
    168 OpenGL ES requires that you specify several parameters to tell the rendering pipeline what you want
    169 to draw and how to draw it. Since drawing options can vary by shape, it's a good idea to have your
    170 shape classes contain their own drawing logic.</p>
    171 
    172 <p>Create a {@code draw()} method for drawing the shape. This code sets the position and
    173 color values to the shapes vertex shader and fragment shader, and then executes the drawing
    174 function.</p>
    175 
    176 <pre>
    177 private int mPositionHandle;
    178 private int mColorHandle;
    179 
    180 private final int vertexCount = triangleCoords.length / COORDS_PER_VERTEX;
    181 private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex
    182 
    183 public void draw() {
    184     // Add program to OpenGL ES environment
    185     GLES20.glUseProgram(mProgram);
    186 
    187     // get handle to vertex shader's vPosition member
    188     mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
    189 
    190     // Enable a handle to the triangle vertices
    191     GLES20.glEnableVertexAttribArray(mPositionHandle);
    192 
    193     // Prepare the triangle coordinate data
    194     GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX,
    195                                  GLES20.GL_FLOAT, false,
    196                                  vertexStride, vertexBuffer);
    197 
    198     // get handle to fragment shader's vColor member
    199     mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");
    200 
    201     // Set color for drawing the triangle
    202     GLES20.glUniform4fv(mColorHandle, 1, color, 0);
    203 
    204     // Draw the triangle
    205     GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);
    206 
    207     // Disable vertex array
    208     GLES20.glDisableVertexAttribArray(mPositionHandle);
    209 }
    210 </pre>
    211 
    212 <p>Once you have all this code in place, drawing this object just requires a call to the
    213 {@code draw()} method from within your renderers {@link
    214 android.opengl.GLSurfaceView.Renderer#onDrawFrame onDrawFrame()} method:
    215 
    216 <pre>
    217 public void onDrawFrame(GL10 unused) {
    218     ...
    219 
    220     mTriangle.draw();
    221 }
    222 </pre>
    223 
    224 <p>When you run the application, it should look something like this:</p>
    225 
    226 <img src="{@docRoot}images/opengl/ogl-triangle.png">
    227 <p class="img-caption">
    228 <strong>Figure 1.</strong> Triangle drawn without a projection or camera view.</p>
    229 
    230 <p>There are a few problems with this code example. First of all, it is not going to impress your
    231 friends. Secondly, the triangle is a bit squashed and changes shape when you change the screen
    232 orientation of the device. The reason the shape is skewed is due to the fact that the objects
    233 vertices have not been corrected for the proportions of the screen area where the {@link
    234 android.opengl.GLSurfaceView} is displayed. You can fix that problem using a projection and camera
    235 view in the next lesson.</p>
    236 
    237 <p>Lastly, the triangle is stationary, which is a bit boring. In the <a href="motion.html">Adding
    238 Motion</a> lesson, you make this shape rotate and make more interesting use of the OpenGL ES
    239 graphics pipeline.</p>