To project a heightmap on a sphere's surface

Hello, for a while now I’ve been trying to make my planet surface generation script(as in a planet with bumps and mountains) but so far all I managed to do is to generate a customizable “cube sphere” type of object like the one in this tutorial. Now that I have a spherical mesh I’m trying to add a heightmap to its surface to add some surface variations but the problem is - my code doesn’t work(at least not as intentioned). I can’t figure out what’s the issue with it, there is no error in runtime and when I try to change things and press play the entire editor crashes.

I have made a ConvertHeightToMesh function which is supposed to take an inputted heightmap and turn it into a physical terrain on the surface of the cube sphere. Here it is:

    private Mesh mesh;
    private Vector3[] vertices; // cube sphere vertices
    private Vector3[] normals; // cube sphere normals
    private Color32[] cubeUV; // currently displays red green blue colors to help differentiate faces
    public Texture2D hMap; // HeightMap received from input
    void ConvertHeightToMesh()
    {
        List<int> triangles = new List<int>();
        int hWidth = hMap.width;
        int hHeight = hMap.height;

        //Bottom left section of the map, other sections are similar
        for (int i = 0; i <= hWidth; i++)
        {
            for (int j = 0; j <= hHeight; j++)
            {
                //Add each new vertex in the plane
                vertices[i] += (new Vector3(i, hMap.GetPixel(i, j).grayscale * 100, j));

                //Skip if a new square on the plane hasn't been formed
                if (i == 0 || j == 0) continue;
      
                //Adds the index of the three vertices in order to make up each of the two tris
                triangles.Add(250 * i + j); //Top right
                triangles.Add(250 * i + j - 1); //Bottom right
                triangles.Add(250 * (i - 1) + j - 1); //Bottom left - First triangle
                triangles.Add(250 * (i - 1) + j - 1); //Bottom left
                triangles.Add(250 * (i - 1) + j); //Top left
                triangles.Add(250 * i + j); //Top right - Second triangle
            }
        }

        Vector2[] uvs = new Vector2[vertices.Length];
        for (var i = 0; i < uvs.Length; i++) //Give UV coords X,Z world coords
            uvs[i] = new Vector2(vertices[i].x, vertices[i].z);

        mesh.vertices = vertices; //Assign verts, uvs, and tris to the mesh
        mesh.uv = uvs;

        // Add heightmap triangles to surface triangles
        for(int i = 0; i < mesh.triangles.Length; i++)
        {
            mesh.triangles[i] += triangles[i];
        }

        mesh.RecalculateNormals(); //Determines which way the triangles are facing
    }

The problem is I can’t get the calculations correctly and its very hard to do trial and error when the editor keeps crashing all the time. This is the latest result I have gotten when pressing play without it crashing:


How would I go about doing this correctly? I’m really lost in the calculations and have read many articles about it.

I don’t have a lot to contribute to the actual algorithm, but I can say that an editor crash usually indicates you have created an infinite loop in the code. If you are modifying the loops, it might be a good idea to create an escape valve in the form of a maximum number of iterations (something like 100,000) just during development to ensure you don’t create an infinite loop. You can do this with a simple counter and a “break” statement.

Hmm, unless “mesh.triangles.Length” is infinite I don’t think its an infinite loop that is causing the issue as there seems to be none. (The rest of the code that is in charge of creating the cube sphere object works, the issues started when I added the ConvertHeightToMesh function). I think it may also be crashing because modifying a mesh’s vertex positions is an expensive operation to process for a program like Unity that is not made for 3D modeling.

Gotcha, I wasn’t sure what kind of modifications you were making. I agree that the code as presented above has no infinite loops.

Hmm, I’ve certainly done this before with no ill effects. But it might depend on how much data we’re talking about. How big are these heightmaps?

By the by:

vertices[i] += (new Vector3(i, hMap.GetPixel(i, j).grayscale * 100, j));

Should this actually be something like:

vertices[(i * hHeight) + j] += (new Vector3(i, hMap.GetPixel(i, j).grayscale * 100, j));

So far I’ve only tested it with one heightmap that is 257x257 in size. I also first made a similar script to the ConvertHeightToMesh script which generated the heightmap on a flat plane which is why I believe my current script should be working. That other plane script worked perfectly fine except for the surface’s color which was pink.

