Height Detection/Correction with Google Photorealistic Terrain Tiles


I am having immense difficulty trying to place objects on the earth’s surface at runtime in UE5.3 with Google Photorealistic Terrain Tiles. I can correctly place an actor above the correct Longitude|Latitude coordinates… but then I need to detect and correct for height changes once the tileset has loaded and resolved its terrain height features.

I am using the methods here for both creating a camera in advance which points at the tileset and thus forces it to load… and for doing a line trace which intercepts the tileset and calculates the difference between the ground-seeking actor’s Globe Anchor Component’s current height and the fully loaded terrain’s height.

After much debugging, I have identified a couple of issues:

  1. When I create a custom collision filtering channel and set it to default to Block… the traces register on the terrain, but also on other objects. If, however I set it to default to Ignore (in project settings)… the traces don’t register on the Google Photorealistic Terrain tileset. I believe this is because they are not a Cesium World Terrain. However, I’m not sure where to find the setting to enable Google Photorealistic Terrain Tiles to Block a custom trace channel. The Collision settings on the Cesium3DTileset instance in the level does not seem to work when set to Block on the appropriate trace channel.

  2. Once the traces are hitting correctly (if I just default the trace channel to Block) - i.e. once the dynamic pawn or other camera is zoomed in close to the tiles - the calculations for evaluating the height offset between the trace-firing actor and the impact point on the tileset are correct (in metres). I have confirmed this using the Debug Camera’s height and comparing it to the traces.

However, once I go to add this height offset to the Globe Anchor Component of the Actor whose height I want to adjust (I want it to make it sit on the ground)… everything goes awry and the node seems to place the Actor 1000s of metres under the earth’s surface instead of directly on top of it. I’m not sure if I’m misunderstanding that I cannot add a height offset in ‘meters’ to the Z component of the <Longitude, Latitude, Height> node without a node? I understood that once I have the correct height delta in meters, I could just set that delta to the ‘Z’ component of a vector and add it to the Actor’s Globe Anchor Component’s original Height value.

Any ideas why this seemingly simple thing would not be working?


Traces are set up correctly but do not register on the tileset.

When I instead set the trace channel to Block by default in Project Settings, then the traces register, but the height calculation behaviour is strange. Here, the camera is at Unreal Z=0 coordinates below the surface. It seems like the trick of loading detailed tilesets by making a new Cesium Camera does work…

It also seems like the height delta calculation is correct if one observes the debug text and the Debug Camera’s Z coordinate. Here, the camera is at +30m, the trace impacts the surface at 136m, and the difference is calculated to be 106m:

The problem seems to arise when I take that height delta number and add it to the Latitude Longitude Height value and move the Globe Anchor Component there:

That puts me for example 263m under the surface

… which corresponds to none of the origin locations given when these globe anchored rocket ships are first generated:

Hm I have a feeling that the reason this isn’t working is because the line trace is hitting the static mesh of the actor emitting the line trace itself… when the trace channel is set to Block, this is what happens. The delta reads the same for every Actor. So, I have a feeling that by answering point 1 - how to get the trace to register only against the Google Photorealistic Terrain Tiles while the trace channel is set to Ignore in the Project Settings… the problem will resolve itself. HELP!

Lots to unpack here, and I’m not exactly sure what is going wrong. So I’ll start by just pulling at the threads that jump out at me and see if that leads anywhere…

I believe this is because they are not a Cesium World Terrain

I think it’s just a name, and can be whatever you want. There’s no reason the tileset has to actually be Cesium World Terrain.

After you change the Collision settings on the Tileset, try hitting the “Refresh Tileset” button, just to make sure any already-created tiles are recreated with the new settings. This shouldn’t be necessary if you’re testing in a Play-in-Editor session, but it’s an easy thing to try.

the calculations for evaluating the height offset between the trace-firing actor and the impact point on the tileset are correct (in metres)

Are you sure this is metres? I would expect it to be in Unreal’s standard world coordinate system, which is centimeters.

Hi @Kevin_Ring, thanks for your reply.

Okay, so I think the issue is definitely stemming from whatever reason drives the Terrain Tiles not being able to be hit by a trace. All other calculation issues are most likely just following on from that and tied to other actors blocking the hit before the terrain does (since the only way to register a hit is to set default to Block in Project Settings). So, I need to find a way to have the Trace Channel default to Ignore, yet register a hit with the tileset. I tried refreshing the tileset instance and no luck.

The following is the blueprint I am using to calculate the height offset. Can you confirm that this looks like the output ZDeltaToTerrain should be in meters?

<iframe src="https://blueprintue.com/render/08akdm61/" scrolling="no" allowfullscreen></iframe>


