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]

Friday, 9 August 2013

The Interjection #1

Hello, Everyone!

   I know I promised to post source code of my first tech demo created with Graphics Framework - both the post and the demo are under construction right now ;-). Example program is ready for a very long time but I'm updating it to utilise the newest version of my library. And of course I'm adding last minute tweaks to the framework before the very first worldwide presentation.
   As I have mentioned in the previous post, I have removed all Create() functions from the so called resource classes (all classes in my framework that represent some of the 3D graphics concepts, like texture, vertex attribute stream, shader or depth buffer state, derive from abstract cResource class). Previously, constructors and Create() functions behaved identically so one could utilise the same variable more than once: if a variable is already constructed it could be easily given new state through mentioned Create() function not only the constructor and assignment operators which lead to unnecessary copies. But then I realised that this leads to redundancy in signatures of Create() functions and constructors (but only signatures, not implementations) and I have read more about RAII principle. Of course behaviour of Create() functions didn't violate the RAII principle - whole state of an object was cleared before new state was assigned, it wasn't possible to change it in any other way - but for me api provided by the resource classes wasn't coherent enough. And finally, I have found out that r-value references are the solution for my problem - I can avoid unnecessary copies and maintain simple api. So I have implemented move assignment operators to all resource classes.
   Earlier object construction could look like this:
// Constructor
cShader vs( eShaderType::VERTEX_SHADER, "sh.vp" );

// Create() function
cShader fs;
fs.Create( eShaderType::FRAGMENT_SHADER, "sh.fp" );
Right now the second example has to be replaced with this:
// Assignment of new object
fs = cShader( eShaderType::FRAGMENT_SHADER, "sh.fp" );
   There is another reason which caused a delay in posting my first demo's source code and screenshots: I'm preparing a training which I will be conducting during the Saturday and Sunday. The scope of this training is of course 3D graphics concepts. I came up with an idea that I could use my framework during the training. I want to build example programs that can show some specific 3D graphics features like different primitive types, texture filtering modes or lighting calculations. Source code of these programs would also be presented so I wanted it to be simple and easy to understand. Graphics Framework allows to quickly and with very few lines of code create new program, it hides 3D graphics api calls but is based on 3D graphics concepts. So I thought it would allow me to emphasize particular feature both in code and usage without the need to explain what some of the OpenGL or Direct3D functions are used for.
   On more thing. Previously I've written that I don't know if I want to make my project open source. Probably I will but right now I'm still not ready to. BUT I always wanted my library to be used freely for learning purposes. More - I hope it will find its way to universities where students could more easily create 3D applications using Graphics Framework.
   That's all for now. See You (probably) after the weekend.