Assuming that you have read Drawing a 3d scene

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

Sketch.h

We can a see a variable holding a Camera, which will be reviewed in details later.

There is also a variable holding a ShaderProgram, which will be populated with a custom shader.

Sketch.cpp

Sketch::Sketch()
:
shader(InputSource::resource("Shader.vert"), InputSource::resource("Shader.frag"))
{}

In the sketch’s constructor we populate our custom shader with a vertex shader and a fragment shader, both loaded from the project’s res folder.

Inside setup()

The modelBatch is populated with a torus mesh, without transformation. setSliceCount() and setLoopCount() control the resolution of the torus. Note that modelBatch is not assigned any shader color because it will be rendered via our custom shader, which takes diffuse color as a parameter.

The lightBatch will be populated with a sphere mesh within draw() later on because it’s a dynamic batch that needs to be animated at each frame.

Inside resize()

We update our Camera with some vertical field-of-view of 60 degrees, some clip values and the size of the window. It must be done either inside resize() or inside draw().

Inside draw()

camera.getViewMatrix()
  .setIdentity()
  .translate(0, 0, -300);

We obtain the view matrix from the Camera and move 300 pixels away on the z axis (the camera distance.)

float t = clock()->getTime() * 1.0f;
float x = cosf(t) * RADIUS;
float y = 0;
float z = sinf(t) * RADIUS;
glm::vec3 lightPosition(x, y, z);

This part is computing the position of the light, which moves indefinitely on a circle-shaped path around the torus.

Then, we set the shader’s Model/View/Projection matrix required by the colorShader and we create a sphere mesh that we center at the light position. Finally, we render the sphere. Note that the vertices of the sphere mesh are of type XYZ.N even though the colorShader ignores the normal information. That is because our mesh generator always provide normal information.

After that, we create a model matrix which rotates indefinitely around the Y axis:

Matrix modelMatrix;
modelMatrix.rotateY(clock()->getTime() * 0.125f);

Then comes the configuration of our custom shader:

State()
  .setShaderMatrix<MODEL>(modelMatrix)
  .setShaderMatrix<VIEW>(camera.getViewMatrix())
  .setShaderMatrix<PROJECTION>(camera.getProjectionMatrix())
  .setShaderMatrix<NORMAL>(modelMatrix.getNormalMatrix())
  .setShaderUniform("u_view_pos", camera.getEyePosition())
  .setShaderUniform("u_material.point_light_count", 1)
  .setShaderUniform("u_point_lights[0].position", lightPosition)
  .setShaderUniform("u_point_lights[0].color", glm::vec3(1, 1, 1))
  .setShaderUniform("u_point_lights[0].intensity", 1.0f)
  .setShaderUniform("u_material.ambient", glm::vec3(0.1f, 0.1f, 0.1f))
  .setShaderUniform("u_material.diffuse", glm::vec3(1.0f, 0.5f, 0.0f))
  .setShaderUniform("u_material.specular", glm::vec3(1, 1, 1))
  .setShaderUniform("u_material.shininess", 25.0f)
  .apply();

It requires to be passed 4 separate matrices: Model, View, Projection and Normal. Then, it requires to be passed several properties:

  • u_view_pos: the position of the camera
  • u_material.point_light_count: the number of lights
  • u_point_lights[0].position: the position of the light
  • u_point_lights[0].color: the color of the light
  • u_point_lights[0].intensity: the intensity of the light
  • u_material.ambient: the material’s ambient color
  • u_material.diffuse: the material’s diffuse color (orange in this case)
  • u_material.specular: the material’s specular color
  • u_material.shininess: the material’s shininess factor

Finally, we render our modelBatch.