Now it’s time to learn a little more about lighting. Throughout the book, there wasn’t any element such a “light” neither a “material”, all what was used to shade a triangle was a light position and a couple of colors: ambient and diffuse (specular was always white). But the lighting equation is a little more complex: it includes interaction between lights and objects. Each object has its own material, which defines the “look” properties for the object. Light and Material are declared as follows:
struct Light
{
glm::vec4 position;
glm::vec4 ambient;
glm::vec4 diffuse;
glm::vec4 specular;
float constantAtt;
float linearAtt;
float quadraticAtt;
glm::vec3 spotDirection;
float spotExponent;
float spotCutoff;
};
struct Material
{
glm::vec4 ambient;
glm::vec4 diffuse;
glm::vec4 specular;
float shininess;
};
When we have our lights and material initiliazed we must send all this data to the GPU, to be used by the shaders. So, first of all, we’ll see the shaders and then see how we can make our lights and material data available to them.
Point light phong shading vertex shader:
#version 330
// Attributes per position: position and normal
in vec4 position;
in vec3 normal;
uniform mat4 MVP;
uniform mat4 MV;
uniform mat3 normalMatrix;
smooth out vec3 vPosition;
smooth out vec3 vNormal;
void main(void)
{
// Get surface normal in eye coordinates
vNormal = normalMatrix * normal;
// Get vertex position in eye coordinates
vec4 vertexPos = MV * position;
vec3 vertexEyePos = vertexPos.xyz / vertexPos.w;
vPosition = vertexEyePos;
// Don't forget to transform the geometry!
gl_Position = MVP * position;
}
Point light phong shading fragment shader:
#version 330
out vec4 fragmentColor;
struct Light
{
vec4 position;
vec4 ambient;
vec4 diffuse;
vec4 specular;
float constant_attenuation;
float linear_attenuation;
float quadratic_attenuation;
vec3 spot_direction;
float spot_cutoff;
float spot_exponent;
};
uniform Lights
{
Light light[8];
} lights;
uniform Material
{
vec4 ambient;
vec4 diffuse;
vec4 specular;
float shininess;
} material;
uniform int num_lights;
smooth in vec3 vPosition;
smooth in vec3 vNormal;
vec4
pointLight (int lightID)
{
float nDotVP; // normal * light direction
float nDotR; // normal * light reflection vector
float pf; // power factor
float attenuation; // computed attenuation factor
float d; // distance from surface to light position
vec3 VP; // direction from surface to light position
vec3 reflection; // direction of maximum highlights
// Compute vector from surface to light position
VP = vec3 (lights.light[lightID].position) - vPosition;
// Compute distance between surface and light position
d = length (VP);
// Normalize the vector from surface to light position
VP = normalize (VP);
// Compute attenuation
attenuation = 1.f / (lights.light[lightID].constant_attenuation +
lights.light[lightID].linear_attenuation * d +
lights.light[lightID].quadratic_attenuation * d * d);
reflection = normalize (reflect (-normalize (VP), normalize
(vNormal)));
nDotVP = max (0.f, dot (vNormal, VP));
nDotR = max (0.f, dot (normalize (vNormal), reflection));
if (nDotVP == 0.f)
pf = 0.f;
else
pf = pow (nDotR, material.shininess);
vec4 ambient = material.ambient * lights.light[lightID].ambient * attenuation;
vec4 diffuse = material.diffuse * lights.light[lightID].diffuse * nDotVP * attenuation;
vec4 specular = material.specular * lights.light[lightID].specular * pf * attenuation;
return ambient + diffuse + specular;
}
void main(void)
{
for (int i = 0; i < num_lights; ++i)
fragmentColor += pointLight (i);
}
The vertex shader is pretty simple. The fragment shader, however, is rather more interesting. It introduces a new concept: uniform blocks. Lights and Material are the two uniform blocks used in this shader. The second is pretty straighforward; it contains four values: ambient, diffuse and specular color and shininess for the current object to be shaded. The Lights uniform block contains a single variable light, which is an array of Lights, a struct mirroring the one with the same name in the client application. The Material uniform block also mirrors the struct in the client side.
Uniform blocks are not like simple uniforms, it’s no longer possible to retrieve the uniform location and set a value there. You need Uniform Buffer Objects (UBO) instead. Filling them is not as straightforward as filling array buffers with vertex data, thought. The GPU will expect the data to be in specific locations within the buffer, and keeping certain conditions regarding data layouts for arrays and matrices for example (stride). Take a look to http://www.opengl.org/wiki/Uniform_Buffer_Object for more information about Uniform Buffer Objects.
So, now it’s the time to fill the uniform buffers with our light and material data:
glGenBuffers (1, &light_ubo);
glBindBuffer (GL_UNIFORM_BUFFER, light_ubo);
const GLchar *uniformNames[1] =
{
"Lights.light"
};
GLuint uniformIndices;
glGetUniformIndices (_phongProgram, 1, uniformNames, &uniformIndices);
GLint uniformOffsets[1];
glGetActiveUniformsiv (_phongProgram, 1, &uniformIndices,
GL_UNIFORM_OFFSET, uniformOffsets);
GLuint uniformBlockIndex = glGetUniformBlockIndex (_phongProgram,
"Lights");
GLsizei uniformBlockSize (0);
glGetActiveUniformBlockiv (_phongProgram, uniformBlockIndex,
GL_UNIFORM_BLOCK_DATA_SIZE, &uniformBlockSize);
const GLchar *names[] =
{
"Lights.light[0].position",
"Lights.light[0].ambient",
"Lights.light[0].diffuse",
"Lights.light[0].specular",
"Lights.light[0].constant_attenuation",
"Lights.light[0].linear_attenuation",
"Lights.light[0].quadratic_attenuation",
"Lights.light[0].spot_direction",
"Lights.light[0].spot_cutoff",
"Lights.light[0].spot_exponent",
"Lights.light[1].position",
"Lights.light[1].ambient",
"Lights.light[1].diffuse",
"Lights.light[1].specular",
"Lights.light[1].constant_attenuation",
"Lights.light[1].linear_attenuation",
"Lights.light[1].quadratic_attenuation",
"Lights.light[1].spot_direction",
"Lights.light[1].spot_cutoff",
"Lights.light[1].spot_exponent",
};
GLuint indices[20];
glGetUniformIndices (_phongProgram, 20, names, indices);
std::vector _lightUniformOffsets (20);
glGetActiveUniformsiv (_phongProgram, _lightUniformOffsets.size (),
indices, GL_UNIFORM_OFFSET, &_lightUniformOffsets[0]);
GLint *offsets = &_lightUniformOffsets[0];
const unsigned int uboSize (uniformBlockSize);
std::vector buffer (uboSize);
int offset;
for (unsigned int n = 0; n < _lights.size (); ++n)
{
// Light position (vec4)
offset = offsets[0 + n * 10];
for (int i = 0; i < 4; ++i)
{
*(reinterpret_cast<float*> (&buffer[0] + offset)) =
_lights[n].position[i];
offset += sizeof (GLfloat);
}
// Light ambient color (vec4)
offset = offsets[1 + n * 10];
for (int i = 0; i < 4; ++i)
{
*(reinterpret_cast<float*> (&buffer[0] + offset)) =
_lights[n].ambient[i];
offset += sizeof (GLfloat);
}
// Light diffuse color (vec4)
offset = offsets[2 + n * 10];
for (int i = 0; i < 4; ++i)
{
*(reinterpret_cast<float*> (&buffer[0] + offset)) =
_lights[n].diffuse[i];
offset += sizeof (GLfloat);
}
// Light specular color (vec4)
offset = offsets[3 + n * 10];
for (int i = 0; i < 4; ++i)
{
*(reinterpret_cast<float*> (&buffer[0] + offset)) =
_lights[n].specular[i];
offset += sizeof (GLfloat);
}
// Light constant attenuation (float)
offset = offsets[4 + n * 10];
*(reinterpret_cast<float*> (&buffer[0] + offset)) =
_lights[n].constantAtt;
// Light linear attenuation (float)
offset = offsets[5 + n * 10];
*(reinterpret_cast<float*> (&buffer[0] + offset)) =
_lights[n].linearAtt;
// Light quadratic attenuation (float)
offset = offsets[6 + n * 10];
*(reinterpret_cast<float*> (&buffer[0] + offset)) =
_lights[n].quadraticAtt;
// Light spot direction (vec3)
offset = offsets[7 + n * 10];
for (int i = 0; i < 3; ++i)
{
*(reinterpret_cast<float*> (&buffer[0] + offset)) =
_lights[n].spotDirection[i];
offset += sizeof (GLfloat);
}
// Light spot cutoff (float)
offset = offsets[8 + n * 10];
*(reinterpret_cast<float*> (&buffer[0] + offset)) =
_lights[n].spotCutoff;
// Light spot exponent (float)
offset = offsets[9 + n * 10];
*(reinterpret_cast<float*> (&buffer[0] + offset)) =
_lights[n].spotExponent;
}
glBufferData (GL_UNIFORM_BUFFER, uboSize, &buffer[0], GL_DYNAMIC_DRAW);
glBindBufferBase (GL_UNIFORM_BUFFER, 0, light_ubo);
glUniformBlockBinding (_phongProgram, uniformBlockIndex, 0)
Filling the Material uniform buffer is easier:
glGenBuffers (1, &material_ubo);
glBindBuffer (GL_UNIFORM_BUFFER, material_ubo);
const GLchar *uniformNames[4] =
{
"Material.ambient",
"Material.diffuse",
"Material.specular",
"Material.shininess"
};
GLuint uniformIndices[4];
glGetUniformIndices (_phongProgram, 4, uniformNames, uniformIndices);
GLint uniformOffsets[4];
glGetActiveUniformsiv (_phongProgram, 4, uniformIndices,
GL_UNIFORM_OFFSET, uniformOffsets);
GLuint uniformBlockIndex = glGetUniformBlockIndex (_phongProgram,
"Material");
GLsizei uniformBlockSize (0);
glGetActiveUniformBlockiv (_phongProgram, uniformBlockIndex,
GL_UNIFORM_BLOCK_DATA_SIZE, &uniformBlockSize);
const unsigned int uboSize (uniformBlockSize);
std::vector<unsigned char> buffer (uboSize);
int offset;
// Sphere material ambient color (vec4)
offset = uniformOffsets[0];
for (int i = 0; i < 4; ++i)
{
*(reinterpret_cast<float *> (&buffer[0] + offset)) =
_sphereMat.ambient[i];
offset += sizeof (GLfloat);
}
// Sphere material diffuse color (vec4)
offset = uniformOffsets[1];
for (int i = 0; i < 4; ++i)
{
*(reinterpret_cast<float *> (&buffer[0] + offset)) =
_sphereMat.diffuse[i];
offset += sizeof (GLfloat);
}
// Sphere material specular color (vec4)
offset = uniformOffsets[2];
for (int i = 0; i < 4; ++i)
{
*(reinterpret_cast<float *> (&buffer[0] + offset)) =
_sphereMat.specular[i];
offset += sizeof (GLfloat);
}
// Sphere material shininess (float)
offset = uniformOffsets[3];
*(reinterpret_cast<float *> (&buffer[0] + offset)) =
_sphereMat.shininess;
glBufferData (GL_UNIFORM_BUFFER, uboSize, &buffer[0], GL_DYNAMIC_DRAW);
glBindBufferBase (GL_UNIFORM_BUFFER, 1, material_ubo);
glUniformBlockBinding (_phongProgram, uniformBlockIndex, 1);
For more information about the subject I’d recommend reading the OpenGL SuperBible 5th ed, chapter 11, whis has a large explanation abaout all you need to know about uniform blocks and Uniform Buffer Objects.











