Skip to content

Utility Classes

Nick McDonald edited this page Apr 14, 2021 · 14 revisions

TinyEngine - Utility Classes

Utility classes wrap boilerplate OpenGL behavior. The classes are extremely small, so it is worth reading through their code to understand what they do and how they can be modified for your specific needs. Otherwise, their default constructors are usually sufficient! These classes have a destructor included that deletes all the data inside, so you don't have to.

Texture

Wraps OpenGL textures. Lets you create blank textures of a size, load from images or generate from raw data.

  //Construction
  Texture tex;                                     //Empty OpenGL Texture
  Texture tex(1200, 800);                          //Size-Based (empty)
  Texture tex(image::load("filename.png"));        //Using Surface Data
  Texture tex(image::make(size, data, algorithm)); //Using raw data + algorithm (function pointer / lambda)

Texture contains a subclass cubetexture, which does the same stuff except as a cube map.

Shader

Wraps OpenGL shaders. Lets you define all input properties, and load files for the shader pipeline.

  //Construction
  Shader shader({"vertex.vs", "fragment.fs"}, {"all", "shader", "input", "properties"});
  Shader shader({"vertex.vs", "geometry.gs", "fragment.fs"}, {"all", "shader", "input", "properties"});

A shader can also be told to have a certain number of SSBOs by declaring their name in the constructor.

  Shader shader({"vertex.vs", "fragment.fs"}, {"prop"}, {"ssbo1", "ssbo2"});

  //Activation
  shader.use();
  
  //Uniform Setting (fully templated - automatic handling of type)
  shader.uniform(string name, int value);
  shader.uniform(string name, glm::vec3 vec);
  shader.uniform(string name, glm::mat4 mat);
  shader.texture("exampleTexture", tex.texture);

SSBO buffering is templated so it is easy to bind the buffer to the shader before rendering. Buffers are declared at construction, and updated at run time. Allows for dynamic arrays in the shader.

  //SSBO Buffering
  std::vector<glm::mat4> models;
  std::vector<glm::vec2> screen_positions;

  shader.buffer("ssbo1", models); //Access in shader with ssbo name "ssbo1"
  shader.buffer("ssbo2", screen_positions);      

  //Raw Update Data - Don't delete buffer (i.e. gl_bufferSubData)
  shader.buffer("ssbo1", models, true); //update = true
  
  //... etc

The TinyEngine shader loader extends GLSL to have basic #include directives. It does this by simply doing a recursive load whenever it finds the appropriate macro. The include directives, like in C++, assume a relative path to the position of the shader which is including it. Note that the #include directive only works if your file name does not have apostrophes, i.e.:

  //Wrong:
  #include "test.incl"

  //Correct:
  #include test.incl

Model

Wraps VAO and VBOs for rendering. Requires a custom construction function that manipulates the position, normal, color and index data.

  //Construction
  Model model(algorithm); //User-defined construction algorithm
  
  //Reconstruct model (and update the buffers)
  model.construct(algorithm);
  
  //Rendering
  model.render(GL_TRIANGLES); //lets you choose how to render

Algorithm is passed as a functional or lambda, that needs to capture the data that the model will be constructed from. Construction process is just pushing vertices into the corresponding vectors.

  //Example Model Construction
  std::function<void(Model* m)> algorithm = [&](Model* h){
     //basic triangle...
     h->positions.push_back(0.0);
     h->positions.push_back(0.0);
     h->positions.push_back(0.0);
     h->positions.push_back(0.5);
     h->positions.push_back(0.0);
     h->positions.push_back(1.0);

     h->indices.push_back(0);
     h->indices.push_back(1);
     h->indices.push_back(2);

     //add normal or color data...
     //...and so on
  }

You can define additional parameters is required inside the functional (e.g. for defining a meshing algorithm for your custom data-type).

  //...
  std::function<void(Model*, MyClass)> algorithm = [&](Model* h, MyClass m){
    //...
  }

  //Constructing a model from a custom class using algorithm
  MyClass m;
  Model model(algorithm, m);

See example programs for some examples on how models are constructed.

Models are derived from a base-class "Primitive", which has a few pre-made classes that contain the buffers for e.g. rendering billboards to screen, or sprites / particles in 3D space.

  Square2D board;  //2D vertex data (for e.g. drawing billboards)
  Square3D sprite; //3D vertex data (for e.g. drawing textures on a flat board in 3D space)
  Cube cube;       //Cube vertex data

These also have render methods.

Target

The target utility class wraps FBOs so that you can render directly for textures. It has two sub-classes "Billboard" and "Cubemap", that give easier construction of the render targets.

  //Construction
  Target target(1200, 800);
  Texture tex(1200, 800);
  target.bind(tex);

  //Easier Binding
  Billboard billboard(1200, 800);
  Cubemap cubemap(1200, 800);

Optionally, you can specify in the constructor whether you want to include a depth-buffer or ignore the color buffer (read target.cpp for more info).

  //Targeting
  billboard.target();
  billboard.target(glm::vec3 clearcolor);

When using the target base class, the bound texture is rendered to. When using the derived classes, they have a member "texture" and "depth" for the respective buffers.

To target the main window again, call:

  Tiny::view.target(glm::vec3 clearcolor);

Instance

This is a class that allows you to instance render any model or primitive with arbitrary data buffers. The instance is constructed with a pointer to the model which it will instance render.

  //Construction
  Instance instance(&model); //using a pointer to the model we want to instance-render

Data is added to the instance class using the addBuffer method. This prepares an instance buffer object and prepares it for passing to an instanced render.

  //Adding buffer data
  std::vector<glm::mat4> models; //for e.g. a particle system

  //Bind to instance
  instance.addBuffer(models);

Finally instance.render can be called with an optional drawing mode. If no argument is passed it assumes GL_TRIANGLE_STRIP which is intended for Square2D and Square3D primitives. For the Model class, just pass whatever drawing mode your VBOs are constructed for.

  //Instanced render!
  instance.render(GLenum drawing_mode);
Clone this wiki locally