Working with lighting and custom shaders
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 camerau_material.point_light_count
: the number of lightsu_point_lights[0].position
: the position of the lightu_point_lights[0].color
: the color of the lightu_point_lights[0].intensity
: the intensity of the lightu_material.ambient
: the material’s ambient coloru_material.diffuse
: the material’s diffuse color (orange in this case)u_material.specular
: the material’s specular coloru_material.shininess
: the material’s shininess factor
Finally, we render our modelBatch.