Rendering basics
This case outlines the main building blocks of the SKY ENGINE AI rendering system.
Agenda:
- Rendering script basic elements
- Scene layout
- Geometry
- Material
- Lights
- Background
- Appendix: Camera
In this tutorial you will learn how to set up the script elements to render default scene, as well as scene loaded
from the prepared geometry files. You will get to know how to prepare a typical pipeline for rendering and
elements that can be changed.
Rendering script basic elements
In order to render a scene, you will need a couple of objects created for this sole task. These are the elements
that have to be set up before any time you call render function. Don't worry if you won't remember it though - you
can come back to this tutorial any time, and you will see those functions again several times.
Logger and root paths
In the first lines of the script, we recommend configuring the logger before anything else, this will help you
track possible errors and warnings.
The most important object in our rendering engine is the Renderer Context, and it will set it up in a second. To
initialize it, we need to specify root paths of assets, cache, configs and GPU sources (None means default).
Renderer Context
The SKY ENGINE AI renderer system is context-based. The RendererContext is responsible for memory,
state and randomization management. It stores all units, unit blueprints, possible configurations,
communicates with GPU and synchronizes the whole rendering process.
Example Assistant and Display Config
For convenience, you can create an ExampleAssistant. This is a utility class, which is used to handle
visualization. As with every other object, it is bound with RendererContext.
ExampleAssistant and is configured with the DisplayConfig.
Now that we explained all elements above...
... we will initialize them for you, so you don't have to be worried about unnecessary initialization.
The initialization of the above elements is hidden in the constructor of the class called SceneComposer.
Therefore, to focus only on the most important aspects of the rendering pipeline, we created new class derived
from SceneComposer. In this class all initialization, as well as logging and rendering functions are already
written in the base class, all that is left to do is to write function setting up the scene.
With SceneComposer, the initialization is equal to calling custom SceneComposer constructor.
from skyrenderer.cases.utils import RenderingBasicsSceneComposer
In the beginning, there was nothing.
scene_composer = RenderingBasicsSceneComposer()
scene_composer.visualize()
2025-01-30 16:45:38,454 | skyrenderer.scene.renderer_context | INFO: Root paths:
- root path: /dli/skyenvironment/skyrenderer/skyrenderer
- assets path: /dli/mount/assets
- config path: /dli/skyenvironment/skyrenderer/skyrenderer/config
- gpu sources path: /dli/skyenvironment/skyrenderer/skyrenderer/optix_sources/sources
- cache path: /dli/mount/cache
- ptx cache path: compiled_ptx/ptx
- ocio path: ocio_configs/aces_1.2/config.ocio
2025-01-30 16:45:38,483 | skyrenderer.scene.renderer_context | WARNING: Scene layout "user_defined" is empty - using default
2025-01-30 16:45:38,883 | skyrenderer.utils.time_measurement | INFO: Setup time: 401 ms
2025-01-30 16:45:38,988 | skyrenderer.utils.time_measurement | INFO: Context update time: 104 ms
2025-01-30 16:45:39,640 | skyrenderer.utils.time_measurement | INFO: Key points calculation time: 0 ms
2025-01-30 16:45:39,641 | skyrenderer.utils.time_measurement | INFO: Render time: 651 ms
...Or there wasn't?
Some 3D software have starting cube being set up in the default layout, some - empty space with only grid lines,
ours - a sphere in the space origin.
This is the default scene that will welcome you when you try to render the scene, without adding any geometry nor
other scene elements.
Scene layout
We can print out the layout of the scene elements in the form of the scene tree. At this moment, while the scene
is being prepared, all elements are stored in the object called SceneLayoutTree.
The SceneLayoutTree is located in RendererContext, while RendererContext instance is stored in SceneComposer, so
to print the layout you need to call only:
scene_composer.log_info(scene_composer.renderer_context.layout("default"))
2025-01-30 16:45:39,796 | skyrenderer | INFO:
top_node
|-- sphere_GEO
|-- camera_CAM
|-- camera_target_NUL
+-- light_LIGHT_NUL
The output of the above method is the scene layout tree that consist of nodes existing in the scene.
The layout that we will work on is called "user_defined", and it's empty, as we didn't load anything yet there.
Usually, the scene consists of several elements:
- geometries,
- materials,
- lights,
- background.
Each of such building blocks of the SKY ENGINE AI rendering engine share a similar structure to make it easier
for the user to build complex scenes using simple tools.
The structure consists of the following elements:
- provider - an asset loader, a link between the rendering engine and the assets,
- procedure - pack of programs responsible for GPU processing,
- parameter_provider - parameters that customize the procedure, each procedure can produce its own default
parameter provider - there is a _create_parameterprovider class method in each procedure, - strategy and randomization_group - rules for distribution and synchronization of the parameters.
More information of the above parts of each of scene entity, check out INTRO_Providers, INTRO_Procedures,
PROVIDER_Parameter_Provider, INTRO_Randomization.
In this tutorial we will start with loading geometry block into the scene.
Geometry
Geometry, or mesh, is a block storing information about the shape of the 3D object. There are multiple ways
to represent the 3D objects data, as well as multiple ways to store it in a file. More about geometry can be found
in the tutorial INTRO_Geometries.
For the case of this tutorial, we have already prepared the geometry to be loaded in the Alembic files. We will
load it for you with dedicated method:
scene_composer.load_geometry()
scene_composer.visualize()
2025-01-30 16:45:39,826 | skyrenderer.basic_types.light.sphere_light | WARNING: Creating light parameter provider in context which is set up - hanging link possible
2025-01-30 16:45:39,827 | skyrenderer.scene.renderer_context | WARNING: Setting light after setup. Origin refers to last scene tree set up. Lights are stored in dict - change is visible immediately; Wrong parameter provider possible
2025-01-30 16:45:39,828 | skyrenderer.basic_types.light.sphere_light | WARNING: Creating light parameter provider in context which is set up - hanging link possible
2025-01-30 16:45:39,828 | skyrenderer.scene.renderer_context | WARNING: Setting light after setup. Origin refers to last scene tree set up. Lights are stored in dict - change is visible immediately; Wrong parameter provider possible
2025-01-30 16:45:39,830 | skyrenderer.basic_types.light.sphere_light | WARNING: Creating light parameter provider in context which is set up - hanging link possible
2025-01-30 16:45:39,830 | skyrenderer.scene.renderer_context | WARNING: Setting light after setup. Origin refers to last scene tree set up. Lights are stored in dict - change is visible immediately; Wrong parameter provider possible
2025-01-30 16:45:39,831 | skyrenderer.basic_types.light.sphere_light | WARNING: Creating light parameter provider in context which is set up - hanging link possible
2025-01-30 16:45:39,832 | skyrenderer.scene.renderer_context | WARNING: Setting light after setup. Origin refers to last scene tree set up. Lights are stored in dict - change is visible immediately; Wrong parameter provider possible
2025-01-30 16:45:39,837 | skyrenderer | INFO:
top_node
|-- window_sill_outside_GEO_NUL
| +-- window_sill_outside_GEO_old_tiles
|-- window_GEO_NUL
| |-- window_GEO_aluminium
| |-- window_GEO_iron
| |-- window_GEO_rubber
| |-- window_GEO_sheet_metal
| |-- window_GEO_window_glass
| +-- window_GEO_wood
|-- window_sill_GEO_NUL
| +-- window_sill_GEO_wood_painted_white
|-- room_GEO_NUL
| |-- room_GEO_walls
| +-- room_GEO_wood_floor
|-- sun2_LIGHT_NUL
|-- indoor_mood_LIGHT_NUL
|-- mat_GEO_NUL
| |-- mat_GEO_cord
| +-- mat_GEO_straw
|-- focus_NUL
|-- indoor_ambient_LIGHT_NUL
|-- coffee_liquid_GEO_NUL
| |-- coffee_liquid_GEO_coffee
| +-- coffee_liquid_GEO_coffee_rossetta
|-- contra_LIGHT_NUL
|-- coffee_glass_GEO_NUL
| +-- coffee_glass_GEO_glass
|-- coffee_glass_handle_GEO_NUL
| +-- coffee_glass_handle_GEO_glass
|-- camera_TARGET_NUL
|-- chair_GEO_NUL
| |-- chair_GEO_black_rubber
| |-- chair_GEO_leather
| |-- chair_GEO_metal
| +-- chair_GEO_wood_painted_black
|-- book_GEO_NUL_008
| |-- book_GEO_cover_008
| +-- book_GEO_paper_008
|-- camera_CAM_NUL
| +-- camera_CAM
|-- book_GEO_NUL_006
| |-- book_GEO_cover_006
| +-- book_GEO_paper_006
|-- book_GEO_NUL_007
| |-- book_GEO_cover_007
| +-- book_GEO_paper_007
|-- book_GEO_NUL_004
| |-- book_GEO_cover_004
| +-- book_GEO_paper_004
|-- book_GEO_NUL_005
| |-- book_GEO_cover_005
| +-- book_GEO_paper_005
|-- book_GEO_NUL_002
| |-- book_GEO_cover_002
| +-- book_GEO_paper_002
|-- book_GEO_NUL_003
| |-- book_GEO_cover_003
| +-- book_GEO_paper_003
|-- book_GEO_NUL_001
| |-- book_GEO_cover_001
| +-- book_GEO_paper_001
+-- book_GEO_NUL
|-- book_GEO_cover
+-- book_GEO_paper
2025-01-30 16:45:40,059 | skyrenderer.utils.time_measurement | INFO: Setup time: 222 ms
/dli/skyenvironment/skyrenderer/skyrenderer/basic_types/provider/unit_providers/geometry.py:36: RuntimeWarning: invalid value encountered in true_divide
dpdu = dpdu / np.linalg.norm(dpdu, axis=1, keepdims=True)
2025-01-30 16:45:40,845 | skyrenderer.utils.time_measurement | INFO: Context update time: 784 ms
2025-01-30 16:45:47,414 | skyrenderer.utils.time_measurement | INFO: Key points calculation time: 0 ms
2025-01-30 16:45:47,415 | skyrenderer.utils.time_measurement | INFO: Render time: 6.57 seconds
Notice that we print for you new layout of the scene, just after loading the geometry. The layout now consists not
only 4 elements, but much more.
The scene wouldn't normally be visible, as there is no light in the scene, but to see any output we adjusted
ambient gain in the default shader, to be able to see the geometry.
If you want to learn how to load geometry into SkyRenderer, feel free to inspect above function. For more
information about loading geometries, check out INTRO_Geometries, INTRO_Intersectors and tutorials with
GEOMETRY tag. More about loading mesh from Alembic file can be found in GEOMETRY_MeshABC.
Materials
Materials, or rather MaterialDefinition in SkyRenderer, are recipies used to define material properties of
the geometries' surfaces existing in the scene. For instance, we can create sphere geometry, but whether it will
appear to be made of glass or plastic, is dependent of the sphere's material definition.
For more information about materials, check out INTRO_Materials tutorial.
Bare materials
First, we will start with adding some materials, more or less in correct colors, to the objects. To add the
materials into the scene, we will use dedicated method:
scene_composer.load_materials(load_textures=False)
scene_composer.visualize()
2025-01-30 16:45:47,893 | skyrenderer.utils.time_measurement | INFO: Setup time: 140 ms
2025-01-30 16:45:48,678 | skyrenderer.utils.time_measurement | INFO: Context update time: 784 ms
2025-01-30 16:45:56,997 | skyrenderer.utils.time_measurement | INFO: Key points calculation time: 0 ms
2025-01-30 16:45:56,998 | skyrenderer.utils.time_measurement | INFO: Render time: 8.32 seconds
Notice the difference between the coffee glass and other materials. That's because even without the textures, we
can assign materials of different physical properties, thanks to using different shaders for specific objects.
Our most common shader is PBRShader which is used for the majority of surfaces rendered in our examples. This is
the shader used in this scene for everything except coffee and window glass - which is rendered with GlassShader -
and coffee liquid rendered with TranslucentShader.
If you want to know more about shaders, be sure to visit INTRO_Shaders and tutorials in SHADER section.
Materials with textures
In order to load textures to our materials, we have to reinitialize them, adding proper texture provider to chosen
materials. Don't worry, our function load_materials has predicted that possibility, and has dedicated flag just
to load texture files into texture providers and pass this parameter to appropriate materials.
scene_composer.load_texture_files()
scene_composer.load_materials(load_textures=True)
scene_composer.visualize()
2025-01-30 16:45:57,525 | skyrenderer.utils.time_measurement | INFO: Setup time: 143 ms
2025-01-30 16:45:59,765 | skyrenderer.utils.time_measurement | INFO: Context update time: 2.24 seconds
2025-01-30 16:46:06,167 | skyrenderer.utils.time_measurement | INFO: Key points calculation time: 0 ms
2025-01-30 16:46:06,168 | skyrenderer.utils.time_measurement | INFO: Render time: 6.40 seconds
You can notice that window received transparent texture, window frame is now wooden and mat under the coffee cup
is now stained with coffee. Loading textures added also details in the surface roughness like fingerprints on the
metal part of the window frame or scratches on the metal of the chair. They might be now visible at first glance
because of the lack of lighting in the scene but after adding the lights it will significantly boost the realism
of the scene.
More information about the texture files can be found in ASSETS_Textures and ASSETS_Substances.
Background
Background is the definition of the surrounding environment. Background definition is used to determine what
should be displayed in the image for pixels where rays do not intersect with any surface.
By default, it is a constant color of very dark gray - RGB (8,8,8). You can notice the same color, just behind the
window.
For most of the scenes, displaying such color for background in not sufficient to render realistic scenes.
In order to enhance the realism, you can load environmental image texture and bind it to the definition of the
displayed background. In our engine we use HDR files of equirectangular environment map. Due to the fact that
High-Dynamic-Range environmental maps store information regarding strength of colors, we can identify in them
brighter regions allowing us to illuminate the scene in a natural way. Let's load such image with a function below
and see how light from the environmental map is added:
scene_composer.load_background()
scene_composer.visualize()
2025-01-30 16:46:06,826 | skyrenderer.scene.renderer_context | WARNING: Setting background definition after setup.
2025-01-30 16:46:06,827 | skyrenderer.scene.renderer_context | WARNING: Setting light after setup. Origin refers to last scene tree set up. Lights are stored in dict - change is visible immediately; Wrong parameter provider possible
2025-01-30 16:46:06,978 | skyrenderer.utils.time_measurement | INFO: Setup time: 150 ms
2025-01-30 16:46:09,584 | skyrenderer.utils.time_measurement | INFO: Context update time: 2.60 seconds
2025-01-30 16:46:16,173 | skyrenderer.utils.time_measurement | INFO: Key points calculation time: 0 ms
2025-01-30 16:46:16,174 | skyrenderer.utils.time_measurement | INFO: Render time: 6.59 seconds
To learn more about environmental image mapping and HDR assets, check out PROVIDER_HDRTextureProvider and
ASSETS_HDR.
Lights
The assets for our scene are loaded, and you might think it lacks this... something. You are correct.
One of the most important things, yet in many cases deeply underestimated, is lighting of the scene. As there are
no custom assets for lights (except partially environment maps), scene lighting is left default or is set up
very roughly.
In this part we will set up the lights. First, we will remove the ambient light factor from the materials, and
we will add 4 lights to the scene:
- light imitating sun from the outside of the window,
- light from the outside, serving as the contrasting light to the sun lighting,
- indoor light from the ceiling,
- indoor light, to brighten the objects inside the room softly.
In a moment, you will see how much the scene changes when proper lighting is introduced to the scene:
scene_composer.load_materials_without_ambient()
scene_composer.load_lights()
scene_composer.visualize()
2025-01-30 16:46:17,131 | skyrenderer.basic_types.light.sphere_light | WARNING: Creating light parameter provider in context which is set up - hanging link possible
2025-01-30 16:46:17,131 | skyrenderer.scene.renderer_context | WARNING: Setting light after setup. Origin refers to last scene tree set up. Lights are stored in dict - change is visible immediately; Wrong parameter provider possible
2025-01-30 16:46:17,132 | skyrenderer.basic_types.light.sphere_light | WARNING: Creating light parameter provider in context which is set up - hanging link possible
2025-01-30 16:46:17,132 | skyrenderer.scene.renderer_context | WARNING: Setting light after setup. Origin refers to last scene tree set up. Lights are stored in dict - change is visible immediately; Wrong parameter provider possible
2025-01-30 16:46:17,132 | skyrenderer.basic_types.light.sphere_light | WARNING: Creating light parameter provider in context which is set up - hanging link possible
2025-01-30 16:46:17,133 | skyrenderer.scene.renderer_context | WARNING: Setting light after setup. Origin refers to last scene tree set up. Lights are stored in dict - change is visible immediately; Wrong parameter provider possible
2025-01-30 16:46:17,133 | skyrenderer.basic_types.light.sphere_light | WARNING: Creating light parameter provider in context which is set up - hanging link possible
2025-01-30 16:46:17,134 | skyrenderer.scene.renderer_context | WARNING: Setting light after setup. Origin refers to last scene tree set up. Lights are stored in dict - change is visible immediately; Wrong parameter provider possible
2025-01-30 16:46:17,379 | skyrenderer.utils.time_measurement | INFO: Setup time: 245 ms
2025-01-30 16:46:20,051 | skyrenderer.utils.time_measurement | INFO: Context update time: 2.67 seconds
2025-01-30 16:47:13,964 | skyrenderer.utils.time_measurement | INFO: Key points calculation time: 0 ms
2025-01-30 16:47:13,965 | skyrenderer.utils.time_measurement | INFO: Render time: 53.91 seconds
The difference between the previous scene render and the new one is enormous. The importance of the proper
lighting in the scene should not be neglected, as its lack might completely ruin the final render.
Appendix: Camera
As the supplement part of this tutorial we will show you how to change last significant part of the scene setup:
the camera. As it is not essential part from the scene setup perspective, treat this part as a supplement chapter
to see other views on our scene.
scene_composer.setup_close_up_camera()
scene_composer.visualize()
2025-01-30 16:47:15,034 | skyrenderer.utils.time_measurement | INFO: Setup time: 161 ms
2025-01-30 16:47:17,691 | skyrenderer.utils.time_measurement | INFO: Context update time: 2.66 seconds
2025-01-30 16:48:36,064 | skyrenderer.utils.time_measurement | INFO: Key points calculation time: 0 ms
2025-01-30 16:48:36,065 | skyrenderer.utils.time_measurement | INFO: Render time: 78.37 seconds
To learn how to set up camera yourself, check out tutorial INTRO_CameraRenderStep and lens tutorials:
INTRO_Lens and SENSOR tutorial section.
Summary
In this section you have learnt:
- Usual scene setup consists of setting up elements such as: geometry, materials, lights, background, camera.