4.4. Controlling lighting effects

This tutorial combines:

  1. Locate the shader example 04 - Lighting and open the lighting.cpp file.

  2. Example 4.17 shows the standard initialization code. This example also has vectors for the light sources:

    Example 4.17. Initialization for the teapot lighting application

    ...
    #include <mde/mde.h>
    using namespace MDE;
    
    static const float TIMEOUT = 0.3f;
    
    static const vec3 AMBIENT_LIGHT = vec3 (0.1f, 0.1f, 0.1f);
    static const vec3 DIFFUSE_LIGHT = vec3(0.5f, 0.5f, 0.5f);
    static const vec3 SPECULAR_LIGHT = vec3(0.5f, 0.5f, 0.5f);
    
    static const vec4 COLOR = vec4(1.0f, 0.0f, 0.0f, 1.0f);
    
    static const float PI = 3.14159265;
    
    /** 
    * Function that calculates the camera position given an angle and a radius. This function
    * enables the camera to rotate around the scene object.
    */
    inline vec3 calculateCamPos(float radius, float angle)
    {
        float angleRad = angle / 180 * PI;
        return vec3(radius * cos(angleRad), radius * sin(angleRad), 0.0f);
    }
    
    int main(int argc, char * argv[])
    {
        try
        {
            // Initialize the demo engine classes
            Managed<System> system = create_system_backend();
            Managed<Context> context = system->createContext(320, 240);
            #ifdef MDE_OS_PC_LINUX        
                Managed<Keyboard> keyboard = system->createKeyboard(context);
            #else
                Managed<Keyboard> keyboard = system->createKeyboard();
            #endif
            Managed<FileSystem> filesystem = system->createFileSystem("./data");
            Managed<Timer> timer = system->createTimer();
    
            Proxy proxy(filesystem, context);
    
    
    

  3. Example 4.18 shows the code that loads the lighting programs:

    Example 4.18. Shaders

            // Loading programs
            Program* perVertexLightingProgram =
                proxy.getProgram("../shaders/per_vertex_lighting.vert;../shaders/per_vertex_lighting.frag");
            Program* perFragmentLightingProgram =
                proxy.getProgram("../shaders/per_fragment_lighting.vert;../shaders/per_fragment_lighting.frag");
    ...
    

    There are two lighting programs, and each program has a fragment shader and a vertex shader:

    Vertex lighting program

    The per_vertex_lighting.frag fragment shader is based on the ambient, diffuse, and specular lighting as shown in Example 4.19:

    Example 4.19. per_vertex_lighting.frag

    #ifdef GL_ES
    	precision mediump float;
    #endif
    
    uniform vec3 AMBIENT_LIGHT;
    uniform vec4 COLOR;
    
    varying vec3 vDiffuse;
    varying vec3 vSpecular;
    
    void main(void)
    {
    	gl_FragColor = vec4(AMBIENT_LIGHT, 1.0) * COLOR + 
    	               vec4(vDiffuse, 1.0)      * COLOR + 
    	               vec4(vSpecular, 1.0);
    }
    

    The per_vertex_lighting.vert vertex shader is listed in Example 4.20:

    Example 4.20. per_vertex_lighting.vert

    uniform mat4 WORLD_VIEW_PROJECTION;
    uniform mat3 WORLD;
    uniform vec3 LIGHT_POSITION;
    uniform vec3 CAMERA_POSITION;
    uniform vec3 DIFFUSE_LIGHT;
    uniform vec3 SPECULAR_LIGHT;
    
    attribute vec4 POSITION;
    attribute vec3 NORMAL;
    
    varying vec3 vDiffuse;
    varying vec3 vSpecular;
    
    void main(void)
    {
        gl_Position = WORLD_VIEW_PROJECTION * POSITION;
        
        vec3 normal = normalize(WORLD * NORMAL);
        vec3 pos = WORLD * POSITION.xyz;
        
        vec3 lightVector = normalize(LIGHT_POSITION - pos);
        float nDotL = max(dot(normal, lightVector), 0.0);
            
        vDiffuse = DIFFUSE_LIGHT * nDotL;
        
        // No point in calculating specular reflections from the backside of vertices.
        float specPow = 0.0;
        if (nDotL > 0.0)
        {
            vec3 cameraVector = normalize(CAMERA_POSITION - pos);
            
            vec3 reflectVector = reflect(-cameraVector, normal);
            specPow = pow(max(dot(reflectVector, lightVector), 0.0), 4.0);
        }
        
        vSpecular = SPECULAR_LIGHT * specPow;
    }
    

    Fragment lighting program

    per_fragment_lighting.vert for the vertex shader and per_fragment_lighting.frag for the fragment shader. The per_fragment_lighting.frag fragment shader uses the data in the normal bitmap to calculate the reflections as shown in Example 4.21:

    Example 4.21. per_fragment_lighting.frag

    #ifdef GL_ES
        precision mediump float;
    #endif
    
    uniform vec3 AMBIENT_LIGHT;
    uniform vec3 DIFFUSE_LIGHT;
    uniform vec3 SPECULAR_LIGHT;
    uniform vec4 COLOR;
    
    varying vec3 vNormal;
    varying vec3 vLightVector;
    varying vec3 vCameraVector;
    
    void main(void)
    {
        vec3 normal = normalize(vNormal);
        vec3 lightVector = normalize(vLightVector);
        
        float nDotL = max(dot(normal, lightVector), 0.0);
        vec3 diffuse = DIFFUSE_LIGHT * nDotL;
        
        float specPow = 0.0;
        if (nDotL > 0.0)
        {
            vec3 cameraVector = normalize(vCameraVector);
            
            vec3 reflectVector = reflect(-cameraVector, normal);
            specPow = pow(max(dot(reflectVector, lightVector), 0.0), 16.0);    
        }
        
        vec3 specular = SPECULAR_LIGHT * specPow;
            
        gl_FragColor = vec4(AMBIENT_LIGHT, 1.0) * COLOR +
                       vec4(diffuse, 1.0)       * COLOR +
                       vec4(specular, 1.0);
    }
    

    The per_fragment_lighting.vert vertex shader is listed in Example 4.22:

    Example 4.22. per_fragment_lighting.vert

    uniform mat4 WORLD_VIEW_PROJECTION;
    uniform mat3 WORLD;
    
    uniform vec3 LIGHT_POSITION;
    uniform vec3 CAMERA_POSITION;
    
    attribute vec4 POSITION;
    attribute vec3 NORMAL;
    
    varying vec3 vNormal;
    varying vec3 vLightVector;
    varying vec3 vCameraVector;
    
    void main(void)
    {
        gl_Position = WORLD_VIEW_PROJECTION * POSITION;
        
        vec3 pos = WORLD * POSITION.xyz;
        
        // No point in normalizing before the fragment shader as the normalization will
        // be offset by the interpolation anyway.
        vNormal = WORLD * NORMAL;
        vLightVector = LIGHT_POSITION - pos;
        vCameraVector = CAMERA_POSITION - pos;
    }
    

  4. Previous examples specified the locations for each vertex. Example 4.23 shows how to load an asset file that contains the information:

    Example 4.23. Load the asset for the teapot

    ...
    		// Loading scene
    		SceneAsset* scene = proxy.getSceneAsset("teapot.mba");
    		GeometryAsset* teapot = 			static_cast<GeometryAsset*>(scene->getAsset(Asset::TYPE_GEOMETRY, 0));
    ...
    

    Example 4.23 is loading a .mba file which enables mesh data to be compressed and conditioned. The COLLADA format source is also present in the data directory as teapot.dae. Using a GeometryAsset and the proxy loader simplifies loading complex shapes.

  5. Example 4.24 shows the camera, projection, view, and world setup:

    Example 4.24. Set the camera, projection, view, and world for the teapot

    ...
            // The camera position, target and up vector for use when creating the view matrix
            float cameraAngle = 0.0f;
            vec3 camPos = calculateCamPos(75.0f, cameraAngle);
            vec3 camTarget = vec3(0.0f, 0.0f, 0.0f);
            vec3 upVector = vec3(0.0f, 0.0f, 1.0f);
    
            // Set up the view, projection and world matrices and multiply them to get the
            // modelviewprojection and modelview matrices.
            mat4 view = mat4::lookAt(camPos, camTarget, upVector);
            mat4 proj = mat4::perspective(60.0f, 4.0f/3.0f, 1.0f, 500.0f);
            mat4 world = mat4::rotation(90.0f, vec3(0.0f, 0.0f, 1.0f));
        
            mat4 wvpMatrix = proj * view * world;
    
            bool perVertexLighting = true; 
            vec3 lightPosition(75.0f, 75.0f, 0.0f);
    
            // Enable depth test
            glEnable(GL_DEPTH_TEST);
    
    ...
    

  6. Example 4.25 shows the part of the draw loop that reacts to keyboard input to change the camera, view, and lighting:

    Example 4.25. Use the to keyboard input to change the teapot view, camera, and lighting

    ...
            do
            {
                // Exit the application if escape key pressed
                if (keyboard->getSpecialKeyState(Keyboard::KEY_ESCAPE))
                {
                    return 0;
                }
    
                // toggle lighting model if UP key is pressed
                static float time = timer->getTime();
                if (keyboard->getSpecialKeyState(Keyboard::KEY_UP)) 
                {
                    perVertexLighting = !perVertexLighting;
                }
    
                if (perVertexLighting)
                {
                    context->setProgram(perVertexLightingProgram);
                }
                else
                {
                    context->setProgram(perFragmentLightingProgram);
                }
    
                // Rotate object or camera
                if (keyboard->getSpecialKeyState(Keyboard::KEY_SPACE))
                {
                    if (keyboard->getSpecialKeyState(Keyboard::KEY_LEFT))
                    {
                        // calculate new camera position
                        camPos = calculateCamPos(75.0f, --cameraAngle);
                        // create a view based on the new camera position
                        view = mat4::lookAt(camPos, camTarget, upVector);
                    }
                    if (keyboard->getSpecialKeyState(Keyboard::KEY_RIGHT))
                    {
                        camPos = calculateCamPos(75.0f, ++cameraAngle);
                        view = mat4::lookAt(camPos, camTarget, upVector);
                    }
                }
                else 
                {
                    if (keyboard->getSpecialKeyState(Keyboard::KEY_RIGHT))
                    {
                        // create a world matrix to rotate the teapot
                        world *= mat4::rotation(1.0f, vec3(0.0f, 0.0f, 1.0f));
                    }
                    if (keyboard->getSpecialKeyState(Keyboard::KEY_LEFT))
                    {
                        world *= mat4::rotation(-1.0f, vec3(0.0f, 0.0f, 1.0f));
                    }
            	}
    ...
    

  7. Example 4.26 shows the part of the draw loop that sets the uniform values and draws the teapot:

    Example 4.26. Drawing the teapot with lighting

    ...
                wvpMatrix = proj * view * world;
    
                // Have to set the uniforms each frame since the above call to setProgram
                // clears them.
                context->setUniformMatrix("WORLD_VIEW_PROJECTION", wvpMatrix);
                context->setUniformMatrix("WORLD", world.toMat3());
                context->setUniform("LIGHT_POSITION", lightPosition);
                context->setUniform("CAMERA_POSITION", camPos);
                context->setUniform("AMBIENT_LIGHT", AMBIENT_LIGHT);
                context->setUniform("DIFFUSE_LIGHT", DIFFUSE_LIGHT);
                context->setUniform("SPECULAR_LIGHT", SPECULAR_LIGHT);
                context->setUniform("COLOR", ::COLOR);
    
                // Clear the screen and draw teapot with lighting
                glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT );
    
                teapot->draw();
            }while ( context->update() );
        }
        catch (Exception &e)
        {
            printf(e.getMessage().getCharString());
        }
    }
    

  8. Rebuild the project and run it. Figure 4.5 shows the shape:

    Figure 4.5. The teapot shape with lighting effects

    The teapot shape with lighting effects

  9. Use the keyboard to change the view:

    • press the left or right arrows to rotate the teapot

    • depress the spacebar and then press the left or right arrows to move the camera

    • press the up arrow to change between Gouraud and Phong lighting.

Copyright © 2010 ARM. All rights reserved.ARM DUI 0527A‑02a
Non-Confidential - Draft - BetaID070710