Example: opengl_custom_shaders_demo

This example demonstrates how to install a custom OpenGL shader replacing one of MRPT default ones.

In particular, the fragment shader is modified such that depth (raw depth, in opengl internal logarithmic scale) with respect to the eye is visualized as grayscale levels.

opengl_custom_shaders_demo screenshot opengl_custom_shaders_demo screenshot opengl_custom_shaders_demo screenshot opengl_custom_shaders_demo screenshot

C++ example source code:

/* +------------------------------------------------------------------------+
   |                     Mobile Robot Programming Toolkit (MRPT)            |
   |                          https://www.mrpt.org/                         |
   |                                                                        |
   | Copyright (c) 2005-2024, Individual contributors, see AUTHORS file     |
   | See: https://www.mrpt.org/Authors - All rights reserved.               |
   | Released under BSD License. See: https://www.mrpt.org/License          |
   +------------------------------------------------------------------------+ */

#include <mrpt/gui/CDisplayWindow3D.h>
#include <mrpt/maps/COctoMap.h>
#include <mrpt/obs/stock_observations.h>
#include <mrpt/opengl.h>
#include <mrpt/opengl/CPlanarLaserScan.h>
#include <mrpt/opengl/DefaultShaders.h>
#include <mrpt/random.h>
#include <mrpt/system/filesystem.h>

#include <chrono>
#include <iostream>
#include <thread>

// We need OpenGL headers for "GL_VERTEX_SHADER","GL_FRAGMENT_SHADER"
#include <mrpt/config.h>
#if MRPT_HAS_OPENGL_GLUT
#include <mrpt/opengl/opengl_api.h>
#endif

// Demo of how to install a custom shader program:
static void installCustomShader(mrpt::opengl::Scene& scene)
{
#if MRPT_HAS_OPENGL_GLUT

    // Define shader program and properties
    // ------------------------------------------
    const char* vertex_shader = nullptr;
    const char* fragment_shader = nullptr;
    std::vector<std::string> attribs, uniforms;

    // For this example, use the standard vertex shader,...
    vertex_shader =
        R"XXX(
#version 330 core

// VERTEX SHADER: Default shader for MRPT CRenderizable objects
// Jose Luis Blanco Claraco (C) 2019-2020
// Part of the MRPT project

layout(location = 0) in vec3 position;
layout(location = 1) in vec4 vertexColor;
layout(location = 2) in vec3 vertexNormal;

uniform mat4 p_matrix;
uniform mat4 mv_matrix;

out vec3 frag_position, frag_normal;
out vec4 frag_materialColor;

void main()
{
    vec4 eye_position = mv_matrix * vec4(position, 1.0);
    gl_Position = p_matrix * eye_position;

    frag_position = eye_position.xyz;
    frag_materialColor = vertexColor;
    frag_normal   = (mv_matrix * vec4(normalize(vertexNormal), 0.0)).xyz;
}
)XXX";

    // ...but modify the fragment shader:
    fragment_shader =
        R"XXX(
#version 330 core

// FRAGMENT SHADER: Demo for custom shaders.
// Set color from depth.

uniform mat4 p_matrix;
uniform mat4 mv_matrix;

in vec3 frag_position;
in vec4 frag_materialColor;

out vec4 color;

void main()
{
    vec4 posWrtEye = p_matrix*mv_matrix * vec4(frag_position, 1.0);
    color = vec4(vec3(posWrtEye.z), 1.0);
}
)XXX";

    uniforms = {"p_matrix", "mv_matrix"};
    attribs = {"position"};

    // Compile shader:
    // ------------------------------------------
    std::string errMsgs;
    std::vector<mrpt::opengl::Shader> lstShaders;
    lstShaders.resize(2);
    if (!lstShaders[0].compile(GL_VERTEX_SHADER, {vertex_shader}, errMsgs))
    {
        THROW_EXCEPTION_FMT(
            "Error compiling GL_VERTEX_SHADER:\n%s", errMsgs.c_str());
    }
    if (!lstShaders[1].compile(GL_FRAGMENT_SHADER, {fragment_shader}, errMsgs))
    {
        THROW_EXCEPTION_FMT(
            "Error compiling GL_FRAGMENT_SHADER:\n%s", errMsgs.c_str());
    }

    auto shader = std::make_shared<mrpt::opengl::Program>();

    if (!shader->linkProgram(lstShaders, errMsgs))
    {
        THROW_EXCEPTION_FMT(
            "Error linking Opengl Shader programs:\n%s", errMsgs.c_str());
    }

