Shader for Cesium World Terrain


I would like to change Saturation and Brightness of the World Terrain. For example to direct the focus of the viewer to something else than the Terrain in the background. For this I was following a thread in the forum (here: World Terrain Shader) and added a Saturation Node directly before the “Fragment Node” and it operates on the BaseColor. So far so good.

I have two problems now:

  1. Changing the Input float value steering the Saturation causes an error of:

Destroying assets is not permitted to avoid data loss. If you really want to remove an asset use DestroyImmediate (theObject, true);

  1. Changing the Input float value steering the Saturation causes a reload of the terrain tiles. I can not understand why there should be a terrain reload happening when changing shader settings.

Hi @MutterOberin,

Item 1 is a known issue, and we’re tracking it on Github here. I’d need more context about your second issue to understand what is the unwanted behavior / problem.

I can provide some background as initial help. Every tile in a 3D tileset has an instance of the “Opaque Material”. When you change a parameter in the Opaque Material, it does not automatically apply the changes to every tile. This was frustrating users who wanted to see material adjustments reflected across the tileset. Now, changing the material in the Editor intentionally refreshes the entire tileset to propagate the changes to each tile.

Changing a parameter at runtime is different. It won’t refresh the entire tileset, but it will not apply the changes to existing tiles. So you’ll have to write a script to manually iterate over the existing tiles and update the parameter on their materials.

You can read more about these changes here.

Hi Janine,

thanks a lot for your answer!

Should the tile information (position, geometry, height map, diffuse map, etc) not be separated from the material / shader and the material be a reference to the same instance shared among all tiles?

Hi @MutterOberin,

I’m not sure what you mean exactly. Can you describe what you’re trying to accomplish?

Each tile has a copy of the “Opaque Material” (made using Object.Instantiate), and the tile provides its own information using the material parameters. For example, glTFs with different textures will set those texture parameters on its copy of the material. In other words, tiles do not share the same instance, they each contain a separate copy.

So at runtime, if you wanted to change the material for all tiles, you’d have to write a script that traverses the existing tiles to change the parameter. You may also want to set the parameter on the instance in the Opaque Material, so that any future tiles that load in will already have this new parameter set. Maybe that’s what you mean?

Hi Janine,

What I want to do is desaturating the environment during specific animations of other game objects to redirect the viewers attention. I use the world map as context information only and would have liked to transition to a less saturated background during a camera animation.

I understand now that cesium is working in a very different way from what I am used to. I was part of the development team for a terrain visualization tool which is now known as Cosmoscout VR and the LOD Bodies PlugIn. Here all necessary resources are accessed as 3d texture(s) and therefor allows manipulation of color computation without reload.

I assume with the cesium approach I can not simply manipulate the appearance without reloading tiles.

Hi @MutterOberin,

Setting the tileset’s opaqueMaterial property will cause a refresh of the tileset. But you can circumvent that at runtime, by getting MeshRenderer components from the child game objects of the tileset, and modifying the material in each of their sharedMaterial properties.

I referenced this approach in my previous posts but for the sake of clarity, I put together a C# script. This script changes the color of a tileset’s material to a random color on spacebar press. This was tested with a basic lit material assigned to the tileset’s “Opaque Material” property.

using UnityEngine;
using CesiumForUnity;
using UnityEngine.InputSystem;

public class MaterialChanger : MonoBehaviour
    public Cesium3DTileset tileset;
    public Color originalColor;

    void Start()
        originalColor = tileset.opaqueMaterial.color;

    void Update()
        if (Keyboard.current.spaceKey.wasPressedThisFrame)
            Color newColor = Color.white;
            newColor.r = Random.Range(0.0f, 1.0f);
            newColor.g = Random.Range(0.0f, 1.0f);
            newColor.b = Random.Range(0.0f, 1.0f);

            // Change the parameter on the opaque material so new tiles spawn with the change.
            tileset.opaqueMaterial.color = newColor;

            // Propagate the change to existing tiles.

    void UpdateExistingTiles(Color color)
        MeshRenderer[] renderers = tileset.transform.GetComponentsInChildren<MeshRenderer>(true);
        foreach (MeshRenderer renderer in renderers)
            renderer.sharedMaterial.color = color;

    void OnDisable()
        // If we don't do this, the material will keep the changes when we exit the application
        // (at least, in the Editor).
        tileset.opaqueMaterial.color = originalColor;

Here is a gif of it in action.

material changes

Let me know if you have any other questions.

Another addendum to the script: if you don’t want to deal with the complications of modifying tileset.opaqueMaterial (which will overwrite the material in the editor), you can also subscribe to the OnTileGameObjectCreated event on the Cesium3DTileset. This event is broadcasted whenever a tile is added. Then, you can just change the parameter on the MeshRenderer, similar to what the script is doing for already existing tiles.

Hello Janine,

thank you very much for your answer, I will look into this.