1/5

1. Code library

This part introduce a library dedicated to your class that will be used for the rest of the exercises. This library provides a set of functionalities to ease 3D graphics programming such as

  • Structure for 3D vectors and matrices (and 2D, 4D).

  • Object structure ready to be displayed with OpenGL (Meshes, lines, etc) and their associated shaders.

  • 3D scene manipulation: Mouse controlled camera

The library tries to be as lightweight as possible. It is written to ease 3D graphics programming, but not to hide underlying working state. Although this is not required, you should be able to understand every part of the code. It keeps in particular the following principles

  • Minimalistic code written to be simple to understand (not necessarily the most generic nor efficient).

  • Objects are written to be lightweight and with minimal hidden states. - You can directly interact with object attributes.

  • The code and functionalities remain close to basic C++/OpenGL . - You can use direct OpenGL calls with the library. You should also keep in mind the way that OpenGL works to avoid unexpected display.

Download and run the library

  • When the program is launched, a camera model that can be manipulated with the mouse/keyboard is already implemented. It allows the following transformations

    • Rotation - left click + mouse displacement (trackball model)

    • Zoom - right click + up/down mouse displacement

    • Panning (/translation in the camera plane) - CTRL + left click + mouse displacement

    • Moving forward/backward (orthogonal to the camera plane) - CTRL + right click + up/down mouse displacement.

General structure of the code