#if 1
    // Debug:
    std::cout << "Built Shader program\n";
    shader->dumpProgramDescription(std::cout);
    std::cout << "\n";
#endif

    // Uniforms:
    for (const auto& name : uniforms)
        shader->declareUniform(name);

    // Attributes:
    for (const auto& name : attribs)
        shader->declareAttribute(name);

    // Store in MRPT object:
    // ------------------------------------------

    // And store as the "TRIANGLES" shader in the MRPT viewport:
    // In MRPT, shaders are a property of viewports:
    auto vp = scene.getViewport();

    // Make sure default shaders are loaded (if not already):
    vp->loadDefaultShaders();

    // Overwrite the shaders we want to customize:
    const auto id = mrpt::opengl::DefaultShaderID::TRIANGLES_NO_LIGHT;
    vp->shaders()[id] = std::move(shader);

#endif
}

// ------------------------------------------------------
//              DemoCustomShaders
// ------------------------------------------------------
void DemoCustomShaders()
{
    using namespace std;
    using namespace mrpt;
    using namespace mrpt::gui;
    using namespace mrpt::opengl;
    using namespace mrpt::math;
    using namespace std::string_literals;

    CDisplayWindow3D win("Demo of MRPT's custom shaders", 640, 480);

    Scene::Ptr& theScene = win.get3DSceneAndLock();

    float off_x = 0;
    // Box
    {
        auto obj = opengl::CBox::Create(
            TPoint3D(0, 0, 0), TPoint3D(1, 1, 1), true, 3.0);
        obj->setLocation(off_x, 0, 0);
        theScene->insert(obj);

        auto obj2 =
            opengl::CBox::Create(TPoint3D(0, 0, 0), TPoint3D(1, 1, 1), false);
        obj2->setLocation(off_x, 4, 0);
        theScene->insert(obj2);

        auto obj3 =
            opengl::CBox::Create(TPoint3D(0, 0, 0), TPoint3D(1, 1, 1), false);
        obj3->enableBoxBorder(true);
        obj3->setLineWidth(3);
        obj3->setColor_u8(0xff, 0x00, 0x00, 0xa0);
        obj3->setLocation(off_x, 8, 0);
        theScene->insert(obj3);
    }

    win.setCameraZoom(25);

    // IMPORTANT!!! IF NOT UNLOCKED, THE WINDOW WILL NOT BE UPDATED!
    win.unlockAccess3DScene();

    // Wait for the window to be open:
    win.wait_for_GL_context();

    // Create the shader after the window is open, so we are sure it has a
    // proper GL context:
    {
        CDisplayWindow3DLocker locker(win, theScene);

        // Shaders must be created from the main OpenGL thread:
        win.sendFunctionToRunOnGUIThread(
            [theScene]() { installCustomShader(*theScene); });
    }

    win.repaint();

    cout << "Close the window to end.\n";

    while (win.isOpen())
    {
        std::this_thread::sleep_for(50ms);
        win.repaint();
    }
}

// ------------------------------------------------------
//                      MAIN
// ------------------------------------------------------
int main()
{
    try
    {
        DemoCustomShaders();

        return 0;
    }
    catch (const std::exception& e)
    {
        std::cerr << mrpt::exception_to_str(e) << std::endl;
        return 1;
    }
}