Sunday, 18 August 2013

The Demo #1 - Light Shafts

    Hi!

    Finally I'm ready to share my very first example of Graphics Framework usage. It is also the first demo I have created with my library - The Light Shafts!



    I wanted to create my own light shafts effect for a very long time. There are a few different techniques allowing to achieve this effect (as with any other graphical effect that can be seen in games) of which most commonly used involves placing static, blended geometry. But this technique requires geometry to prepared for a given scene. And is, of course, static. My favourite light shaft technique is the one simulating light glowing through clouds (sometimes called god rays) or through trees in misty air - in both rays change depending on the observer's point of view.
    I've been wondering how to create this effect for some time and finally I've encountered some great examples and explanations, like the one in GPU Gems 3 or in this WebGL blog. So I wanted to create similar effect with Graphics Framework. When I started working on the demo, development of my library was in early stage. I didn't have any 3D models, which I could use to create nice environment, and I didn't have 3D model loading classes (I still don't have any of those ;-)) so I had to simplify the demo a little. I thought I would create a 2D demo with Moon rising from behind the trees in the dark night. Technique I used in the demo is exactly the same as in many other games and examples: it is created as a postprocess phase, so the effect itself is really 2D. There can be many variations of shaders involved in the rendering process, of course, like blurring algorithms used but that's just tweaking the final impression.
    So here it is: source code of my very first working example created with Graphics Framework.

    Global variables:
cMesh           wood_mesh;
cEffect         wood_effect;
cTexture        wood_texture;

cMesh           moon_mesh;
cEffect         moon_effect;
cTexture        moon_texture;

cTexture        shaft_texture1;
cTexture        shaft_texture2;
cRenderTarget   shaft_rt1;
cRenderTarget   shaft_rt2;
cEffect         shaft_effect;

cTexture        scene_texture;
cRenderTarget   scene_rt;
cEffect         scene_effect;

cFullscreenQuad quad;

float           x = 0.0f;
float           y = 0.0f;

cWindow& window = cWindow::GetInstance();

    WinMain(...) function:
window.Create( "Light Shafts",
               eGraphicsDeviceType::OPENGL_3,
               eWindowMode::FULLSCREEN,
               false );

///// Wood
size_t wood_indices_data[] = { 0, 1, 2, 3 };
float wood_vertices_data[] = { -1.0f,  0.5f, 0.0f,
                               -1.0f, -1.0f, 0.0f,
                                1.0f,  0.5f, 0.0f,
                                1.0f, -1.0f, 0.0f };
float wood_texcoords0_data[] = { 0.0f, 1.0f,
                                 0.0f, 0.0f,
                                 1.0f, 1.0f,
                                 1.0f, 0.0f };

cVertexAttribute wood_positions( eVertexAttributeType::POSITION,
                                 3, 4, wood_vertices_data );
cVertexAttribute wood_texcoord0( eVertexAttributeType::TEXCOORD0,
                                 2, 4, wood_texcoords0_data );
cVertexIndices wood_indices( ePrimitiveTopologyType::TRIANGLE_STRIP,
                             4, wood_indices_data );
wood_mesh = cMesh( &wood_indices, { &wood_positions, &wood_texcoord0 } );
if( !wood_mesh.Init() )
    return 0;

cShader wood_vertex_shader( eShaderType::VERTEX_SHADER, "Data/wood.vp" );
cShader wood_fragment_shader( eShaderType::FRAGMENT_SHADER, "Data/wood.fp" );
wood_effect = cEffect( &wood_vertex_shader, &wood_fragment_shader );
if( !wood_effect.Init() )
    return 0;

cImage wood_image( "Data/wood.png" );
cSampler wood_sampler( eTextureWrapMode::CLAMP, eTextureFilteringMode::LINEAR );
cTextureImage wood_texture_image( eTextureType::IMAGE_2D, {&wood_image} );
wood_texture = cTexture( &wood_texture_image, &wood_sampler );
if( !wood_texture.Init() )
    return 0;

wood_effect.SetParameter( "uTexture", wood_texture );

///// Moon
size_t moon_indices_data[] = { 0, 1, 2, 3 };
float moon_vertices_data[] = { -100.0f,  100.0f, 1.0f,
                               -100.0f, -100.0f, 1.0f,
                                100.0f,  100.0f, 1.0f,
                                100.0f, -100.0f, 1.0f };
float moon_texcoords0_data[] = { 0.0f, 1.0f,
                                 0.0f, 0.0f,
                                 1.0f, 1.0f,
                                 1.0f, 0.0f };

cVertexAttribute moon_positions( eVertexAttributeType::POSITION,
                                 3, 4, moon_vertices_data );
cVertexAttribute moon_texcoord0( eVertexAttributeType::TEXCOORD0,
                                 2, 4, moon_texcoords0_data );
cVertexIndices moon_indices( ePrimitiveTopologyType::TRIANGLE_STRIP,
                             4, moon_indices_data );
moon_mesh = cMesh( &moon_indices, { &moon_positions, &moon_texcoord0 } );
if( !moon_mesh.Init() )
    return 0;

cShader moon_vertex_shader( eShaderType::VERTEX_SHADER, "Data/moon.vp" );
cShader moon_fragment_shader( eShaderType::FRAGMENT_SHADER, "Data/moon.fp" );
moon_effect = cEffect( &moon_vertex_shader, &moon_fragment_shader );
if( !moon_effect.Init() )
    return 0;

cImage moon_image( "Data/moon.png" );
cSampler moon_sampler( eTextureWrapMode::CLAMP, eTextureFilteringMode::LINEAR );
cTextureImage moon_texture_image( eTextureType::IMAGE_2D, { &moon_image } );
moon_texture = cTexture( &moon_texture_image, &moon_sampler );
if( !moon_texture.Init() )
    return 0;

///// Render Targets
cTextureImage shaft_texture_image1( eTextureType::IMAGE_2D, eImageFormat::COLORS_4, eImageChannelDataType::BYTE );
shaft_texture1 = cTexture( &shaft_texture_image1, &wood_sampler );
shaft_rt1 = cRenderTarget( { &shaft_texture1 } );
if( !shaft_rt1.Init() )
    return 0;

cTextureImage shaft_texture_image2( eTextureType::IMAGE_2D, eImageFormat::COLORS_4, eImageChannelDataType::BYTE );
shaft_texture2 = cTexture( &shaft_texture_image2, &wood_sampler );
shaft_rt2 = cRenderTarget( { &shaft_texture2 } );
if( !shaft_rt2.Init() )
    return 0;

cTextureImage scene_texture_image( eTextureType::IMAGE_2D, eImageFormat::COLORS_4, eImageChannelDataType::BYTE );
scene_texture = cTexture( &scene_texture_image, &wood_sampler );
scene_rt = cRenderTarget( { &scene_texture } );
if( !scene_rt.Init() )
    return 0;

// Effects
cShader scene_vs( eShaderType::VERTEX_SHADER, "Data/quad.vp" );
cShader scene_fs( eShaderType::FRAGMENT_SHADER, "Data/quad.fp" );
scene_effect = cEffect( &scene_vs, &scene_fs );
if( !scene_effect.Init() )
    return 0;

cShader shaft_vs( eShaderType::VERTEX_SHADER, "Data/shaft.vp" );
cShader shaft_fs( eShaderType::FRAGMENT_SHADER, "Data/shaft.fp" );
shaft_effect = cEffect( &shaft_vs, &shaft_fs );
if( !shaft_effect.Init() )
    return 0;

if( !quad.Init() )
    return 0;

moon_effect.SetParameter( "uTexture", moon_texture );
scene_effect.SetParameter( "uScene", scene_texture );
shaft_effect.SetParameter( "uShafts", shaft_texture1 );
scene_effect.SetParameter( "uShafts", shaft_texture2 );

window.Run( Draw );
window.Destroy();

return 0;

    Drawing function:
void Draw( iGraphicsDevice* i_GraphicsDevice,
           iInputDevice*    i_InputDevice,
           iTimer*          i_Timer ) {

const sKeyboardInputState* keys = i_InputDevice->GetKeyboardState();
const sMouseInputState* mouse = i_InputDevice->GetMouseState();

if( keys->UP.IsDown() ) {
    y += 5.0f * i_Timer->GetDeltaTime();
}
if( keys->DOWN.IsDown() ) {
    y -= 5.0f * i_Timer->GetDeltaTime();
}
if( keys->LEFT.IsDown() ) {
    x -= 5.0f * i_Timer->GetDeltaTime();
}
if( keys->RIGHT.IsDown() ) {
    x += 5.0f * i_Timer->GetDeltaTime();
}
x += mouse->Position.Delta.X*0.1f;
y += mouse->Position.Delta.Y*0.1f;

i_GraphicsDevice->StartScene();

i_GraphicsDevice->SetBackgroundColor( 0.05f, 0.05f, 0.1f, 1.0f );
scene_rt.Use();
scene_rt.Clear();

moon_effect.Use();
moon_effect.SetParameter( "MVP",
                          i_GraphicsDevice->CreateOrthographicProjectionMatrix( 0.0f, (float)window.GetWidth(),
                                                                                0.0f, (float)window.GetHeight(),
                                                                                1.0f, -1.0f ) *
                          i_GraphicsDevice->CreateTranslationMatrix( (float)mouse->Position.Window.X,
                                                                     (float)mouse->Position.Window.Y,
                                                                     0.0f ) );
moon_mesh.Draw();
wood_effect.Use();
wood_mesh.Draw();

shaft_rt1.Use();
i_GraphicsDevice->SetBackgroundColor( 0.0f, 0.0f, 0.0f, 1.0f );
shaft_rt1.Clear();

moon_effect.Use();
moon_mesh.Draw();
wood_effect.Use();
wood_mesh.Draw();

shaft_rt2.Use();
shaft_effect.Use();

for( size_t i = 0; i<5; ++i ) {
    cVector4 vec = cVector4( (float)Position.Window.X / (float)window.GetWidth(),
                             (float)mouse->Position.Window.Y / (float)window.GetHeight(),
                             0.0f,
                             1.0f );
    shaft_effect.SetParameter( "uPosition", vec );
    quad.Draw();
}
if( const cRenderTarget* rt = cRenderTarget::GetActiveResource() ) {
    rt->StopUsing();
}

scene_effect.Use();
scene_effect.SetParameter( "uScene", scene_texture );
quad.Draw();

i_GraphicsDevice->EndScene();
}

    Shaders (written in GLSL):

    Wood.vp, Quad.vp, Shaft.vp:
#version 150
precision highp float;

in vec4 inPosition;
in vec2 inTexcoord0;

out vec2 Texcoord0;

void main() {
    gl_Position = inPosition;
    Texcoord0 = inTexcoord0;
}

    Wood.fp:
#version 150
precision highp float;

in vec2 Texcoord0;
out vec4 oColor;

uniform sampler2D uTexture;

void main() {
    oColor = texture( uTexture, Texcoord0 ).rrra;
    if( oColor.a < 0.5 ) {
        discard;
    }
}

    Moon.vp:
#version 150
precision highp float;

uniform mat4 MVP;

in vec4 inPosition;
in vec2 inTexcoord0;

out vec2 Texcoord0;

void main() {
    gl_Position = MVP * inPosition;
    Texcoord0 = inTexcoord0;
}

    Moon.fp:
#version 150
precision highp float;

in vec2 Texcoord0;
out vec4 oColor;

uniform sampler2D uTexture;

void main() {
    oColor = texture( uTexture, Texcoord0 );
    if( oColor.a < 0.1 ) {
        discard;
    }
}

    Quad.fp:
#version 150
precision highp float;

in vec2 Texcoord0;
out vec4 oColor;

uniform sampler2D uScene;
uniform sampler2D uShafts;

void main() {
    oColor = texture( uScene, Texcoord0 );
    oColor += texture( uShafts, Texcoord0 ) * 0.5;
}

    Shaft.fp:
#version 150
precision highp float;
 
in vec2 Texcoord0;
out vec4 oColor;
 
uniform sampler2D uShafts;
uniform vec2 uPosition;
const float scale = 1.5;
 
void main() {
    vec2 offset = (uPosition - Texcoord0) * 0.3;
 
    oColor = texture( uShafts, Texcoord0 ) * 0.8;
    vec4 color;
    float sum_weights = 0.8;
    float weight;
    for( int i=1; i<8; ++i ) {
        weight = (8-i)*0.1;
        sum_weights += weight;
        color = texture( uShafts, Texcoord0 + offset*i*0.1*scale ) * weight;
        oColor += color;
    }
    oColor /= sum_weights;
}


    That's all - about 200 lines of code (including #includes ;-)) and a few shader files. This example has some flaws (i.e. Moon is always the same size in pixels - so if the resolution of the application window is changed, the size of the Moon will also change; and wood always covers the same portion of screen so its proportions will also change) but I wanted the source code to be as simple as possible.
    I know it is hard to analyse someone else's code, especially when one just reads it on the internet without the access to original project. That's why in the future I will share my library along with header files so anyone will be able to try it and give his or her feedback.
     [Update] I have found a better way to post source code examples... ;-) [Update]

No comments:

Post a Comment