Realtime-Clipping of Tileset for Map Table Visualization

Hey guys!

I am looking to replicate realtime clipping of the Cesium terrain in Unity as seen in various different applications.

3

Source: corriedotdev/vr-modular-3d-gui: Novel 3D VR Interface developed to demo alongisde its paper counterpart. Showcasing James Web Telescope imagery and more.

From what I gathered Cesium Catrographic Polygons are not the way to go. I should rather look more into shaders and materials in Unity. I am still trying to figure out how to do this exactly.

Id say I need a “cutter object” that the Cesium terrain needs to intersect to get rendered. Since I am not that well versed in shader programming I would like to get some pointers from folks that know how to do this.

What are the keywords I need to look into? Stencil buffers are something that look promising, however they only define an area where everything that is within the area and successfully passes the stencil test gets rendered.

Id appreciate some help, or maybe there are open source projects already achieving this effect that I could look into.

Thanks for taking the time!

Hi @RSDev,

The CesiumTileExcluder, combined with a material that discards pixels via opacity mask, may be what you need. There are some details about this approach in this PR where the CesiumTileExcluder was originally added:

You may also be interested in this related thread, though it doesn’t have a concrete answer just yet:

1 Like

Hi @Kevin_Ring,

thanks for the heads up. I was able to implement the CesiumTileExcluder based on the PR you linked, as well as your initial implementation for the mesh clipping functionality.

The thread you linked is also very interesting. Havent delved deeper into the topic myself yet. I was busy trying to adapt the tileset shader to my needs.

What I stumbled upon are issues other people also reported when trying to implement the tabletop map concept. Problems such as Y position variation when changing the Cesium Georeference scale property and how to implement map panning.

Setting Cesium Georeferenceorigin and possibly using SampleHeightMostDetailed is what Ill look into for the height variation based on these posts:

Changing Cesium Georeference origin should also do the trick for map panning according to your reply in this post:

Feel free to correct me if my approach has some flaws I am missing.

Your proposed approach makes sense to me. Let us know how it goes!

I decided to go back optimizing my terrain shader. A limitation of my initial adjustment of the CesiumDefaultTilesetShader was that the masked area stayed at 0, 0, 0 in world space.

From what I gathered in this tutorial: Waterfall Shader Breakdown | Cyanilux

I need to pass the position of the object I want the clipping mask to follow to the shader.

Looking at this thread I eventually managed to implement passing of values to material properties of loaded tiles during runtime after initially trying to do it through the Opaque Material property of the Cesium3DTileset component: Shader for Cesium World Terrain - Cesium for Unity - Cesium Community

Because I want the map table object to be fully interactable (transforming, scaling, rotating) I will look into passing the appropriate values to the shader and adapting the shader further eventually.

Right now I am struggling to wrap my head around the whole maintaining tileset height issue.

So I played around with the Cesium Georeference components Height and Scale properties as well as the related game objects positions but changing the scale property still leads to a change in the displayed terrains height in Unity.

Apparently I didnt manage to set up my scene as described here because that approach does not work for me:

20250826-1306-52.2325198

This lead me to toy around with CesiumSampleHeightMostDetailed. I have the following script on the CesiumGeoreference game object in my scene.

public class CesiumMaintainTilesetHeight : MonoBehaviour
{

    [SerializeField] private Cesium3DTileset tileset;

    private CesiumGeoreference _georeference;

