Assuming that you have read Drawing a triangle

Here is a WebGL demo showing what’s taking place in Sketch.h and Sketch.cpp from the TestingDrawing2d project’s source folder.

Sketch.h

In addition of setup() and draw() we can see resize(). This function is called at the beginning, after setup(), and then everytime the window size changes (which happens only on the browser platform.)

Then come the variables… First, we can see a group IndexedVertexBatch constructs. This is similar to VertexBatch because it contains vertices, but in addition it contains a list of indices that tell the GPU which vertex to use or re-use when it assemble the geometry’s triangles.

Then come the shader variables. We can see a new type: TextureShader which is a stock shader for rendering RGBA textures.

Then, we have a SVGDocument and a Texture construct.

Finally, we have a group of internal functions that will be described later.

Sketch.cpp

Inside setup()

We’re calling the loadMoon() function:

moonDocument
  .setOriginAtBottom(false)
  .setSamplingTolerance(50)
  .load(InputSource::resource("moon.svg"));

The default coordinate system of our SVGDocument has its origin at the bottom, but we want it to be at the top. Then, we set a high sampling tolerance which has the effect of lowering the number of vertices in the polygons that will result from the sampling of the document’s Bezier curves. Finally, we load the SVG file from the res folder (every project has such a folder, for storing the resources to be used.)

Then we’re calling the loadUFO() function:

ufoTexture = Texture(Texture::ImageRequest("ufo.png")
  .setFlags(image::FLAGS_RBGA)
  .setMipmap(true));

We load a PNG file from the res folder and we ensure that it will be uploaded to the GPU as a GL_RGBA texture. Then, we activate mip-mapping. In short, it produces better results in term of visual quality and of performance when rendering the texture. Note that the image must be power-of-two sized for mip-mapping to work. In this case, the resolution is 512x256, which is fine.

Then we assign properties to the batches. Starting with starsBatch and moonBatch: we define a color for the shader. That’s because each vertex holds only XYZ information. gradientBatch doesn’t need shader color because each vertex holds XYZ.RGBA information. As for ufoBatch, we need to define white for the shader color in order for the texture to be rendered with its actual colors.

Inside resize()

The operations taking place here can’t just occur at the beginning, i.e. inside setup(). Otherwise, when the window size is changing, the visuals won’t adapt.

Let’s examine the createGradient() function:

float x1 = 0;
float y1 = 0;
float x2 = windowInfo.width;
float y2 = windowInfo.height;

glm::vec4 color1(0, 0, 0, 0);
glm::vec4 color2(1, 0, 1, 0.33f);

gradientBatch.clear();

gradientBatch
  .addVertex(x1, y1, color1)
  .addVertex(x2, y1, color1)
  .addVertex(x2, y2, color2)
  .addVertex(x1, y2, color2);

gradientBatch.addIndices(0, 1, 2, 2, 3, 0);

First it’s important to clear the batch. Otherwise vertices will keep accumulating and we won’t achieve our goal.

Our batch is made of 4 vertices, corresponding the the 4 corners of the window. The topmost vertices are associated with a transparent color and the bottommost vertices are associated with a semi-transparent purple color.

Since the GPU is only capable of drawing triangle primitives, we can’t simply send these 4 vertices. We need to split the rectangle we would like to draw into 2 triangles. This is done via the batch indices: the 3 first indices are for the first triangle and the 3 other ones for the second triangle.

Let’s continue with the createStars() function, which take the number of stars to create as input:

MatrixAffine affine;
Triangulator triangulator;
Random rnd(42);

A MatrixAffine is a special kind of matrix, specialized in 2d operations. A Triangulator is an object that takes polygons as input and transforms them into a set of indexed triangles. Finally, the Random object (seeded with the arbitrary value 42) will help us generate random values.

auto starPoints = shape::FivePointedStar()
  .setOuterRadius(1)
  .setInnerRadiusRatio(0.333f)
  .append();

This will create a star-shaped polygon (a std::vector<glm::vec2> for instance.)

Then comes a loop which executes n times:

glm::vec2 position(rnd.nextFloat(width), rnd.nextFloat(height));
float scale = rnd.nextFloat(3, 15);
float rotation = rnd.nextFloat(0, 360);

This is using our random generator to produce a position within the window’s boundaries, a scale factor between 3 and 15 and a rotation angle between 0 and 360 degrees.

Shape star;
star
  .addPath(affine
     .setTranslate(position)
     .scale(scale)
     .rotate(rotation * D2R)
     .transformPoints(starPoints));

This creates a Shape object by using our MatrixAffine to apply our position, scale and rotation to the star-shaped polygon defined earlier. Note that the setTranslate() operation is replacing two operations (setIdentity() followed by setTranslate().) Also, the rotate() function takes a parameter in radians, hence the need to multiply by D2R.

triangulator
  .add(star)
  .fill(starsBatch);

Finally, we add our star to the Triangulator and ask it to tranform it into indexed triangles that will be added to our starBatch. That’s it for the loop.

Now let’s take a look at the placeMoon() function:

It’s also using a Triangulator to which we add all the shapes our moon is made of (note that in our case, the moon is made of one single shape.)

triangulator.fill(moonBatch, Matrix()
  .translate(windowInfo.width * 0.25f, windowInfo.height * 0.125f)
  .scale(0.5f));

Then, we transform the added shapes into indexed triangles that will be added to moonBatch, with a custom matrix transformation (pinning the moon around the top left of the window and scaling it down.)

Inside draw()

The only part that should not be clear to you at this stage is the drawUFO() function:

Sprite()
  .setAnchor(0.5f, 0.5f)
  .append(ufoBatch, Matrix()
    .translate(windowInfo.center())
    .rotateZ(sinf(clock()->getTime() * 3.0f) * 0.125f)
    .scale(0.5f));

We use a Sprite construct to draw our UFO texture via ufoBatch, with a matrix transformation (pinning the UFO to the window’s center, making its rotation angle oscillate, and scaling it down.) Note that setAnchor(0.5f, 0.5f) will cause the texture to be drawn from its center.