Thank you for your helpful post! Nice work!
Thanks for your feedback! Nice you liked it!
Am I in the right place if I want to learn how to translate the light position?
What do you mean? You translate the light position like you translate any other object in your 3D world.
Hey, great site! Would it be possible for you to provide a working example of this? I tried copying the code into VS and I got error after error… I’m assuming this is C++, that may also be the problem. Other than that, awesome site mate, a lot of information clearly, I’m a fan!
Hi Mad, thank you for the comment!
The code listings here are both in C++ and GLSL. I implemented and tested it under Linux, but the code is standard C++. The problem is I didn’t build the libraries to make it work under Windows.
I have all the code in a repository, under Bitbucket (https://bitbucket.org), but I don’t want to make it public since it was only for learning purposes and it’s a mess
. Although, I’ll be happy to let you download the code from the repository if you want to. Just send me an email (you can find my mail in the About page, it’s in the Resume) with your Bitbucket user and I’ll share the code with you to let you grab a copy.
Hello.
I’m learning how to pass data to shader from your code but I don’t get one thing. How do you pack data to light buffer without type in reinterpret_cast? And could you also send me a repo with that code? Thanks.
Hello,
Sorry, it’s just a markup error from wordpress the type of the reinterpret_cast is ‘ float* ‘, the same I use for materials. Thanks for pointing it out! It’s fixed now!
For the reasons I stated in a previous comment, I keep my repository private, but I can share the code with you and let you download a copy. Please send me your Bitbucket (https://bitbucket.org) username.
Username and mail is same as in this comment – JakobekS.
Hi, I’m learning glsl and i try to make point lights. This tut is awesome. Could you share for me source ? my bitbucket login: sanct
Hi, Sanct! For the articles before this one, I refer to the OpenGL SuperBible webpage, were the original code is from (my demos are simple adaptations). There you’ll find lots of example on using simple point lights.