Terrain exaggeration steps

Hi everyone,

I know unity does not support terrain exaggeration ‘yet’ but I was wondering where someone would start to make it work? Would modifying the `CesiumTilesetShader` be a step in the right direction to make use of vertex displacement?

In my mind terrain exaggeration needs to work with other 3DTileset objects like pointclouds and also take the CesiumGlobeAnchor into account, so objects are correctly placed on the Y axis based on the exaggeration.

A small workaround for quick results is this:

In my example I have a terrain I created as a quantized mesh where I exaggerated the terrain itself before creating the quantized mesh by 20x. Loading this into unity obviously visualizes the terrain nicely with correct exaggeration however objects placed with the `CesiumGlobeAnchor` are not at the right height. A simple solution is to adjust the height by my original terrain exaggeration factor, so a original object height of 50 becomes 50 * 20 = 1000, which now displays the object at the right height.

This works for that small example but it’s really not ideal or flexible.

What steps would someone need to make in order to add terrain exaggeration? It’s such an important visualisation setting and a bummer it’s not in yet for unity.

Happy to have some small guidance here on that topic.

Thanks a lot!

Id like to get some movement here so I’ll share what Ive tried so far and how far I got.

My goal first is to expand the CesiumDefaultTilesetShader to transform the terrain by modifying the vertex position.

First I tried to adjust the Y position of the vertices but that didn’t work due to the curvature I assume so the tiles relativ to the globe center.

The most logical solution would be to scale the vertice (with a factor) along its normal direction relativly to the earth globe center but I was not able to get the math correctly working. For some reason it always thought that the globe center was at unity position 0,0,0.

Variables used:
EarthCenter: (6378137, 6378137, 6356753)
TerrainExaggeration: 1 (default)

This shows the terrain normal (that’s a good start!)

However when trying to increase the exaggeration factor to like 30 we see something interesting

At the unity coordinates 0,0,0 it forms a curvature.

NO general terrain exaggeration is happening and I think it’s because I’m unable to get the correct normal direction.

Anyone an idea or a solution?

Hi @JanikCodes,

Interesting use case! So that I understand what you’re going for, you would like every vertex to be scaled “upwards”, such that two neighboring vertices that are 5cm different in height from the source data could be multiplied by 10 and now the exaggeration is 50cm. That’s the isolated example, but in practice the whole world stretches to these rules. Am I correct?

For some reason it always thought that the globe center was at unity position 0,0,0.

Check out more info on georeferenced placement here: Adding Datasets – Cesium
You can place the origin at the center of the earth however you’ll be dealing with large positional values and you might need a high precision framework to handle the distance: GitHub - Unity-Technologies/com.unity.gis.high-precision-framework: This high precision framework is a package which allows for the easy creation of large-scale visualizations. It has the ambition of solving problems outside of the pure geospatial streaming applications.. You can also place the center wherever you would like, and can in fact change it as the application goes on.

For the object normal that will be difficult to diagnose internally from the shader as it only knows surface normals and world directions.

  • The roughest example would be to use world up directions and keep your georeference center to the surface and area of interest. However this is limited to a few square kilometers until it starts to look strange. Alternatively, reset the
  • A better example would be to extract a normal by measuring a direction from an object placed at the center of the earth, and the tileset position. Then feed that normal into a material property block for the tile.
  • An even better way would be to extract 4 normals for each corner of the tile and then interpolate the normal of the vertex between the 4 points, kind of like how flat surfaces are smoothed in 3D graphics using interpolated normals. This approach would potentially remove any issues with tile seams, however be somewhat difficult to determine interpolations performantly.

I referenced this image from here → Introduction to Shading

I hope that helps,

-Ben

Thanks for this answer! Looking forward to get this topic going a little more.

I agree with your 3 ideas, both the first 2 aren’t ideal as you said yourself and pretty limited.

The third option regarding ‘interpolated normals’ sounds reasonable, yet difficult. I believe it’s pretty hard to get the actual tile corners from the shader itself.

Would it perhaps be easier to implement exaggeration if we don’t use the shader but rather modify the way unity generates the actual terrain? Couldn’t we plug in there somewhere to adjust the given Y value for each vertex and exaggerate it? Haven’t looked yet where the actual terrain is being generated but it was just an idea (not even sure if that’s accessable)

Great point Janik. It more than likely would be easier to achieve from the mesh construction point, with a few nuances:

  • Tile bounds would be adjusted as well in order to ensure proper frustum culling.

  • Physics meshes are super expensive to create/manipulate. This feature would more than likely only be usable if physics is disabled, or altering exaggeration is set but not expected to animate between smoothly, at least with physics the whole way through.

A little information I could uncover from the Cesium Native and Cesium for Unity codebases:

Cesium Native calls the engine integration via its IPrepareRendererResources interface (“prepare renderer resources on-demand for the glTF models it provides”). In Cesium for Unity, that implementation is UnityPrepareRendererResources, and that’s where the MeshDataArray is allocated and then fed into loadPrimitive. There are several other steps before and after, but I would say this is the middle of the workflow.

Here’s another related issue on exaggeration on the Cesium for Unreal forum.

Thanks for the insight, very helpful.

Sadly I thought it might’ve been easier and the downsides are relatively big compared to the difficulty so I’ll have to accept defeat till a proper implementation is released.

The shader still seems like the most correct way, coupled with additional logic in the other related components like the CesiumGlobeAnchor.

I might give it another shot with the shader approach but chances are very slim that I’ll achieve anything good.

Thanks tho! I guess there is no ETA on exaggeration for unity yet?

@JanikCodes It’s not on the immediate roadmap for something we’re trying to implement as far as I know. But to add to the above - if anyone’s looking to implement this, it might be worth looking at the GltfModifier class. This class lets you implement a function that manipulates mesh data as it’s loaded, and allows for this modification to be re-run at any time by just calling trigger on the modifier. The mesh you output from this fits seamlessly into the tileset loading process and should work with physics meshes as well.

That sounds incredible useful.

I’m not to familiar with the native stuff from cesium, how hard would it be to access? Is that something I would do in C# or in the c++ files?

@JanikCodes It would be something to do in the C++ side of things. You can take a look at a prototype we’re working on using the GltfModifier in Native and Unreal. There’s a lot of stuff happening in the apply method of the modifier, but the key part is just that we need to take in the input.previousModel and return a model in output.modifiedModel. As long as you can implement terrain exaggeration as an operation that turns one model into another model, it can fit into this system. The other bit is just working it into the tileset code so that we create and activate the modifier and include it in TilesetExternals for the tileset to use. It will look a bit different in Unity but if you look at the code of cesium-unity/native~/src/Runtime/Cesium3DTilesetImpl.cpp and cesium-unity/native~/src/Runtime/UnityTilesetExternals.cpp there should be a place to fit it in around there.