- Why Cards
- Data Structure
- OBB
- Struct-of-Array
- Card Pre-build
- Tips
- Heightfield-based Card Capture
- Clustering
- How to treat internal surfaces
- Surfel
- Cluster
- After Build
Why Cards
As discussed in the previous chapter, the calculation process for ray tracing under normal circumstances is as follows:
We hope to cache the last step to save computation time.
Of course, there are many forms of caching, and Lumen has chosen Card as the form of caching.
Data Structure
This image shows the relationship between key data structures. FLumenMeshCards
and FLumenCard
are the most central data structures.
On the left, it shows how these two are related to the data structure we previously discussed, such as FPrimitiveSceneProxy
.
On the right, it shows the data structure that serves as the data hub for the entire LumenScene: FLumenSceneData
, and the relationship between this data structure and FLumenMeshCards
and FLumenCard
.
OBB
Please note that the representation of FLumenCard is a rotated bounding box (OBB). The card is not positioned in the world space in the form of AABB. Please note that in the following image, as the cube rotates, the card also rotates simultaneously.
Struct-of-Array
FLumenCard
is stored continuously in LumenSceneData
in SOA style.
On the other hand, FLumenMeshCards
simply holds the index pointing to the array and the number of Cards.
In addition, FLumenMeshCards
itself is also stored continuously in LumenSceneData
.
In order to easily query the information of belonging to FLumenMeshCards
in reverse, FLumenCard
itself also holds the index MeshCardsIndex
pointing to the MeshCards
array.
Card Pre-build
No. Complex meshes have more than 6 cards, check this example
Obviously, Lumen realizes that wrapping the Card only on the outside is not enough, and more Cards need to be generated for the surface inside the bounding box of this complex Mesh.
Tips
If you want to debug this part yourself, remember to set this console command first. Otherwise, importing the same mesh will not trigger the card building again:
r.MeshCardRepresentation.Debug 1
Heightfield-based Card Capture
The simplest way I can think of to understand the process of generating a Card is to see it as Ray Marching through height fields along 6 axes, clustering the resulting Surfels, and finally saving the bounding boxes for each cluster.
Let me use a sphere as an example. A cube is too simple.
We will capture the height field of the sphere from six directions, as indicated by the directional arrows in the figure.
What is "capture"? This means initializing a projection area on the corresponding axis, which is slightly larger than the bounding box of the sphere in that direction.
And the Z-axis in this area corresponds to the height direction of the captured height field. Therefore, the axis is as shown in the figure.
Next is the important sampling process.
Firstly, the projection plane will be divided into a series of cells in the X-Y direction. The size of the cell is specified by the VoxelSize parameter, which is set to 20 in my test.
Each cell will perform 32 ray-tracing Ray Marching operations to sample the height field. It is important to note that:
- Ray Marching is used here, so when the first surface is encountered, the ray-tracing will not stop, but continue to sample multiple times inside. This is necessary to capture the surfaces inside the Mesh.
- 32 samples are not evenly distributed, please do not be misled by the neat lines drawn in the graph for ease of understanding.
After sorting and organizing the results obtained from sampling, they will be used to generate surfels.
At the same time, visibility check will also be performed on Surfel here. Specifically, it performs multiple ray samples on the upper hemisphere space of the surfel to determine if it is visible.
Surfels can be understood as small surface fragments. These fragments will form the basic units for clustering in the following step.
If it's easier for you to imagine, you can treat this as a "height-field".
Clustering
Then, clustering will be performed on the generated Surfel. The result of clustering will be an oriented bounding box (OBB). This is the Card that we cached.
The same process will be repeated for each axis, so in the end we will obtain Card OBBs in 6 directions.
Please note that this does not mean we will only obtain 6 Cards. I will explain the reason in the following.
How to treat internal surfaces
As the image shown earlier proves, Lumen does not only generate a card for each of the six axes. Otherwise, for meshes with complex topological structures, cards would not be able to capture a large amount of internal structures, which means that normal global illumination cannot be generated.
Lumen supports capturing internal surfaces through several levels of schemes.
Surfel
During the Cell sampling stage in Surfel, if it is found that different Surfel Samples within the same Cell are at different heights, they will be allocated to multiple Surfels.
It achieves this by sorting the discrete SurfelSamples by height, and then merging SurfelSamples with the same height into the same Surfel.
Cluster
During the Cluster stage, pushing the merging near plane of the Cluster gradually from near to far can capture the surface inside the Mesh.
This image shows an example of how the near clipping plane is pushed forward to capture surfaces inside. Interestingly, the bounding box of the outermost Card actually wraps around the inner Card surfaces.
r.Lumen.Visualize.CardPlacementIndex 1
to display only one card with index 1.After Build
If we ignore the Debug-related data, then actually the only information that is truly stored is the bounding box information of the Card. The intermediate results of the calculations, including SurfelSample and Surfel, will be discarded and not truly stored in the game data (after packaging).
Material data for Card will not be stored during the preprocessing stage, nor can it be stored at this stage. This is because the material node may contain dynamically calculated results (such as the Time node), and the precalculated results will be invalid.