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.

Tuesday, 30 July 2013

The Design

Hi again!

   Today I want to share some design principles which I tried to both fulfill and achieve during development of graphics framework. Some of them are more important, some less, others changed during last year or probably will change in the future. But in general this is something I'm trying to keep in mind when I sit down in front of my computer and continue to create the library.
   Here's a list (in no particular order):
  1. Graphics API independence - this the most important rule for me and the one from which everything started. I wanted to create a library that could be independent of the graphics api. Developer should be able to choose the api used for rendering and that's all - no other code specific neither for OpenGL nor for Direct3D should be required in the development process of the program linking to graphics framework.
    Probably I'm not that far in stated api independence as I would like to be (mainly because I don't have too much expirience in D3D) and I'm sure this will come with some sacrifices but I hope it is something I could gradually draw nearer to.
    And I should probably clarify one more thing - my library exposes set of classes which allows to easily develop 3D application but 3D graphics programming knowledge is required when using graphics framework. For example shaders aren't translated from one shading language to another and there's no abstraction layer provided for the shaders. So if one chooses to use OpenGL as the graphics api used for rendering but will provide shader code written in HLSL compilation errors will occur (meaning some objects won't initialize).
  2. Coherent, clean interface - all classes should expose similar set of functions, functions should follow the same naming conventions and the same rules when taking parameters etc. Graphics framework should be easy to use, all objects should be used in similar ways etc. It is quite obvious but when the number of classes gets bigger and bigger such things are not always easy to control. That's why I try to keep interface clean and I don't want to litter it with too many functions and members. To keep it that way I follow next 2 rules.
  3. RAII - "resource acquisition is initialization" is a quite popular design pattern. During development process I have tried different approaches when prototyping classes for my graphics framework. Sometimes it felt natural for a particular class to expose additional functions (i.e. SetShader() for effect class or SetTargetTexture() for render target class) but after testing and during further development it became clear that this approach has more flaws than advantages. Classes that had such functions were harder to maintain. I had to deal with some special cases. And I also had to add functions to other classes that were connected with the ones not following the RAII principle. So I removed all setter functions. Resources can be defined only through constructors or Create() functions (which behave identically to constructors). I'm also thinking about removing all Create() functions so the resources could be created only through constructors and assignment operators.
  4. PIMPL - "pointer to implementation" idiom is something I was considering from the beginning. I wanted to hide all member variables from library clients (to keep api clean) which also allows me to modify all operations that are going inside the class without modifying class's definition (which reduces compile time). And I wanted to learn how to use it properly because I didn't have the opportunity yet. And I still don't know if I want to fully release (meaning open) code of my creation.
  5. Not only graphics - as a framework (not only graphics api wrapper) library should also include some classes that will allow to create application window, manage system messages and drawing loop, measure time or read user input. I have added some helper classes for these tasks. Right now graphics framework supports windowed and fullscreen modes (with default, desktop resolution), reads input from keyboard and mouse (relative and absolute), and has classes which allow to track beginning and end of rendering a frame or changing the size of a window. There are also 2D, 3D and 4D vector and 3D and 4D matrix classes which implement typical linear algebra operations. Probably this list of helper classes or interfaces will evolve in the future.
  6. Windows - as a target platform. I know many of You will hate me for this and will stop reading my blog (if there is anyone) but I have almost no experience writing programs for Linux/Unix systems (not to mention creating and using libraries). I'm windows developer, I'm using Visual Studio, and right now I just don't have time to change that. BUT...
    Most of the classes don't have any OS specific code. I was using some Visual Studio specific C++ language extensions (like "abstract" keyword) but I'm trying to remove most of it. So I think it wouldn't be too hard to migrate it to another operating system. And speaking about C++...
  7. C++11 - I wanted to learn the newest version of a C++ standard. I had to dig into C++11 in my job so it was easier for me to also use it in my private project. Then in November 2012 Visual Studio compiler CTP was released which had better comliance with the standard. So I finally incorporated it into graphics framework. There are many advantages of using C++11 compared to the previous version that made working on graphics framework whole lot easier: delegating constructors, initializer lists, unique and shared smart pointers, variadic templates, scoped or strongly typed enums, r-value references, auto, range-based loops, virtual functions overrides and virtual functions final identifier, nullptr. I've listed only those I'm using in graphics framework library and the ones that are supported by Visual Studio. I think all of them were very usefull. There are even more usefull features like uniform initialization, member initialization or STL C++11 compliance which VS2012 doesn't support (that's why I'm thinking about changing VS2012 to 2013).
  8. Tech demos/small 3D apps - graphics framework is designed to ease the development of small, realtime 3D apps and technological demonstrations. Why?
    Some principles like RAII or graphics api indepence come with a cost. There has to be a tradeoff between flexibility, maintainability, performance and a usability. Not all features from D3D can be exposed if OpenGL doesn't support them and vice versa. If some feature is absent it can be for example simulated. But performance will suffer is such cases. There also has to be some abstraction layer that provides interface for using both graphics libraries. Performance once again will decrease.
    It is against my nature. I've always tried to improve performace. If I could or if I knew how I always wanted my software to work as fast as possible. But I also know my limitations. It is one my biggest projects. I've never used some of the design patterns or principles. So I know that performance won't probably be too high. Maybe it won't also be so bad - I didn't test it in a performance crucial applications. But tech demos feels like a natural fit, like a common usage for a graphics framework.
   What do You think about my list? What would You add, remove or change? (I need to encourage You to post comments ;-)) I know it is too early for You to have Your own thoughts about the project so next time I will post my first sample usage - full code and screenshots of my "Light Shafts" tech demo.
   Till next time. Bye!