Just tested this, it appears to not have changed anything, the resulting surface is still the exact same as in the screenshots. This is good though, now I know to take a look at the triangles assignment portion of the code instead.

        for (int i = 0; i <= hWidth; i++)
        {
            for (int j = 0; j <= hHeight; j++)

Just a wild guess here, but shouldn’t that be a i < hWidth and j < hHeight (“<” NOT “<=”). Since you start iterating at zero it is usually up to max bounds (exclusive). You say width is 257 but if you start at zero you have 258 iteration steps. That may get an out of bounds access into your array. I just wonder why Editor does not catch it and points it out. But I have been wrong before. But it’s something you could try.

If that does not solve the crash problem I would try to comment out blocks beggining from the end and see if it works (ie does not crash). The block you commented out last then contains the problem.

The “correctness” of your mesh generation is a entirely different problem though.

1 Like

Keep in mind if you have sufficiently large input dimensions and do enough code in each cell, it will appear to crash even though it is simply taking a looooooooooong time to finish.

With engineering always simplify. Start with a 10x10 array. If it still crashes, yeah, we can probably assert that there’s a failure in the looping. But if something huge seems to crash, the Halting Problem (look it up) specifies there’s no way for us to actually see if it crashed, or if it’s just taking a long time.

2 Likes

Good point, I corrected the code but the result is still the same.

Since my cube sphere object is configurable I adjusted the vertex amount to a lower one. There is no crash now, but I haven’t tried adjusting the loops - and whenever I did it seemed to crash with the higher vertex count. The mesh is still disfigured even with a lower vertex count and so I presume there is an issue with how I try to put the heightmap onto the mesh. I’m not too sure what is it in the code that is wrong but that is the result I get when pressing play:
6193530--679272--Screenshot_345.png 6193530--679275--Screenshot_346.png

Ok did some major code rewrite and this is the new function:

    void ConvertHeightToMesh()
    {
        Vector3 vertex;
        int maxHeight = hMap.height;
        for (int i = 0; i < vertices.Length; i++)
        {
            // Get existing vertex from mesh vertices array
            vertex = vertices[i];

            // Calculate latitude and longtitude for each vertex
            float latitude = Vector3.Angle(Vector3.up, vertex);
            float longitude = Vector3.SignedAngle(Vector3.right, vertex, Vector3.up);
            float height = hMap.GetPixelBilinear(latitude / 180, longitude / 360).r;

            // Apply heightmap position to existing vertex
            vertex *= 1 + maxHeight * height;

            // Set the new vertex in the cube sphere
            vertices[i] = vertex;
        }

        //what does this do???
        Vector2[] uvs = new Vector2[vertices.Length];
        for (var i = 0; i < uvs.Length; i++)
            uvs[i] = new Vector2(vertices[i].x, vertices[i].z);

        mesh.vertices = vertices; //Assign verts, uvs, and tris to the mesh
        mesh.uv = uvs;

        mesh.RecalculateNormals(); //Determines which way the triangles are facing
    }

It now doen’t crash at all and applies the heightmap very quickly, but I think I have an issue with my vertex direction because the mesh ends up looking like it was blown up.


Would be grateful if someone could help me with this.

What help specifically do you expect? Do you think someone should debug the code for you? As I guess there is only a handful of people on the entire planet which can spot an error in procedural planet mesh generation code just by looking at it.

So the usual workflow of DEVELOPING something is to imporove it incrementally. When it does not work as expected find out why, fix the issue and proceed. If my code were broken I would put some debug logs on strategically placed positions in my code and verify that my assumptions are correct, my formulas are right and my logic is solid.

Procedural planet generation is a complex topic. And I applaud you for endeavouring it. But it is unlikely you will sit down, write some lines of code and have a versatile and feature complete product available after a few hours. If it WOULD be that easy it would have be done many times before. There are plenty of resources out there you can learn from. But in the end you MUST understand what your code does and why it does it. If you don’t, then it does not help you when someone else fixes your problem now, as you will run to the forum with the next question when you change something and it does not work as expected. I don’t say this to discourage you. But what I learnt in my years of developing (not only in Unity) is, that its always best to understand what you write. And the best tool to understand that is extensive debugging. It’s ok to ask for opinions on different aproaches, or if you have issues with an API. But I doubt you will find someone who debugs the code for you. Just think wether you would do this for strangers on the forum. (If you WOULD do it I have a pile of buggy code laying around :wink: ).

So my suggestion is:

  1. validate your vertex positions and the lat and long for them (you could write them in a file).
  2. Visualize stuff you calculate (place spheres there, use colors or Debug.Line etc.).
  3. it looks like your mesh gets distorted.
vertex *= 1 + maxHeight * height;

This looks odd. You should have a normalized vector around a sphere and scale it (multiply it with the new height). When you add stuff it is shifted and does no longer have its position in a spherical manner. Here is again the point of UNDERSTANDING what your code does.
3)

        //what does this do???

