Mach::GL Documentation

Mach GL is an OpenGL game engine that combines the ease of high-level programming with the control of low level access.

Current features:

  • High performance 2D & 3D renderers
  • Framebuffers
  • OBJ file loading
  • Texture mipmapping and anisotropic filtering
  • Random terrain generation
  • Keyboard & mouse input
  • GLSL shader loaders
  • HDR
  • Static environment maps for objects (from loaded cubemap)
  • Dynamic skyboxes
  • API Abstraction
  • Dynamic enviornment maps for objects
  • Scenes

Currently working on:

  • Particle system
  • Texture atlases
  • Documenting code
  • Post-processing effects (bloom effects etc.)
  • Scene file saving/loading (using YAML)
  • OpenAL support

Features I want to add soon:

  • Scene editor
  • Mesh simplifier
  • Normal maps
  • Shadows
  • Lua scripting
  • FBX file loading (for faster 3D object loading)
  • 3D physics using PhysX (including a non-nVidia alternative)
  • Vulkan support

If you have any questions or issues feel free to file an issue on this GitHub page or email me at: [email protected]

Installation

How to install:

  • For all platforms ensure you have git installed. See Guide

Windows (Support for VS 2019 and VS 2022):

  • Create a directory and navigate to it using: cd path_to_directory
  • Run the command (INCLUDE the dot at the end) git clone --recursive https://github.com/machgl/MachGL .
  • Within the working directory double-click on the windows_build_vs2019.bat or windows_build_vs2022.bat file (depending on the version of VS you are targetting). This will make a VS solution file which can be opened
  • Once VS is open, right click on the 'Sandbox' project and select 'Set Default Startup Project'
  • Build the solution

MacOS (Support for XCode only):

  • Create a directory and navigate to it using: cd path_to_directory
  • Run the command (INCLUDE the dot at the end) git clone --recursive https://github.com/machgl/MachGL .
  • Run the command ./macos_build_xcode.sh to build the xcode workspace
  • Open the generated XCode workspace file and navigate in the menu bar to Product -> Scheme and click on 'Sandbox'
  • Build the solution
Note (MacOS Only)

Once the solution has been built, the 'CoreAssets' folder in MachGL must be copied into the build directory (To be fixed).

Custom Projects

Please see the Sandbox project for OpenGL and Vulkan Sandbox for Vulkan projects. These are good bases to start from, however, if you want to create your own project, please duplicate the premake5.lua script into your custom project folder and edit the premake5.lua script in the root directory to include this custom project.

GitHub - Click Here Website - Click Here E-Mail - [email protected]

First Window

Create the Window

Include the Mach::GL header and set the namespace to MachGL (using MachGL namespace not necessary, but a convenience) N.B. All functions and objects related to Mach::GL are under the MachGL namespace.

#include "../MachGL/MachGL.h"

using namespace MachGL;

In the "main" function of your program, create an object of MACH_WINDOW. This is a shared pointer to a "Window" object and is API abstracted. Currently, only GLFW is supported, but the window created will be dependant on operating system and chosen graphics API in the future. Any window properties like MSAA(x), disableCursor() or fullscreen() have to be called before the init() function.

MACH_WINDOW window = Window::createWindow("Window Title", width, height);
window->MSAA(4);
window->init();

The main game loop should be defined in the "main" function of the program. This can be created by checking if the window is not closed using the window->closed() function. This loop should contain any functions that need to be updated every frame. For example, window->clear() to clear the screen and window->update() to swap the framebuffers (ie. render any changes from the within the function to the screen).

while(!window->closed()) {

    window->clear();
    window->update();
}

When the main game loop has exited, the window should be destroyed and closed using the window->close() function

Example code:

#include "../MachGL/MachGL.h"

using namespace MachGL;

int main() {

    uint32_t width = 1920;
    uint32_t height = 1080;

    //Create window, define MSAA settings (4x) and initilize window
    MACH_WINDOW window = Window::createWindow("Window Title", width, height);
    window->MSAA(4);
    window->init();

    //Start game loop which will run until window is closed
    while (!window->closed()) {

        //Clear the window
        window->clear();

        //Swap the framebuffers
        window->update();
    }

    //Close and destroy the window
    window->close();
    
    //End of program
    return 0;
}

Scenes

In Mach::GL, the best way to handle rendering objects and grouping these objects is with a scene. Scenes are created by creating a MACH_SCENE object - this object is located in the MachGL::Scene namespace.

A scene takes in the parameters of a Projection Matrix and a Camera object.

Scene::MACH_SCENE scene = Scene::Scene::createScene(projectionMatrix, camera);

Any objects that are to be rendered in the scene need to be pushed to the scene by the scene->pushObject(object) function. If there are many objects that share the same shader then it is recommended to use Groups instead to reduce the number of shader enable/disable functions to the graphics API.

Groups have to be pushed to the scene in a similar way as objects by calling the group->pushObject(object) function. However, objects that are in a group should not also be pushed to a scene separately.

Skyboxes can easily be added to a scene by calling the scene->setSkybox(skybox) function during the initialization of the scene.

Groups

Groups are a container for multiple objects that all share the same shader.

Scene::MACH_GROUP group = Scene::Group::createGroup(shader, projectionMatrix);

To be able to render objects, they have to be pushed into the group using the function group->pushObject(object).

All groups have to be pushed to scenes (see Scenes) to be able to render the objects in the group.

Plane

Planes are underlying functionality of the Simple Shapes. However, planes can still be used for more complex 2D applictions.

All 2D functions and objects are accessed using the MachGL::Plane namespace.

Planes are configred by creating a Plane::MACH_PLANE object and calling one of the 3 constructors:

Plane::MACH_PLANE plane1 = Plane::createPlane(planeProperties);
Plane::MACH_PLANE plane2 = Plane::createPlane(vertices, indices, uvs, planeProperties);
Plane::MACH_PLANE plane3 = Plane::createPlane(position, size, textureID);

Both the first and second constructor take in a Plane Properties object. This defines the position, size, color, shape, image and type. Eg:

Plane::PlaneProperties properties;
properties.position = float3(0, 0, 0);
properties.size = float2(100, 100);
properties.color = float4(1, 1, 1, 1);
properties.shape = Plane::PlaneShape::QUAD;
properties.image = nullptr; // nullptr can be defined when no texture is required. Otherwise set to a Graphics::MACH_IMAGE object
properties.type = Plane::PlaneType::STATIC;

When creating a PlaneProperties object, shape is set to PlaneShape::QUAD and type is set to PlaneType::STATIC by default.

The shape of a Plane can be set to PlaneShape::QUAD, PlaneShape::TRIANGLE or PlaneShape::CUSTOM. QUAD and TRIANGLE have pre-set up vertices, indices and UVs. Please note that for rendering a circle, it is recommended to use shape QUAD and remove the unwanted pixels in the fragment shader. For an easier way to create a circle, please see Simple Shapes.

A Plane can have 2 types PlaneType::STATIC and PlaneType::DYNAMIC, these indicate if the texure is static or dynamic. In constructor 3 above, the type is set to dynamic since the texture in is just a GLuint texture ID - this can be changed in runtime.

As with other object types in this engine, the create() function needs to be called to initilize the Plane.

Planes have a veriety of getters and setters:

getPosition() //float3
getSize() //float2
getTID() //TextureID - uint32_t
getColor() //float4
getType() //PlaneType
getShape() //PlaneShape
getVAO() //uint32_t
getVBO() //uint32_t
getIBO() //uint32_t
getVertices() //std::vector<float3>
getIndices() //std::vector<unsigned short>
getUVs() //std::vector<float2>

To render a plane, a Plane::MACH_PLANE can be submitted to a Graphics::MACH_RENDERER_2D object. This can be sent either one object at a time or in a std::vector of Plane::MACH_PLANE objects. See Renderer

Simple Shapes

There are 3 pre-defined Simple Shapes in Mach::GL. Rectangle, Circle and Triangle. These can be created using the following constructors:

Plane::SimpleRect rect(simpleProperties, windowDimension);
Plane::SimpleCircle rect(simpleProperties, windowDimension);
Plane::SimpleTriangle rect(simpleProperties, windowDimension);

Plane::SimpleProperties example


Plane::SimpleProperties properties;

properties.position = float2(20, 20); // Pixel space
properties.size = float2(300, 300);
properties.color = float4(1, 0, 1, 1);
properties.image = nullptr; //Can take in a Graphics::MACH_IMAGE and will override the color

