General purpose engine written in C++ with emphasis on materials rendering (PBR, clear coat, anisotropy, iridescence)
I integrated into the engine a physics assignment I had to do in university... The physics part will stay like this for some time, as I have to finish up with the PBR project for university. I hope to be able to add more stuff soon, I really enjoyed programming physics and collisions! :^)
Changelog at the end!
One of the key parts of building a PBR material model is being able to capture the environment and process it so that you can later use it to calculate indirect lighting, specular reflections, and others. In my case for example, I need to get irradiance maps out of a cubemap. This kind of process is quite expensive and cannot be done each frame (note: It can be done quicker using other methods involving Spherical Harmonics), that is why I need to have a way in the engine to handle pre-processing (at least that is what I call that!).
From my point of view, the kind of pre-processing I have in mind is very similar to a post-processing effect (in the way that both have to render a material, but they may need some textures to output the result, a framebuffer), only that the pre-process is done once (or once every a number of frames).
The last time I had to implement a post/pre-processing pipeline, I decided to make all of the actual post-process objects inherit from the Material
object, as the only difference (mostly) would be the need to add a framebuffer object and some helper functions to retrieve the texture outputs.
This ended up creating some confusion, because even though they were so similar, you cannot replace any material with a post-processing material, it just won't work (and it does not need to either...) and vice versa. So this time I decided to make them different, unrelated objects: Material
and RenderPass
. They both implement similar functionalities, and they both use the Material / MaterialInstance
workflow I wrote about in the last Release notes.
The Composer is an object that I really like to have in an engine, but it can be tricky to make it properly (and properly flexible). It is still not finished, just a simple version of it is added at the moment, but the main idea is for it to be an easy-to-use post-processing pipeline organizer.
It has to be capable of receiving new RenderPass
instances with an specified relation with each other and create a queue of execution that is updated automatically.
What I mean by specified relations is that each pass takes some input textures that have been previously set, but others will come from the actual scene, or other passes' results.
For example, you having this simple pipeline:
The SSAO-Compute pass needs receive the scene as an input, and it will output an ambient occlusion texture that the SSAO-Blur pass will process. But the SSAO-Blend needs to have not only the original scene texture, but the SSAO-Blur blurred SSAO texture as well in order to blend them in some way and output the final scene to the screen.
Apart from this basic concatenating stuff, it also manages the actual pipeline, so different composers can be created with relative ease to implement forward, deferred and other custom pipelines, and replace them with a click!
I am still not 100% sure how I will create this dependencies in an easy-to-expand, readable manner, but you will most likely know soon. Last time I did it, I managed to make it very readable and easy to configure, but no user-expansion was contemplated.
Quite an important change I've been wanting to do for a while was to separate the actual GPU material from its settings completely (color, textures, and other uniforms). I am quite happy with how it turned out, but it will surely undergo some more changes in the future as I really get used to it, but at the moment this is how it stands:
Creating materials and their instances goes as follows:
class Unlit : public Material
{
VXR_OBJECT(Unlit, Material);
public:
Unlit();
class Instance : public MaterialInstance
{
VXR_OBJECT(Instance, MaterialInstance);
public:
Instance();
virtual void onGUI() override;
void set_color(Color color);
Color color() const;
};
};
The Unlit()
constructor contains the actual gpu shader data, while the Instance()
constructor just needs to initialize itself with the name of the material (textures could be initialized here if wanted, as its a per-instance operation (a single MaterialInstance
can be used in multiple objects!)):
Unlit::Unlit()
{
set_name("Unlit");
set_shaders("unlit.vert", "unlit.frag");
set_num_textures(0);
set_uniforms_enabled(true);
set_uniforms_name("Unlit");
}
Unlit::Instance::Instance()
{
init("Unlit");
}
The AssetManager
comes to play in the Instance()
constructor, as the actual material reference will be retrieved via the manager. In order for a new user-created Material to be considered by the manager, you just need to make the following call:
Engine::ref().assetManager()->addMaterial<MyNewMaterialClass>();
In the future, materials will be loaded from files in the assets folder, and the process will be even more automatic!
Some useful material instances have been added as engine provided materials, and more will shortly join the crew! At the moment Screen, Unlit, Wireframe, Skybox and Standard (lit) are available.
What is cool about the System, is that more complex instances can be created by initializing more than one material in them. This comes in handy for example when using materials that can be rendered using a flat color, a 2D texture, a CubeMap texture, or any other type. This is the case of the Standard material, which is actually 3 different materials (Standard, Standard::Textured and Standard::TexturedCubemap) but a single instance class. Depending on which texture you set it to render (or if you set a texture at all) a material will be selected internally.
You can see this example here:
And for an example of use, check: https://github.com/avilapa/vxr/blob/master/examples/dev/dev.cpp#L59
Material
/ MaterialInstance
workflow.set_uniforms_enabled(false)
.graphics
.Getting ready for Christmas! And getting ready for going physically based, really.
#version 330
tag.getPosition()
are provided for its use.setupWorldSpaceOutput()
, setupScreenSpaceOutput()
) or setup each of the values separately (setupPositionOutput()
, setupNormalOutput(vec3())
).getClipPosition()
.out fragColor
. Output color can be set using setFragmentColor(vec3 / vec4)
.computeLightContribution()
have been added. At the moment, not much functionality is available, but soon a lot of other functions will follow!This is a small update, mainly to provide fixes to some misleading error messages, and to improve the documentation of the examples.