LOL. Thats exactly what I mean by “you need to understand what your code does”. If you don’t that would be a great specific question to ask (Instead of “please someone fix my code for me”). And this looks very odd too. Since UV coordinates are usually between 0 and 1 each and you can’t simply assing vertex positions to them. If any you would use your lat/long properly scaled at the range[0,1].
4) fragment your LARGE issue of procedural planet generation into many smaller ones. How to create a normalized sphere? How to scale the vertices to certain heights? How to apply proper UV’s. I just think that your code has more than one issue. Fix them one by one.
5) Back to the drawing board. It’s nothing bad with acknowledging that you are not (yet) capable of doing this. Do some tutorials on the topic. Try to understand other peoples code (when it works). Purchase a product that does what you want. Come back when you have more experience.
6) Be persistent and don’t let problems discourage you. Solve one at a time, learn from them and be proud of it.

But just asking that someone fixes your code (ie develops something for you) will probably not work. But as I said already. I have been wrong before (in one or two occasions :wink: ). Albeit that may not be the answer you expected I think it will still help you more in the long run. When you learn the principals of debugging and developing. Just copying someones code over from the internet will only get you so far.

Good luck.

Edit: formatting was screwed

1 Like

Found the issue, maxHeight was set to a way too big of a value, I lowered it to 0.5~ and now the heightmap appears on the surface of the planet seamlessly.
This is the final code for anyone who is interested, also make sure to read what I type under it because this alone won’t work in your project:
(maxHeight is a public float variable)

    void ConvertHeightToMesh()
    {
        Vector3 vertex;
        for (int i = 0; i < vertices.Length; i++)
        {
            // Get existing vertex from mesh vertices array
            vertex = vertices[i];

            // Calculate latitude and longtitude for each vertex
            float latitude = Vector3.Angle(Vector3.up, vertex);
            float longitude = Vector3.SignedAngle(Vector3.right, vertex, Vector3.up);
            longitude = (longitude + 360) % 360;
            float height = hMap.GetPixelBilinear(longitude / 360, latitude / 180).r;

            // Apply heightmap position to existing vertex
            vertex *= 1 + maxHeight * height;

            // Set the new vertex in the cube sphere
            vertices[i] = vertex;
        }

        //what does this do???
        Vector2[] uvs = new Vector2[vertices.Length];
        for (var i = 0; i < uvs.Length; i++)
            uvs[i] = new Vector2(vertices[i].x, vertices[i].z);

        mesh.vertices = vertices; //Assign verts, uvs, and tris to the mesh
        mesh.uv = uvs;

        mesh.RecalculateNormals(); //Determines which way the triangles are facing
    }

In my code I call the ConvertHeightToMesh function inside my CreateVertex function(the function that creates the vertex for the cube sphere shape) and then after I call the ConvertHeightToMesh function I also set the vertex and tris values to the mesh inside my CreateVertex function, so make sure you have all these saved as variables and that you then set them to the mesh. This is how I assign them to the mesh:

        mesh.vertices = vertices;
        mesh.normals = normals;
        mesh.colors32 = cubeUV;

I also made it so I can change the maxHeight, vertex amount and sphere radius values inside the editor in runtime, to make it so the mesh changes when I change the fields without having the mesh constantly regenerate itself I used the OnValidate function that is called whenever a value in the editor is changed, and inside it I called my mesh createvertex function.

Some photos of my “planet surface”, I still need to assign different materials and textures to the landscape of the planet based on the vertex’s distance from its center but I’ll do that later, for now that’s how it looks like:

3 Likes

You’re completely correct, I was not sure how to debug this kind of code, there are no real set values I could print here or stuff that are not being called when they’re supposed to to check on. This is a logical and mathmatical miscalculation and debugging it via code is hard.

That is exactly the reason I chose this kind of project, I’m looking for a challenge and to learn a thing or two from this. its not my first time trying to do procedural planet generation or specifically trying to put a heightmap onto a spherical surface and so after working on it on my spare time for a few weeks I decided to go and ask online as it seems other questions of this kind weren’t answered, in general it was pretty hard to find any relavent information about this topic that is not deprecated or old.

Thank you.

1 Like

Procedural geometry is a pretty steep challenge… welcome!

I personally find procgen very rewarding and many of my games use various procedural geometry processes, just because I can’t be arsed to produce that much bespoke content, plus I want the computer to surprise ME when I’m playing.

I have a package called makegeo with some other ways of making procedural geometry, just sort of a hodgepodge of stuff I have tested and fiddled with. You can check it out here:

MakeGeo is presently hosted at these locations:

https://bitbucket.org/kurtdekker/makegeo

2 Likes

Thank you, I’ll check it out later