All Simple Shapes are created equally. In their contructors, they all take in a Plane::SimpleProperties parameter. The SimepleProperties object defines the characteristics of the shape. As with all Mach::GL objects, any that take in a "properties" object will need to have the function create() called to initilize the object.

All SimpleShapes have a translate(trMatrix) function which takes in a matrix4x4 object. This allows for for runtime translations of the shape.

All Simple Shapes can be rendered calling the render() function on the object.

Skybox

A skybox can be created by using a MACH_SKYBOX object, the createSkybox() function takes in a MACH_IMAGE (see Image).

Object::MACH_SKYBOX skybox = Object::Skybox::createSkybox(skyboxTexture);

To render the skybox, the skybox->render(projectionMatrix, cameraViewMatrix) function needs to be called.

The skybox object should be rendered last, however if the skybox is included in a scene, then the render(...) function does not have to be called.

Objects

In Mach::GL, 3D models are stored in a Object::MACH_OBJECT object type. This type has multiple constructors:

    Object::MACH_OBJECT object = Object::Object::createObject(model, position, texture);

    Object::MACH_OBJECT object = Object::Object::createObject(model, position, texture, objectType);

    Object::MACH_OBJECT object = Object::Object::createObject(model, position, texture, texture2, objectType);

All three constructors take in a Object::MACH_MODEL, float3 and Graphics::MACH_IMAGE. See Model for more information on how to create an Object::MACH_MODEL. Two of these contructors take in an Object::ObjectType object which is an enum conistsing of three options:

ObjectType::MESH

ObjectType::TERRAIN

ObjectType::SKYBOX

Please note that ObjectType::SKYBOX is usually reserved for internal engine use, however, is left available for custom skybox objects in which the built-in Skybox is not suitable.

The ObjectType::TERRAIN object type is mainly reserved for use with the built in Object::Terrain model generation. The only effect this currently has is to make the object not included in the render distance calculations in the renderer, see Renderer.

The constructor does not acutally load the object into memory/buffers, to do this the object->create(objectProperties) function has to be called and before this object is pushed to a scene, group or renderer. This function takes in an Object::ObjectProperties type and allows the developer to set the following properties:

Object::ObjectProperties objectProperties;

objectProperties.shineDamper = 1.0f;
objectProperties.reflectivity = 0.0f;
objectProperties.textureScale = 1.0f;
objectProperties.scale = float3(1);
objectProperties.color = float4(1);

The values above are the preset values and if these values are wanted then there is no need to re-set them in the code. If all values wanted are default, then there is no need to pass in the Object::ObjectProperties into the create function, and just object->create() can be called.

Model

A model is a type that stores all of the vertex, index and UV information for a single model. This model can then be used to create a renderable MACH_OBJECT type. Since this is a 3D type, it is accessed from the Object namesapce.

A model can be created by using one of 3 constructors:

Object::MACH_MODEL model1 = Object::Model::createModel("model.obj");
Object::MACH_MODEL model2 = Object::Model::createModel(vertices);
Object::MACH_MODEL model3 = Object::Model::createModel(vertices, normals, uvs, indices);

For the first constructor that takes in an std::string to link to a .OBJ file, this pulls all of the model information from the file. If the model has UVs, then the function hasTexture() will return true, otherwise it will return false.

Models also have a variety of getters and setters:

getVertices() //std::vector<float3>
getIndices() //std::vector<unsigned int>
getNormals() //std::vector<float3>
getUVs() //std::vector<float2>
hasTexture() //bool
getVertexSize() //size_t - returns how many vertices are within the model
getIndexSize() //size_t - returns how many indices are within the model

Terrain

Mach::GL has a built in terrain generator. This is based on Perlin Noise and can take in many of the same parameters as a standard Perlin Noise generator.

A terrain object can be created by using one of the following constructors:

Object::Terrain terrain1(size, vertexCount);
Object::Terrain terrain2(size, vertexCount, amplitude, octaves, roughness, seed);

Notice that the second constructor has a seed option, this allows for control over the randomness of the terrain or allows the terrain to generated the same if a fixed value is entered. If the first constructor is used (without seed), the seed is randomly generated.

To use the generated terrain, call the function terrain1.getModel() this return an Object::MACH_MODEL type that can be used directly in a Object::MACH_OBJECT.

