Dynamic Terrain height inconsistency with different sizes of heightmaps

The idea is the following:

  • server generates a heightmap, serves it as a .png image file
  • client creates a mesh with BabylonJS Dynamic Terrain

Expected behaviour:
A height value (let it be X) on a small heightmap (129x129) is the same height in game as a height value (let it be Y) on a different, big heightmap (2049x2049), in case X == Y.

Observed behaviour:

  • small heightmap: works well
  • big heightmap: big flat piece of ground

129x129 heightmap:
heightmap_small
Terrain generated from 129x129 heightmap:

2049x2049 heightmap:


Terrain generated from 2049x2049 heightmap:

Client code of terrain creation:

private initDynamicTerrain(scene: any, terrainDataId: string) {
this.heightmapService.getHeightmapInfo(terrainDataId).subscribe(heightmapInfo => {
const heightmapUrl = this.heightmapService.getHeightmapUrl(terrainDataId);
const heightmapOptions = {

          width: heightmapInfo.width, height: heightmapInfo.height,
          subX: heightmapInfo.width, subZ: heightmapInfo.height,
          onReady: this.createTerrain.bind(this),
          minHeight: 0,
          maxHeight: 10
    };
    const mapData = new Float32Array(heightmapInfo.width * heightmapInfo.height * 3);
    BABYLON.DynamicTerrain.CreateMapFromHeightMapToRef(heightmapUrl, heightmapOptions as any, mapData, scene);
  });

}

private createTerrain(mapData: number[], mapSubX: number, mapSubZ: number): void {
const options = {

 terrainSub: 200,
  mapData,
  mapSubX,
  mapSubZ
};
const terrainMaterial = this.createTerrainMaterial(this.scene, this.heightmapService.getSplatmapUrl(this.terrainDataId));
const terrain = new BABYLON.DynamicTerrain('terrain', options, this.scene);
terrain.mesh.material = terrainMaterial;
terrain.subToleranceX = 16;
terrain.subToleranceZ = 16;
terrain.LODLimits = [4, 3, 2, 1, 1];
terrain.createUVMap();

this.scene.registerBeforeRender(() => this.setCameraHeight(this.camera, terrain));

}

Ultimately my goal is to have a mapData with fixed resolution, so it’s size depends only on the heightmap.png (that’s why width: heightmapInfo.width == subX: heightmapInfo.width)
And a fixed draw distance (the moving, morphing terrain size), regardless of the in memory mapData size.

And I think this is the right way to do it, I just can’t figure out why my terrain gets flatter when I increase the size.

Hi Attila_Lorincz,

tl;dr: The third paragraph contains the only actionable piece of information in this reply, so you can skip to that if you want. The first two paragraphs are just background. Sorry if it gets a little long-winded; terrain generation is a perennial passion of mine. :slight_smile:

This is just a guess, but from looking at your heightmaps, it looks like the frequency of the noise on your large map is much lower than on your small map; in fact it looks like the frequency is perhaps being calculated based on the width of your image, not the resolution. Put a different way, when you cross your small image from one edge to the other, it looks (again, just eyeballing it) like you can expect to cross roughly two major hills and two major valleys on a typical crossing. The same appears to be true for your large heightmap; even though a crossing traverses many more pixels, broadly speaking it looks like you can expect to pass two hills and two valleys on a typical traversal.

If this is accurate, and if the generated terrains are sized based on pixels (i.e., one pixel is 32 meters wide, or something like that), then the output you’re seeing may actually be correct because the slopes on the larger image will be much shallower. As an example, in your small image, it takes roughly 30 pixels (once again, eyeballing it), or 20% of your image width to go from one of the darkest pixels to one of the brightest. In the large image it still takes about 20% of the image width, but that now corresponds to 400 pixels. If the terrain heights are the same – that is, if the height difference between the brightest and darkest pixels is identical – then this difference in noise frequency would cause the slope I just described to be about 13 times shallower in the large image than in the small one, leading to the appearance of flatness you observed.

Again, this is just a guess as to what’s happening; but fortunately, there’s an easy way to check whether noise frequency is really what’s wrong. Generate a new large heightmap that is just your made of your small heightmap tiled over and over again. It will have seams and ugly artifacts, of course, but that doesn’t matter for this test. What’s important is that your new heightmap will have the same noise frequency relative to the pixels as your small map does. If this map comes out looking like your small map tiled over and over again (you may have to fly in a little to see the details), then noise frequency was almost certainly the issue. If the map comes out looking super flat, even from up close, then there might be something else going on.

Again, sorry if I got a bit long-winded. Terrain stuff is just awesome. :smiley:

2 Likes

Thanks for the detailed explanation :slightly_smiling_face: , this was the problem indeed.
I’m just starting to dive into terrain generation, so the problem wasn’t apparent to me.

I’m using a basic diamond square algorithm by the way, I guess with this if I increase the size, I have to increase max height too, but then I just lose the detail.
Any idea how to effectively increase frequency with diamond square algorithm?
I know, I know, just use perlin, will do that too.
I tried to increase the randomness range, but that just makes the shallow hills pointy. :smiley:

Hmm… My diamond-square recollection is a little rusty, but I think what you mean by “randomness range” is to decrease the attenuation of midpoint displacement as you go down in scale. That’s normally how I’d recommend increasing frequency with diamond-square, but again my knowledge is a bit rusty. Just as a rule of thumb, it’s useful to have something in your scene that you know the size of, like a 1-meter cube, as a point of reference; that helps avoid situations where things that are actually tall look shallow just because of how wide an environment they’re in.

As for Perlin, again I’m pretty rusty, but my recollection is that it has some patent/copyright issues that make it a little complicated to use. As an alternative, most of my projects have been based on OpenSimplex, particularly the optimized C# implementation from digitalshadow. This is an awesome algorithm that can be used for all sorts of cool effects; and better yet, the C# implementation has the concept of noise frequency built right in. As far as I know, there’s not a JavaScript/TypeScript implementation yet, but I bet it wouldn’t be too hard to port. :smiley:

Man, I’d forgotten how much I love this stuff.
image

2 Likes

Yeah I will place some fixed sized objects now for sure, to better see the effects of the parameters.

Actually, I was just looking for a good C# implementation of Perlin noise (or Simplex, as I know it’s pretty similar).
I generate the heightmap from a .NET Core backend, so this code was on point, thanks. :smiley:

Thank you for your help, and special thanks for the doggo pic. :smile: