• MRF - Main Page
  • Malia Renderer
  • Blender Bridge
  • Spectral Viewer
  • MRF - Scene format
  • Gallery
  • RGB To Spectral
  • For Developpers
  • MRF - Developper documentation
    This is the developper page of the MRF Documentation. We provide some basic information to compile the Malia Rendering Framework as well as the Malia rendering engine. !!! Warning Installing MRF DOES NOT compile nor install Spectral Viewer. To compile and install it, please refer to its dedicated documentation: Spectral Viewer Documentation. !!! All the following guidelines assume you build the Malia renderer along with MRF. Otherwise, if you just need the MRF library, ignore any instruction regarding CUDA, OptiX and Malia. # Requirements ## [CMake](https://cmake.org/download/) MRF requires CMake version at least 3.9. ## [OptiX library](https://developer.nvidia.com/designworks/optix/downloads/legacy) Please make sure you use a version supported by your GPU architecture: GPU Arch | OptiX version -------------------|------------- < Kepler | No supported version for Malia Kepler to Maxwell | 5.1.1 Maxwell or newer | 6.0.0 Maxwell or newer | 6.5.0 For more information, see the OptiX [release notes](https://developer.nvidia.com/designworks/optix/download). !!! Warning OptiX 7 versions are NOT yet supported by Malia. !!! Warning OptiX versions below 7 are not guaranteed to operate well with CUDA 11 and above. Check the CUDA and Optix versions compatiblity with the Table [CUDA-OPTIX]. OptiX version | CUDA version --------------|------------- 5.1.1 | 7.0 to 9.0 6.0.0 | 7.0 to 10.0 6.5.0 | 7.0 to 10.1 [Table [CUDA-OPTIX]: Compatible versions of the CUDA SDK for the Optix library.] For Windows, an Optix installer can be downloaded at [NVIDIA's dedicated page](https://developer.nvidia.com/designworks/optix/downloads/legacy). For Unices, please refer to the installation guide below. ### Optix Installation Guide (Ubuntu) After downloading OptiX (at [NVIDIA's dedicated page](https://developer.nvidia.com/designworks/optix/downloads/legacy)), move the installation file to `/opt` and run it from there. For instance, with OptiX 6.5: ```bash mv NVIDIA-OptiX-SDK-6.5.0-linux64.sh /opt cd /opt sudo chmod +x NVIDIA-OptiX-SDK-6.5.0-linux64.sh sudo ./NVIDIA-OptiX-SDK-6.5.0-linux64.sh ``` If you do not have administrator privilegies on your computer, install optix in your home directory. You will have to remember this path for the CMake configuration. ## [CUDA toolkit](https://developer.nvidia.com/cuda-toolkit-archive) First, ensure that you have the required minimal versions for your NVIDIA drivers. Depending on the version of CUDA you wish to use, there is minimal compatible version for your graphics card driver : CUDA Toolkit | Linux x86_64 Driver Version | Windows x86_64 Driver Version ------------------|-----------------------------|------------------------------ CUDA 10.0.130 | >= 410.48 | >= 411.31 CUDA 9.0 (9.0.76) | >= 384.81 | >= 385.54 See Table 1 in the [Release notes](https://docs.nvidia.com/cuda/cuda-toolkit-release-notes/index.html) for a complete list. Also, make sure your CUDA version is compatible with your OptiX version (see Table [CUDA-OPTIX]) For Windows, a CUDA installer can be downloaded [NVIDIA's dedicated page](https://developer.nvidia.com/cuda-toolkit-archive). For Unices, please refer to the guide below. ### CUDA Installation Guide (Ubuntu) For Ubuntu (>=18.04 ) you can use the following commands: ```bash sudo apt install linux-headers$(uname -r) sudo apt install nvidia-cuda-toolkit ``` After installation, set `CUDA_BIN_PATH` environment variable. For example on Ubuntu, append on `~/.bashrc` : ```bash export CUDA_BIN_PATH=/usr/lib/cuda ``` If this does not work for you, you might need to install manually CUDA and the NVIDIA drivers. Follow the detailed documentation on [NVIDIA Cuda](https://developer.nvidia.com/cuda-downloads). Be sure to use a version previous to the 11.x one. Pay especially attention to the [system requirements](https://docs.nvidia.com/cuda/cuda-installation-guide-linux/index.html#system-requirements) that depends on your Linux distribution. ## Optional: GLFW The renderer Malia, provided by the MRF project can be use in interactive mode. This mode depends on GLFW. Installing GLFW is not required as it is provided and compiled along Malia if necessary. However, for Linux users, you need the Xorg development package for building GLFW, which depends on X11 for window creation (see compile [guide](https://www.glfw.org/docs/latest/compile.html#compile_deps_x11)). You need to install the `xorg-dev` package: ```bash sudo apt install xorg-dev ``` ## Optional: [Eigen 3](http://eigen.tuxfamily.org/index.php?title=Main_Page) for fast Vector Matrices computation and for [rgb2spec](./apps/rgb2spec/rgb2spec.md.html) application # Download Source Check our Gitlab Repository to access: - The Continous Intgreation (CI) status - The current issues as well as our todo lists (including next features etc.) - The planned Milestones - The [coverage report](https://gitlab.com/mrf-devteam/mrf/-/jobs/artifacts/master/file/doc/coverage_report/cov_report.html?job=coverage_Ubuntu19) generated by [gcovr](https://gcovr.com/en/stable/guide.html) To get the sources, clone the MRF repository with: ```bash git@gitlab.com:mrf-devteam/mrf.git ``` or ```bash git clone https://gitlab.com/mrf-devteam/mrf.git ``` # Building MRF The MRF Project uses Cmake for project generation. Below are the instruction to properly generate the project, as well as guidance with different approaches for running cmake and building the project. ## Project generation with Cmake When running CMake, the following variable may require manual intervention: Mandatory variables: - **OptiX_INSTALL_DIR** : should be automatically detected if OptiX is installed in default directory. If it is not, set this variable manually to the install location. - **CUDA_TOOLKIT_ROOT_DIR** : same as OptiX_INSTALL_DIR for CUDA. Optionnal variables: - **DEV_BUILD** : Windows only, "off" by default. Specify if you want to build in devevelopper mode: the resources are not copied to build directory and instead use the sources' resource folder. Set it to "on" when developping in MRF, "off" when packaging. - **BUILD_MALIA** : "on" by default. Specify if you want to build the Malia renderer along MRF. - **RENDERER_INTERACTIVE** : "on" by default when building Malia to enable the interactive mode. - **CUDA_ARCHITECTURE** : default is Kepler. The available options are given by **GPU_Arch_Values**, choose the one corresponding to your GPU for optimal perfomances. - **BUILD_SHARED_LIBS** : cmake variable to export project's libraries as dynamic librairies. Enabled by default. - **ENABLE_TEST** : disabled by default. Enable the generation of unit-test targets. Note that the variable *CUDA_SDK_ROOT_DIR* is unused and one should not worry if it is not found. Now, proceed to the configuration and generation of the project with your favorite approach.
    Using Command-line (click to expand) From your build directory: ```bash cmake ./path/to/mrf_dir/cmakelist.txt ``` At this point you should see: ![](images/dev/malia_cmake.png) If you need to specify the aforementionned variable (with Malia enabled): ```bash cmake ./path/to/mrf_srcs/cmakelist.txt -DOptiX_INSTALL_DIR=/path/to/optix_dir -DCUDA_TOOLKIT_ROOT_DIR=/path/to/cuda_dir -DBUILD_MALIA=on ``` MRF project is now generated in your build directory. You can either build it manually or using an IDE. For instruction on building if necessary, jump to: * Section Building MRF on Unices * Section Building MRF on Windows
    Using CMake-gui (click to expand) Alternatively, you can use tools like **cmake-gui** to perform this task. Enter the path (or browse) to MRF root dir in the source code input box: ![](images/dev/cmake_gui_src.png) Enter the path (or browse) to the build directory in the binaries input box: ![](images/dev/cmake_gui_build.png) Then, (1) Click **configure** and (2) choose your desired generator in the dropdown list. (3) Make sure you use a **64bit** generator: ![](images/dev/cmake_gui_configure.png) Edit any variables if needed (path to install directory, check enable Malia...) directly in the GUI. We recommend to check the *Grouped* checkbox for better understanding of the variables. If you modified anything, configure again to apply the modification. Then click on **generate**: ![](images/dev/cmake_gui_generate.png) MRF project is now generated in your build directory. You can either build it manually or using an IDE (by clicking **Open Project** if available). For instruction on building if necessary, jump to: * Section Building MRF on Unices * Section Building MRF on Windows
    Using CCMake (click to expand) Launch `ccmake` from your build directory to edit and configure the paths: ```bash ccmake ./path/to/srcs ``` After a first configure (hit `C` key) and edit (`E` key), you should get this screen: ![](images/dev/malia_ccmake.png) Make sure `CUDA_TOOLKIT_ROOT_DIR` and `OptiX_INSTALL_DIR` are properly set. Here, CUDA is installed in `/opt/cuda` and OptiX in `/opt/NVIDIA-OptiX-SDK-6.5.0-linux64`. Edit accordingly to your environment. Then, hit again `C`, and `E`. Finally hit `G` to generate the Makefile. MRF project is now generated in your build directory. You can either build it manually or using an IDE. For instruction on building if necessary, jump to: * Section Building MRF on Unices * Section Building MRF on Windows
    ## Building MRF on unices
    Command-line on Unices (click to expand) To build/install MRF, navigate to your build directory, where you generated the project file with CMake: ```bash cd build ``` You can then compile MRF: ```bash make -j 8 ``` When successful, all libaries and utility should be located in the *build/bin/* directory. Check your installation by launching malia from the command line: ``` bash cd bin ./malia ``` A window with the Cornell Box scene should appear with progressive rendering. To build only a specific target (e.g., [rgb2spec](./apps/rgb2spec/rgb2spec.md.html)), use: ```bash make rgb2spec -j 8 ``` Optionaly, you can install the binaries with: ``` make install ``` This last step will install the MRF librairies as well as Malia and small utiliies (e.g., [rgb2spec](./apps/rgb2spec/rgb2spec.md.html) or mic) in the directory CMAKE_INSTALL_PREFIX (on Unix the default value is */usr/* ) !!! Warning You need to have write permission on the CMAKE_INSTALL_PREFIX project otherwise the cmake install target will fail.
    ## Building MRF on Windows The workflows below assume that you have Microsoft Compilation Tools installed (e.g., [build tools for VS2019](https://visualstudio.microsoft.com/downloads/#build-tools-for-visual-studio-2019))
    [Optional] Install [Eigen](eigen.tuxfamily.org) if you want [rgb2spec](./apps/rgb2spec/rgb2spec.md.html) support (click to expand) - Download and decompress the Eigen archive where you want For User with administrator privilegies - Run an elevated ("Run as administrator") Native Tools Command Prompt for VS - Go to the Eigen directory - Enter the following commands in the prompt: ~~~~~~~~~~~~ bash mkdir build cd build cmake -G"NMake Makefiles" .. nmake install ~~~~~~~~~~~~ - The last command will install [Eigen](eigen.tuxfamily.org) in *C:\Program Files (x86)\Eigen3* For user *without* administrator privilegies Users without administrator privilegies should install eigen in a directory where they have writting permissions (e.g., in their home directory) - Run the Native Tools Command Prompt for VS - Go to the Eigen directory - Enter the following commands in the prompt to install Eigen in *%HOMEPATH%\eigen3*: ~~~~~~~~~~~~ bash mkdir build cd build cmake -G"NMake Makefiles" -DCMAKE_INSTALL_PREFIX=%HOMEPATH%\eigen3 .. nmake install ~~~~~~~~~~~~ - The last command will install Eigen in %HOMEPATH%\eigen3 (%HOMEPATH% is W(indows environment variable which value is the user's home directory) Finally, set the environment variable **Eigen3_DIR**, pointing to "C:\Program Files (x86)\Eigen3\share\eigen3\cmake" or "%HOMEPATH%\eigen3\cmake".
    Command-line build (click to expand) To build/install MRF, navigate to your build directory, where you generated the project file with CMake: ```bash cd build ``` You can then compile MRF: ```bash cmake --build . ``` When successful, all libaries and utility should be located in the *build/bin/* directory. Check your installation by launching malia from the command line: ``` bash cd bin ./malia ``` A window with the Cornell Box scene should appear with progressive rendering. To build only a specific target (e.g., [rgb2spec](./apps/rgb2spec/rgb2spec.md.html)), use: ```bash cmake --build . rgb2spec ``` Optionaly, you can install the binaries with: ``` cmake --install ``` This last step will install the MRF librairies as well as Malia and small utiliies (e.g., [rgb2spec](./apps/rgb2spec/rgb2spec.md.html) or mic) in the directory CMAKE_INSTALL_PREFIX (on Unix the default value is */usr/* ) !!! Warning You need to have write permission on the CMAKE_INSTALL_PREFIX project otherwise the cmake install target will fail.
    Visual Studio build (click to expand) Use `cmake-gui` to generate your project as described in Section Using CMake-gui, using your Visual Studio configuration. Either start Visual Studio by using the **Open Project** button from CMake-gui, or by opening the *mrf.sln* file in the build directory. The overview will list all the cmake targets, including the "CMakePredefinedTargets": ![](images/dev/visual_mrf_sln.png) To build any of them, right-click it and select "build". To build all of them use the "ALL_BUILD" target, and the "INSTALL" one to install libraries and executables at **CMAKE_INSTALL_PREFIX**. !!! Warning **CMAKE_INSTALL_PREFIX** points by default to either "C:\Program Files\mrf" or "C:\Program Files (x86)\mrf". Both requires administrator privilegies to write in. If you want to keep this path for installation, run Visual Studio as administrator. To build and run (not just build) as specific target like Malia, right-click it and select "Set as stratup project". Then use "Ctrl+F5" to build and run, or "F5" to build and run in debug mode. To edit the arguments used when running the target, right-click the target, select "Properties" -> "Configuration Properties" -> "Debugging" -> "Command Arguments": ![](images/dev/visual_add_args.png) Any arguments added here will be added to "target_name.exe" when running.
    Visual Studio Code (VSCode) build (click to expand) This section first describe the integration of CMake tools in VSCode, then how to build the project. ### Integrate CMake into VSCode - Start [VSCode](https://code.visualstudio.com/) and install the [CMake Tools extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cmake-tools) - Open the MRF Folder with [VSCode](https://code.visualstudio.com/) - Choose your active kit (e.g., [Clang](https://github.com/llvm/llvm-project/releases/latest), Visual Studio Community, ...) At this point the cmake-tools extension will launch cmake and start configuring the MRF project. - Open and edit the file *CMakeCache.txt* located in *MRF_DIR/build/* - Check that the CMake variable OptiX_INSTALL_DIR is set to the correct path. For example, by default, OptiX 6.5 is installed on Windows in *C:\ProgramData\NVIDIA Corportation\OptiX SDK 6.5.0* - If you did install Eigen 3, check that the CMake variable *Eigen3_DIR* is set to the correct path (either the one your chose or the default one) By default on Windows, Eigen 3 is installed in *C:\Program Files(x86)\Eigen3\* **BUT** the correct value for Eigen3_DIR:PATH is ~~~~~~~~~~~~ Bash Eigen3_DIR:PATH=C:\Program Files(x86)\Eigen3\share\eigen3\cmake ~~~~~~~~~~~~ - If you did some changes in the CMakeCache.txt file, you need to configure again your project (CMake: Configure) - Launch the build by either pressing the F7 key or pressing the button in VSCode status bar. If the compilation was successful you will have in MRF_DIR/build/bin/ all MRF executables: - *malia.exe* - *malia_rgb.exe* - *mic.exe* - *mdiff.exe* - (if Eigen was used) *rgb2spec.exe* Verify your compilation by double clicking on malia.exe and then on malia_rgb.exe, this should launch a progressive rendering with the classical Cornell Box scene. - Optional: Install MRF by launching the cmake install target. Unless you did modify the Cmake variable CMAKE_INSTALL_PREFIX (see Other CMake variables Section), this will copy all shared libraries and applications to C:\Program Files\mrf !!! Warning You need to have write permission on the CMAKE_INSTALL_PREFIX project otherwise the cmake install target will fail.
    # API Documentation * Subsection API Overview * Subsection API Doxygen * Subsection Building The API with Doxygen ## API Overview The MRF API is based on three libraries: - the core library (mrf_core). - the rendering backend library (currently only mrf_optix). - the plugins library (mrf_plugin). On top of these libraries, one may build applications, such as the Malia renderer that uses all three librairies, or Spectral Viewer that only uses mrf_core. ## mrf_core The core is the basis of the framework. It converts the content parsed from a .msf file into objects in RAM. This content is loaded into a *Scene* object, which is fed to the backend for rendering. It also implements the base classes for: - the renderer: to be inherited from by the dedicated backend renderer class. - a material: to be inherited from in a material plugin. Describes the CPU representation of a material. - a plugin parser: to be inherited from in a plugin. Describes how an element corresponding to the plugin type should be parsed ## Available backends Currently, only the OptiX backend is available in MRF. The backend should implement a *backend_renderer* object, inherited from the core *renderer*, to access the *scene* and do any related process. The backend should also implement the base classes for: - a material: to be inherited from in a material plugin. Handles backend-related tasks for the material plugin. - a plugin converter: to be inherited from in a plugin. Converts the plugin from mrf_core to the backend specific format.
    Optix (click to expand) It implements: - OptixRenderer: inherited from the base renderer of mrf_core. - OptixHandler: several handlers to manage the conversion between mrf objects to optix objects and GPU buffers. There are currently handlers for geometry, materials, and lights. The OptiX backend implements the base class for: - an optix material: to be inherited from in a material plugin. Handles backend related task for the material plugin. - a optix plugin converter: to be inherited from in a plugin. Converts the plugin from mrf_core to the backend specific format.
    ## mrf_plugins Each current plugin has a dedicated folder within the libmrf/mrf_plugins folder: libmrf/mrf_plugins/type/plugin_name where type is either material, light, camera or background. All the code specific to a plugin is located in that folder: - plugin_name.hpp/cpp: the class handling the CPU representation of the plugin (attributes, images, etc.) - plugin_parser.hpp/(optionnal)cpp: the implementation of the parser for this plugin. - (optionnal) backend/a_backend/plugin_a_backend.hpp/cpp/other: the backend-specific implementation to compile the shader and upload buffers on the GPU. - CMakeList.txt !!! Example for the optix backend: - backend/optix/plugin_optix.hpp and .cpp: the optix-specific implementation to compile the shader and upload buffers on the GPU. - backend/optix/plugin_optix.cu: the optix shader. Plugins must be registered upon starting an application. There are currently two categories of plugins: materials and cameras. Below is an overview of the API for these categories.
    Materials (click to expand) ![Example of Lambert material plugin](images/dev/material_plugin.png)
    Materials Correspondences (click to expand) Here is the matching between the MRF materials you can set up in a .msf file, their c++ storage and their Cuda implementation. Scattering materials (markup ): Name in .msf | Class in MRF | Cuda file ------------------|----------------|------ Lambert | Lambert | lambert.cu CheckerBoard | Checkerboard | checkerboard.cu PhongNormalized | LPhysicalPhong | phong.cu PerfectMirror | PerfectMirror | microfacet_conductor.cu FresnelMirror | FresnelMirror | microfacet_conductor.cu ggx | GGX | microfacet_conductor.cu Glass | Glass | microfacet_dielectric.cu FresnelGlass | FresnelGlass | microfacet_dielectric.cu walter_bsdf | WalterBSDF| microfacet_dielectric.cu MeasuredIsotropic | MeasuredIsotropic | measured_isotropic.cu Flat | FlatBRDF | DebugShader.cu Tangent | TangentShader | DebugShader.cu UV | UVShader | DebugShader.cu Normal | NormalShader | DebugShader.cu Emitting materials (markup ): Name in .msf | Class in MRF | Cuda file ------------------|----------------|------ diffuse | DiffuseEmittance | diffuse_emitter.cu uniform | UniformEmittance | TBA conic | ConicalEmittance | TBA dirac | DiracEmittance | TBA
    Lights (click to expand) WIP
    Cameras (click to expand) TODO
    Backgrounds (click to expand) TODO
    ## API Doxygen The current Doxygen documentation is available here ## Building The API with Doxygen Assuming you have installed Doxygen tool you can compile the documentation with: ``` [From MRF_DIR] cd doc/ doxygen doxyfile ``` # Adding a new Material Material are handled as plugins within MRF. Thus adding a material means creating a new plugin: - create a folder for the plugin in the libmrf/mrf_plugins/plugin_type folder. - populate the folder with the necessary files (see Section API). You can use the "template" folder as a base to build your plugin upon. ## In MRF Core The C++ class stores and represents the material parameters. Ideally, it should also implement the material evaluation for CPU backends. First, you must code a new class (*MyNewMaterial.hpp* / *MyNewMaterial.cpp*, placed in its plugin folder) that inherits either: ~~~ mrf::materials::BRDF ~~~ ~~~ mrf::materials::BSDF ~~~ ~~~ mrf::materials::Emittance ~~~ if the material is a one of these three type, otherwise: ~~~ mrf::materials::UMat ~~~ Also, add its type name in the header *MyNewMaterial.hpp*, within the namespace: ~~~ mrf::materials::BRDFTypes ~~~ ~~~ mrf::materials::BSDFTypes ~~~ ~~~ mrf::materials::EmittanceTypes ~~~ ~~~ mrf::materials::ANewType //if necessary. ~~~ Then add a parsing code in *MyNewMaterial_parser.hpp* (placed in its plugin folder). It must implement: ~~~ CPP //The static function to the char* name static std::string getID() { return mrf::materials::BRDFTypes/BSDFTypes/EmittanceTypes::NameAsChar; } //The static function to register an instance of the parser. static BasePluginParser* create() {return new TheNameOfTheParserClass();} //The parsing function. virtual bool parse(tinyxml2::XMLElement *a_mat_element, mrf::materials::UMat *a_mat, ParserHelper &parser) //The parsing function, already implement by parent class, can be overriden if necessary. virtual mrf::materials::UMat* parse(tinyxml2::XMLElement *a_mat_element, std::string &name, ParserHelper &parser) ~~~ If the plugin require specific mark up tags that are not in the mrf_core *scene_lexer.hpp*, add them in the *MyNewMaterial_parser.hpp* to load the material from its XML description. ## In OptiX-backend: C++ side As for the MRF Core side, the backend version of the plugin requires two classes: - An OptiX implementation of the plugin, inheriting from either *OptixBRDFMaterial*, *OptixBSDFMaterial* or *OptixEmittanceMaterial*. It must implement the constructors present in the parent parent and set the *_kernel_path* attribute (see the template folder for example). If the material has specific textures/buffers, the method *update*/*updateTextures* must be overriden in the class. - A plugin converter, taking a *UMat** to create an *OptixBaseMaterial**. It must implement: ~~~ CPP //The static function to register an instance of the parser. static OptixMaterialPlugin* create() {return new TheNameOfThePuginClass();} //The converter function, converting a UMat from the MRF Core into a backend representation. //In this case, it should creates variables and buffers and also compiles the kernel corresponding to the aforementionned "_kernel_path". virtual OptixBaseMaterial *createMaterialFromMRF(mrf::materials::UMat* material, optix::Context &context, std::vector<std::string> const &cuda_compile_options) ~~~ ## In Optix-backend: Kernel To implement your kernel, you can either: - code a kernel from sratch. In this case it MUST contain the function: ~~~ RT_PROGRAM void material_eval() ~~~ which is the main function of your kernel where you will handle BRDF computation and sample the propagation direction. This function will be called after a ray-hit event. The ray information, such as attenuation, direction, etc. is defined in *libmrf/backends/optix/mrf_optix_programs/include/path_tracer_definitions.h*. When coding from scratch, you can refer to *libmrf/backends/optix/mrf_optix_programs/BRDF_kernel.cu* for an exemple. In this case, you must also override, in your optix c++ material implementation, the compile function: ~~~ virtual void compile(optix::Context &context); ~~~ - code a kernel using the existing central kernel. Your kernel will be included to the central kernel and thus must provide some functions depending on wether it is a BRDF or a BSDF: - BRDF, central kernel *BRDF_kernel.cu* ~~~ CPP bool brdf_is_dirac() float bsdf_pdf(float3 ffnormal, float3 eye, float3 light, int lobe) COLOR bsdf_eval(float3 ffnormal, float3 eye, float3 light, int lobe) COLOR bsdf_eval_optim(float3 ffnormal, float3 eye, float3 light, int lobe, float external_pdf) float4 generate_reflective_sample(float3 n, float3 in_dir) ~~~ - BSDF, central kernel *BSDF_kernel.cu* ~~~ CPP bool brdf_is_dirac() float reflection_pdf(float3 normal, float3 in_dir) float bsdf_pdf(float3 ffnormal, float3 eye, float3 light, int lobe) COLOR brdf_transmittance(float length) COLOR bsdf_eval(float3 ffnormal, float3 eye, float3 light, int lobe) COLOR bsdf_eval_optim(float3 ffnormal, float3 eye, float3 light, int lobe, float external_pdf) float4 generate_reflective_sample(float3 n, float3 in_dir) float4 generate_transmissive_sample(float3 n, float3 in_dir) ~~~ In order to get the parameters of your Material from C++ to the CUDA kernel, your kernel should declare some Optix variables. Below are some valid Optix 6.x types and how to declare them in your CUDA kernel: ~~~ CPP // A simple floating point variable rtDeclareVariable(float, a_float_variable, , ); // A simple unsigned integer variable rtDeclareVariable(unsigned int, an_int_variable, , ); rtBuffer<float,1> variable_name1; // 1D buffer of floating point value // float4: 4 consecutive floating point values (aka vec4 in GLSL) rtBuffer<float4,1> variable_name2; // 1D buffer of float4 rtBuffer<float4,2> variable_name3; // 2D buffer of float4; rtBuffer<float3,3> variable_name4; // 3D buffer of float3; ~~~ A complete list of variable types can be found in the [Optix API Guide](https://raytracing-docs.nvidia.com/optix_6_0/api_6_0/html/index.html) or [Optix C++ Developer Guide](https://raytracing-docs.nvidia.com/optix_6_0/guide_6_0/index.html#optixpp#api-objects) ## Registering the material To register a plugin (replace the template name by yours), add the following: - At the bottom of the header "mrf_plugins/materials/mrf_materials.hpp": ~~~ CPP #include <mrf_plugins/materials/material_template/material_template.hpp> ~~~ - At the bottom of the header "mrf_plugins/materials/mrf_material_parsers.hpp": ~~~ CPP #include <mrf_plugins/materials/material_template/template_parser.hpp> ~~~ - At the bottom of the header "mrf_plugins/materials/optix_mat_plugins.hpp": ~~~ CPP #include <mrf_plugins/materials/material_template/backend/optix/template_optix.hpp> ~~~ - In the header "mrf_plugins/register_mrf_plugins.hpp": ~~~ CPP register_plugin<mrf::materials::UMat, mrf::io::TemplateMatParser>(ids, fcts); ~~~ At the end of the function ~~~ CPP static void register_all_mat(std::vector<std::string> &ids, std::map<std::string,BasePluginParser<mrf::materials::UMat> *> &fcts) ~~~ - In the header "mrf_plugins/register_optix_plugins.hpp": ~~~ CPP register_plugin<mrf::optix_backend::TemplateMatOptixPlugin>(mrf::materials::BRDFTypes::TEMPLATE_MAT_XML_NAME, fcts); ~~~ At the end of the function ~~~ CPP static void register_all_mat(std::map<std::string,OptixMaterialPlugin*> &fcts) ~~~ # Adding a new Camera ## Global principle All the data needed to generate a ray in stored inside the camera class. The ray generation is implemented in the __"libmrf/backends/optix/mrf_optix_programs/include/cameras.cuh"__. The data transfer from the CPU to the GPU is done by the function __"setCameraParameters"__ in the file __"libmrf/backends/optix/mrf_optix/optix_camera_handler.hpp"__. The following section explain how to implement a new camera and give the exemple of the creation of the __Environment__ camera. ## Adding a new camera type In the file __"libmrf/mrf_core/rendering/camera.hpp"__ : - Add a new camera type in the __CameraType__ enum. ~~~ CPP enum class CameraType { ... Environment, ... }; ~~~ - Complete the __CameraType2string__ and __string2CameraType__ functions based on the new type. ~~~CPP static std::string CameraType2string(const CameraType &type) { switch (type) { ... case CameraType::Environment: return "environment"; ... } } ~~~
    ~~~CPP static CameraType string2CameraType(const std::string &str) { ... if (str == "environment") return CameraType::Environment; ... } ~~~ ## Adding a new camera class - Create a class inheriting from the virtual class Camera. ~~~CPP class MRF_CORE_EXPORT Environment: public Camera { public: Environment( ... float theta, // Camera orientation float phi, // Camera orientation ...); //-------------------------------------------------------- // Interface from Camera //-------------------------------------------------------- mrf::lighting::Ray generateCameraRay(unsigned int i, unsigned int j, unsigned int k, unsigned int l) const; void generateCameraRay(unsigned int i, unsigned int j, unsigned int k, unsigned int l, mrf::lighting::Ray &ray) const; virtual mrf::math::Mat4f projMat() const; virtual mrf::math::Mat4f viewMat() const; virtual void updateInnerData(); virtual CameraType getType() const { return CameraType::Environment; } virtual Camera * copy() const { return (Camera *)new Environment(*this); } void theta(float ntheta); float theta() const; void phi(float nphi); float phi() const; private: float _theta; // Camera orientation float _phi; // Camera orientation }; ~~~ - Make sur that the method __getType__ return the type added previously in the __CameraType__ enum. - The created files should be added inside the CMakeList __"libmrf/mrf_core/rendering/CMakeLists.txt"__ ~~~ set(SOURCES ${SOURCES} ... ${CMAKE_CURRENT_LIST_DIR}/environment_camera.cpp ... ) set(HEADERS ${HEADERS} ... ${CMAKE_CURRENT_LIST_DIR}/environment_camera.hpp ... ) ~~~ ## Implementation of the camera - Implement the ray generation program in the cuda file __"libmrf/backends/optix/mrf_optix_programs/include/cameras.cuh"__ by adding a preprocessor macro inside the __ray_generation__ function. - __ray_origin__ and __ray_direction__ have to be defined here. ~~~ CPP void ray_generation(..., float3& ray_origin, float3& ray_direction){ ... // CAMERA ENVIRONMENT #elif defined(CAM_ENVIRONMENT) float2 jitter = make_float2(x - rnd(seed), y - rnd(seed)); float2 d = pixel + jitter * jitter_scale; d.y = -d.y; d.y *= 3.141593 * 0.5; d.y += theta; d.x *= 3.141593; d.x += phi - 0.5 * 3.141593; ray_origin = E; ray_direction = make_float3(sin(d.y) * cos(d.x), cos(d.y), sin(d.x) * sin(d.y)); ... #endif } ~~~ - With the macro, it is possible to declare camera specific variables. ~~~ CPP #elif defined(CAM_ENVIRONMENT) rtDeclareVariable(float, theta, , ); rtDeclareVariable(float, phi, , ); ... #endif ~~~ ## Link between MRF and the optix backend For the camera to be used, the __"libmrf/backends/optix/mrf_optix/optix_camera_handler.hpp"__ file must be completed. This file manages the link between the created camera and the optix backend. It is necessary to add a specific compilation option in the __getCameraCompileOption__ function. This will be use to select the right camera in the __"libmrf/backends/optix/mrf_optix_programs/include/cameras.cuh"__ file. ~~~CPP static std::vector getCameraCompileOptions(mrf::rendering::Camera *camera) { std::vector options; switch(camera->getType()){ ... case mrf::rendering::CameraType::Environment: options.push_back("-DCAM_ENVIRONMENT"); break; .. } return options; } ~~~ In the __setCameraParameters__ function, add the variables to be transfered to the optix context. ~~~CPP static void setCameraParameters( mrf::rendering::Camera* camera, optix::Context& context ) { ... switch (camera->getType()) { ... case mrf::rendering::CameraType::Environment: { std::cout << "Environment" << std::endl; mrf::rendering::Environment *cam = dynamic_cast(camera); context["theta"]->setFloat(cam->theta()); context["phi"]->setFloat(cam->phi()); break; } ... } ... } ~~~ ## Read camera from .mcf files To use the new type of camera, it is necessary to add in the file __"libmrf/mrf_core/io/camera_parser"__, a specific treatment for the new camera. This code is used to read the data necessary to create the camera: ~~~CPP #include ... void CameraParser::parseFile(std::string const &filename, std::unordered_map &cameras) { ... ... case CameraType::Environment: { //---------------------------------------------------------------------------- // Theta //---------------------------------------------------------------------------- float theta = 3.141593 * 0.5; try { theta = Parser::scalar_from_value(*camera_element, "theta"); } catch (...) { mrf::gui::fb::Loger::getInstance()->warn(" Could not retrieve theta value. Assigning default one"); } mrf::gui::fb::Loger::getInstance()->trace(" theta set to : ", theta); //---------------------------------------------------------------------------- // Phi //---------------------------------------------------------------------------- float phi = 0.; try { phi = Parser::scalar_from_value(*camera_element, "phi"); } catch (...) { mrf::gui::fb::Loger::getInstance()->warn(" Could not retrieve phi value. Assigning default one"); } mrf::gui::fb::Loger::getInstance()->trace(" phi set to : ", phi); // Now creating the Environment camera Camera *a_new_environment_cam = new Environment( ... theta, phi, ...); cameras[id_camera_str] = a_new_environment_cam; break; } ... ... } ~~~ Example of a camera inside an .mcf file : ~~~XML ~~~ ## Optionnal The two following steps are important for a better integration of the camera inside Malia.
    Save camera to mcf (click to expand) - Add a new case for in the function __BaseUI::saveCameraToFile__ in the file __"apps/malia/src/gui/mrf_baseUI"__. Elements previously definied in the camera class and read in the file __"libmrf/mrf_core/io/camera_parser"__ have to be writen here. ~~~CPP bool BaseUI::saveCameraToFile() { ... switch (main_camera_type) { ... case mrf::rendering::CameraType::Environment: { mrf::rendering::Environment *cam = dynamic_cast(main_camera); auto theta_elem = xmlDoc.NewElement("theta"); theta_elem->SetAttribute("value", cam->theta()); auto phi_elem = xmlDoc.NewElement("phi"); phi_elem->SetAttribute("value", cam->phi()); cam_elem->InsertEndChild(theta_elem); cam_elem->InsertEndChild(phi_elem); break; } ... } ... } ~~~
    Integrate the camera in Malia GUI (click to expand) - Add a new case for the camera in the function __InteractiveUI::controlPanel__ in the file __"apps/malia/src/gui/mrf_interactiveUI"__ to control the parameters of the camera. ~~~CPP void InteractiveUI::controlPanel(UI_MODE mode){ ... ... case mrf::rendering::CameraType::Environment: { mrf::rendering::Environment *cam = dynamic_cast(main_camera); float theta = cam->theta(); if (ImGui::SliderFloat("Theta", &theta, 0., 3.141593)) { _camera_changed = true; cam->theta(theta); } float phi = cam->phi(); if (ImGui::SliderFloat("Phi", &phi, -3.141593, 3.141593)) { _camera_changed = true; cam->phi(phi); } break; } ... ... } ~~~
    # Non-regresstion Tests in MRF * Subsection Building and executing Tests * Subsection Unit Tests * Subsection Tests Coverage * Subsection Rendering Tests ## Building and executing Tests MRF uses CTest to launch the different tests. Tests source code are located in `MRF_DIR/unit_tests/`. To build and run MRF tests you need to set the variable `ENABLE_TEST=ON` when executing CMake to generate the test targets. - Command-line: - Build MRF: ``` make -j 8 ``` - Execute the tests: ``` ctest ``` - Visual Studio: - Build MRF by running the "ALL_BUILD" target. - Run the tests by building the target "RUN_TESTS". If everything is correct you should see something like (X may vary depending on the version) : ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ shell 100% tests passed, 0 tests failed out of X Total Test time (real) = 0.28 sec ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ## Unit Tests The unit tests are executed in the CI process. A branch shall not be merged if does not successfully pass all of them. All unit tests are located in subfolders the `MRF_DIR/unit_tests/` folder, with the exception of the `MRF_DIR/unit_tests/rendering` folder, which are rendering tests (see below). ## Tests Coverage The coverage report generated by gcov is available [here](https://gitlab.com/mrf-devteam/mrf/-/jobs/artifacts/master/file/doc/coverage_report/cov_report.html?job=test_coverage_ubuntu_20_04). This report represents *only* the coverage of the C/C++ (not GLSL nor CUDA !) code. It is a recommended practice to add a unit test each time you add a class. When modifying an existing class, add code in its unit test to maintain the percentage of code coverage. ### Using GCOV locally !!! Warning You need to build MRF with GCC, which provides gcov. !!! Windows users may install GCC with [MSYS2](https://www.msys2.org/) To visualize locally the unit tests coverage, you use gcov locally to generate the report. To get the report, you can use the following commands: ``` cd YOUR_MRF_BUILD_DIRECTORY mkdir -p ../doc/coverage_report/ ctest gcovr -r ../libmrf/ . -b --exclude '(.+/)?externals.*$' -exclude-unreachable-branch -exclude-throw-branches gcovr -r ../libmrf/ . -b --html-details -o cov_report.html --html-title MRF --exclude '(.+/)?externals.*$' -exclude-unreachable-branch -exclude-throw-branches mv *.html ../doc/coverage_report/ ``` Now open the file `../doc/coverage_report/cov_report.html` in your favorite browser. Remember that you must have build and exectuted the tests (see Subsection Building and executing Tests). ## Rendering Tests The rendering tests are intended to ensure that a piece of code does not introduce regressions in the rendering process. Each one contains reference images for a use case, the output images of the tests are compared against the reference to detect regression. All rendering tests are located in the `MRF_DIR/unit_tests/rendering` folder. A python script (`render.py`) is located in this folder and use by all rendering test to execute malia and generate the output and to execute mdiff to compare with the reference. Currently, there are two categories of rendering test: - materials: to test each implemented material (`materials` subfolder). - cameras: to test each implemented camera (`cameras` folder). These rendering tests must be executed manually by the developper, integration within the CI process will be added later. A branch shall not be merged if does not successfully pass all of them.
    Adding a test (click to expand) A test consists of: - a minimal scene file (e.g., a sphere with a material on a plane with an area light) and camera file. - some use-case specific assets (some are commonly available to all tests). The assets should be as light as possible (analytic shape, minimal textures, etc.). Moreover, images (output and reference) must have a spatial and spectral resolution as small as possible. Each one should have a dedicated subfolder containing: - a `sene` subfolder, with the scene and camera files, and assets if necessary. There may be more that one scene to test for multiple configurations. - an `output` subfolder, where the output(s) of the rendering shall be saved. - a `reference` subfolder, where the reference image(s) are. #### Registering the test It then must be registered in the `CMakeLists.txt` file located in the `MRF_DIR/unit_tests/rendering` folder by adding: ~~~ non_regression_test(type name) ~~~ in the block: ~~~ #Registering the rendering tests if(${BUILD_MALIA}) #CAMERAS non_regression_test(cameras pinhole) #SOME OTHER TEST HERE #MATERIALS non_regression_test(materials all) #SOME OTHER TEST HERE #Add your test where fitted endif() ~~~ with: - "type" being either `cameras`, `materials` or another/a new one if necessary. - "name" being the name of the dedicated subfolder (e.g., `phong` for test with the phong material). Also, it must be added to the batch generation of references by adding: ~~~ regen_references_rgb(target type name) regen_references_spectral(target type name) ~~~ in the block: ~~~ #Registering the targets and function to regenerates reference images in batch. if(${BUILD_MALIA}) #CAMERAS add_custom_target(regen_references_camera_rgb) regen_references_rgb(regen_references_camera_rgb cameras pinhole) #SOME OTHER TEST HERE add_custom_target(regen_references_camera_spectral) regen_references_spectral(regen_references_camera_spectral cameras pinhole) #SOME OTHER TEST HERE #MATERIALS add_custom_target(regen_references_materials_rgb) regen_references_rgb(regen_references_materials_rgb materials all) #SOME OTHER TEST HERE add_custom_target(regen_references_materials_spectral) regen_references_spectral(regen_references_materials_spectral materials all) #SOME OTHER TEST HERE endif() ~~~ with: - "target" being one the custom targets specified here, or a new one if necessary. - "type" being either `cameras`, `materials` or another/a new one if necessary. - "name" being the name of the dedicated subfolder (e.g., `phong` for test with the phong material). #### Running tests manually For debugging purposes, a test may be run manually using the `render.py`: ~~~ shell python relative_path_to/render.py --app_path "path_to/MRF/bin_folder" --scene_path "path_to/current_test/scene/" --output "path_to/current_test/output/" --references "path_to/current_test/reference/" --spectral --compare ~~~ Note that: - `app_path` is the path to the folder containing the malia and mdiff executables. - `scene_path` is the path to the folder containing the test scene(s) and its assets. - `output` is the path to the folder where the ouput of malia should be saved. - `reference` is the path to the folder where the reference images are located. - `spectral` specifies that the rendering is done in spectral, for RGB, omit this option. - `compare` specifies that the script should perform the comparison. All pathes MUST point to folders and not specific files.
    Generate reference images (click to expand) To generate the reference images for a test, one should use the rendering script `render.py` as follow: ~~~ shell python relative_path_to/render.py --app_path "path_to/MRF/bin_folder" --scene_path "path_to/current_test/scene/" --output "path_to/current_test/reference/" --spectral ~~~ Note that: - `app_path` is the path to the folder CONTAINING the malia executable. It is NOT the full path OF an executable. - `scene_path` is the path to the folder CONTAINING the test scene(s) and its assets. It is NOT the full path OF an scene file. - `output` is the path to the folder where the ouput of malia should be saved. In this case, we save it in the reference folder. - `spectral` specifies that the rendering is done in spectral, for RGB, omit this option.
    # Submitting a merge-request Before submitting a merge-request through the gitlab interface, you **must** format your submitted code by running the cmake clang-format target: ``` make clang-format ``` Once this is done. Commit all modified files with a specific message (e.g., "Formatting to code conventions"). !!! If you forget to format your code, a CI job will detect it and raise an error making it impossible to merge your request # Reporting Bugs Bug reports are always welcome. They should be reported through the Gitlab Interface by opening a new [issue](https://gitlab.com/mrf-devteam/mrf/-/issues). # Known Bugs - Reading a ply exported from blender crashes tinyply in sceneparser - Bug readSpd in spectum.cpp: need to setlocale to US and reset to previous state after reading. The bug was detected using [SpectralViewer](https://gitlab.com/mrf-devteam/spectral-viewer) on Linux.