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::Buffer _buffer;
        GL::Mesh _mesh;
        Shaders::VertexColor2D _shader;
};

In the constructor we pass all necessary arguments to the application class.

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 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. The data won't be changed or read back into main memory during application lifetime, so we mark them with appropriate GL::BufferUsage.

    _buffer.setData(data, GL::BufferUsage::StaticDraw);

Now we configure the mesh — we specify GL::MeshPrimitive and vertex count, add vertex buffer and specify 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. Position is implicitly three-component float vector, colors are implicitly also floats, but because it's not immediately clear whether we want to use RGB or RGBA colors, the shader attribute requires to say how many components our colors have. Note that it's also possible to override attribute type and many other things — see the GL::Attribute class for details.

    _mesh.setPrimitive(GL::MeshPrimitive::Triangles)
        .setCount(3)
        .addVertexBuffer(_buffer, 0,
            Shaders::VertexColor2D::Position{},
            Shaders::VertexColor2D::Color{Shaders::VertexColor2D::Color::Components::Three});
}

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.