This recipe basically comprises of all the knowledge we gathered from our previous recipes in this chapter. The output of this recipe will be a NativeTemplate.h/cpp
file that contains OpenGL ES 3.0 code, which demonstrates a rotating colored triangle. The output of this recipe is not executable on its own. It needs a host application that provides the necessary OpenGL ES 3.0 prerequisites to render this program on a device screen. Therefore, this recipe will be used later by the following two recipes, which will provide the host environment for OpenGL ES 3.0 in Android and iOS:
Developing Android OpenGL ES 3.0 application
Developing iOS OpenGL ES 3.0 application
This recipe will provide all the necessary prerequisites that are required to set up OpenGL ES, rendering and querying necessary attributes from shaders to render our OpenGL ES 3.0 "Hello World Triangle" program. In this program, we will render a simple colored triangle on the screen.
OpenGL ES requires a physical size (pixels) to define a 2D rendering surface called a viewport. This is used to define the OpenGL ES Framebuffer size.
A buffer in OpenGL ES is a 2D array in the memory that represents pixels in the viewport region. OpenGL ES has three types of buffers: color buffer, depth buffer, and stencil buffer. These buffers are collectively known as a framebuffer. All the drawings commands effect the information in the framebuffer.
The life cycle of this recipe is broadly divided into three states:
Initialization: Shaders are compiled and linked to create program objects
Resizing: This state defines the viewport size of rendering surface
Rendering: This state uses the shader program object to render geometry on screen
In our recipe, these states are represented by the GraphicsInit()
, GraphicsResize()
, and GraphicsRender()
functions.
Follow these steps to program this recipe:
Use the
NativeTemplate.cpp
file and create acreateProgramExec
function. This is a high-level function to load, compile, and link a shader program. This function will return the program object ID after successful execution:GLuint createProgramExec(const char* vertexSource, const char* fragmentSource) { GLuint vsID = loadAndCompileShader(GL_VERTEX_SHADER, vertexSource); GLuint fsID = loadAndCompileShader(GL_FRAGMENT_SHADER, fragmentSource); return linkShader(vsID, fsID); }
Visit the loading and compiling a shader program and linking a shader program recipes for more information on the working of
loadAndCompileShader
andlinkShader
.Use
NativeTemplate.cpp
, create a functionGraphicsInit
and create the shader program object by callingcreateProgramExec
:GLuint programID; // Global shader program handler bool GraphicsInit(){ // Print GLES3.0 system metrics printOpenGLESInfo(); // Create program object and cache the ID programID = createProgramExec(vertexShader, fragmentShader); if (!programID) { // Failure !!! return printf("Could not create program."); return false; } checkGlError("GraphicsInit"); // Check for errors }
Create a new function
GraphicsResize
. This will set the viewport region:// Set viewing window dimensions bool GraphicsResize( int width, int height ){ glViewport(0, 0, width, height); }
The viewport determines the portion of the OpenGL ES surface window on which the rendering of the primitives will be performed. The viewport in OpenGL ES is set using the
glViewPort
API.Syntax:
void glViewport( GLint x, GLint y, GLsizei width, GLsizei height);
Variable
Description
x
,y
These represent lower-left rectangle for viewport specified in pixels
width
,height
This specifies the width and height of the viewport in pixels
Create the
gTriangleVertices
global variable that contains the vertices of the triangle:GLfloat gTriangleVertices[] = { { 0.0f, 0.5f}, // Vertex 0 {-0.5f, -0.5f}, // Vertex 1 { 0.5f, -0.5f} // Vertex 2 }; // Triangle vertices
Create the
GraphicsRender
renderer function. This function is responsible for rendering the scene. Add the following code in it and perform the following steps to understand this function:bool GraphicsRender(){ // Which buffer to clear? – color buffer glClear( GL_COLOR_BUFFER_BIT ); // Clear color with black color glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // Use shader program and apply glUseProgram( programID ); radian = degree++/57.2957795; // Query and send the uniform variable. radianAngle = glGetUniformLocation(programID, "RadianAngle"); glUniform1f(radianAngle, radian); // Query 'VertexPosition' from vertex shader positionAttribHandle = glGetAttribLocation (programID, "VertexPosition"); colorAttribHandle = glGetAttribLocation (programID, "VertexColor"); // Send data to shader using queried attribute glVertexAttribPointer(positionAttribHandle, 2, GL_FLOAT, GL_FALSE, 0, gTriangleVertices); glVertexAttribPointer(colorAttribHandle, 3, GL_FLOAT, GL_FALSE, 0, gTriangleColors); // Enable vertex position attribute glEnableVertexAttribArray(positionAttribHandle); glEnableVertexAttribArray(colorAttribHandle); // Draw 3 triangle vertices from 0th index glDrawArrays(GL_TRIANGLES, 0, 3); }
Choose the appropriate buffer from the framebuffer (color, depth, and stencil) that we want to clear each time the frame is rendered using the
glClear
API. In our recipe, we want to clear color buffer. TheglClear
API can be used to select the buffers that needs to be cleared. This API accepts a bitwiseOR
argument mask that can be used to set any combination of buffers.Syntax:
void glClear( GLbitfield mask )
Variable
Description
mask
Bitwise
OR
masks, each mask points to a specific buffer. These masks areGL_COLOR_BUFFER_BIT
,GL_DEPTH_BUFFER_BIT
, andGL_STENCIL_BUFFER_BIT
.The possible value mask could be a bitwise or of
GL_COLOR_BUFFER_BIT
(color buffer),GL_DEPTH_BUFFER_BIT
(depth buffer) andGL_STENCIL_BUFFER_BIT
(stencil buffer).glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
Clear the color buffer with black color using the
glClearColor
API. This buffer is responsible for storing color information of the scene. It accepts the argument as RGBA space that ranges between 0.0 and 1.0.Use a shader program and set as the current rendering state using the
glUseProgram
API. TheglUseProgram
API installs the program object specified by the program as the current rendering state. The program's executable for the vertex shader runs on the programmable vertex processor. Similarly, the fragment shader executable runs on the programmable fragment processor.Syntax:
void glUseProgram(GLuint program);
Variable
Description
program
This specifies the handle (ID) of the shader program.
Query the
VertexPosition
generic vertex attribute location ID from the vertex shader intopositionAttribHandle
usingglGetAttribLocation
. This location will be used to send triangle vertex data that is stored ingTriangleVertices
to the shader usingglVertexAttribPointer
. Follow the same instruction in order to get the handle ofVertexColor
intocolorAttributeHandle
:// Query attribute location & send data using them positionAttribHandle = glGetAttribLocation (programID, "VertexPosition"); colorAttribHandle = glGetAttribLocation (programID, "VertexColor"); glVertexAttribPointer(positionAttribHandle, 2, GL_FLOAT, GL_FALSE, 0, gTriangleVertices); glVertexAttribPointer(colorAttribHandle, 3, GL_FLOAT, GL_FALSE, 0, gTriangleColors);
Enable the generic vertex attribute location using
positionAttribHandle
before the rendering call and render the triangle geometry. Similarly, for the per-vertex color information, usecolorAttribHandle
:glEnableVertexAttribArray(positionAttribHandle); glDrawArrays(GL_TRIANGLES, 0, 3);
When the application starts, the control begins with GraphicsInit
, where the system metrics are printed out to make sure that the device supports OpenGL ES 3.0. The OpenGL ES programmable pipeline requires vertex shader and fragment shader program executables in the rendering pipeline. The program object contains one or more executables after attaching the compiled shader objects and linking them to program. In the createProgramExec
function the vertex and fragment shaders are compiled and linked, in order to generate the program object.
The GraphicsResize
function generates the viewport of the given dimension. This is used internally by OpenGL ES 3.0 to maintain the framebuffer. In our current application, it is used to manage color buffer. Refer to the There's more … section for more information on other available buffers in OpenGL ES 3.0.
Finally, the rendering of the scene is performed by GraphicsRender
, this function clears the color buffer with black background and renders the triangle on the screen. It uses a shader object program and sets it as the current rendering state using the glUseProgram
API.
Each time a frame is rendered, data is sent from the client side (CPU) to the shader executable on the server side (GPU) using glVertexAttribPointer
. This function uses the queried generic vertex attribute to bind the data with OpenGL ES pipeline.
There are other buffers also available in OpenGL ES 3.0:
Depth buffer: This is used to prevent background pixels from rendering if there is a closer pixel available. The rule of prevention of the pixels can be controlled using special depth rules provided by OpenGL ES 3.0. For more information on this, refer to Chapter 2, OpenGL ES 3.0 Essentials.
Stencil buffer: The stencil buffer stores the per-pixel information and is used to limit the area of rendering.
The OpenGL ES API allows us to control each buffer separately. These buffers can be enabled and disabled as per the requirement of the rendering. The OpenGL ES can use any of these buffers (including color buffer) directly to act differently. These buffers can be set via preset values by using OpenGL ES APIs, such as glClearColor
, glClearDepthf
, and glClearStencil
.
Note
You can refer to http://www.khronos.org/opengles/sdk/docs/man3/ for more information on glClearDepthf
, glClearStencilAPI
and all other APIs. The same link can be used to explore OpenGL ES 3.0 official API specifications.
Refer to the Depth testing in OpenGL ES 3.0 recipe in Chapter 2, OpenGL ES 3.0 Essentials
Developing an Android OpenGL ES 3.0 application
Developing an iOS OpenGL ES 3.0 application