MinVR Tutorial


Written by Ruiqi Mao, Edited/Added onto by Mandy He (March 2022)


ABOUT

MinVR is an Open Source Project developed and maintained collaboratively by the University of Minnesota, Macalester College, and Brown University.

The goal of MinVR is to facilitate a variety of data visualization and virtual reality research projects by providing a robust, cross-platform VR toolkit for use with many different VR displays (e.g., CAVE's, PowerWalls, multi-touch stereoscopic tables, 3DTV's, head-mounted displays) and input devices (e.g., 6 degree-of-freedom trackers, multi-touch input devices, haptic devices, home-built devices).

More information at: https://github.com/MinVR/MinVR


NOTE from 2022: This is a really helpful tutorial, but I spent about 3 hours struggling through the first 2 steps which should have taken around 30 minutes partially because I was just not used to navigating around a Windows computer/Paperspace machine, but also because this tutorial was made ~3 years ago and there were some plugins and executables that needed to be checked for the testing to work that were not explicitly listed in this tutorial but are in MinVR's Github which has been updated since this tutorial was made. I made some notes about places I struggled and tips that will hopefully be helpful.

REQUIREMENTS

Development Machine

SETUP

Estimated Time: 15 minutes

1.   Install Microsoft Visual Studio Community edition from https://www.visualstudio.com/. When installing, make sure to include the "Desktop development with C++" workload.

2.   Install Git from https://git-scm.com/download/win. We will need this when compiling MinVR.

3.   Install CMake from https://cmake.org/download/. This will be used to generate compilation files for MinVR.

4.   Install Doxygen from http://www.stack.nl/~dimitri/doxygen/download.html. This will be used to generate documentation for MinVR.

Note from 2022: The Doxygen link did not work for me, so I just ignored it.

MINVR

Estimated Time: 15 minutes

MinVR CMake Setup (Pt. 1)

To start off, we want to ensure that we can compile MinVR.

1.   Download the latest version of MinVR from https://github.com/MinVR/MinVR.

Note: This can be done by either downloading the zip.file from Github or git cloning. If you download the zip-file, the name of the folder will be "minvr-master" instead of "minvr", so make sure to use "minvr-master" instead of "minvr" areas where the tutorial says to use "minvr" in filepaths or rename the folder.

2.  (You can skip this part, this has been fixed)  At the current time of writing, there is a bug that causes MinVR to crash whenever a warning occurs. As a workaround, in "src/plugin/VRSharedLibrary.cpp", change the two instances of

const char *error;

to

const char *error = "";

3.   In the root directory of the MinVR project, create a "build" folder.

4.   Launch CMake GUI. Point the source code directory to the MinVR directory, and the build directory to the newly created build folder.

