Skip to content

RenderSystem

Jacco Bikker edited this page Jul 16, 2019 · 5 revisions

RenderSystem Overview

The RenderSystem has the following responsibilities:

  • loading and storing a host-side copy of the scene;
  • run-time scene management;
  • providing the render core with an up-to-date copy of the scene;
  • handling communication between the application and the core.

To the application, the RenderSystem is 'the rendering engine', which provides a camera, a render function and statistics on the rendering process. For actual rendering however the RenderSystem depends on a render core, which will visualize a scene using GPU hardware or other means.

Host-scene scene versus core scene

A scene consists of typical 3D engine objects:

  • meshes and instances
  • textures and materials
  • lights (various types), a skydome and a camera.

In most cases the RenderSystem distinguishes host objects and core objects. E.g., a HostMaterial, which differs from the CoreMaterial (defined in common_classes.h): the host version is optimized for accurate material representation and includes methods for conversion from imported materials (e.g. tinypbj::material_t, tinygltf::Material), while the core version is optimized for efficient rendering. This potentially happens at the expense of readability: although the RenderSystem is designed to be easily maintained, the render cores need to be as fast as possible.

The host and core classes also help to identify where an object resides. A host-side mesh can be changed without affecting the core-side mesh immediately, and a texture may not even be stored on the host once it has been uploaded to a core.

Tracking changes

The RenderSystem contains a single method for synchronizing the host-side copy of the scene with the core: SynchronizeSceneData. This method will update all objects that changed since the previous update. Changes to scene objects are tracked automatically, using a 64-bit crc. The checksum replaces tracking of dirty objects, and allows for the use of direct member access for scene objects. This is a deliberate design choice: the lack of get/set methods yields compact code and acknowledges the responsibility of the developer.

Rendering

Apart from SynchronizeSceneData, the Render method is arguably the most important function of the RenderSystem. It takes a ViewPyramid and a flag that indicates whether the core is expected to converge the image. The RenderSystem passes the request on to the render core without any processing: by the time a render command is invoked, the job of the RenderSystem is already done.

Settings

Just before passing control to the render core, the Render method will pass settings to the core. To acknowledge that not all settings are relevant to all cores, settings are passed with a string as identifier. Cores can thus accept any setting, and ignore any setting. The idea is that future cores may require settings that the current set of cores did not anticipate; the settings system makes the cores future-proof.

API

The RenderSyetem implements the following API:

static RenderAPI* CreateRenderAPI( const char* dllName );

Loads a core. A single core can be active. Changing cores at run-time is not guaranteed to work yet.

void SerializeMaterials( const char* xmlFile );
void DeserializeMaterials( const char* xmlFile );

Save or load the current set of materials. These functions exist because most supported file formats do not support all the material properties that Lighthouse 2 supports. The serialization functions allow run-time changes to be persistent.

void DeserializeCamera( const char* camera );
void SerializeCamera( const char* camera );

Like the materials, the camera can be stored to and restored from a human-readable xml file.

int AddMesh( const char* file, const char* dir, const float scale );

The AddMesh function invokes the appropriate importer to add a mesh to the system. Supported formats are: obj, gltf and fbx. It is expected that gltf will be the format of choice in future releases of Lighthouse 2.

int AddQuad( const float3 N, const float3 pos, const float width, const float height, const int material );

Creating geometry from code is feasible, and for light sources, often desirable. The AddQuad function simply creates a quad, which can be used as a floor plane or as a light source. Note that the quad can be oriented using an arbitrary transform after instantiation.

int AddInstance( const int meshId, const mat4& transform = mat4() );
void SetInstanceTransform( const int instId, const mat4& transform );

Meshes do not show up in a scene unless an instance is added. One mesh can be instanced multiple times. An instance can use an arbitrary transform stored in a 4x4 matrix. The mesh/instace model is supported by Optix, Optix Prime, Embree and RadeonRays.

void SynchronizeSceneData();

Synchronize the current host-side scene with the render core.

void Render( Convergence converge );

Invoke the render core.

Camera* GetCamera();

Get the scene camera. This is mostly for convenience: the scene is not limited to a single camera, and cameras are not directly used for rendering (a ViewPyramid is used instead). For simple applications a single camera is intuitive however.

int AddMaterial( const float3 color );

Like geometry, materials can be created from code. The method takes a base color, but after this, any of the material properties can be altered by directly accessing the data members. Any change to a material will trigger synchronization at the next call to SynchronizeSceneData.

int GetTriangleMaterialID( const int triId, const int instId );
HostMaterial* GetTriangleMaterial( const int triId, const int instId );

A host material can be retrieved using its identifier or using a direct pointer. Obtaining materials is typically used with the 'probe' functionality.

HostMaterial* GetMaterial( const int matId );
int FindMaterialID( const char* name );

Materials can also be obtained directly, by id or by name.

int AddPointLight( ... );
int AddSpotLight( ... );
int AddDirectionalLight( ... );

Besides area lights (which can be created using AddQuad) the scene may contain point lights, spot lights and directional lights. Not all cores may support all light types (or any lighting at all).

void SetTarget( GLTexture* tex, const uint spp );

All cores are expected to render to an OpenGL texture, for efficiency reasons. Path tracing cores may need to allocate room for various buffers based on 'samples per pixel'; hence this information is passed here as well.

void SetProbePos( const int2 pos );

This method asks the core to record intersection information for the specified pixel. After the Render call completes, the result of the query can be found in the core statistics.

CoreStats GetCoreStats();

Core statistics typically include ray counts, and, if the core supports it, the result of a pixel probe.

Clone this wiki locally