A few things from looking at that Blueprint:

  1. Why are you using SphereTraceSingle instead of LineTraceSingle?
  2. The logic for the Start position is not great. It’s tranforming (0, 0, 999999999.0) in the Actor’s coordinate system to world coordinates. If the Actor’s +Z is aligned with the world +Z, that’s probably ok, but it could also be some arbitrary direction of the Actor is tilted at all. I think you want to just get the Actor position in World coordinates and then add your large Z offset to it. But even that will only work near the georeference origin where +Z is aligned with the globe up direction. A better approach is to get the Actor’s Longitude/Latitude/Height position, then add a large number to the height, and then convert that back to Unreal coordinates. The tutorial you followed should probably be changed here.
  3. I’m not really sure what’s going on with the part of the Blueprint after the trace. I don’t understand the purpose for all the subtraction and multiplication. The hit location represents the position of the ground. If your model’s feet, for example, are at Z=0.0, then you want to simply set the model’s height to the height of the trace position.

Thanks @Kevin_Ring.

  1. Yes indeed, I have added to the blueprint trying different things because the simple way was not working. Sphere trace or line trace should both work.

  2. Basically, do you suggest doing all calculations in Long/Lat/Height and then only converting back to Unreal to make the trace? I.e. the following:

<iframe src="https://blueprintue.com/render/5y3iqqij/" scrolling="no" allowfullscreen></iframe>

I find that the traces now jitter strangely when doing it this way.

  1. The calculations are to return a height delta for debugging. Furthermore, to return a vector and rotator in order to construct a camera near the impact point, looking at the tileset right near the impact point. This is in order to preload tiles there as per Height Detection/Correction with Google Photorealistic Terrain Tiles and Original Custom Camera Implementation.

I’m not sure if this is working though. It looks like nothing is working since none of the traces seem to be registering correctly the point of impact with the tileset - the traces are just passing through. Any thoughts specifically on this? Solving this would help to make all other debug much easier.


I don’t know why that would happen. I suggest going back to the basics. Start with the Cesium for Unreal Samples project and set up a simple level to do line traces against Cesium World Terrain. Does that work? Now try your tileset. Does it still work? Now try to put objects at the traced locations. Working ok? You may see the problem in the course of doing this, but if not then at least you’ll end up with a reproducible test case that we can use to debug it.

Okay even in the Cesium Unreal Samples project on map 12, which uses Google Photorealistic Terrain Tiles… the Speed Trace function uses the ‘Visibility’ trace channel to check for collisions which seems ill-advised. If I use a custom trace channel, traces do not work. Can you replicate this?

I just tried this out and it worked ok for me. Here’s what I did.

  1. Open Level 12 in the Cesium for Unreal Samples.
  2. Go to Edit → Project Settings → Engine → Collision and add a new “Trace Channel” named “Tilesets”. Set the “Default Reponse” to Ignore:
  3. Open the DynamicPawn Blueprint, Speed Line Trace function, and change the “Trace Channel” on the two “Line Trace By Channel” nodes to “Tilesets” (instead of “Visibility”)
  4. Press Play, and confirm that this breaks the speed control. The camera ends up moving very slowly. This is expected because the trace channel ignores the tileset.
  5. Click the “Google Photorealistic 3D Tiles” Actor in the Outliner and set the Trace Response of the Tilesets channel to Block:
  6. Press Play and confirm that the speed control is now working correctly again.

I tried this in a development version of Cesium for Unreal v2.0, but I don’t have any reason to think it would be different in earlier versions.

Interestingly, the exact same thing does not work for me

Let me quadruple check it

Okay. I can confirm that doing exactly as you have done also works for me. However, when I implement the ‘Search for Ground function’ in this project and have it emit a trace upon a key press, using the exact same trace channel… it passes through the ground without registering a hit. This is a view of the debug camera placed exactly at ground level, looking at the two traces.

Oh you know what I just found?

When I set up the tileset in the editor… it’s clear that the trace channel on the Tileset instance in the outliner is set to ‘Block’

But upon pressing Play in Editor… the Trace Channel changes back to Ignore. Why on earth would this be happening?

@Kevin_Ring when I switch it at runtime (after hitting play), it works…

But then once I have stopped it, it resets to Ignore the custom trace channel. So, perhaps there’s something in the construction script for the tileset?

Interestingly, in my other project (not the samples), this does not happen - and at runtime, the tileset remains with the trace channel set to Block

@Kevin_Ring also it’s completely unclear to me how the Cesium Georeference and Globe Anchor system works. Is there only ONE Georeference per Unreal scene? And the Globe Anchor components simply reference or resolve that Georeference? I.e. when I set a globe anchor component’s georeference am I just resolving the scene’s georeference and setting that?

Is it best practice therefore to always change the georeference location to the vicinity (or location) of an object I would like to move on the globe just before changing its location?

I am not sure where to look for even basic documentation on how this works. It’s confusing…

It sounds like something in your project itself is changing the custom trace channel. Not sure…

Regarding Globe Anchor and Georeference, have you been through the tutorials? Especially this one:

when I set a globe anchor component’s georeference am I just resolving the scene’s georeference and setting that?

Definitely not.

Is it best practice therefore to always change the georeference location to the vicinity (or location) of an object I would like to move on the globe just before changing its location?

No. If your level takes place in a single small-ish area, the CesiumGeoreference should be near the center of that area and then shouldn’t change. If you’re working in multiple areas and moving between them, use sub-levels. In rare cases you might want to change the CesiumGeoreference dynamically at runtime. This is essentially an origin rebase.