5.   Click "Configure". Choose "Visual Studio 16 2019 Win64" as the generator, then click "Finish", then wait for the configuration to finish. (Or alternatively at the time of writing "Visual Studio 15 2017)

NOTE from 2022: GO FOLLOW STEP 2 & 3 in the Github for MinVR (https://github.com/MinVR/MinVR) otherwise certain steps stated in the testing portion will not show up/work. Basically, make sure you should configure with the "itest-" executable(s) checked. I followed the Github and checked " itest-opengl-shaderpipeline-with-api" but this tutorial later uses "itest-opengl-multithreaded" for testing. The one on the Github worked for me (aka. I checked "itest-opengl-shaderpipeline-with-api" and followed the testing instructions in this tutorial.
NOTE from 2024: Along with what the repo said to check, I also checked GLEW_AUTOBUILD, "itest-opengl-multithreaded" (but NOT "itest-opengl-multithreaded") and "WITH_PLUGIN_THREADING" as that was required with "itest-opengl-multithreaded". 

OpenVR Setup

If you do not intend on running your application on an OpenVR device (HTC Vive or Oculus Rift), then you can skip this section.

1.   Download the latest version of OpenVR from https://github.com/ValveSoftware/openvr.

2.   Check the MINVR_OPENVR_PLUGIN box.

NOTE from 2022: I could not find MINVR_OPENVR_PLUGIN in the CMake configuration window, so I just checked the WITH_PLUGIN_OPENVR box.

3.   In the CMake configuration window, check the "Advanced" box and find the OPENVR_INCLUDE_DIR, OPENVR_LIBRARY, and OPENVR_LIB_DIR entries. Set them to point to part of the OpenVR library according to:

4.   If you don't plan on building documentation, uncheck "BUILD_DOCUMENTATION".

5.   Click "Configure" again to reconfigure MinVR with OpenVR support.

MinVR CMake Setup (Pt. 2)

1.   Click "Generate" to generate the Visual Studio project files.

2.   Navigate to your build folder and open "MinVR_Project.sln" in Visual Studio.

NOTE from 2022: I could not find this. Instead I found MinVR_Toolkit.sln (aka. the only sln file that I could find in the directory) and opened that.

3.   Under "CMakePredefinedTargets" in the Solution Explorer on the right, right click "ALL_BUILD, and click "Build". Wait for the build process to finish.

4.   Then, right click "INSTALL", and click "Build. Wait for the build process to finish.

Testing

Before writing our own MinVR application, let's make sure MinVR is working.

1.   In the Solution Explorer on the right sidebar, under "tests-interactive", right click on "itest-opengl-multithreaded" and open "Properties".

NOTE from 2022: "tests-interactive" will not show up if you did not check at least on of the "itest-opengl-multithreaded" or the "itest-...-api" executables and related plugins when configuring. If you did and still can't find it, look in the /bin folder and it should be there.

2.   In the configuration window that shows up, choose "Debugging" under "Configuration Properties".

3.   Set "Command Arguments" to "-c ../../../config/default.minvr".

4.   Close the configuration window and right click "itest-opengl-multithreaded" again, then choose "Debug > Start New Instance". If everything built correctly, you should see a rotating colored cube:

You might run into an error that says something along the lines of "cannot find instance of glew32.dll." 

If this is the case, navigate to your file system and go to "MinVR/build/install/bin" you should find two files named "glew32.dll" and "glew32d.dll." Copy the two files, and paste them into MinVR/build/bin.

HELLO WORLD

Estimated Time: 1 hour

Visual Studio Setup

Let's write a MinVR application!

1.   Start by opening Visual Studio and creating a new project. We want a generic project, so select Visual C++ > General > Empty Project.

2.   Once you've created your project, we need to first add MinVR to our project. In your MinVR build folder, there will be a folder named "install". From the install folder, copy the include, lib, and plugins folders to your project root in Windows Explorer (don't do anything with Visual Studio yet!).

3.   Download GLEW from https://sourceforge.net/projects/glew/files/glew/2.1.0/glew-2.1.0-win32.zip/download. Copy the contents of include into the include folder in your project, and copy lib/Release/x64/glew32s.lib into the lib folder in your project. GLEW will be used to help us write OpenGL code.

4.   Download GLM from https://github.com/g-truc/glm. Copy the entire glm folder into the include folder in your project. GLM is a mathematics library that is helpful when writing graphics applications.

5.   Go back to Visual Studio. Right click your project in the Solution Explorer and click "Properties".

6.   Under "VC++ Directories", we're going to add our MinVR, GLEW, and GLM files to our project. Add the "include" and "include\MinVR-2.0" folders to "Include Directories" and "lib" to "Library Directories".

7.   Under "Linker > Input", we need to add our libraries. Add MinVR-2.0\MinVRd.lib, glew32s.lib, opengl32.lib, and glu32.lib to "Additional Dependencies". Note that we don't need to do anything here for GLM, as GLM is a purely header-based library. Close the properties window.'

Writing the Code

Note: This tutorial glosses over the details of the OpenGL calls in the code. This is because OpenGL itself would take an entire tutorial to explain. Further details on what is going on can be figured out by researching online or by taking CS123.

1.   Let's make our main function. Right click "Source Files" and choose Add > New Item. Create a new .cpp file named "main.cpp".

2.   Put an empty main function in the file for now:

int main(int argc, char *argv[]) {

  return 0;

}

3.   Let's make sure everything is able to be built. Press F5 to run the application. If Visual Studio asks to rebuild out-of-date projects, accept. If the application ends with code 0, you're in good shape!

4.   There are some includes that we want to share across all of our files. To make things a little easier, let's make a new header file to store our common code in. Right click "Header Files" and choose Add > New Item. Create a new .h file named "common.h".

5.   Put in the following:

#ifndef COMMON_H

#define COMMON_H


// MinVR.

#include <api/MinVR.h>


// GLM.

#define GLM_ENABLE_EXPERIMENTAL

#include <glm/glm.hpp>

#include <glm/gtx/transform.hpp>

#include <glm/gtc/type_ptr.hpp>


// GLEW.

#define GLEW_STATIC

#include "GL/glew.h"

#ifdef _WIN32

#include "GL/wglew.h"

#elif (!defined(__APPLE__))

#include "GL/glxew.h"

#endif


// OpenGL.

#if defined(WIN32)

#define NOMINMAX

#include <windows.h>

#include <GL/gl.h>

#elif defined(__APPLE__)

#define GL_GLEXT_PROTOTYPES

#include <OpenGL/gl3.h>

#include <OpenGL/glext.h>

#else

#define GL_GLEXT_PROTOTYPES

#include <GL/gl.h>

#endif


#include <iostream>

#include <string>

#include <vector>


#endif

6.   Now we can make our application class. Right click "Source Files" and choose Add > Class. Name the new class "MyVRApp" and click OK.

7.   Fill out MyVRApp.h:

#ifndef MYVRAPP_H

#define MYVRAPP_H


#include "common.h"


class MyVRApp : public MinVR::VRApp

{


public:

    MyVRApp(int argc, char *argv[]);


    virtual ~MyVRApp();


    /**

     * Called whenever an new input event happens.

     */

    void onVREvent(const MinVR::VREvent &event);


    /**

     * Called before renders to allow the user to set context-specific variables.

     */

    void onVRRenderGraphicsContext(const MinVR::VRGraphicsState &renderState);


    /**

     * Called when the application draws.

     */

    void onVRRenderGraphics(const MinVR::VRGraphicsState &renderState);


private:


    glm::mat4 m_model;


    GLuint m_vbo;

    GLuint m_vao;


    GLuint m_vs;

    GLuint m_fs;

    GLuint m_shader;


};


#endif

8.   Now we can work on MyVRApp.cpp. Start by setting the constructor and destructor:

#include "MyVRApp.h"


MyVRApp::MyVRApp(int argc, char *argv[])

  : m_model(1.0f),

    m_vbo(0),

    m_vao(0),

    m_vs(0),

    m_fs(0),

    m_shader(0),

    VRApp(argc, argv)

{ }


MyVRApp::~MyVRApp() {

    glDeleteBuffers(1, &m_vbo);

    glDeleteVertexArrays(1, &m_vao);

    glDetachShader(m_shader, m_vs);

    glDetachShader(m_shader, m_fs);

    glDeleteShader(m_vs);

    glDeleteShader(m_fs);

    glDeleteProgram(m_shader);

}

9.   MinVR uses an event system to handle interactions and application ticks. In our case, we only care about two events: when a tick, or a frame, starts, and when the Escape key is pressed so that we can exit the application. In this code, we're going to be rotating a cube. To determine how much to rotate the cute by every second, we use the amount of time elapsed since the application started to calculate a rotation matrix that we will apply to the cube. Add the event handler to MyVRApp.cpp:

void MyVRApp::onVREvent(const MinVR::VREvent &event) {

    // Called on every tick.

    if (event.getName() == "FrameStart") {

        // Get the total amount of time elapsed.

        float time = event.getDataAsFloat("ElapsedSeconds");


        // Rotate the cube.

        m_model = glm::mat4(1.0f);

        m_model *= glm::translate(glm::vec3(0.0f, 0.0f, -5.0f));

        m_model *= glm::rotate(time, glm::vec3(0.0f, 1.0f, 0.0f));

    }


    // Press escape to quit.

    if (event.getName() == "KbdEsc_Down") {

        shutdown();

    }

}

10.   Next is our code for setting the graphics context variables. This is essentially code for configuring flags in OpenGL before a draw call so that OpenGL knows how to draw. In our case, we only care about the first time this is called so that we can generate the geometry for a cube and send it to the GPU for drawing. Add the function to MyVRApp.cpp:

void MyVRApp::onVRRenderGraphicsContext(const MinVR::VRGraphicsState &renderState) {

    // Run setup if this is the initial call.

    if (renderState.isInitialRenderCall()) {

        // Initialize GLEW.

        glewExperimental = GL_TRUE;

        if (glewInit() != GLEW_OK) {

            std::cout << "Error initializing GLEW." << std::endl;

        }


        // Initialize OpenGL.

        glEnable(GL_DEPTH_TEST);

        glClearDepth(1.0f);

        glDepthFunc(GL_LEQUAL);

        glClearColor(0.0f, 0.0f, 0.0f, 1.0f);


        // Create cube vertices.

        GLfloat vertices[] = {

            1.0f, 1.0f, 1.0f,  -1.0f, 1.0f, 1.0f,  -1.0f,-1.0f, 1.0f,      // v0-v1-v2 (front)

            -1.0f,-1.0f, 1.0f,   1.0f,-1.0f, 1.0f,   1.0f, 1.0f, 1.0f,     // v2-v3-v0


            1.0f, 1.0f, 1.0f,   1.0f,-1.0f, 1.0f,   1.0f,-1.0f,-1.0f,      // v0-v3-v4 (right)

            1.0f,-1.0f,-1.0f,   1.0f, 1.0f,-1.0f,   1.0f, 1.0f, 1.0f,      // v4-v5-v0


            1.0f, 1.0f, 1.0f,   1.0f, 1.0f,-1.0f,  -1.0f, 1.0f,-1.0f,      // v0-v5-v6 (top)

            -1.0f, 1.0f,-1.0f,  -1.0f, 1.0f, 1.0f,   1.0f, 1.0f, 1.0f,     // v6-v1-v0


            -1.0f, 1.0f, 1.0f,  -1.0f, 1.0f,-1.0f,  -1.0f,-1.0f,-1.0f,     // v1-v6-v7 (left)

            -1.0f,-1.0f,-1.0f,  -1.0f,-1.0f, 1.0f,  -1.0f, 1.0f, 1.0f,     // v7-v2-v1.0


            -1.0f,-1.0f,-1.0f,   1.0f,-1.0f,-1.0f,   1.0f,-1.0f, 1.0f,     // v7-v4-v3 (bottom)

            1.0f,-1.0f, 1.0f,  -1.0f,-1.0f, 1.0f,  -1.0f,-1.0f,-1.0f,      // v3-v2-v7


            1.0f,-1.0f,-1.0f,  -1.0f,-1.0f,-1.0f,  -1.0f, 1.0f,-1.0f,      // v4-v7-v6 (back)

            -1.0f, 1.0f,-1.0f,   1.0f, 1.0f,-1.0f,   1.0f,-1.0f,-1.0f      // v6-v5-v4

        };


        // Cube normals.

        GLfloat normals[] = {

            0.0f, 0.0f, 1.0f,   0.0f, 0.0f, 1.0f,   0.0f, 0.0f, 1.0f,      // v0-v1-v2 (front)

            0.0f, 0.0f, 1.0f,   0.0f, 0.0f, 1.0f,   0.0f, 0.0f, 1.0f,      // v2-v3-v0


            1.0f, 0.0f, 0.0f,   1.0f, 0.0f, 0.0f,   1.0f, 0.0f, 0.0f,      // v0-v3-v4 (right)

            1.0f, 0.0f, 0.0f,   1.0f, 0.0f, 0.0f,   1.0f, 0.0f, 0.0f,      // v4-v5-v0


            0.0f, 1.0f, 0.0f,   0.0f, 1.0f, 0.0f,   0.0f, 1.0f, 0.0f,      // v0-v5-v6 (top)

            0.0f, 1.0f, 0.0f,   0.0f, 1.0f, 0.0f,   0.0f, 1.0f, 0.0f,      // v6-v1-v0


            -1.0f, 0.0f, 0.0f,  -1.0f, 0.0f, 0.0f,  -1.0f, 0.0f, 0.0f,      // v1-v6-v7 (left)

            -1.0f, 0.0f, 0.0f,  -1.0f, 0.0f, 0.0f,  -1.0f, 0.0f, 0.0f,      // v7-v2-v1


            0.0f,-1.0f, 0.0f,   0.0f,-1.0f, 0.0f,   0.0f,-1.0f, 0.0f,      // v7-v4-v3 (bottom)

            0.0f,-1.0f, 0.0f,   0.0f,-1.0f, 0.0f,   0.0f,-1.0f, 0.0f,      // v3-v2-v7


            0.0f, 0.0f,-1.0f,   0.0f, 0.0f,-1.0f,   0.0f, 0.0f,-1.0f,      // v4-v7-v6 (back)

            0.0f, 0.0f,-1.0f,   0.0f, 0.0f,-1.0f,   0.0f, 0.0f,-1.0f       // v6-v5-v4

        };


        // Create the VBO.

        glGenBuffers(1, &m_vbo);

        glBindBuffer(GL_ARRAY_BUFFER, m_vbo);

        glBufferData(GL_ARRAY_BUFFER, sizeof(vertices) + sizeof(normals), 0, GL_STATIC_DRAW);

        glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices);

        glBufferSubData(GL_ARRAY_BUFFER, sizeof(vertices), sizeof(normals), normals);

        glBindBuffer(GL_ARRAY_BUFFER, 0);


        // Create the VAO.

        glGenVertexArrays(1, &m_vao);

        glBindVertexArray(m_vao);

        glBindBuffer(GL_ARRAY_BUFFER, m_vbo);

        glEnableVertexAttribArray(0);

        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (char*)0);

        glEnableVertexAttribArray(1);

        glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (char*) sizeof(vertices));

        glBindBuffer(GL_ARRAY_BUFFER, 0);

        glBindVertexArray(0);


        // Create the shader.

        m_vs = Util::compileShader(Util::load("shader.vert"), GL_VERTEX_SHADER);

        m_fs = Util::compileShader(Util::load("shader.frag"), GL_FRAGMENT_SHADER);

        m_shader = Util::linkShaderProgram(m_vs, m_fs);

    }

}

11.   Your editor may start complaining about missing functions at this point. This is because we're missing three helper functions. Add these functions to MyVRApp.cpp before the constructor, but after the include:

#include <fstream>

#include <sstream>


namespace Util {


std::string load(std::string path) {

    // Load the file.

    std::ifstream file(path);


    // Load the contents of the file into a string stream.

    std::stringstream buffer;

    buffer << file.rdbuf();


    // Return the contents of the file.

    return buffer.str();

}


GLuint compileShader(std::string code, GLuint type) {

    // Convert the code into a C style string.

    const char *source = code.c_str();

    int length = code.size();


    // Create and compile a shader.

    GLuint shader = glCreateShader(type);

    glShaderSource(shader, 1, &source, &length);

    glCompileShader(shader);


    // Verify that the shader compiled correctly.

    GLint status;

    glGetShaderiv(shader, GL_COMPILE_STATUS, &status);

    if (!status) {

        // Get the shader log.

        GLint length;

        glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &length);

        std::vector<char> log(length);

        glGetShaderInfoLog(shader, length, &length, log.data());


        // Print the error.

        std::cerr << log.data() << std::endl;

    }


    // Return the shader.

    return shader;

}


GLuint linkShaderProgram(GLuint vs, GLuint fs) {

    // Create a new shader program.

    GLuint program = glCreateProgram();


    // Attach the shaders.

    glAttachShader(program, vs);

    glAttachShader(program, fs);


    // Link the shaders.

    glLinkProgram(program);


    // Verify that the program linked correctly.

    GLint status;

    glGetProgramiv(program, GL_LINK_STATUS, &status);

    if (!status) {

        // Get the program log.

        GLint length;

        glGetProgramiv(program, GL_INFO_LOG_LENGTH, &length);

        std::vector<char> log(length);

        glGetProgramInfoLog(program, length, &length, log.data());


        // Print the error.

        std::cerr << log.data() << std::endl;;

    }


    // Return the shader program.

    return program;

}


}

