Tag Archive : open-source

/ open-source

Ogre_glTF: A glTF loader plugin for Ogre 2.x

18 February 2019 | C++, glTF, Ogre | No Comments

If there’s one open source library that I really like and think has a great level of usefulness for both myself, and a whole community, it’s Ogre.

Before going on the story of why I felt loading glTF files into ogre was a necessary thing to do, and why I decided to actually write a loader myself, I need to tell you a bit about Ogre:

Good old Ogre3D

I prefer to write it “Ogre”, but really, it’s name is OGRE. This is an acronym that stands for “Object-oriented Graphical Rendering Engine”.

Ogre existed for ages, and is a well regarded 3D rendering engine. The main advantages of Ogre are:

  • Its code base is stable,
  • It’s full of features,
  • It’s cross-platform,
  • It only exposes to you a simple scene graph, abstracting away any graphics API the system is using,
  • Has a simple “resource manager” to get your 3D objects and textures,
  • And is graphics API agnostic. Through Ogre’s public API you can choose the backend (called a “RenderSystem”) to use between OpenGL, Direct 3D, Metal…

This last point means that when using Ogre’s public API, you don’t need to know about the way to talk to the GPU, and your code should work the same whenever the rendering is happening thanks to OpenGL, Direct3D, Metal, or whatever else it could be supporting.

Recently (few years ago now), Ogre development has been pushed in a new direction in a parallel branch that is called Ogre 2.x. As far as I can tell, this is mostly the work of Matías N. Goldberg.

This new branch is focused on modernizing Ogre. Doing so by removing a lot of overhead from the original design. (Ogre was really “design pattern” focused, exposing listeners for pretty much anything, lots of virtual calls…) This is done in several ways: by adopting new best practices for performance like starting to use concept of DOD, using half precision floating point number where it doesn’t hurt, improving usage of caches, using AZDO (Approaching Zero Driver Overhead) techniques in the renderers, remove pointless preprocessing when modern hardware where now the more simpler and direct approaches can be faster… And a lot of things I’m less familiar with to be honest.

But the performance boost can be really significant, notably on CPU bound situations, Ogre now does really better usage of your CPU time in large scenes with instantiated objects and lots of nodes in the scene graph to go through.

What I was really interested about when upgrading to Ogre 2.1 was the introduction of a new “material” system in Ogre 2. It’s called HLMS, and stands for the “High Level Material System”. It is a shader preprocessor/template system where the definition of a material will generate the shader code needed to render it, using a a set of fixed parameters.

This replaces the older “dynamically generated” shader system Ogre used to have called RTSS (Run-Time Shader System). One of the main advantages of HLMS is a really simplified syntax for the end user, and the fact that it’s main goal is not to emulate an old fixed-function pipeline, like RTSS was originally conceived for.

Speaking of modernity, HLMS comes with two standard implementation (You can modify them/write your own if you know shader programming): Unlit and PBS. (with a “mobile” versions of each)

Unlit, like it’s name suggest, is the simplest thing a material can do. It only defines the color values of pixels, and doesn’t do any shading. This is intended to be used for things like UI interface, HUDs, overlays, things along those lines

PBS stands for Physically Based Shading. This is what most of the industry would refer to as “PBR”. (R for “Rendering”. There’s a distinction between them, Shading is one part of “Rendering” in general). The HLMS PBS implementation in Ogre 2.x is comparable to the standard PBR material you’ll find in Unreal Engine, and many other. It uses standard physical parameters to describe how light interact with the surface.

This is all fine and dandy, but, at least at the time I started working on the glTF plugin (about a year ago now), there was no way to export, for example, from blender, a Ogre 2.x “.mesh” file with an accompanying “.material” that would be compatible with the new HLMS system.

This would have not been a problem if Ogre could just load glTF assets. As explained in my previous article, glTF is a 3D file format that represent 3D assets (and even full scenes if you want!) including meshes, animations, textures and PBR Material. The material in question using a standar “metal-roughness” workflow (more explanations on the excellent guide from Allegorithmic, the makers of Substance).