Thanks @Kevin_Ring for your response.

The project in which the custom trace channel is being changed upon Begin Play is a freshly minted Cesium Sample Project on map 12 - Google Photorealistic Tiles

I have been through the tutorials for:

And I have also tracked issues such as:

So, I understand that there are multiple approaches to using Georeferenced actors depending on the goals of the project. In my project, I need to use the entire globe as a planet-wide space on which to pin road locations, and then fly down to those locations (geomarkers) and drive a vehicle on the surface there. I have avoided sub-levels thus far, since I don’t need different assets per sub-level at the moment, and since I know World Composition is deprecated and a new sublevel system is in the works. I have instead opted to pin ‘spawn points’ on the map with Cesium Globe Anchor components attached to a geomarker mesh… I then fly down from outer space to the appropriate point using the ‘Fly to Location’ function… at which point the default scene which is not georeferenced and is built at the Origin in Unreal space takes place. This non-georeferenced scene consists of a landing ship dropping off the pawn vehicle at the correct point.

So perhaps it would help to disambiguate Georeferences in the scene. Is this correct understanding? In my scene, there is:

  • Georeference Actor (max. 1 per level)
  • Cesium Globe Anchor - Georeference (1 georeference per Actor with a Cesium Globe Anchor Component)
  • 3D Tileset - Georeference (1)
  • DynamicPawn - Georeference (1)

I understand that what you’re telling me is that each Globe Anchor Component has its own Georeference.

The confusing thing is that the documentation about Cesium Georeferences doesn’t clearly distinguish between how these different Georeferences are working, and which function needs to be called to have Unreal use either the Georeference Actor or a ‘Resolved’ Georeference on a Cesium Globe Anchor Component for example as the origin. Which ‘Set Georeference’ function needs to be called?

Am I understanding then that ‘Set Georeference Origin’ is the function to call to actually rebase the world. The other ‘Set Georeference’ functions are just storing a location which can be resolved later?

I’m understanding that the ‘Fly To Location’ function doesn’t actually rebase the Unreal world’s coordinates to the Georeference of the location that has been flown to? So, do I need to call ‘Set Georeference Origin’ at the same time as ‘Fly To Location’ in order to make sure that the Unreal scene at that point is oriented correctly and has the correct accuracy?

When I fly to a Georeference location, do I need to set the Georeference Actor in the outliner? Is it a master georeference of the scene? Or just another container for location data to be stored? If it is a master Georeference, do I need to get e.g. the coordinates of a desired location’s geomarker and get its GlobeAnchorComponent’s resolved georeference and set the Georeference Actor’s location to the same coordinates?

It’s also unclear why the 3DTileset Actor has a Georeference. Is it better to leave this unset and use the Georeference Actor? Basically the Resolved Georeference function is confusing, and this fallback in the function descriptions that some Georeference in the scene is chosen if no location is set makes it very unclear what’s going on.


Please help to make sense of this, and I would recommend to include some sort of detailed disambiguation in the documentation (unless that already exists and I haven’t found it yet).


Perhaps it’s best to shift already to the v2.0.0 Preview release just so that anything I do implement doesn’t get outdated immediately?

What do you suggest?

Hmm I think there might be some quite fundamental misunderstanding here. We are working on updated documentation for the v2.0 release, which might help. And yes, I think you may want to consider using v2.0 even now, as long as you can tolerate some bugs. The v2.0 release will be very soon.

Really briefly… you can think of the CesiumGeoreference Actor as defining where on the globe the Unreal world is located. It really only ever makes sense to have one CesiumGeoreference because there’s only one Unreal World and it can’t be in two places at once. However, it does sometimes make sense to change the Origin Latitude, Longitude, and Height of this one CesiumGeoreference at runtime, which is an origin rebase.

There are some corner cases, barely worth discussing, where it makes sense to have multiple CesiumGeoreference objects. That’s why there’s a Set Georeference property on things like tilesets. But you’ll almost never use this. Leave it undefined, and Cesium for Unreal will automatically find the (one!) CesiumGeoreference and use it.

CesiumGlobeAnchor, on the other hand, attaches a given Actor, such as a mesh, to a particular location on the globe. This is different from simply setting the Actor’s Unreal world coordinates in two ways:

  1. It’s more convenient when you have latitude/longitude/height, because you can use those values directly to specify the Actor’s position.
  2. When the CesiumGeoreference origin latitude/longitude/height changes, an Actor with a CesiumGlobeAnchor component will stay in the same place on the globe (which means it will have new Unreal world coordinates). Basically when you do that origin rebasing by moving the CesiumGeoreference origin, only anchored objects (including tilesets) will correctly maintain their position on the globe.

Sub-levels provide a way to simultaneously a) set the CesiumGeoreference to a new origin location and b) activate a bunch of new content stored in the sub-level. It lets you do that origin rebase when you approach a particular area on the globe. The objects in the sub-level will never see any rebasing happen (because it happens before they’re loaded), so they don’t need a CesiumGlobeAnchor or any other globe awareness at all.

Hope that helps a little!

1 Like