Role of the different high level directories

  • scenes/ Contains the codes associated to the 3D scenes setup.

    • The exercises correspond to the code described in the scenes/animation/ directory. You will edit the code mainly in these directories.

    • The default scene can be found in the scenes/animation/00_default/ directory.

    • The file scenes/current_scene.hpp defines which scene (or exercise) should be compiled. Each scene is associated with a keyword. To compile another scene, just modify this keyword.

  • vcl/ Contains the source code of the VCL library: set of structures and functions to facilitate the implementation of 3D scenes.

  • third_party/** External libraries used by VCL such as glad (OpenGL loader), imgui (GUI), lodepng (PNG image loader).

  • main/ Describes the main calls of the program, the basic structure of a scene, initializes the external libraries, loads the data and launches the animation loop.

VCL Library

The source code of the VCL library is organized as follows

  • vcl/base/ basic library features such as assertions and error handling.

  • vcl/containers/ defines basic containers with features to facilitate the use of data buffers: e.g. buffer = extended version of std::vector, and buffer_stack = extended version of std::array.

  • vcl/math/ defines mathematical help functions and 2/3/4D vector and matrix structures.

  • vcl/opengl/ contains objects and functions that facilitate OpenGL calls.

  • vcl/shape/ corresponds to 3D objects such as meshes and curves. 3D shapes can be stored in main memory (RAM) (e.g. position buffer storage), or in GPU memory in their display version (_drawable) (stores only VBO/VAO indices and uniform parameters).

  • vcl/interaction/ contains the non-geometric objects and functions associated with the interaction with the 3D scene such as the camera or the timers.

  • vcl/wrapper/ structures and "wrapper" functions for the use of external libraries (glfw, imgui, lodepng).

Using the library

Vector and matrix

  • Observe the files in scenes/animation/00_default/default.h/cpp. Each exercise file has the following organization:

    • An object scene_model is defined and implement, at least, the functions setup_data and frame_draw. scene_model derives from scene_base which provides the signature (and empty body) of several functions expected by the main structure. In addition, each scene_model object can handle all internal data you need for the current exercise.

Application

  • Change the rotation (axis and angle) from some of the object within the scene in the function frame_draw.

    • Note that the structure vec3 implements a model of 3D vector with (x,y,z) coordinates.

    • The rotation is stored as a 3x3 matrix using the mat3 structure.

    • You can apply most of the basic operations between vec3 and mat3 using mathematical operators (+,-,*,/).

Implementation details in C++

The objects vec2, vec3, and vec4 share lots of common operations as they differ only by their size (similarly with mat2, mat3, mat4). To avoid duplicate large portion of the code in all of these files, a generic vector (resp. matrix) class has been implemented in vcl/core/math/vec/vec/vec.hpp (resp. vcl/core/math/mat/mat/mat.hpp). These generic classes are parametezied by their respective size as template parameters, and provide common functions implementations such as +/-/*, etc.

The specific implementation of vec2/3/4 (resp. mat2/3/4), are then implemented as template specialization from the generic class in order to only define specific structure and functions related to their size, while being compatible with the generically defined functions.

  • Note that there is two type of mesh structure handled

    • mesh storing buffer on data (per vertex: position, normal, uv, color, and triangle connectivity) on CPU

    • mesh_drawable storing VBOs associated to these buffer once sent on the GPU memory (in the sub-structure mesh_drawable_gpu_data) as well as its VAO. The structure also stores uniform parameters that are sent to the shader at every draw call. A default shader and texture id can also be stored with the structure. Note that a mesh_drawable can be automatically generated from a mesh structure.

Meshes

Adding a sphere

In this first part we display a new sphere to the scene.

  • Add the variable

vcl::mesh_drawable sphere;

as a class attribute (in the file default_animation.hpp)

  • Initialize this variable in the setup_data function

sphere = mesh_primitive_sphere(); // Create a default sphere model
sphere.shader = shaders["mesh"];  // Associate its default shader
  • Display this sphere in the frame_draw function with the following code

sphere.uniform.transform.scaling = 0.2f;
sphere.uniform.transform.translation = {-1,1,-2};
draw(sphere, scene.camera);
  • Observe that the sphere can be seen in the 3D scene.

    • Note: You can apply geometric transformation using the uniform properties of the mesh_drawable structure (reminder: uniform are parameters passed to the shaders).

    • Note also that the draw function will use by default the shader set attached to the structure.

  • Add the following line before calling draw on the sphere and observe that the color (as well as any other uniform parameter) can be changed through time in modifying its value at every frame.

sphere.uniform.color = vec3(1+std::cos(time), 1+std::sin(time), 2.0)/2.0f;

Adding a checkbox interface

We will now add a checkbox (button that can be checked) to activate/deactivate the wireframe display of the sphere.

  • Add the following line in the header of the class used to store a boolean state indicating when the wireframe should be displayed or not

bool is_wireframe = false;
  • In the function set_gui add a Checkbox (handled by ImGui library) and link it (through its adress) to the variable is_wireframe in adding this code

ImGui::Checkbox("Wireframe", &is_wireframe);
  • In running the code, the checkbox should appear. Every time you select/unselect it, the state of the variable is_wireframe change from true to false.

  • The last step consists in adapting the behavior of the display. Add the following code in the frame_draw function

if(is_wireframe)
    draw(sphere, scene.camera, shaders["wireframe"]);

Note: In this case, the draw is called with an explicit shader parameter and it instead of the one stored with its structure.

  • Check that you can now interactively display the wireframe.

Generating a scene with several elements

Add the following pieces of code

(in the class header)

std::vector<vcl::vec3> positions;
vcl::mesh_drawable cone;

(in the function setup_data)

cone = mesh_primitive_cone(0.1f,{0,0,0},{0,0.2f,0});
const int N_cone = 40;
positions.resize(N_cone);
for(int k=0; k<N_cone; ++k)
{
    float x = vcl::rand_interval(-2,2);
    float z = vcl::rand_interval(-2,2);

    positions[k] = {x,-1,z};
}

(in the function frame_draw)

const int N = positions.size();
for(int k=0; k<N; ++k)
{
    cone.uniform.transform.translation = positions[k];
    draw(cone, scene.camera, shaders["mesh"]);
}
  • Display the scene and observe the result (you should see a set of cones on the floor)

  • Note that

    • Only one cone is created and stored in memory

    • This single cone is displayed several times at different pre-stored positions

    • What would have happend if the loop initializing the variable positions was placed in the frame_draw function instead of setup_data ?

Exercise

  • Adapt the code to model the following scene

    • Note that trees should not intersect each others