glTF being an industry standard file format, we don’t need to rely on an up-to-date exporter for multiple 3D modeling software being able to produce files compatible with the current version of Ogre, and being able to correctly translate material definitions and animations tracks to Ogre.

This is what I thought, and this is the reason why I started working on a glTF loader for Ogre.

Loading glTF in practice

A glTF asset is either one or multiple files that are composed of 3 parts:

  • A JSON object containing metadata about the scenes, nodes, meshes, material, animation and everything inside the asset
  • Binary buffers containing raw vertex/index/animation data
  • Image assets containing textures

A .gltf file is a simple JSON file, either containing base64, data URI, or URLs pointing to binary buffers (generally in .bin files) and images.

A .glb file is a binary file containing a standard header, followed by the JSON metadata in ASCII text, followed by every binary data required (including images).

Poster explaining how a glTF asset is structured

In my Ogre_glTF plugin, I choose to rely on TinyGLTF to handle the parsing and loading of the images and binary data. TinyGLTF is header only and itself uses STB libraries for image loading and (currently, this is subject to change relatively soon) Niels Lohmann JSON library for modern C++.

To avoid any problems with the fact that TinyGLTF will include a JSON loader and other libraries as header only, and that the plugin will need to enclose the implementation of these things, the plugin follows the pimpl idiom. The only headers required are the ones shipped with the built plugin, and all the internal implementation is inside the dynamic library.

The plugin itself is built as a standard dynamic library that has a pluginized for Ogre afterwards. This permit you to either dynamically load it as an Ogre plugin (and maybe have conditional support for it) or to link it as a regular dynamic library and bypass the Ogre plugin system all together.

The repository for the Ogre plugin is hosted at https://github.com/Ybalrid/Ogre_glTF

Code is hosted on GitHub, under the MIT license, and at the time of writing this, we are at this commit.

I expect most people to use it as an Ogre plugin (as it is way simpler. You will probably just need to copy a few header files around and add a line in a “plugins.cfg” if you are using the regular Ogre framework for configuration). There are two example programs that loads random files chosen from the sample collection from Khronos, one that use the library by itself, and another one that loads it as a plugin.

The plugin is currently alpha software quality, but is promising. Iis available in a version 0.2.1 at the time or writing. and is compatible with the head of the v2-1 branch of Ogre.

Plans for the future is to rewrite the texture and material loading code to make it compatible with Ogre v2-2, but I cannot give an ETA on this.

The plugin is for now limited into being a “model” loader, intended as using .glb files as replacement for Ogre “.mesh + .skeleton + .material” classic resource format.

The plugin’s functionality includes:

  • Loading glTF meshes and create v2 “mesh” objects in Ogre MeshManager (including Draco compressed meshes).
  • Loading glTF skin metadata and create Ogre skeleton objects.
  • Loading glTF animations track for a given skeleton and adding them as skeleton animations into Ogre.
  • Loading glTF materials (standard metal-rougness workflow) and create HlmsPbs Datablock for Ogre v2. The plugin doesn’t support the Specular-Glossiness glTF extension.
  • Loading images in glTF assets and create texture out of them.
  • “Remuxing” textures from glTF into texture usable by HlmsPbs. Including separating the metalness and roughness maps into gray-scale images.
  • Adding a ResouceManager in Ogre’s ResourceGroupManager that retrieve .glb files from Ogre’s resource groups and get them through the “load mesh – load textures – load materials” pipeline to get Ogre Meshes and Items.

This is the most useful open-source project that I have ever started, and the one that has received the most contribution and interest from the community. I am really pleased with that yet.

For the projects future development, there’s a number of features that would be really valuable to have in the plugin, notably the support for a number of glTF extensions that may be used often in future assets, notably ones directly developed by khronos:

  • KHR_materials_pbrSpecularGlossiness to support the other common PBR workflow using specular and gloss values
  • KHR_materials_unlit to translate unlit materials into HlmsUnlit datablocks
  • KHR_texture_transform to be able to factor a scale and translation when reading texture coordinates, to load some types of texture atlases