    async void Start()
    {
        _georeference = GetComponent<CesiumGeoreference>();
        
        double3[] positions = new double3[1];
        
        positions[0] = new double3(
            _georeference.longitude,
            _georeference.latitude,
            _georeference.height
        );
        Debug.Log("Georeference (Long,Lat,Height): " + positions[0]);
        
        CesiumSampleHeightResult result = await tileset.SampleHeightMostDetailed(positions);
        
        if (result != null)
        {
            foreach (string warning in result.warnings)
            {
                Debug.Log("Warning: " + warning);
            }

            foreach (bool success in result.sampleSuccess)
            {
                Debug.Log("Success: " + success);
            }
            
            double3 sampledPosition = result.longitudeLatitudeHeightPositions[0];
            
            Debug.Log("Result (Long,Lat,Height): " + sampledPosition);
            Debug.Log("Unity Height (x,y,z): " + _georeference.TransformEarthCenteredEarthFixedPositionToUnity(CesiumWgs84Ellipsoid.LongitudeLatitudeHeightToEarthCenteredEarthFixed(sampledPosition)));
        }
        else
        {
            Debug.Log("Could not sample height at the given position.");
        }
    }
}

Running the application prints the following:

Maybe I can get some input on both issues.

Another thing that just came to my mind that will be relevant down the line is if Cesium for Unity allows for scaling of the displayed terrain. I read that the parent of the tilesets that hold the CesiumGeoreference component should have a local position of 0,0,0 and a scale of 1. But can I scale the georeferences parent object which would in my case be the map table without running into issues with the plugin?

Let’s start with the easiest problem (hopefully!) first. The SampleHeightMostDetailed is failing because the underlying C++ Tileset object is being destroyed after the height sampling is started but before it finishes. This is most likely because you’re changing a property of the Cesium3DTileset - possibly elsewhere in your application - and that is triggering recreation of the C++ Tileset. Does that seem plausible?

Beyond that, I’m having a little trouble following what you’re saying about the scale. Janine’s advice in the thread you linked certainly will only work if you know the exact location and height that you want to “scale around”, though, so getting SampleHeightMostDetailedworking seems like the first step.

Disabling all other scripts that access the Cesium3DTileset to see if that would prevent SampleHeightMostDetailed from failing did not work.

So I created a new Unity Project (Unity 6.2, URP Sample) and added only the Cesium for Unity plugin. In the sample scene I added the Cesium World Terrain + Bing Maps Aerial and added my script to the CesiumGeoreference game object.

Sampling of the height is unfortunately still failing.

Moving the code out of the Start() event function resolved the issue. I tested sampling the height on a button press and that works. Still not sure what happens in Start() that caused the Tileset to be destroyed though during sampling.

My approach to fixing the “scaling issue” would be:

Whenever the user pans around the map or changes the height I need to sample the height at that new Cesium Georeference origin position using SampleHeightMostDetailed, then set that sampled height as the new Cesium Georeference origin position height.

Guess I should hook into the Cesium Georeference changed event to execute the sampling and setting the new origin? I am not that experienced with async/await or coroutines but I suspect having these tasks called so frequently will be an issue. Or is there a clean way to handle that?

Since I dont want the position to change instantaneously the change should also be interpolated.

Okay, so hooking into the changed event caused my application to freeze since that calls the async method way too often I assume.

Maybe fixing the height variation once the CesiumGeoreference origin position deviates a certain amount from the sampled position is more sensible.

Which property of CesiumGeoreference would be the best indicator to check for that deviation? Its still not obvious to me what exactly the problem is when you move further from the CesiumGeoreference origin that causes the height variation when scaling.

Hi @RSDev,

Have you considered using Physics.LineTraceSingle instead of SampleHeightMostDetailed? The advantage is that it’s synchronous and fast. You can do a line trace every frame and get the result immediately, on the very next line of code, without the need for a coroutine.

The disadvantage is that it only traces against the terrain that is currently loaded and rendered. So it’s not suitable at startup, before you show the terrain on your table. SampleHeightMostDetailed is probably still useful for that. But it may be a better way to adjust the height during movement.

Another disadvantage (maybe) is that it returns the position in Unity world coordinates, rather than Longitude / Latitude / Height that you can use to set the CesiumGeoreference origin. But you can use TransformUnityPositionToEarthCenteredEarthFixed to transform Unity coordinates to ECEF, and then georeference.ellipsoid.CenteredFixedToLongitudeLatitudeHeight to transform the ECEF coordinates to longitude/latitude/height.