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.

About these ads