The texture used on the Object can be scaled using the properties.textureScale property in the Object::ObjectProperties type linked with the object the terrain is being used in.

Please note that setting the ObjectType to TERRAIN will not allow terrain objects to be culled in the renderer.

Example code

#include "../MachGL/MachGL.h"

using namespace MachGL;

int main() {

    uint32_t width = 1920;
    uint32_t height = 1080;

    MACH_WINDOW window = Window::createWindow("Window Title", width, height);
    window->MSAA(4);
    window->init();

    float3 terrainPos(0, 0, 0);

    Graphics::MACH_RENDERER_3D renderer = Graphics::Renderer3D::createRenderer();
    Graphics::MACH_IMAGE terrainTexture = Graphics::Image::createImage("terrain.png", Graphics::ImageType::RGB);
    Object::Terrain terrain(800, 50, 2.5f, 1, 1.5f, 1);
    
    //Create the Object using the Terrain model and setting the ObjectType to TERRAIN 
    Object:MACH_OBJECT terrainObject = Object::Object::createObject(terrain.getModel(), terrainPos, terrainTexture, Object::ObjectType::TERRAIN);

    //Setting the properties for the terrain object
    //Scaling the texture by a factor of 10 (this makes it 10x smaller)
    Object::ObjectProperties terrainProperties;
    terrainProperties.textureScale = 10;

    terrainObject->create(terrainProperties);

    while (!window->closed()) {
        window->clear();

        renderer->submit(terrainObject);

        window->update();
    }

    window->close();
    
    return 0;
}

Images

Images can be loaded into Mach::GL by creating a MACH_IMAGE object.

Graphics::MACH_IMAGE image = Graphics::Image::createImage("path.jpg", Graphics::ImageType::RGB);

When creating an image, the image type has to be defined. There are two types of images in Mach::GL:

Graphics::ImageType::RGB

Graphics::ImageType::RGBA

By default, mipmapping is enabled when creating an image. However, to disable this create the image with the createImage(...) function varient: Graphics::Image::createImage("path.jpg", Graphics::ImageType::RGBA, false). Where the third parameter is a boolean that toggles mipmapping.

When creating a Skybox, a Graphics::MACH_IMAGE is required. However, a standard image will not work. For skyboxes, a cubemap image must be generated. This can be done by calling the constructor which takes in a list of 6 strings - this has to be a list of 6 or 12 strings. Any other number will cause a fail:

std::vector<std::string> filenames = {

    right.jpg,
    left.jpg,
    top.jpg,
    bottom.jpg,
    front.jpg,
    back.jpg
};

Graphics::MACH_IMAGE skyboxImage = Graphics::Image::createImage(filenames);

Renderers

Mach::GL has 2 renderers: Renderer2D and Renderer3D. Both renderers are created the same as below:

Graphics::MACH_RENDERER_2D renderer2D = Graphics::Renderer2D::createRenderer();
Graphics::MACH_RENDERER_3D renderer3D = Graphics::Renderer3D::createRenderer();

Mach::GL renderers needs Objects or Planes submitted to it, once they are submitted they will be instantly rendered. Objects and Planes can be submitted individually or by submitting a list with: std:vector<Plane::MACH_PLANE> or std::vector<Object::MACH_OBJECT>.

However, the 3D renderer has more functions that is tailored to 3D applications:

sumbit(object, camera, renderDistance);
sumbit(objectList, camera, renderDistance);

These functions allow for objects to be culled from the render queue if the object is further away from the camera than the value specified in renderDistance.

Shaders

Mach::GL uses a proprietary file for shaders that takes in all valid types of shaders and combines them into one file that needs to be read. This file is a plain text file with the extension of .mglsdr. This standard is recommended, however, a vertex shader and fragment shader can be loaded in separately. With this, there are 2 constructors available for a Graphics::MACH_SHADER object.

Graphics::MACH_SHADER shader1 = Graphics::Shader::createShader("shader.mglsdr");
Graphics::MACH_SHADER shader2 = Graphics::Shader::createShader("shader.vert", "shader.frag");

There are many functions available to send information (uniforms) to shaders:

FunctionTypeSize (bytes)
setUniform1i(name, value)int4
setUniform1iv(name, value, count)int (array)4 * count
setUniform1f(name, value)float4
setUniform1fv(name, value, count)float (array)4 * count
setUniform2f(name, value)float28
setUniform2fv(name, value, count)float2 (array)8 * count
setUniform3f(name, value)float312
setUniform3fv(name, value, count)float3 (array)12 * count
setUniform4f(name, value)float416
setUniform1fv(name, value, count)float4 (array)16 * count
setUniformMatrix4fv(name, matrix)matrix4x464

The mglsdr Format

When using the mglsdr format, each shader needs to have a header from the list below:

%vertex_shader
%fragment_shader 

Each header starts with a % sign. This indicates the start of a shader type and can be thought of as two separate files. This means that as with standard shaders, variables created in one shader are not automatically available in another shader and need to be passed through using 'out' and 'in'. The shader version also needs to be declared in both shaders.

Example Shader

%vertex_shader

#version 450 core

layout (location = 0) in vec4 position;

out vec4 pos;

uniform mat4 trMatrix;

void main() {

    gl_Position = position * trMatrix;
    pos = position;
}

%fragment_shader

#version 450 core

layout (location = 0) out vec4 color;

in vec4 pos;

void main() {

    color = pos;
}

SPIR-V

Loading and Playing Audio

Audio files are loaded by creating an AudioFile object. This uses the following constructor:

Audio::AudioFile audioFile("sound.ogg");

Mach::GL currently supports the loading of .OGG and .MP3 files. Loading any other filetype will resulst in a error being thrown.

Then, a SoundSource object needs to be created using the following constructor:

Audio::MACH_SOUND_SOURCE source = Audio::SoundSource::createSoundSource(sourceProperties);

This constructor takes in a SoundSourceProperties object. This holds configurable properties about the SoundSource object. The properties consist of:

PropertyTypeDefaultsDescription
Pitchfloat1.0fSets the pitch of the source
Gainfloat1.0fSets the gain of the source
Positionfloat3(0,0,0)Position for 3D audio
Velocityfloat3(0,0,0)Velocity for moving 3D audio
LoopboolfalseDetermines if the track will loop or play once

A SoundBuffer object also has to be created, this can be thought of the queue of audio tracks to be played. If multiple tracks are to be played simultainiously, multiple buffers need to be created.

Audio::MACH_SOUND_BUFFER soundBuffer = Audio::SoundBuffer::createSoundBuffer();

AudioFile objects can be added to the queue by calling the function:

soundBuffer->addSoundEffect(audioFile);

To then play the audio file, call the function:

soundSource->play(audioFile.getBufferID());

Example Code:

#include "../MachGL/MachGL.h"

using namespace MachGL;

int main() {

    uint32_t width = 1920;
    uint32_t height = 1080;

    MACH_WINDOW window = Window::createWindow("Window Title", width, height);
    window->MSAA(4);
    window->init();

    //Loads the audio file, creates the sound source and buffer
    Audio::AudioFile audioFile("sound.ogg");
    Audio::SoundSourceProperties soundSourceProperties = {};
    Audio::MACH_SOUND_BUFFER soundBuffer = Audio::SoundBuffer::createSoundBuffer();
    Audio::MACH_SOUND_SOURCE soundSource = Audio::SoundSource::createSoundSource(soundSourceProperties);

    //Adds the audio file to the buffer and then plays the sound source using the bufferID
    soundBuffer->addSoundSource(audioFile);
    soundSource->play(audioFile.getBufferID());

    while (!window->closed()) {

        window->clear();
        window->update();
    }

    window->close();
 
    return 0;
}

Maths

Mach::GL includes the entire glm maths library. However some types and functions have been renamed/simplified.

Renamed Types

float2 = glm::vec2

float3 = glm::vec3

float4 = glm::vec4

matrix4x4 = glm::mat4

These renamed types have access to all glm functions, operator overrides and can be used interchangably with the glm type names.

Projection Matrices

There are 2 different projection matrices that have been implemented from glm specifically for Mach::GL, perspective and orthographic. Both have a normal and simplified version available.

Maths::perspective(fov, windowDimension, near, far);

Maths::simplePerspective(fov, windowDimension);

Maths::orthographic(left, right, top, bottom, near, far);

Maths::simpleOrthographic(windowDimension);

Please note that these functions take in a WindowDimension object which can be retrieved from a Window object.