(solved) Terrain: how to eliminate contour lines?

Hi all!

By trying to use a nice heightmap in JME I encountered some problems and need some help if possible:

1st: The heightmap being used always gets spiky as hell. Therefore I tried to blur the whole grayscale heightmap. After using a 25x (!!) Gaussian Blur I received a result like that:







Note: (the pic on the left side is the original heightmap, not the blurred one)



However, the Terrain was smoothed a lot and the Mountain tops were rounded, which wasn’t that great at all. How are you supposed to create rock faces or mountain ridges? The greater problem however are all these curved “contour lines” which you see in the image above. They will always be visible even when using textures. Is there a possibility to smooth it some way, that the landscape looks like here?



A heightmap is just a load of numbers, smoothing, plateau's , peaks etc is just changing those numbers …



Have a look at hillheightmap or some of the oter classes that extends abstractheightmap, there are some washing down effects that smooth the terrain by sampling adjacent points and compute the average

looks like you are using an 8 bit heightmap, with 256 height values which would give you that look when blurred (often see the ridges even before).

Either use a 16-bit raw and make sure the heigh ranges use the whole possible value range or if in the 8-bit range (0-255) multiply all your values with a big scale before starting your blurring.



Another option is modding our heightmap tech to do floats…

True, I’m using a 8 bit heightmap with 256 values. The problem is, how can I create a 16 bit grayscale Image? I don’t see such an option in GIMP or PS. Also CinePaint which might create 16 bit grayscales doesn’t work (obivously the Windows-version is from 2003 and each time I tried to safe a picture blurred using by 16 bit range the application crashes)



Therefore I tried to use a .RAW file as a base as you suggested: Using an export of terragen in 8 or 16 bit raw files:



8 bit RAW:

- also note how the terrain is distorted and most of all cut off at the diagonal, for I don’t know what reason  :?



16 bit RAW:

No matter which values I change, it’s either completely flat or spiky like hell  :oops:





btw. this is the code I used for “importing” the heightmap:

RawHeightMap rhm = new RawHeightMap("src/heightmap.raw",256);
       
        int[] map2 = rhm.getHeightMap();

        TerrainBlock tb = new TerrainBlock(
                      "block2",
                       rhm.getSize(),
                       new Vector3f(5.0f,1.0f,5.0f),
                       map2,
                       new Vector3f(0, 0, 0),
                       false);



Also I am wondering why there is no such method or constructor for ImageBased Heightmaps as seen in "MidPointHeightMaps";
e.g. in MidPointHeightMap(64,1.5f) the float value seems to determine how spikey or smooth the Terrain is (I figured out it gets spikey at values < 0.5, and smoother > 1). That's exactly what I would need. Is it really that one has to write an own Filter for a Terrain that was based on a grayscale Image??

I am really stuck with these issues and would appreciate any help.

Have you tried just eroding the terrain once its constructed??



That should help smooth alot.



And if that doesn't work.  You could try averaging all the points yourself.

loop through all the vertices, getting the surrounding values and average them, setting the point to that average. 

There is probably a much more elegant (and less artifact prone) algorithm than that, but it should work.



Also, try playing with the terrain scale on your 16 bit.  I think you want the Y-scale to be 1/4 of what the 8 bit is.

Ah finally the 16bit RAW is working: I found out, that I always had the bold values missing in the import:



RawHeightMap rhm = new RawHeightMap("src/heightmap.raw",513,RawHeightMap.FORMAT_16BITLE, true);



The result is much better now:




basixs said:

Have you tried just eroding the terrain once its constructed??


Hmm as it seems the function erodeTerrain() requires a 2-dimensional FLOAT array, whereas .getHeightMap() delivers a 1-dimensional INT array only. Now going back to importing an 8bit grayscale imagebased heightmap only - here is what I did: I converted the array into a float array, and then filled it into a 2D matrix. After that I called erodeTerrain(); I do not get but how the TerrainBlock should use the "eroded Terrain"??? The constructor of the TerrainBlock has no use for this method.