Note that in a proper C++ development environment, these functions should be moved to separate Utils.h and Utils.cpp files. However, in our case, due to the nature of the application as extremely simple, it is more convenient to place these in MyVRApp.cpp instead.

12.   Finally, let's have our application actually render something! Fill in the last function:

void MyVRApp::onVRRenderGraphics(const MinVR::VRGraphicsState &renderState) {

    // Only render if running.

    if (isRunning()) {

        // Clear the screen.

        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);


        // Use the shader.

        glUseProgram(m_shader);


        // Set the shader uniforms.

        glUniformMatrix4fv(glGetUniformLocation(m_shader, "p"), 1, GL_FALSE, renderState.getProjectionMatrix());

        glUniformMatrix4fv(glGetUniformLocation(m_shader, "v"), 1, GL_FALSE, renderState.getViewMatrix());

        glUniformMatrix4fv(glGetUniformLocation(m_shader, "m"), 1, GL_FALSE, glm::value_ptr(m_model));


        // Draw the cube.

        glBindVertexArray(m_vao);

        glDrawArrays(GL_TRIANGLES, 0, 36);

        glBindVertexArray(0);


        // Reset the shader.

        glUseProgram(0);

    }

}

13.   Let's get our main function to run the application. Go back to main.cpp and include MyVRApp.h:

