Background
Our philosophy with cesium-native integrations has always been to bring in 3D Tiles, with the experience being as close as possible to working with any other meshes or objects in the runtime engine. When it came to support for metadata styling in Unreal, we looked to implement this with Unreal's built-in material system. That way, people take advantage of their existing expertise to write complex material graphs.
However, this means that styling inherits the limitations of the material systems. For instance:
- Materials or shaders cannot be generated at runtime. They must be created in-Editor, anticipating all possible cases that a material should handle or in which it should change.
- Properties must be passed in as textures. Custom vertex attributes are typically not supported (defeating the purpose of property attributes)
- Some property types, such as strings and arrays, are not easily supported (and may not be reasonably possible).
Additionally, we are seeing use cases that push the limitations of this system. Notably, the use of UINT64 identifiers to categorize elements in in large design models is becoming increasingly relevant for Unreal. Users want to highlight elements based on these category identifiers, or otherwise toggle the visibility of them. Some examples of how this is accomplished in CesiumJS are:
// Hide specific categories by their int64 element IDs
const hiddenCategories = new Set([
BigInt("2199023255776"), // OST_Walls
BigInt("2199023255724"), // OST_Ceilings
BigInt("2199023255990"), // OST_Floors
BigInt("2199023255765"), // OST_Stairs
BigInt("2199023255795"), // OST_Roofs
]);
// Apply style that hides features in hidden categories
tileset.style = new Cesium.Cesium3DTileStyle();
tileset.style.show = {
evaluate(feature) {
return !hiddenCategories.has(feature.getProperty("category"));
},
};
// Highlight specific categories by their int64 element IDs
const highlightedCategories = new Set([
BigInt("2199023255776"), // OST_Walls
BigInt("2199023255724"), // OST_Ceilings
BigInt("2199023255990"), // OST_Floors
BigInt("2199023255765"), // OST_Stairs
BigInt("2199023255795"), // OST_Roofs
]);
// Apply style that colors matching features yellow
tileset.style = new Cesium.Cesium3DTileStyle();
tileset.style.color = {
evaluateColor(feature, result) {
if (highlightedCategories.has(feature.getProperty("category"))) {
return Cesium.Color.YELLOW.withAlpha(1.0, result);
}
return Cesium.Color.WHITE.withAlpha(1.0, result);
},
};
Although we could hack uint64_ts into a material (apparently supported in Shader Model 6!), it would still not provide a comparable user experience. We would still need to handle a dynamic list of categories, no matter if it's to highlight / hide/ show. One could pass the IDs of the affected categories to through a texture, but to act conditionally on them would require something like:
int categoryID;
Texture2D hiddenCategories;
uint width, height;
hiddenCategories.GetDimensions(width, height);
for (uint w = 0; w < width; w++) {
for (uint h = 0; h < height; h++) {
if (categoryID == hiddenCategories.Load(w, h)) {
outColor.a = 0.f;
break;
}
}
}
Even so, dynamic for loop lengths are not supported in HLSL. And even if they were, performance could be questionable given the sheer number of categories at play for a given tileset.
Possible Solutions
CesiumJS already implements a form of CPU styling. Given some style expression (as in the examples above), it evaluates a show/hide condition, or a highlight color one, for all the features in a tileset. The results are saved and applied to the features in the shader.
We may want to do something similar in cesium-native. Evaluate conditions per-feature, then pass the computed color value to the shader. It's questionable whether we want to implement full Javascript-like styling (e.g. CesiumGS/cesium-unreal#1392), but this has also come up for #1380.
Although it may take some time to evaluate the styling conditions across the entire tileset, the one-time computation of complex conditions for consistent framerate afterwards seems like a worthy tradeoff, and it allows for flexibility/dynamism that is difficult to achieve with in-game materials.
Background
Our philosophy with cesium-native integrations has always been to bring in 3D Tiles, with the experience being as close as possible to working with any other meshes or objects in the runtime engine. When it came to support for metadata styling in Unreal, we looked to implement this with Unreal's built-in material system. That way, people take advantage of their existing expertise to write complex material graphs.
However, this means that styling inherits the limitations of the material systems. For instance:
Additionally, we are seeing use cases that push the limitations of this system. Notably, the use of UINT64 identifiers to categorize elements in in large design models is becoming increasingly relevant for Unreal. Users want to highlight elements based on these category identifiers, or otherwise toggle the visibility of them. Some examples of how this is accomplished in CesiumJS are:
Although we could hack
uint64_ts into a material (apparently supported in Shader Model 6!), it would still not provide a comparable user experience. We would still need to handle a dynamic list of categories, no matter if it's to highlight / hide/ show. One could pass the IDs of the affected categories to through a texture, but to act conditionally on them would require something like:Even so, dynamic for loop lengths are not supported in HLSL. And even if they were, performance could be questionable given the sheer number of categories at play for a given tileset.
Possible Solutions
CesiumJS already implements a form of CPU styling. Given some style expression (as in the examples above), it evaluates a show/hide condition, or a highlight color one, for all the features in a tileset. The results are saved and applied to the features in the shader.
We may want to do something similar in cesium-native. Evaluate conditions per-feature, then pass the computed color value to the shader. It's questionable whether we want to implement full Javascript-like styling (e.g. CesiumGS/cesium-unreal#1392), but this has also come up for #1380.
Although it may take some time to evaluate the styling conditions across the entire tileset, the one-time computation of complex conditions for consistent framerate afterwards seems like a worthy tradeoff, and it allows for flexibility/dynamism that is difficult to achieve with in-game materials.