4.8. Lightshow animation with moving sprite and camera

This tutorial combines:

Open the Lightshow example application:

  1. Example 4.58 shows the code in main.cpp that draws the flare:

    Example 4.58. Drawing the flare

    ...
    void prepareFlares(Context* context, Program* p)
    {
        float texcoords[] = { 0,0, 1,0, 1,1, 0,1 };
        flareTexcoord = context->createBuffer(GL_ARRAY_BUFFER, sizeof(texcoords), 8);
        flareTexcoord->setData(0, sizeof(texcoords), texcoords);
    
        flareTexcoordsLoc = p->getAttribLocation(TEXCOORD0);
    
    }
    
    void drawFlare(Context* context, vec3 position, vec3 color, float size)
    {
        context->setUniform("position", position);
        context->setUniform("color", color);
        context->setUniform("size", size);
    
        glEnableVertexAttribArray(flareTexcoordsLoc);
        glBindBuffer(GL_ARRAY_BUFFER, flareTexcoord->getHandle());
        glVertexAttribPointer(flareTexcoordsLoc, 2, GL_FLOAT, GL_FALSE, 0, 0);
    
    
        unsigned short indices[] =
        {
            0, 1, 2,
            0, 2, 3
        };
        
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, indices);
    
        glDisableVertexAttribArray(flareTexcoordsLoc);
    }
    ...
    

  2. Example 4.59 shows the code that traverses the tree that contains the paths:

    Example 4.59. Path traversal functions

    void traverseTransform(Tree<NodeAsset*>* node, mat4 parentTransform, float time)
    {
        if (!node) return;
        traverseTransform(node->getNextSibling(), parentTransform, time);
        if (node->data)
        {
            mat4 localTransform = node->data->sampleLocalTransform(time);
            parentTransform = localTransform * parentTransform;
            node->data->setAbsoluteTransform(parentTransform);
        }    
        traverseTransform(node->getFirstChild(), parentTransform, time);
    }
    
    void applyMaterial(MaterialAsset* m, Context* context)
    {
        if (m)
        {
            if (m->commonMaps.diffuse) context->setUniformSampler("diffuseMap", m->commonMaps.diffuse->getTexture());
            
            if (m->commonMaps.bump) context->setUniformSampler("normalMap", m->commonMaps.bump->getTexture());
            
            if (m->commonMaps.spec_level) 
                context->setUniformSampler("glossMap", m->commonMaps.spec_level->getTexture());
        }
    }
    void traverseDraw(Tree<NodeAsset*>* node, Context* context)
    {
        if (!node) return;
        traverseDraw(node->getNextSibling(), context);
        if (node->data)
        {
            mat4 world = node->data->getAbsoluteTransform();
            context->setUniformMatrix("WORLD", world);
    
            mat4 inv_trans_world = world.transposed();
            inv_trans_world.invert(inv_trans_world);
            context->setUniformMatrix("INV_TRANS_WORLD", inv_trans_world);
    
    
            for (unsigned int i = 0; i < node->data->getBatchCount(); i++)
            {
                Batch b = node->data->getBatch(i);
                applyMaterial(b.material, context);
                b.geometry->draw();
            }
        }
        traverseDraw(node->getFirstChild(), context);
    }
    ...
    

  3. Example 4.60 shows the initialization and loading of the asset files:

    Example 4.60. Creating the context and loading the scene assets

    ...
    int main(int argc, char** argv)
    {
        t_args args;
        int width = 640;
        int height = 480;
        static int frameCount = 1;
        float current_time = 0.0;
        float old_time = 0.0;
        static int first_time = 1;
        
        if ( 0 != parse_arguments( argc, argv, &args )) exit(1);
    
        /* Note that the following are already swapped in case of user input = -rotated */
        if ( args.height != 0 ) height = args.height;
        if ( args.width  != 0 ) width  = args.width;
    
        try
        {
            // Run from the directory containing the executable, so that we can find the data wherever it is run from
            cdToExecutable();
    
            Managed<System> system = create_system_backend();
            Managed<Context> context = system->createContext(width, height);
            Managed<FileSystem> fs = system->createFileSystem("data");
            Managed<Timer> timer = system->createTimer();
            Managed<Timer> fps_timer = system->createTimer();
    
            Proxy proxy(fs, context);
    
            SceneAsset* scene = proxy.getSceneAsset("lightshow.MBA");
            Program* flarePrg = proxy.getProgram("flare.vert;flare.frag");
            Program* prg = proxy.getProgram("lightshow.vert;lightshow.frag");
            NodeAsset* camNode = (NodeAsset*)scene->getAsset(Asset::TYPE_NODE, "Camera01-node");
            NodeAsset* targetNode = (NodeAsset*)scene->getAsset(Asset::TYPE_NODE, "Camera01.Target-node");
            NodeAsset* light0Node = (NodeAsset*)scene->getAsset(Asset::TYPE_NODE, "Omni01-node");
            NodeAsset* light1Node = (NodeAsset*)scene->getAsset(Asset::TYPE_NODE, "Omni02-node");
    
            Texture* flareTex = proxy.getTexture2D("flare.png");
    
            glEnable(GL_DEPTH_TEST);
    
    ...
    

  4. Example 4.61 shows start of the draw loop and the code that calculates and displays the current frame rate:

    Example 4.61. Draw loop with frame rate calculation

    ...
            float time = 0.0;
    
            do
            {        
                /*
                   If FPS measurement is required and start_frame has been entered and current frame is the
                   start_frame then store current time only once
                    OR    
                   if FPS measurement is required and start_frame has not been entered then store current 
                   time every frame
                */
                if ( args.print_fps && ( args.start_frame == 0))
                {                
                    current_time = fps_timer->getTime();
                }
    
                if ( args.print_fps && ( args.start_frame != 0) && ( frameCount == args.start_frame )) 
                {                
                    old_time = fps_timer->getTime();
                }
    
                if( first_time )
                {
                    first_time=0;
                    old_time=current_time;
                }
                
                if ( 0 != args.start_frame && ( frameCount >= ( args.start_frame + args.number_of_frames )))
                {
                    break;
                }
    
                if ( 0 != args.framerate )
                    time += (float)1/args.framerate;
                else
                    time = timer->getTime()*0.3f;
    
                /* Measure each frame if start_frame was not entered */
                if ( args.print_fps  && ( args.start_frame == 0))
                {
                    printf("Frame %d: ", frameCount);
                    printf("%.3f fps\n", 1/(current_time-old_time));
                    old_time = current_time;
                }
                else
                    printf("Frame %d\n", frameCount);
    
    
                float scenetime = time;
                while (scenetime > 10) scenetime -= 10;
    ...
    

  5. Example 4.62 shows the camera, lighting, and environment code:

    Example 4.62. Set the camera and drawing the environment

    ...
                glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
    
                mat4 rootTransform = mat4::identity();
                traverseTransform(scene->getTree(0), rootTransform, scenetime);
                vec3 camPos = camNode->getAbsolutePosition();
                vec3 targetPos = targetNode->getAbsolutePosition();
    
                mat4 view = mat4::lookAt(camPos, targetPos, vec3(0,0,1));
                mat4 proj = mat4::perspective(80, 1.33333f, 1, 500);
    
                context->setProgram(prg);            
                context->setUniformMatrix("VIEW_PROJECTION", proj*view);
    
                vec3 light0position = light0Node->getAbsolutePosition();
                vec3 light0color(0.66f + fabs(sinf(time * 0.1f)) * 0.4f,
                      0.66f + fabs(sinf(time * 0.12f + 0.4f))*0.4f, 
                      0.66f + fabs(sinf(time * 0.18f + 0.989f)) * 0.4f);
                context->setUniform("light0position", light0position);
                context->setUniform("light0color", light0color);
    
                vec3 light1position = light1Node->getAbsolutePosition();
                vec3 light1color(0.66f + fabs(sinf(time * 0.1f)) * 0.4f,
                      0.66f+fabs(sinf(time*0.12f+0.14f))*0.4f, 
    
                      0.66f+fabs(sinf(time*0.18f+0.389f))*0.4f);
                context->setUniform("light1position", light1position);
                context->setUniform("light1color", light1color);
    
                context->setUniform("cameraPosition", camPos);
    
                float cyl_radius = sqrt(30.0f);
    
                vec3 shadow_info[2] = 
                {
                  calculate_cyl_info(vec2(-30.0, -30.0), cyl_radius, vec2(light0position.x, light0position.y), 0.0),
                  calculate_cyl_info(vec2( 30.0,  30.0), cyl_radius, vec2(light0position.x, light0position.y), 0.0)
                };
    
                context->setUniform("shadow_info[0]", shadow_info[0]);
                context->setUniform("shadow_info[1]", shadow_info[1]);
    
                
                traverseDraw(scene->getTree(0), context);
    ...
    

  6. Example 4.63 shows the vertex shader code for the environment:

    Example 4.63. Environment vertex shader lightshow.vert

    ...
    uniform mat4 WORLD;
    uniform mat4 INV_TRANS_WORLD;
    uniform mat4 VIEW_PROJECTION;
    uniform vec3 cameraPosition;
    
    uniform vec3 light0position;
    uniform vec3 light1position;
    uniform vec3 light2position;
    
    attribute vec4 POSITION;
    attribute vec2 TEXCOORD0;
    attribute vec3 NORMAL;
    attribute vec3 BINORMAL;
    attribute vec3 TANGENT;
    
    varying vec2 outTexcoord;
    
    varying vec3 oPos;
    varying vec3 tViewDir;
    
    /* lights */
    varying vec3 oLight0Dir;
    varying vec3 tLight0Dir;
    varying vec3 oLight1Dir;
    varying vec3 tLight1Dir;
    
    void main()
    {
        oPos = (WORLD * POSITION).xyz;
        gl_Position = VIEW_PROJECTION * vec4(oPos, 1);
        
        outTexcoord.xy = TEXCOORD0 * vec2(1,-1);
            
        mat3 m = mat3(INV_TRANS_WORLD[0].xyz, INV_TRANS_WORLD[1].xyz,
                      INV_TRANS_WORLD[2].xyz);
        mat3 tangentSpace = mat3(m * TANGENT, m * BINORMAL, m * NORMAL);
        
        vec3 oViewDir = oPos - cameraPosition;
        
        oLight0Dir = light0position - oPos;
        tLight0Dir = oLight0Dir * tangentSpace;
        
        oLight1Dir = light1position - oPos;
        tLight1Dir = oLight1Dir * tangentSpace;
        
        tViewDir = oViewDir * tangentSpace;
    }
    

  7. Example 4.64 shows the fragment shader code for the environment:

    Example 4.64. Environment fragment shader lightshow.frag

    ...
    varying vec3 oPos;
    varying vec3 tViewDir;
    varying vec2 outTexcoord;
    
    varying vec3 oLight0Dir;
    varying vec3 tLight0Dir;
    varying vec3 oLight1Dir;
    varying vec3 tLight1Dir;
    
    uniform sampler2D diffuseMap;
    uniform sampler2D normalMap;
    uniform sampler2D glossMap;
    
    uniform vec3 cameraPosition;
    uniform vec3 light0color;
    uniform vec3 light1color;
    uniform vec3 light2color;
    uniform vec3 light0position;
    
    const float pi = 3.1415;
    
    uniform vec3 shadow_info[2];
    const float soft_shadow_eps = 0.015;
    
    // calculate how the light source intersects the column
    float intersectRayCylinder(vec2 ray_origin, vec3 shadow_info)
    {
        const float scale_factor = (0.5/pi);
        
        float offset     = shadow_info.x;
        float light_dist = shadow_info.y;
        float limit = shadow_info.z;
    
        vec2 light_pixel = ray_origin.xy - light0position.xy;
        float angle = fract(atan(light_pixel.y, light_pixel.x)*scale_factor - offset);
        float adjusted_angle = abs(angle - 0.5);
    
        return smoothstep(limit, limit+soft_shadow_eps, adjusted_angle) *
                          float(light_dist < length(light_pixel));
    }
    
    // is this point in shadow
    float getShadow(vec2 oPos)
    {
        float shadow = 0.0;
        shadow = max(shadow, intersectRayCylinder(oPos, shadow_info[0]));
        shadow = max(shadow, intersectRayCylinder(oPos, shadow_info[1]));
        shadow = 1.0 - shadow;
        
        shadow = max(0.0, shadow);
        shadow = min(1.0, shadow);
        
        return shadow;
    }
    
    vec2 calcLight(vec3 tLightDir, vec3 oLightDir, vec3 oPos, vec3 tNormal,
                   vec3 tRefViewDir, vec2 texcoord)
    {
        float lightDist = length(tLightDir);
        tLightDir = normalize(tLightDir);
        
        float l_dot_n = max(dot(tNormal,     tLightDir), 0.0);
        float h_dot_n = max(dot(tRefViewDir, tLightDir), 0.0);
        
        float attenuation = max(0.0, 120.0 - lightDist) / 90.0;
        attenuation *= attenuation;
    
        // use the shadow value to calculate the diffuse and specular light
        float shadow = getShadow(oPos.xy) * attenuation;
        float diffuse = l_dot_n * shadow;
        float specular = pow(h_dot_n, 50.0) * shadow;
        
        return vec2(diffuse, specular);
    }
    
    void main(void)
    {
        vec2 texcoord = outTexcoord.xy;
        vec3 tNormal = normalize((2.0*texture2D(normalMap, outTexcoord.xy).xyz)-1.0);
        
        vec3 tViewDirNorm = normalize(tViewDir);
        vec3 tRefViewDir  = reflect(tViewDirNorm, tNormal);
    
        vec3 diffuseLight = vec3(0.0, 0,0);
        vec3 specularLight = vec3(0,0,0);
    
        {
            vec3 lightColor = light0color;
            vec2 diffuseSpecular = calcLight(tLight0Dir, oLight0Dir, oPos, tNormal,
                                             tRefViewDir, texcoord);
            diffuseLight.xyz += lightColor * diffuseSpecular.x;
            specularLight.xyz += lightColor * diffuseSpecular.y * texture2D(glossMap,
                                 texcoord).x;
        }
        
        vec3 texDiffuse = texture2D(diffuseMap, outTexcoord.xy).xyz;
        gl_FragColor = vec4(diffuseLight*texDiffuse + specularLight,1); 
    }
    

  8. Example 4.65 shows the code that sets the uniforms for the flare and calls drawFlare():

    Example 4.65. Drawing the flare

    ...
                
                glDepthMask(GL_FALSE);
                glEnable(GL_BLEND);
                glBlendFunc(GL_SRC_ALPHA, GL_ONE);
                // specify flare.vert and flare.frag as the active shaders 
                context->setProgram(flarePrg);
                context->setUniformMatrix("VIEW", view);
                context->setUniformMatrix("PROJECTION", proj);
                context->setUniformSampler("colorTex", flareTex);
                // call drawFlare with the light position, color, and flare size
                drawFlare(context, light0position, light0color, 10);
                glDepthMask(GL_TRUE);
                glDisable(GL_BLEND);
                
                frameCount++;
            }while (context->update());
    
        catch(Exception& e)
        {
            printf("An exception was thrown:\n%s\n", e.getMessage().getCharString() );
        }
    }
    

  9. Example 4.66 shows the vertex shader code for the flare:

    Example 4.66. Flare vertex shader flare.vert

    ...
    uniform mat4 VIEW;
    uniform mat4 PROJECTION;
    uniform float size;
    uniform vec3 position;
    
    attribute vec2 TEXCOORD0;
    
    varying vec2 outTexcoord;
    
    void main(void)
    {
        // calculate the location of the flare in the world view
        vec4 p = VIEW * vec4(position, 1.0);
        // increase the size of the flare
        // shift by -size to +size depending on value of TEXCOORD0
        p.xy += (TEXCOORD0 * 2.0 - 1.0) * size;
    
        // calculate the display position
        gl_Position =  PROJECTION * p; 
        // forward the text coordinate to the fragment shader
        outTexcoord = TEXCOORD0;
    }
    

  10. Example 4.67 shows the fragment shader code for the flare:

    Example 4.67. Flare fragment shader flare.frag

    ...
    uniform sampler2D colorTex;
    uniform vec3 color;
    
    void main(void)
    {
        // look up color in texture map and multiply by the color uniform
        gl_FragColor =  vec4(texture2D(colorTex, outTexcoord).xyz * 2.0 * color,1);
    }
    

  11. Rebuild the project and run it. See Figure 4.23:

    Figure 4.23. The Lighting image with moving camera and object

    The Lighting image with moving camera and object

    The asset contains:

    • all of the shaders

    • the environment bitmaps

    • the path for the moving flare

    • the camera path

    • the lighting sources.

    The asset file is organized in nodes. The application calls the traverseDraw() and drawFlare() functions to calculate the current positions and draw the environment and flare.

  12. Modify the flare code in main.cpp as shown in Example 4.68 to make the flare oscillate in size:

    Example 4.68. Modifying the flare

    ...
                
                glDepthMask(GL_FALSE);
                glEnable(GL_BLEND);
                glBlendFunc(GL_SRC_ALPHA, GL_ONE);
                // specify flare.vert and flare.frag as the active shaders 
                context->setProgram(flarePrg);
                context->setUniformMatrix("VIEW", view);
                context->setUniformMatrix("PROJECTION", proj);
                context->setUniformSampler("colorTex", flareTex);
                // call drawFlare with the light position, color, and flare size
                // drawFlare(context, light0position, light0color, 10);
                // vary the size of the flare based on the frame number
                drawFlare(context, light0position, light0color, (5+3*sin(frameCount*.020)));
                glDepthMask(GL_TRUE);
                glDisable(GL_BLEND);
                
                frameCount++;
            }while (context->update());
    
        catch(Exception& e)
        {
            printf("An exception was thrown:\n%s\n", e.getMessage().getCharString() );
        }
    }
    

  13. Rebuild and run the application. The flare now varies in size.

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