#include "MyVRApp.h"

14.   Add code to run the application inside the body of main:

MyVRApp app(argc, argv);

app.run();

We're almost done here! At this point, we've told OpenGL what to draw, but we haven't told it how to draw it. If you've been paying attention to the code, you'll notice that we refer to two files: shader.vert and shader.frag. These two files are our vertex and fragment shaders, which are GPU code that will be used to process the data we pass to the GPU and render to the screen.

15.   Right click "Resource Files" and select Add > New Item. Create a file named "shader.vert" and put the following shader code in:

#version 410


layout(location = 0) in vec3 position;

layout(location = 1) in vec3 normal;


uniform mat4 p;

uniform mat4 v;

uniform mat4 m;


out vec4 pos;

out vec4 norm;


void main() {

    pos = v * m * vec4(position, 1.0);

    norm = normalize(v * m * vec4(normal, 0.0));


    gl_Position = p * pos;

}

16.   Likewise, create another file named "shader.frag" and put the following shader code in:

#version 410


const vec4 lightPos = vec4(0.0, 2.0, 2.0, 1.0);

const vec4 color = vec4(1.0, 0.0, 0.0, 1.0);


in vec4 pos;

in vec4 norm;


out vec4 fragColor;


void main() {

    float ambient = 0.1;

    float diffuse = clamp(dot(norm, normalize(lightPos - pos)), 0.0, 1.0);


    fragColor = (ambient + diffuse) * color;

}