Wednesday, 24 July 2013

Graphics Framework - The Beginning

   Hello.

    My name is Paul (or PaweÅ‚ in my language) and I thought I would write a blog about 3D graphics and a small project I'm developing. For a very long time now I was interested in 3D graphics. During my studies I learned OpenGL through nehe.gamedev.net and that's how one of my biggest "loves" started. I worked on different projects connected with 3D realtime graphics - both in my jobs and at home - and in the 2012 I thought I would create a library that would allow me to quickly create simple tech demos, interactive logos or other, small 3D applications. In the mid 2012 I started developing it. I didn't have any name for it so I called it just "Graphics Framework".
    Now, after over a year of development, I thought I could share it with the rest of the world. I don't have many examples of how to use my library but that's why I wanted to start a blog. If there will be someone who would like to read and see more of it, maybe I will have motivation to create new apps and improve my library.
    Something more about my creation - Graphics Framework is a C++ library designed to ease development of small, realtime 3D applications like interactive logos or tech demos. It wraps graphics api calls into set of simple to use classes (i.e. shader, render target, vertex attribute, texture) that are independent of the graphics api. Currently it only supports OpenGL 3.3 core profile (not whole of course). In the future it will support other versions of OpenGL (with some extensions) and, hopefully, different versions of Direct3D. Library also contains additional class, which allows to easily create and display application window, and a set of interfaces that help track size of a application window, begin and end of a frame rendering, mouse and keyboard input and passage of time.
    So it's not only a 3D api wrapper but it also includes other classes that help create small 3D apps in a relatively short time. And that's why I didn't call it "Graphics Library" but "Graphics Framework". Besides, there are many different graphics libraries out there so I didn't want to create one more.
One more thing - right now library is developed for Windows only. But most of the classes are system independent so it should be possible to port it to some other OS.
    Here is some example of a usage:
cWindow& window = cWindow::GetInstance();
if( !window.Create( "Shaiddir - Graphics Test",
                    eGraphicsDeviceType::OPENGL_3,
                    eWindowMode::WINDOWED,
                    eAntialiasingMode::OFF,
                    nullptr,
                    nullptr ) ) {
    return 0;
}

cImage image( "image.jpg" );
cTextureImage texture_image( eTextureType::IMAGE_2D, { &image } );
cSampler sampler( eTextureWrapMode::MIRROR, eTextureFilteringMode::LINEAR );
cTexture texture( &texture_image, &sampler );

cShader vs( eShaderType::VERTEX_SHADER, "shader.vp" );
cShader fs( eShaderType::FRAGMENT_SHADER, "shader.fp" );
cEffect effect( &vs, &fs );
if( !effect.Init() ) {
    window.Destroy();
    return 0;
}
effect.SetParameter( "uImage", texture );

cFullscreenQuad quad;
quad.Init();

window.Run( Draw );
And a drawing function could look like this:
void Draw( iGraphicsDevice* i_GraphicsDevice, iInputDevice* i_InputDevice, iTimer* i_Timer ) {
    i_GraphicsDevice->StartScene();
    effect.Use();
    quad.Draw();
    i_GraphicsDevice->EndScene();
}
And with this code image should be drawn covering whole application window (but I didn't test it ;-)). Of course You would need proper vertex and fragment shaders. But I hope this gives You an overview of an approach I have taken when I started creating this library.
    That's all for now. I hope I'll have time to write something new soon.
    Best regards.