Let’s concentrate on these 2 files: Sketch.h and Sketch.cpp in the TestingTriangle project’s source folder.

Sketch.h

First, we include the necessary files. Then we define that there will be a setup() function and a draw() function. Finally, we define 2 variables:

colorShader

The chronotext-cross framework is based on OpenGL 2 (which works on the desktop) and is compatible with OpenGL ES 2 (for mobile devices) and WebGL 1 on the browser. It requires that a shader is defined in order to render something.

So the following is assigning a stock shader of type ColorShader to the corresponding variable:

chr::gl::shaders::ColorShader colorShader;

This stock shader is very basic, as it simply fills the geometry based on color.

batch

OpenGL is using the concept of vertices to describe geometry. Each vertex has a position in 3d space as well as some optional properties such as:

  • Color
  • Normal (used for lighting)
  • UV (used for texture mapping)

Vertices are grouped in batches and sent to the GPU for being rendered. So the following is assigning a batch of vertices (with position and color properties) to the corresponding variable:

chr::gl::VertexBatch<chr::gl::XYZ.RGBA> batch;

Sketch.cpp

Inside setup()

First, we assign the colorShader to our batch, so that the shader can be activated when the batch is sent to the GPU later on.

Then we fill the batch with 3 vertices:

batch
  .addVertex(0, -100, glm::vec4(0, 0, 1, 1))
  .addVertex(-120, 100, glm::vec4(1, 0, 0, 1))
  .addVertex(120, 100, glm::vec4(0, 1, 0, 1));

We’re drawing a 2d triangle, so there is no need to pass a 3d position for each vertex. Instead, we pass some x and y values. In addition, we pass a color for each vertex. The chronotext-cross framework is using the glm library which includes constructs adapted to OpenGL and allows to perform mathematical operations on them.

Briefly, colors are defined in OpenGL by 4 float numbers in the order RGBA. Each float can hold a value betwen 0 and 1. So glm::vec4(1, 0, 0, 1) corresponds to non-transparent red for instance.

Now what is the idea of assigning a color to each vertex? OpenGL will automatically blend the 3 different colors used here inside the triangle.

Last thing regarding our batch: this is a static one. It means that it is filled once at the beginning, not each frame. It has some obvious implications in term of performance (imagine you have a batch with thousand of vertices: filling it with data takes some time and you don’t want to repeat such an operation each frame if it’s not necessary.)

Finally, we define some OpenGL properties related to 2d drawing:

glDisable(GL_DEPTH_TEST);
glDepthMask(GL_FALSE);

And some generic OpenGL properties that should be defined in most of the sketches:

glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

Inside draw()

First, we fill the window with black:

glClearColor(0, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);

And now, the matrices… Our colorShader requires to be passed a 4x4 matrix represented by a glm::mat4 construct. This matrix is defined as Model/View/Projection. Model and View are not mandatory. So if we pass to the shader solely a Projection matrix as follows:

glm::mat4 projectionMatrix = glm::ortho(0.0f, windowInfo.width, 0.0f, windowInfo.height);

State()
  .setShaderMatrix<MVP>(projectionMatrix)
  .apply();

We will get the simplest setup possible: a 2d window with the origin at bottom left and the Y axis going up.

But we want a more advanced setup: a 2d window with the origin at the center and the Y axis going down:

glm::mat4 projectionMatrix = glm::ortho(0.0f, windowInfo.width, 0.0f, windowInfo.height);

Matrix viewMatrix;
viewMatrix
  .translate(windowInfo.center())
  .scale(1, -1);

State()
  .setShaderMatrix<MVP>(viewMatrix * projectionMatrix)
  .apply();

The chronotext-cross framework is offering the Matrix construct — which is another kind of 4x4 matrix — more suited for performing chained operations among other things.

Note that behind the scenes, the setShaderMatrix<MVP>(...) operation is setting a shader uniform named u_mvp_matrix. It’s purely a matter of conventions. We could have used a setShaderUniform("modelViewProjectionMatrix", ...) operation as long as the shader is using the same uniform name.

And finally, we send the batch to the GPU for rendering.

Check the WebGL demo.