Running the Application

1.   Try to run the application. You'll notice that the application will immediately crash. This is because MinVR requires configuration files that dictate how to display the application, which is the secret behind its versatility. In your project folder, create another folder named "config", and create a new file "stereo.minvr". We are going to create a configuration file that tells MinVR to display two separate views: one for each eye.

<MinVR>

    <GLFWPlugin pluginType="MinVR_GLFW"/>

    <OpenGLPlugin pluginType="MinVR_OpenGL"/>


    <RGBBits>8</RGBBits>

    <AlphaBits>8</AlphaBits>

    <DepthBits>24</DepthBits>

    <StencilBits>8</StencilBits>

    <FullScreen>0</FullScreen>

    <Resizable>1</Resizable>

    <AllowMaximize>1</AllowMaximize>

    <Visible>1</Visible>

    <SharedContextGroupID>-1</SharedContextGroupID>

    <ContextVersionMajor>3</ContextVersionMajor>

    <ContextVersionMinor>3</ContextVersionMinor>

    <UseGPUAffinity>1</UseGPUAffinity>

    <UseDebugContext>0</UseDebugContext>

    <MSAASamples>1</MSAASamples>

    <QuadBuffered>0</QuadBuffered>

    <StereoFormat>SideBySide</StereoFormat>


    <HeadTrackingEvent>Head_Move</HeadTrackingEvent>

    <NearClip>0.001</NearClip>

    <FarClip>500.0</FarClip>


    <VRSetups>

        <Desktop hostType="VRStandAlone">

            <GLFWToolkit windowtoolkitType="VRGLFWWindowToolkit"/>

            <OpenGLToolkit graphicstoolkitType="VROpenGLGraphicsToolkit"/>

            <RootNode displaynodeType="VRGraphicsWindowNode">

                <Border>1</Border>

                <Caption>Desktop</Caption>

                <GPUAffinity>0</GPUAffinity>

                <XPos>100</XPos>

                <YPos>100</YPos>

                <Width>1280</Width>

                <Height>640</Height>

                <LookAtNode displaynodeType="VRTrackedLookAtNode">

                    <LookAtUp type="floatarray">0,1,0</LookAtUp>

                    <LookAtEye type="floatarray">0,0,8</LookAtEye>

                    <LookAtCenter type="floatarray">0,0,0</LookAtCenter>


                    <StereoNode displaynodeType="VRStereoNode">

                        <EyeSeparation>0.203</EyeSeparation>

                        <ProjectionNode displaynodeType="VROffAxisProjectionNode">

                             <TopLeft type="floatarray">-2,2,0</TopLeft>

                             <TopRight type="floatarray">-2,2,0</TopRight>

                             <BottomLeft type="floatarray">-2,-2,0</BottomLeft>

                             <BottomRight type="floatarray">2,-2,0</BottomRight>

                             <DUMMY/>

                        </ProjectionNode>

                    </StereoNode>

                </LookAtNode>

            </RootNode>

        </Desktop>

    </VRSetups>

