How to design Graphics for ios Apps

how to design ios graphics and how to make graphics for ios games and how to create graphics for ios games and how to draw graphics for ios
Dr.KiranArora Profile Pic
Published Date:27-10-2017
Your Website URL(Optional)
Setting Up Your Graphic Projections Before you can draw any graphics onscreen, you first need to create a projection matrix. The type of graphics you plan to use will have a direct impact on the creation of this matrix. Whether it is 2D, 2.5D, or 3D, each type of projection matrix will require a different initialization, allowing you to create the necessary perspective for your specific needs. In this chapter, you will learn about the three primary types of projections used in modern mobile games and how to use them. In addition, this chapter will teach you how to work with this book’stemplate project and walk you through three progressive exercises. In these exercises, you will learn how to manipulate the most common types of graphic projections and draw simple geometry onscreen; handle vertex and fragment shaders and link them to a shader program; manipulate vertex attributes and uniform variables; translate, rotate, and scale basic geometry; and create a simple camera look-at matrix. CHAPTER 2 SETTING UP YOUR GRAPHIC PROJECTIONS ❘ THE THREE BASIC TYPES OF PROJECTIONS When drawing using OpenGL ES, you always have to keep in mind the sequence of your drawings and in which perspective space you want to draw. Needless to say, this sequence will directly affect the type of projection and the sequence of creation of your projection’s matrix. For example, if you want to draw a heads-up display (HUD) that contains your character data on top of your scene, you first need to set up a 2D, 2.5D, or 3D perspective (depending on the type of game you are working on), and then draw your game scene. After your scene is rendered in the color buffer, you need to render your character life bar, ammo, etc. on top of it. Simply scaling your HUD graphics onscreen to fit the current drawing perspective would deteriorate their overall aspect ratio, eventually making them distorted. Knowing this, the right way to draw the HUD of your game would be to create a projection matrix that has a ratio of 1 unit to 1 pixel. Since your HUD consists of multiple 2D graphics, and it is important to respect their ratio onscreen, a 1:1 2D projection will allow you to draw them consistently onscreen. There are three distinct types of projections that can be used in any game genre: ➤ Orthographic 2D Projection: This type of projection is used to draw any HUD, as in the example described previously. This type of projection was also used in the “old school” side-scroller genre and other types of games, such as the classic Tetris and older grid-based role-playing games. As mentioned, this type of projection will have a 1:1 ratio onscreen, which means that the size of your drawing is directly affected by the size in pixels of the squares (quads) that you are sending to the GPU (graphical processing unit). In a more modern usage, this types of projection is mostly used only to draw menus, text, HUD, or other types of static (or semi-dynamic) 2D information onscreen. Since a third level of dimension is not available, and because the depth range is limited from -1 to 1 (with -1 being the nearest point onscreen and 1 the farthest), the use of the depth buffer is basically obsolete and should be turned off. In addition, the rendering sequence should be done from back to front to avoid overwriting pixels. ➤ Orthographic (Ortho) Projection: This type of projection allows you to draw in a semi-2D environment while still considering a third level of dimension, where the perspective is strictly based on the current screen ratio. This type of projection is the one used by every modern 2D/2.5D shoot ’em up-type game and real-time strategy games. When using this type of projection, you can still have access to a third level of dimension and make full use of the depth buffer. Figure 2-1 shows an example of a game that uses a 2D orthographic projection in conjunction FIGURE 2-1: Ragdoll Launcher by SIO2 Interactive with the depth buffer. ➤ Perspective Projection: This type of projection matrix is the one that you see at work inside all fancy 3D games that render in real-time dynamic and realistic 3D worlds 2D Projection 11 ❘ onscreen. With this kind of projection, you can simulate what a 3D world would look like from a real human-eye perspective. In addition to the screen aspect ratio, this sort of projection takes into consideration the field of view of the camera looking at the scene, as in the futuristic car game for iOS shown in Figure 2-2. FIGURE 2-2: Sky Racer by SIO2 Interactive ORTHOGRAPHIC 2D PROJECTION Now it’s time to get your hands dirty and start looking at the necessary code to set up a 2D orthographic projection matrix. In this section, you will create from scratch a simple program using the template project from this book’s SDK. Your first app will use a screen projection to draw a scaled colored quad onscreen in absolute pixel coordinates. Before diving into the code, first duplicate the template project directory located at the root of the SDK. In order to do this, simply right-click on it and create a local copy of the folder, and then rename it chapter2-1. Once the project is loaded into your favorite IDE, A Av va ai il la ab bl le e f fo or r d do ow wn nl lo oa ad d o on n locate the templateApp.cpp source file and open it in the code editor. W Wr ro ox x. .c co om m The first step for you to get started with the tutorials of this chapter is to adjust your newly created template project by removing the callback functions that you will not need for the exercises in this chapter. You will concentrate your efforts on the templateAppInit and the templateAppDraw function callbacks, so you can remove the rest of the callbacks and code comments and have a clean template to start working on. Modify the code so you get the following result: include “templateApp.h” TEMPLATEAPP templateApp = templateAppInit, templateAppDraw ; void templateAppInit( int width, int height ) atexit( templateAppExit ); GFX_start(); glViewport( 0, 0, width, height ); void templateAppDraw( void ) glClear( GL_COLOR_BUFFER_BIT ); void templateAppExit( void ) CHAPTER 2 SETTING UP YOUR GRAPHIC PROJECTIONS ❘ Program and Project Initialization The following steps will guide you through the necessary procedure in order to set up global variables used by your first program. In addition, you will learn about the different types of structures provided in this book’s SDK that will help you manipulate different aspects of your programs, such as shaders and loading assets from disk. You will also learn how to create your first 2D screen projection matrix, using the API provided in the SDK. 1. At the top of the templateApp.cpp source file (on the line just before the templateAppInit function declaration), define your vertex and fragment shader filenames as follows: defi ne VERTEX_SHADER ( char )”vertex.glsl” defi ne FRAGMENT_SHADER ( char )”fragment.glsl” 2. Declare a flag to toggle ON (1) or OFF (0) the shader debugging functionalities. No debugging usually means faster shader compilation, but no errors will be reported, and the result is undefined if an error does occur. Therefore, you should keep this flag toggle ON while you are developing your program. defi ne DEBUG_SHADERS 1 3. Create an empty PROGRAM structure for managing all your shader programs, as follows: PROGRAM program = NULL; The PROGRAM structure is the one that you are going to use throughout this book to handle shader programs. The full source code of the implementation is available inside the SDK/ common/program.cpp and program.h source files. The code of the SHADER structures linked to the PROGRAM can be found inside the shader.cpp and shader.h source files, which are also located inside the SDK/common directory of the book’s source code package. Basically, this pre-made structure handles all interactions between the vertex and fragment shaders and the main shader program. This allows you to compile your shaders and link them to a shader program automatically. In addition, this structure provides you with an easy-to-use way to gain access to uniform variable(s) and vertex attributes that are automatically assigned by the GPU. It also provides an easy-to-use callback mechanism that allows you to access, set, and modify these uniform variables in real time. 4. Declare a MEMORY structure pointer as follows: MEMORY m = NULL; This object is also part of the SDK and basically behaves like FILE in C, with the exception that all the work is done in memory. On mobile devices, the system memory is the fastest way to deal with data. In this exercise, you are going to use this structure to read your shader files from disk. As a general rule, you should avoid disk access if possible. By using this structure instead, you will get a better loading speed and more flexibility when loading your assets. 5. In this step, you’ll modify the content of templateAppInit to suit your needs. Since it’s the first time you’re working with this function, I’ll explain a few things, starting with the 2D Projection 13 ❘ first function call. This call uses the standard atexit function, which will allow you to get a feedback when the application exits. You can then program the necessary code to flush whatever is still alive in memory. For every tutorial and exercise in this book, make sure that the templateAppExit is always linked to atexit, as in the following line: void templateAppInit( int width, int height ) atexit( templateAppExit ); 6. Start the GLES initialization using the GFX helper function, which is also part of this book’s SDK: GFX_start(); This line initializes all the OpenGL ES standard machine states to make sure that everything is set up properly, regardless of the current driver of the device you are using. For more information concerning the GFX_start function, do not hesitate to consult its source code located in SDK/common/gfx.cpp. Quick side note on the GFX implementation: It also provides matrix manipulation functionalities that are not available in GLES2, and mimics the matrix mechanism found in GLES1 and GL desktop implementations. By using the GFX helpers, you can easily push, pop, load, and multiply matrices and gain direct access to the model view, projection, normal, and texture matrix in a similar fashion as you would normally do with the older version of OpenGL ES and OpenGL. 7. Use the standard glViewport command to set the GL viewport with the current screen dimensions: glViewport( 0, 0, width, height ); 8. Now use the GFX_set_matrix mode function to tell the GFX implementation to focus the projection matrix in order for you to setup your 2D projection: GFX_set_matrix_mode( PROJECTION_MATRIX ); 9. Declare two temporary float variables to hold half of the screen width and height, as follows: fl oat half_width = ( fl oat )width 0.5f, half_height = ( fl oat )height 0.5f; 10. Next you need to make sure that the current projection matrix is clean. To clean it up, simply load the identity matrix using the following function call. GFX_load_identity(); 11. Now you are ready to set up your 2D screen projection using theGFX_set_orthographic_2d function by passing in parameters half of the screen dimensions on both the positive and negative side of the origin of the viewport matrix (left, width, right, height). This operation CHAPTER 2 SETTING UP YOUR GRAPHIC PROJECTIONS ❘ will position the point of origin, or pivot point if you prefer, of the projection matrix right in the middle of the screen using a 1:1 ratio between GL units and screen pixels. GFX_set_orthographic_2d( -half_width, half_width, -half_height, half_height ); 12. Next, in order to be consistent with OGL, translate the matrix to the bottom left of the screen as follows: GFX_translate( -half_width, -half_height, 0.0f ); As you might already know, OpenGL uses the bottom-left corner of the color buffer as the 0,0 coordinate. The GFX_translate function call you’ve just inserted will translate the default location of the matrix to be aligned with the GL color buffer coordinate. This will ensure that all your drawings will be relative to the bottom-left corner of the screen. 13. As mentioned earlier in this chapter, you don’t really need to use the depth buffer when using this type of projection because most of the time when using this mode, you simply want to overwrite the color buffer. So turn off the depth buffer as follows: glDisable( GL_DEPTH_TEST ); 14. Since the depth buffer is OFF, you can also turn OFF the depth mask. glDepthMask( GL_FALSE ); Vertex and Fragment Shader Because the purpose of this book is to present you with a straightforward approach to implementing the different elements of a game and graphic engine, I will not go into detail about the specifics of the GLSL ES language. For more information about GLSL ES, feel free to visit http://www Before moving on with core code to load the necessary vertex and fragment shaders for this exercise inside the templateAppInit function, you first need to create a vertex and fragment shader. Since shaders are all text based, you can use any text editor that you want to write them. For this example, create two empty shader files. Name them vertex.glsl and fragment.glsl, and save them at the root of the current exercise directory (SDK/chapter2-1/). To package these two shaders and make them accessible within your app bundle, you need to link them to your project. If you are an XCode user, simply select the two .glsl files using Finder, then drag and drop them directly inside the Resources directory of your project tree, and confirm the operation. If you are an Eclipse user, simply select the two .glsl, files and then copy and paste them inside the assets directory of your project. 2D Projection 15 ❘ For both iOS and Android, the shader files will be bundled within your application and will be accessible at runtime. In addition, please take note that from now on, I will refer to this procedure every time you will be required to link an asset to your project (since it will be the same for models, textures, sounds, physics files, etc.). vertex.glsl For this example, you are simply going to draw a colored square onscreen. First, you need to write the necessary code to transform the vertices, and then you need to get the color for each vertex and send it over to the fragment shader for pixel processing. To do this, open the vertex.glsl file and execute the following steps: 1. On the first line of the vertex shader, you will have to define a uniform variable (meaning that the value of this variable can be manipulated within your C/C++ code). This variable is going to hold the result of the current projection matrix multiplied by the current model view matrix in order to transform each and every vertex that will be sent down to the shader to be displayed onscreen. uniform mediump mat4 MODELVIEWPROJECTIONMATRIX; 2. Then you need to have a variable to contain the vertex position that the vertex shader is currently handling. In order to handle this type of variable, you need to declare it using the attribute specifier, and because it’s handling the vertex position, call it POSITION, as shown here: attribute mediump vec4 POSITION; 3. The quad will also receive a color associated with each vertex position. So declare another variable using the attribute keyword to define the color per vertex, and name it COLOR: attribute lowp vec4 COLOR; 4. As mentioned previously, vertex shaders strictly deal with vertices, so in order to be able to pass the COLOR variable to the fragment shader for pixel processing, you have to use a middleman variable that will send this color over to the fragment shader. The specifier used for this type of task is varying, and since you have declared COLOR in uppercase as the attribute, you can call this one color (lowercase). varying lowp vec4 color; 5. Next, you need to insert the main function of the shader. Just like in C/C++, every shader is required to have a main function in order to determine the default entry point of the execution pointer. void main( void ) 6. Every time you have to process a vertex and make it visible onscreen, you will have to use the built-in gl_Position variable. In order to be able to see this vertex onscreen, you will CHAPTER 2 SETTING UP YOUR GRAPHIC PROJECTIONS ❘ have to assign it the result of the vertex position multiplied by the projection matrix and the model view matrix, just like this: gl_Position = MODELVIEWPROJECTIONMATRIX POSITION; 7. Only one crucial operation remains to set up your vertex shader: sending the vertex color to the fragment shader. In order to do this, simply assign the value COLOR to the varying variable color so the value can be associated with the similar variable inside the fragment shader that you will create in a minute. color = COLOR; / Close the main function / 8. Save the file. A Few Words about Precision Qualifiers Before moving on with the fragment shader code, you might already notice that before declaring any variables in GLSL ES, you have the opportunity to use a precision qualifier. Especially implemented for GLSL ES, precision qualifiers can control the level of floating-point precision (including vectors and matrices) as well as integer variables. When used wisely, these qualifiers can drastically increase the performance of your shader programs and improve their execution time. The less time it takes the GPU to execute your shader programs, the more GL instructions can be added, giving you the opportunity to render more complex drawing onscreen while keeping an acceptable frame rate. Table 2.1 lists the precision keywords and their ranges for floats and integers. TABLE 2-1: Precision Qualifi ers Table PRECISION FLOATING POINT RANGE INTEGER RANGE highp 262 to 262 216 to 216 mediump 214 to 214 210 to 210 lowp 2.0 to 2.0 28 to 28 fragment.glsl It’s time to write the fragment shader for this exercise. As you might have guessed, the fragment shaders strictly deal with pixel-based operations. In this first example, there won’t be too much code inside your fragment shader; however, as you are going forward in this book, the level of complexity will drastically increase. Use the text editor of your choice to open fragment.glsl, and then follow these steps to create your first fragment shader: 2D Projection 17 ❘ 1. Declare a varying variable named color using the same syntax as the one you declared in the vertex.glsl file. Since they are exactly the same, and since both of them are declared using the varying specifier, the shader compiler will automatically associate them and will send you the value that you have set in the vertex shader to your fragment shader for processing. varying lowp vec4 color; 2. Now create the main entry point of your fragment shader by inserting the main function: void main( void ) 3. To assign the color that you specify in your vertex shader, simply assign the varying variable color to the built-in gl_FragColor: gl_FragColor = color; 4. Save the file. Linking a Shader Program It’s now time to attack the necessary code to actually link your vertex and fragment shader to the PROGRAM structure. Since this is the first example in this book, and since you are going to work with the PROGRAM structure and its functionalities in subsequent chapters, I’m going to walk you through the full initialization process. Now let’s go back to templateApp.cpp, or more precisely, back to the templateAppInit function. 1. First, you have to initialize your program variable pointer, so add the following line: program = PROGRAM_init( ( char )”default” ); This function will automatically assign the required memory and set the whole structure to be blank and ready to receive other commands in order to successfully link a shader program. The last parameter of this function, the name, is strictly there for your convenience. In this exercise, you are basically only going to deal with one PROGRAM. But you will have to deal with multiple PROGRAM structures as you are progressing throughout this book, so this function will enable you to associate a name with them for identification purposes. 2. Next, you need to create a new vertex and fragment SHADER pointer by calling their associated _init function as follows: program-vertex_shader = SHADER_init( VERTEX_SHADER, GL_VERTEX_SHADER ); program-fragment_shader = SHADER_init( FRAGMENT_SHADER, GL_FRAGMENT_SHADER ); CHAPTER 2 SETTING UP YOUR GRAPHIC PROJECTIONS ❘ The first parameter of the SHADER_init function represents the internal name to use for the shader, and the second one represents the type so GLES can associate them accordingly. As you can see, you have now initialized the two SHADER pointers of the PROGRAM structure. Alternatively, you could initialize them as independent SHADER pointers and attach them manually to your PROGRAMs before the linking phase, making them reusable. 3. In order to be able to load the shaders you’ve created from disk, you now need to load their content in memory. To do this, simply use the MEMORY structure like this: m = mopen( VERTEX_SHADER, 1 ); The first parameter is the file name, and the next parameter lets you specify whether the path is relative to the application or not (either 1 for yes or 0 or no). 4. For safety purposes, and in order to see how the loading mechanism behind mopen works, effectuate a pointer check on the m variable as follows: if( m ) Please take note that if the file fails to load, the pointer will be NULL. A non-empty pointer will confirm that the file has been loaded and currently resides in memory. 5. Now you need to compile your vertex shader code that is currently contained inside the MEMORY buffer pointer (m-buffer) by passing it to the SHADER_compile function as follows: if( SHADER_compile( program-vertex_shader, ( char )m-buffer, DEBUG_SHADERS ) ) exit( 1 ); The first parameter of this function is, of course, a valid SHADER structure pointer that represents the shader you want to compile the code for. The next parameter is the shader source, which in this case is accessible from memory. The last parameter allows you to toggle debugging functionalities ON or OFF. As you can see from the preceding code, if the SHADER_compile function fails to compile the code, the function will return 0. In this case, this will trigger an early exit and call the templateAppExit function. 6. Enter the following code to free the MEMORY structure pointer m, because you are going to reuse it for loading the fragment shader in the next step. m = mclose( m ); 7. It’s time to load the fragment shader and compile it. To do this, simply reuse the same loading code structure that you used for the vertex shader, except this time, you have to specify the FRAGMENT_SHADER file as shown here: m = mopen( FRAGMENT_SHADER, 1 ); if( m ) 2D Projection 19 ❘ if( SHADER_compile( program-fragment_shader, ( char )m-buffer, DEBUG_SHADERS ) ) exit( 2 ); m = mclose( m ); You now have your vertex and fragment shader properly compiled and ready to be linked to your shader PROGRAM. Please note that it takes at least one valid and compiled vertex shader and one valid and compiled fragment shader to be able to successfully link a shader program; otherwise, the shader compiler will fail and report an error on the system console. 8. Call the PROGRAM_link function in order to execute the final linking phase of your vertex and fragment shader: if( PROGRAM_link( program, DEBUG_SHADERS ) ) exit( 3 ); Take note that similar to the previous SHADER_compile function, the last parameter of PROGRAM_link determines if the debug functionality should be used. And as previously mentioned, if an error occurs, the function will return 0 and the error message(s) will be reported on the system console. The Drawing Code The next step in order to get your app up and running is to deal with the templateAppDraw function. In this function, you will plug in the necessary code to be able to actually render something onscreen. In addition, you will also set up the different vertex attributes and control the uniform variable within your application. Preparing the Frame Before being able to actually start drawing something, you first have to prepare the data that you need in order render a single frame. At this point, one important piece is missing before any frame can be displayed onscreen. You need to actually declare all the vertices and the vertex color as well as the visual transformation that your quad will use to be displayed onscreen. To do this, just follow these steps: 1. Start by declaring some 2D positions (the vertices) that will then be linked together in order to draw a quad onscreen. To declare the vertices, insert the following declaration right after the start bracket of templateAppDraw: static const fl oat POSITION 8 = 0.0f, 0.0f, // Down left (pivot point) 1.0f, 0.0f, // Up left 0.0f, 1.0f, // Down right 1.0f, 1.0f // Up right ; 2. You now need to declare the vertex colors and associate them with the vertex position array you created in step 1. For this, all you have to do is to declare them in the same order as their counterpart in the POSITION array. As a result, the first vertex will be associated CHAPTER 2 SETTING UP YOUR GRAPHIC PROJECTIONS ❘ with the first RGBA color, the second vertex with the second color, and so on. Now append this code: static const fl oat COLOR 16 = 1.0f / R /, 0.0f / G /, 0.0f / B /, 1.0f / A /, / Red / 0.0f, 1.0f, 0.0f, 1.0f, / Green / 0.0f, 0.0f, 1.0f, 1.0f, / Blue / 1.0f, 1.0f, 0.0f, 1.0f / Yellow / ; 3. Now that you have all the necessary values required to draw, it’s time to start constructing the rendering loop of your application. First, specify which color value will be used to clean the color buffer (in this case, a light gray) by inserting the following code before the glClear function call: glClearColor( 0.5f, 0.5f, 0.5f, 1.0f ); 4. In order to avoid overwriting pixels, you need to tell OpenGL to clear the color buffer every time the templateAppDraw function is called, as follows: glClear( CL_COLOR_BUFFER_BIT ); 5. In step 1, you basically created a 1 by 1 pixel quad, which will be really hard to see onscreen. To make the quad more visible onscreen, all you have to do is scale it on the X and Y axis by adding this code: / Select the model view matrix. / GFX_set_matrix_mode( MODELVIEW_MATRIX ); / Reset it to make sure you are going to deal with a clean identity matrix. / GFX_load_identity(); / Scale the quad to be 100px by 100px. / GFX_scale( 100.0f, 100.0f, 0.0f ); Drawing the Quad The next logical step is to start calling the necessary APIs to tell the GPU to actually draw something onscreen. To do this, follow these steps: 1. Make sure that you have a valid shader program ID by adding the following if statement: if( program-pid ) As you might already know, GLES indexes always start at 1, so if the compilation of one of your shaders fails, the program-pid will be 0 and the subsequent code will not be executed. 2. Declare two temporary variables that you will use to hold the vertex attribute and uniform locations, as follows: char attribute, uniform; 2D Projection 21 ❘ These locations are necessary in order to effectuate the bridge between your application data and the GPU data. 3. Now you have to tell the GPU which program you want to use for drawing; otherwise, the GPU won’t know what to do with the data that is sent to it. To do this, call the following: glUseProgram( program-pid ); 4. Enter the following code to retrieve your uniform variable location from video memory: uniform = PROGRAM_get_uniform_location( program, ( char )”MODELVIEWPROJECTIONMATRIX” ); Please note that the variable name you wish to retrieve the location for has to be exactly the same as the one you declared in your shader code. 5. The location of the uniform variable has now been retrieved, and it’s time to actually update the data on the GPU. To do this, enter the following code: glUniformMatrix4fv( / The location value of the uniform. / uniform, / How many 4x4 matrix / 1, / Specify to do not transpose the matrix. / GL_FALSE, / Use the GFX helper function to calculate the result of the current model view matrix multiplied by the current projection matrix. / ( fl oat )GFX_get_modelview_projection_matrix() ); 6. Now you’ll deal with the vertex attributes in a similar fashion as in the previous step. Retrieve the location of the vertex position by entering the following: attribute = PROGRAM_get_vertex_attrib_location( program, ( char )”POSITION” ); 7. Once you have the location of the POSITION attribute, you can tell GLES to enable this vertex attribute location by using the glEnableVertexAttribArray function as follows: glEnableVertexAttribArray( attribute ); Remember that OpenGL ES is a machine state–based implementation, so you have to make sure that everything you want to use is enabled, and everything that you do not need is disabled. 8. Now you need to tell GLES which data to use for this attribute. To do this, use the glVertexAttribPointer function as follows: glVertexAttribPointer( / The attribute location / CHAPTER 2 SETTING UP YOUR GRAPHIC PROJECTIONS ❘ attribute, / How many elements; XY in this case, so 2. / 2, / The variable type. / GL_FLOAT, / Do not normalize the data. / GL_FALSE, / The stride in bytes of the array delimiting the elements, in this case none. / 0, / The vertex position array pointer. / POSITION ); 9. Do the same for the COLOR array by entering the following code: attribute = PROGRAM_get_vertex_attrib_location( program, ( char )”COLOR” ); glEnableVertexAttribArray( attribute ); glVertexAttribPointer( attribute, 4, GL_FLOAT, GL_FALSE, 0, COLOR ); 10. Now call the glDrawArrays function to tell the GPU to draw the data using a specific mode starting from which index in the array and to use how many data: glDrawArrays( / The drawing mode. / GL_TRIANGLE_STRIP, / Start at which index. / 0, / Start at which index. / 4 ); / Close the program-pid check. / 11. Finally call the GFX_error helper function, as a safety measure. This function will check if a GL error occurs while drawing the current frame (if an error occurs, it will be printed on the console for XCode users or LogCat for Eclipse users). GFX_error(); Cleaning Up Move to the templateAppExit function and follow these few steps in order to clean up what has been assigned in memory: 1. Right after the start bracket of the function, insert a simple print to notify that the application exits: printf(“templateAppExit...\n”); Projection 23 ❘ 2. Then do a pointer check on theMEMORY structure variablem since it may be still allocated when the execution pointer reaches this line and if it is, simply free it using themclose function: if( m ) m = mclose( m ); 3. Add the following code to effectuate a similar check as in step 2 but this time on the two SHADER structures as well as for the PROGRAM structure, and free them if necessary: if( program && program-vertex_shader ) program-vertex_shader = SHADER_free( program-vertex_shader ); if( program && program-fragment_shader ) program-fragment_shader = SHADER_free( program-fragment_shader ); if( program ) program = PROGRAM_free( program ); You are now ready to run the program, so hit the build and run button You should have the same result running as shown in Figure 2-3. This concludes the section on 2D orthographic projections. After reading this section, you are now able to draw a simple colored quad using a 2D orthographic projection where 1 unit equals 1 pixel. As you progress, you will realize that you will be able to reuse this implementation to draw HUD and menus onscreen for your game. ORTHOGRAPHIC PROJECTION In this section, you will learn how to create a scaled orthographic projection that will allow you to use the screen aspect ratio to establish a simple perspective view. In order to explore the orthographic possibilities, you will start by simply modifying the code that you created in the previous section. You will then add a new dimension to your FIGURE 2-3: Your fi rst ortho 2D projection existing coordinate system. Finally, you will create a simple camera using a look-at matrix, which allows you to specify the position and the eye direction of the viewer inside a three-dimensional scene. Getting Orthographic First duplicate the previous project, chapter2-1 (located in the SDK directory), and rename the copy for chapter2-2. You are now ready to modify the project to integrate an orthographic projection. Follow these steps: 1. Locate the GFX_set_matrix_mode function call inside the templateAppInit function, and remove the block of code between the brackets . 2. Insert the following lines of code between the to replace the previous screen projection with an orthographic projection initialization: / Clean the projection matrix by loading an identity matrix. / GFX_load_identity(); GFX_set_orthographic( CHAPTER 2 SETTING UP YOUR GRAPHIC PROJECTIONS ❘ / The screen ratio. / ( fl oat )height / ( fl oat )width, / The scale of the orthographic projection; the higher to 0, the wider the projection will be. / 5.0f, / The aspect ratio. / ( fl oat )width / ( fl oat )height, / The near clipping plane distance. / 1.0f, / The far clipping plane distance. / 100.0f, / The rotation angle of the projection. / 0.0f ); Pay attention to the near and far clipping planes — these planes basically control at which distance from the viewer the geometry can be seen. If your vertices after transformation fall behind the far clipping plane or before the near clipping plane, you won’t be able to see them onscreen because they will be clipped. The last parameter of the GFX_set_orthographic function allows you to control the rotation angle of the projection. Most modern devices have the ability to flip the screen according to the current orientation of the device. You can then use this parameter to adjust your scene orientation based on your device orientation. 3. Add the following code after the orthographic projection setup call: glDisable( GL_CULL_FACE ); This function tells GLES to not clip backfaces. In order to gain more speed, most modern GPUs automatically analyze triangles to determine whether they are facing the viewer or not, and if not, automatically discard them at an early stage to avoid extra calculations. Later in this tutorial, you will animate the quad, so you have to be sure that it will draw properly onscreen regardless of its rotation angle. You do not want the GPU to discard any vertex data onscreen if the quad becomes inverted. 4. Overwrite the POSITION array declaration in the templateAppDraw function with the following: static const fl oat POSITION 12 = -0.5f, 0.0f, -0.5f, // Bottom left 0.5f, 0.0f, -0.5f, -0.5f, 0.0f, 0.5f, 0.5f, 0.0f, 0.5f // Top right ; Notice that you have now added a third dimension to your vertex position data. Since you are now moving from an XY screen coordinate system to an XYZ coordinate system where the positive Z axis represents the up vector, shift the pivot point of your quad to revolve around X:0 Y:0, Z:0. 5. Since the depth buffer and depth mask need to be fully active (not like in the previous example), you have to tell OpenGL ES to clean the depth buffer every frame: glClear( GL_COLOR_BUFFER_BIT GL_DEPTH_BUFFER_BIT ); Projection 25 ❘ 6. To begin creating the camera matrix, locate the GFX_scale function call inside the templateAppDraw function and replace it with the following variable declarations: / The eye position in world coordinates. / vec3 e = 0.0f, -3.0f, 0.0f , / The position in world space where the eye is looking. / c = 0.0f, 0.0f, 0.0f , / Use the positive Z axis as the up vector. / u = 0.0f, 0.0f, 1.0f ; 7. Next, call the GFX_look_at function, use e, c, and u as parameters: GFX_look_at( &e, &c, &u ); This GFX_look_at call will create the necessary look-at matrix. For more information about the math behind this function, feel free to consult the source code in SDK/common/gfx.cpp. 8. It’s time to animate the quad onscreen First, you need to create a static float variable that will increment dynamically to animate the Y location of the quad pivot point. To do this, enter the following code: static float y = 0.0f; / Increment the Y location value every frame. / y += 0.1f; 9. Use the following GFX_translate function to translate the pivot of the quad on the Y axis: GFX_translate( 0.0f / X /, y, 0.0f / Z / ); 10. Since you already have the variable Y, you can reuse it to also animate the rotation of the quad using the GFX_rotate function: GFX_rotate( / Boost the angle a bit by multiplying it. / y 50.0f, / X axis / 1.0f, / Y axis / 1.0f, / Z axis / 1.0f ); This function takes four parameters: the first one represents the rotation angle in degrees, and the next three represent the axis that should be affected by the rotation angle. 11. In step 4, you modified the size of each POSITION component. In order for GLES to be able to draw the quad properly using the new tri-dimensional vertex position array, you need to adjust the parameter of glVertexAttribPointer from 2 (XY) to 3 (XYZ), as follows: glVertexAttribPointer( attribute, / 2 / 3, CHAPTER 2 SETTING UP YOUR GRAPHIC PROJECTIONS ❘ GL_FLOAT, GL_FALSE, 0, POSITION ); Build and run the program You should now see the quad rotating on the X, Y, and Z axes (as shown in Figure 2-4), and then disappear after a while when the Y location is above 100 (which means that it has been clipped by the far plane). Congratulations You have successfully set up an orthographic projection, used the depth buffer, created a full-fledged 3D object (albeit a very simple object — but hey, that’s a start). You were able to animate it onscreen using translation and rotation, and you have set up a basic look-at matrix to simulate a camera in world space. Quite a lot for a start already As you might have already noticed, when the quad is rotating onscreen, the projection does not seem quite right — especially when the quad has FIGURE 2-4: Orthographic a hard angle onscreen. This is because the projection is strictly based on quad the screen and aspect ratio (no real perspective). However, this type of mode is used by many 3D editing software programs because it provides a straight look at the geometry that the viewer is facing. It is also ideal for some 2D or 2.5D games (since the depth buffer can still be used while drawing) that have a static camera angle looking down at a scene panning left, right, forward, and backward, giving it a true 2.5D aspect. This concludes this section on orthographic projection. In the next section, you are going to fix the slight perspective problem that occurred in orthographic projection by adding a fi eld of view. This fi eld of view represents the eye angle of the viewer, creating a real 3D projection matrix. PERSPECTIVE PROJECTION In this section, you will learn how to set up a true 3D perspective view. This is basically the last type of projection that this book will teach you, and it will be the type of projection that will be used in almost all examples and exercises in the rest of this book. Once again, before diving in with more code, simply duplicate the project from the last section (chapter2-2), and for consistency, rename the duplicated project folder for chapter2-3. Since you already have all the necessary code in place, changing from ortho projection to a true 3D perspective projection couldn’t be easier Basically all you have to do is to locate the GFX_set_ orthographic function inside templateAppInit, and replace the function call with the following: GFX_set_perspective( / Field of view angle in degree. / 45.0f, / The screen aspect ratio. / ( fl oat )width / ( fl oat )height, / The near clipping plane. / 27 ❘ 0.01f, / The far clipping plane. / 100.0f, / The device screen orientation in angle to use. / 0.0f ); As you can see, this function is almost the same as GFX_set_ orthographic, with the exception of the fi rst parameter, the field of view (FOV). When it comes to FOV, the larger the angle is, the wider the perspective will be; and the smaller the angle, the narrower the projection. Optionally, before you have a test run at the app, you can also remove or comment theGFX_translate call (located inside thetemplateAppDraw) to be able to really observe the effect of the 3D perspective on the projection matrix while the quad is rotating. At this point, build and go You should see something similar to what’s shown in Figure 2-5. While running the application, observe how the projection matrix created by the GFX_set_perspective function is different from the one you created earlier. The major difference is that the quad looks good from every angle, because the perspective matrix calculation includes the fi eld FIGURE 2-5: Quad of view and represents the way someone would look at the quads in the rendered using a true real world. 3D perspective SUMMARY This chapter showed you how to set up all three primary projection matrices that directly affect the perspective of your drawings. Using those projections, you can now create any type of 2D, 2.5D, or 3D game. You also learned how to set up and draw a basic shape onscreen, use the depth buffer, manipulate vertex attributes and uniform variables, create a look-at matrix to manipulate the viewer eye location and direction, and a lot more. Pretty good for a start, don’t you think? Even if you don’t fully realize it yet, you now have all the basic knowledge that you need to start creating some real games and 3D apps Before you move on with the next chapter, I suggest that you go back over this chapter’s exercises and make sure that you fully understand everything that has been demonstrated. In addition, you should try to modify some parameters just to get more familiar with the overall program structures and possibilities. As an extra exercise, try to mix a screen projection with an orthographic or a 3D perspective projection by moving the projection matrix creation code from the templateAppInit function to the templateAppDraw function.