I may also extend the scope of this plugin to make it a scene loader, not just an “object” loader. This would allow to directly use a 3D modeling software to create scenes and environments, but I haven’t decided on that.

And the only real missing feature is the loading of morph targets animations. Currently Ogre 2.x does not support animation targets in the v2-1 or v2-2 branches. I know somebody was working on this some time ago, and had support for that in OpenGL and metal, but last time I’ve checked this person had closed his pull request.

Sometimes I wonder why some things are inside the C and C++ standard libraries, and some aren’t.

As far as I can be bothered to read the actual “standards” document (that are mostly written in legalise, not in understandable english for you and me). Theses languages are defined against an “abstract machine”, and the actual real-world implementation of them, on computers that actually exists, should follow the behavior described for that thing, modulo some implementations details.

Beside the specific case of having theses languages in a “free standing environment” (meaning that the code written isn’t actually relying on being executed inside an operating system, but is directly running on the bare hardware), It seems that some notions, like the OS having a “filesystem” where textual paths can points to files that can be opened, read and modified, is pretty standard.

What is strange about that is, if the notion of files and file systems are part of the standard library for both C and C++, (and were present since the beginning) networking sockets doesn’t exist in both languages. And C++ gained the notion of creating multiple threads of execution, and manipulating them inside it’s standard library only in 2011.

All of theses concepts : files, threads, and sockets, are Operating System specific constructs. Opening a file on Linux is fairly different that opening a file on Windows for example. But the standard library offer a single, unique, and portable way of doing so.

These three things are present in all operating systems in use for the past decades now (since the 70’s at least?). I find it strange that the C standard library only includes files. Since C++ now also has threads, I will consider this a non-issue. So let’s talk about the other one…

A song of files and sockets

I would like to take some time to discuss writing some low-level network code in C++. The current interface to get data to and from a network we know today are using a notion called sockets.

For lack of a better analogy, a socket can be thought as some kind of “magical file” that, when, written into, will send the bytes that has been written to on the network, and when receiving bytes, they will be accessible by reading said file. This notion comes from the UNIX world, where everything is effectively a file. And nothing is wrong with that, it’s actually a really simple, straightforward way of doing things.

Most of the operating systems that matters today are using this analogy. When I say OSes that matters today, I’m thinking of both the modern UNIX derivatives (Linux, macOS, and the rest of the BSD family). And Microsoft Windows.

The Windows socket API has been mostly borrowed from BSD anyway, if you remove some oddities like a few renamed functions, a few changed data types, and the added initialization procedure, Windows sockets are equivalent of sockets you have on Linux.

100% non-standard code

But, none of this exist inside the standard library of these languages. When you are doing socket programming on Linux, you are not calling functions of the library, you are performing Linux Kernel System calls, and you are dealing with file descriptors and bytes.

On Windows, you are calling part of the Win32 API (called WSA for Windows Socket API). This situation is unfortunate, because it means that I, as a C++ programmer, I need to make sure that my code will work both under Linux and under Windows. There’s no one single networking API that I can use everywhere without thinking about it. Sure, it’s 80% similar, or maybe 90% similar, but still, if I need to put #ifdef WIN32 in my code for something as fundamental as sending bytes to another computer in a network in 2018, we are doing something wrong.

Moreover, all theses OS-level API are implemented in C. Not C++. This means that everything you have are functions and structures. When describing socket configuration, you are filling structures and passing them to functions. When you have to reference a specific socket, you need to keep a little token and give it to a functions. When you need to read data, you need to have a buffer with the correct amount of bytes ready and give a pointer, alongside a variable containing the size of said buffer to a function, and make sure that you don’t mix them together.

Basically, you are doing 1970’s level computer science. This is fine for the lowest level code out there, but not for the code of an application.

Unnecessary added complexity