</MinVR>

2.   In order to tell MinVR to use this configuration file, we have to change its command line arguments. Right click the project in Solution Explorer and click "Properties". Under "Debugging", set "Command Arguments" to -c config/stereo.minvr. Click OK to apply changes.

3.   Try to run again. This time, you should see two views both looking at a rotating red cube, each at a slightly different angle.

Running with OpenVR

If you do not intend on running your application on an OpenVR device (HTC Vive or Oculus Rift), then you can skip this section.

Simply displaying a stereo view on desktop is boring. Let's look at our application through a headset!

1.   From the OpenVR folder downloaded earlier, copy "openvr_api.dll" from "bin/win64" into your project directory. This is required in order to run OpenVR.

2.   In the config folder, create another configuration file "openvr.minvr". This will be the configuration file used to run applications through OpenVR.

<MinVR>

    <GLFWPlugin pluginType="MinVR_GLFW"/>

    <OpenGLPlugin pluginType="MinVR_OpenGL"/>

    <OpenVRPlugin pluginType="MinVR_OpenVR"/>


    <RGBBits>8</RGBBits>

    <AlphaBits>8</AlphaBits>

    <DepthBits>24</DepthBits>

    <StencilBits>8</StencilBits>

    <FullScreen>0</FullScreen>

    <Resizable>1</Resizable>

    <AllowMaximize>1</AllowMaximize>

    <Visible>1</Visible>

    <SharedContextGroupID>-1</SharedContextGroupID>

    <ContextVersionMajor>3</ContextVersionMajor>

    <ContextVersionMinor>3</ContextVersionMinor>

    <UseGPUAffinity>1</UseGPUAffinity>

    <UseDebugContext>0</UseDebugContext>

    <MSAASamples>1</MSAASamples>

    <QuadBuffered>0</QuadBuffered>


    <StereoFormat>Mono</StereoFormat>


    <VRSetups>

        <Desktop hostType="VRStandAlone">

            <GLFWToolkit windowtoolkitType="VRGLFWWindowToolkit"/>

            <OpenGLToolkit graphicstoolkitType="VROpenGLGraphicsToolkit"/>

            <RootNode displaynodeType="VRGraphicsWindowNode">

                <Border>1</Border>

                <Caption>Mirror</Caption>

                <GPUAffinity>0</GPUAffinity>

                <XPos>100</XPos>

                <YPos>100</YPos>

                <Width>640</Width>

                <Height>640</Height>

                <HTC displaynodeType="VROpenVRNode">

                    <HideTracker>0</HideTracker>

                    <ReportStatePressed>1</ReportStatePressed>

                    <ReportStateTouched>1</ReportStateTouched>

                    <ReportStateAxis>1</ReportStateAxis>

                    <ReportStatePose>1</ReportStatePose>

                    <DrawHMDOnly>0</DrawHMDOnly>

                    <MSAA_buffers>4</MSAA_buffers>

                </HTC>

            </RootNode>

        </Desktop>

    </VRSetups>

</MinVR>

3.   Now go back to our project properties. Change the command line arguments from using "stereo.minvr" to use "openvr.minvr" instead.

4.   Connect your SteamVR-compatible headset and launch SteamVR. Perform any setup as needed.

5.   Try running your application again. This time, you should be able to view your spinning red cube through your headset with a mirror displayed on the desktop as well!

SCRIPT TO AUTOMATIC DOWNLOAD AND LINK MINVR TO YOUR C++/OPENGL APPLICATION ( WINDOWS, LINUX AND MAC )

Requirements:


The following is a CMake script that clones and build MinVR automatically and links it to a C++/OpenGL project. It has been tested in Windows (10,11), MacOS (Big Sur, Monterrey ) and Linux.

Disclaimer

Most of the images on this tutorial have been taken on Windows O.S, but the file structure and CMake execution is the same in all Operational Systems.


Instruccions

Clone the repo located in this [link](https://github.com/kmilo9999/MinVRApplication/tree/main) and follow the instructions in the README.md