URL grayscale = HelloTerrain.class.getClassLoader().getResource("heightmap.png");
        ImageBasedHeightMap ib = new ImageBasedHeightMap(new ImageIcon(grayscale).getImage());
       
        int[] arr = ib.getHeightMap();   
       
        float[] floatarr = new float[arr.length];   //initialize new float array
       
        for(int i=0;i<arr.length;i++)
        {
           float arrfloat = (float) arr[i];      //convert int values to float
           floatarr[i] = arrfloat;                 //save float values in float-array
        }       
       
        float[][] floatarr2d = new float[ib.getSize()][ib.getSize()];   //create 2D matrix
        int x = 0;   //initialize x-axis (rows)
        int y = 0;   //initialize y-axis (columns)
       
        for(int j=0;j<arr.length;j++)
        {
           if(y > (ib.getSize()-1))   //switch to next line if row is full
           {
              x++;
              y = 0;
           }
           floatarr2d[x][y] = floatarr[j];   //fill in the numbers
           y++;
        }
       
       
       
        //Test Matrix before eroding
        System.out.println("Position 3: " + floatarr[2] + "tPosition 512:" + floatarr[511]);
        System.out.println("Row [0], Column [2]: " + floatarr2d[0][2] + "tRow [1], Column [255]:" + floatarr2d[1][255]);
       
        ib.erodeTerrain(floatarr2d);
               
        //Test Matrix after eroding
        System.out.println("Position 3: " + floatarr[2] + "tPosition 512:" + floatarr[511]);
        System.out.println("Row [0], Column [2]: " + floatarr2d[0][2] + "tRow [1], Column [255]:" + floatarr2d[1][255]);
       
        // Create a terrain block from the image's grey scale
        TerrainBlock tb = new TerrainBlock("image icon",
              ib.getSize(),
              new Vector3f(2.0f,0.5f,2.0f),
              arr,
              new Vector3f(0, 0, 0),
              false);

That is an issue with the terrain classes.  I had to convert the heightmap data to a dual indexed array for the errode terrain, then convert it back to a single indexed array for the terrain construction.



  public void erodeTerrain( float erodeAmount ) {

        if( erodeAmount < 0 ){
            erodeAmount = 0;
        } else if( erodeAmount > 1 ){
            erodeAmount = 1;
        }

        filter = erodeAmount;     // erosion filter
        float[][] tempData = convertDimensions( heightData );
        erodeTerrain( tempData );
        heightData  = convertDimensions( tempData );
    }

    // Converts and returns stored from single to dual indexed array
    private float[][] convertDimensions( int[] data ) {
       int size = Integer.valueOf( heightData.length / heightData.length );
        float[][] floatArray = new float[ size ][ size ];

        for( int row = 0; row < size; ++row ){
            for( int col = 0; col < size; ++col ){
                floatArray[row][col] = data[col + ( row * size )];
            }
        }

        return floatArray;
    }

    // Converts and stores array from dual to single indexed array
    private int[] convertDimensions( float[][] floatArray ) {
        int[] data = new int[ floatArray.length*floatArray.length ]
        Float tempFloat;

        for( int row = 0; row < size; ++row ){
            for( int col = 0; col < size; ++col ){
                tempFloat = floatArray[row][col];
                data[col + ( row * size )] = tempFloat.intValue();
            }
        }

     return data;
    }




Ah damn, I already thought that way! I didn't realize however that the method erodeTerrain() stored the changed values in the same 2D Matrix, where it gets the values! I was therefore stuck on the thought "how can I get the changed values from a void method?", although I had a println with the changed values…  XD



Now the result looks very nice! No need to blur the Image with GIMP/PS  :slight_smile:


MrCoder said:
or if in the 8-bit range (0-255) multiply all your values with a big scale before starting your blurring.


Still, since it was an 8bit grayscale only it contains 65536 numbers each one ranging from 0-255, it is not as smooth as a 16 bit RAW. Hmmm multiplying with all values will not change anything of course. I guess you meant in detail something like that: expanding the 256x256 (in this case) to 65536x65536 - then putting in our known 65536 color values, multiply them by 256 each one of them and then - the difficult part: interpolate all missing values mathematically? Did i get this right? :s this sounds challenging if there is no function  :D

Actually, I think you could just let the errodeTerrain function do all the heavy lifting for you. :slight_smile:



Try just re-scaling all the numbers, then applying the errodeTerrain.  You might be pleasantly surprised, but then again maybe not…

Thanx! That worked and is satisfying enough. I multiplied all values from the floatarray by 10 before smoothening them.



For all beginners who might have problems too, i will post these motvating results:

Pic1 - original heightmap, eroded - note the "steps"

Pic2 - values from heightmap multiplied by 10, eroded => "steps" are gone - note the pixelated shadows in the red circle

Pic3 - same as Pic2 but using a base texture for the landscape - also the pixelated shadows are gone :smiley: