Examples » Your first triangle

Basic rendering with builtin shaders.

Image

The Hello World of 3D graphics, rendering a single colored triangle using OpenGL.

For this example we will use SDL2, which is the most widely used multiplatform windowing toolkit. Support for it is in the Platform::Sdl2Application class. We also need GL::Mesh for encapsulating our triangle, GL::Buffer to store vertex data, Shaders::VertexColor2D shader which will take care of rendering and an ability to control the GL::DefaultFramebuffer.

#include <Magnum/GL/Buffer.h>
#include <Magnum/GL/DefaultFramebuffer.h>
#include <Magnum/GL/Mesh.h>
#include <Magnum/Platform/Sdl2Application.h>
#include <Magnum/Shaders/VertexColor.h>

We subclass the application and next to the constructor we implement the one single required function which is needed for rendering into the window. You can read more about platform support and various portability tricks in Platform support.

class TriangleExample: public Platform::Application {
    public:
        explicit TriangleExample(const Arguments& arguments);

    private:
        void drawEvent() override;

        GL::Mesh _mesh;
        Shaders::VertexColor2D _shader;
};

In the constructor we pass arguments to the application class. The Configuration configuration is optional and allows us to set a window title and other things.

TriangleExample::TriangleExample(const Arguments& arguments):
    Platform::Application{arguments, Configuration{}.setTitle("Magnum Triangle Example")}
{

Now we specify vertex attributes, consisting of positions and colors. For performance reasons it is common to interleave them, so data for each vertex are in one continuous place in memory. In this case, because we need just three vertices, we will interleave them manually in-place. In the next tutorial we will learn how to interleave them programatically. See Type system for more information about scalar and vector types used in Magnum — one of the notable convenience features is an ability to use a custom literal to specify hexadecimal colors, just like you are used to from CSS and various graphics editors.

    using namespace Math::Literals;

    struct TriangleVertex {
        Vector2 position;
        Color3 color;
    };
    const TriangleVertex data[]{
        {{-0.5f, -0.5f}, 0xff0000_rgbf},    /* Left vertex, red color */
        {{ 0.5f, -0.5f}, 0x00ff00_rgbf},    /* Right vertex, green color */
        {{ 0.0f,  0.5f}, 0x0000ff_rgbf}     /* Top vertex, blue color */
    };

We then create vertex buffer and fill it with data.

    GL::Buffer buffer;
    buffer.setData(data);

Now we configure the mesh. The mesh is GL::MeshPrimitive::Triangles by default, so we need to specify just vertex count and add the vertex buffer with specified attribute locations for use with the shader. We need to describe physical location of vertex attributes in the buffer — zero offset from the beginning, first the position, then the color; with no gaps in between. Because we use a 2D shader, position is implicitly a two-component float vector and we use three-component RGB colors (we could also say Shaders::VertexColor::Color4 for RGBA).

Here we use std::move() instead of just passing it by reference to make the mesh an owner of the buffer. In this case it simplifies things for us, otherwise we would need to ensure buffer exists for the whole mesh lifetime. On the other hand, we lose the ability to modify that buffer afterwards.

    _mesh.setCount(3)
         .addVertexBuffer(std::move(buffer), 0,
            Shaders::VertexColor2D::Position{},
            Shaders::VertexColor2D::Color3{});
}

The drawEvent() function will take care of rendering the scene. We will clear color buffer of the default framebuffer (which is also the default rendering target) and then we draw the mesh using our shader. The context is double-buffered, so we need to swap the buffers after drawing.

void TriangleExample::drawEvent() {
    GL::defaultFramebuffer.clear(GL::FramebufferClear::Color);

    _mesh.draw(_shader);

    swapBuffers();
}

Lastly, we need a main() function. The MAGNUM_APPLICATION_MAIN() macro will handle that conveniently for us, silently covering platform differences in the background:

MAGNUM_APPLICATION_MAIN(TriangleExample)

That's all, now we can compile the whole example using CMake. First we require the Magnum package with GL, Shaders and Sdl2Application components. It's recommended to use also Corrade's set of compiler flags to enable additional warnings. Then we create our executable and link to all Magnum libraries we requested. See Usage with CMake for more information.

find_package(Magnum REQUIRED GL Shaders Sdl2Application)

set_directory_properties(PROPERTIES CORRADE_USE_PEDANTIC_FLAGS ON)

add_executable(magnum-triangle TriangleExample.cpp)
target_link_libraries(magnum-triangle PRIVATE
    Magnum::Application
    Magnum::GL
    Magnum::Magnum
    Magnum::Shaders)

You can now try changing vertex count, positions or colors to see how the shader behaves. The full file content is linked below. Full source code is also available in the magnum-examples GitHub repository.

The ports branch contains additional patches for iOS, Android and Emscripten support that aren't present in master in order to keep the example code as simple as possible.

There's also an alternative version of this example that sidesteps the Platform::Application wrapper classes to show that it's possible to hook into OpenGL context and window surface provided by a third party library. See Triangle using plain GLFW for details.