There are solutions to solve this, and some that are even well advanced in the path of getting standardized inside the C++ language, like Boost.Asio. But, In my humble opinion, there’s a fundamental problem with Asio itself: it is Asio.

For those who aren’t familiar with Asio, it’s name stands for “asynchronous input and output”. It’s a library, a good library for what it’s name stands for: doing intput and output in an asynchronous manner. For doing this, Asio as multiple contexts and constructs to deal with multiple threads and non blocking calls, and who executes them, and when they are executed.

The problem is: If I just want to connect to the network and exchange data, do I need to worry about io-context and handlers, and executors? Probably not.

Keep It Simple, Stupid.

In the C++ world, we struggle at keeping simple thing simple. The collection of libraries from the Boost project is one good example. Don’t get me wrong, these are high-quality, peer-reviewed C++ libraries. They are good code written by smart people, with the seal of approval by other smart people.

They are a demonstration of what you can do when you want to push the language forward. They contains a lot of useful pieces of generic code that you can reuse. A lot of really important and useful things from Boost finally landed inside the C++ standard library since 2011, like smart pointers, chrono, array, lambdas, and probably a lot more things like that are going to jump from existing on boost, to being implemented inside the standard.

And, if today you ask for a recommendation of something to use to do networking code in C++, I’m almost sure that you’ll get pointed to either Asio, the version of Asio inside of Boost, or, the Networking TS (addition to the standard that will probably land in a future version of the C++ language) that is… Basically based on Asio.

As you can guess… It’s not like I don’t like Asio, I find it genuinely interesting, and potentially useful. But I’m unsure it is the thing that I want to see standardized.

As stated earlier, if you’re just going to do some TCP/UDP communication, Asio comes wrapped in unnecessarily complexity.

Moreover, your OS comes with a socket API, but it isn’t super convenient to use in C++, and it’s not portable without doing the ugly #ifdef preprocessor dance.

Few months ago, I was thinking about this situation, and thought why not just wrap the os library in a nice C++17 interface?

Introducing kissnet

Kissnet is a little personal project I started during the summer, and that I tweak a little from time to time, it’s mostly for fun, but I feel like some people could have some use for something like this.

The design goals of kissnet are pretty straightforward:

  • Be a single header library, nothing to link against
  • Be a single API for all supported operating systems
  • Use C++17 std::byte as a “safe” way of representing non-arthmetic binary data
  • Be the lighthest/thinest possible layer on top of the operating system as possible
  • Handle all (or most) of what TCP and UDP sockets can do in ipv4/v6
  • Don’t require the user to worry about the endianess of the network layer.
  • Just transport the bytes, and do nothing else
  • Hide os-specific weirdness
  • Optional exception support (You can chose to replace throwing exception with the program either aborting or calling your own error handler)
  • stay simple

Kissnet only implement 3 kinds of objects:

  • A socket class. The behavior of the socked is templated around the protocol used (TCP vs UDP) and the version of IP used (ipv4 vs ipv6)
  • An endpoint class that permit you to specify a host and port as just a string and a number, or a “hostname:port” string.
  • A “buffer<size>” class that is just syntactic sugar around an std::array<std::byte, size>

Buffer are for holding received data, buffer know their own size, and can read the correct amount of bytes for you. Kissnet doesn’t care what data is sent, it’s not kissnet job.

Sockets are non-copyable (but movable) objects and they have the typical operations you can apply on socket implemented (bind, listen, accept, connect, send, receive).

Kissnet automatically manages the initialization/de initialization of the underlying socket API if needed (like on windows). This is done by exploiting the reference-counting of std::shared_ptr<>. This is the only overhead on top of holding a socket “file descriptor” ( = an simple integer variable).

I’ve only used kissnet in a couple of toy programs and not in a real project, however, I already think that I prefer this simple, down to the metal but yet type-safe and cross platform library as using something like Asio. Asio feels like using a bazooka to do kill a fly. I’ve heard a few opinions going the same way as I do. This is why I’ve put this little experimental project on GitHub, under a permissive MIT license.