Help: Sphere to Plane Terrain Transition and CubeMap creation

Hi,

As one of my learning curves with terrain creation I was hoping to have a simple sphere textured using a 3D noise function set to give the visual of what the world looks like.

Then, I want to pick a point ( coordinates etc ) and build a planar terrain around that point and have it generated based on the same noise function set that created the sphere and thus should show the same visual.

As you can see from the images attached the sphere looks somewhat valid ( the poles have a mess of texture, which may be resolved if I use something different to the Unity Sphere ). However, the four planes are not quite working the way I was expecting. I can see that the top left when wrapped round will line up with the bottom left and similarly on the right. The row of 4 image shows this working. But not the way I thought it would.

I was hoping to work the planes in as a 3 x 3 or 5 x 5 block of smaller terrains, with their respective positioning information, which would be tiled across the x and z axis based on the distance ratio and planet radius and thus eventually reach the original tile.

Am I missing something obvious here? Here are the two main code blocks that generate the texture from the ComplexNoise noise class.

Thanks in advance.

xSphere Monobehaviour Class

    [Header("Terrain Information")]
    public int width = 360;
    public int height = 180;
    public float radius = 1f;
    public int seed = 0;

    [Header("Texture Information")]
    public int texWidth = 512;
    public int texHeight = 256;
    public Gradient gradient;
    public Shader shader;

    [Header("Positioning Information")]
    public Vector3 virtualOffset;
    public float eastBounds;
    public float westBounds;
    public float northBounds;
    public float southBounds;

    Bounds bounds;
    xSphericalMap sphereMap;
    ComplexNoise complexNoise;

    // Use this for initialization
    void Start () {

        float ewStep = eastBounds - westBounds;
        float nsStep = northBounds - southBounds;
        bounds = new Bounds();
        bounds.center = virtualOffset;
        bounds.min = new Vector3(westBounds, 0, southBounds);
        bounds.max = new Vector3(eastBounds, 0, northBounds);

        complexNoise = new ComplexNoise();
        complexNoise.Define(seed);

        sphereMap = new xSphericalMap();
        sphereMap[0] = (ComplexNoise)complexNoise;
        sphereMap.MapBounds = bounds;
        sphereMap.MapRadius = radius;
        sphereMap.MapResolution = new Vector2(texWidth,texHeight);
        sphereMap.MapSeed = seed;
        sphereMap.Build();

        Texture2D texData = xTexture.Create(sphereMap.MapData, gradient, false);
        xTexture.Write2PNG(Application.dataPath + "/Heightmap_W" + width + "H" + height + "_Seed_" + seed + ".png", texData);

        Material mat = new Material(shader);
        mat.mainTexture = texData;
        GetComponent<Renderer>().material = mat;
    }

xSphericalMap Class

    // Build the spherical map
    void BoundsToPolar()
    {
        westBounds = mapBounds.min.x;
        eastBounds = mapBounds.max.x;
        southBounds = mapBounds.min.z;
        northBounds = mapBounds.max.z;
    }

    public override void Build()
    {

        // Make sure we have one sub module assigned
        base.HasEnoughModules(1);

        BoundsToPolar();

        float startTime = xUtils.StartTimer();

        float lonExtent = eastBounds - westBounds;
        float latExtent = northBounds - southBounds;
        float xDelta = lonExtent / MapResolution.x;
        float yDelta = latExtent / MapResolution.y;
        float curLon = westBounds;
        float curLat = southBounds;

        mapData = new float[(int)MapResolution.x, (int)MapResolution.y];
        int width = (int)MapResolution.x;
        int height = (int)MapResolution.y;

        Vector3 virtualOffset = mapBounds.center;

        for (int y = 0; y < MapResolution.y; y++)
        {
            curLon = westBounds;
            for (int x = 0; x < MapResolution.x; x++)
            {

                float _r = mapRadius * Mathf.Cos(Mathf.Deg2Rad * curLon);
                float _x = _r * Mathf.Cos(Mathf.Deg2Rad * curLat);
                float _y = _r * Mathf.Sin(Mathf.Deg2Rad * curLon);
                float _z = mapRadius * Mathf.Sin(Mathf.Deg2Rad * curLat);

                mapData[x, y] = ((ComplexNoise)subModules[0]).Evaluate(_x, _y, _z);
                curLon += xDelta;
            }
            curLat += yDelta;
        }

        float endTime = xUtils.EndTimer();
        Debug.Log(string.Format("Spherical Data Generated in {0} milliseconds", xUtils.TimerDuration(startTime, endTime) * 1000.0f));
    }

The other day I stumbled across another way for building the sphere texture and remove the artifacts but not sure if it will work with latitude/longitude noise generation yet as documentation for this feature assumes you are using it from an active scene.

Anyway, the item I found was a cubemap made of 6 textures for each side of the cube. Once converted into a cubemap and fitted to a sphere all artifacts and seams disappear. This worked great when using a cubemap 6 texture cross set up already created.

However, I need to be able to create these 6 textures at run time and fit it to the cubemap and apply to a sphere. Seeing as this part doesn’t necessarily have to be done at run time in the final game we can get away with just creating the 6 textures and manually create the cubemap but this isn’t the problem. The problem is there is no documentation on how to create the 6 textures with the correct offset for each side. All unity examples show how to attach already existing textures to the cubemap but not how to generate each side programmatically without using the camera to create a scenic cube map. This is not what I want.

The following line was explained to be the first texel of the positive x texture based on the center point of the side but try as I might I can’t figure out how to make it work for the others.

Vector3(1.0f, 1.0f - halfPixelOffset, -1.0f + halfPixelOffset).normalized);

I assumed moving the fixed value between the x,y,z values and the relative setting moved to the other setting would be enough, switching between negative and positive accordingly, but all it did was create the same texture for each side and not based on the offsets sent to the creation function below :

    Texture2D CreateCubeSide(CubemapFace face,Color c, Vector3 v)
    {

        Texture2D tex = new Texture2D(cubemapSize, cubemapSize, TextureFormat.ARGB32,true);
        tex.wrapMode = TextureWrapMode.Clamp;
        Color[] colors = new Color[cubemapSize * cubemapSize];
        for ( int y = 0; y < cubemapSize; y++)
        {
            for ( int x = 0; x < cubemapSize; x++)
            {
                [b]float h = ((ComplexNoise)noise).Evaluate(noiseScale * new Vector3(v.x + x, v.y, v.z + y));[/b]
                colors[y * cubemapSize + x] = gradient.Evaluate(h);
            }
        }
        tex.SetPixels(colors);
        tex.Apply();

        xTexture.Write2PNG(Application.dataPath + "/CubeMap_" + face.ToString() + "_" + + seed + ".png", tex);

        return tex;
    }

So, what I need to know is …

How to create each of the 6 sides of the cubemap so that the same noise formula can be used to create them as well as when creating the landscape linked to the planet the cubemap is created for. Using latitude and longitude values preferably as these seem to be the best way to work with spherical landscape creation.

And is it in fact possible to do it this way, or should I go via the proper 3D noise route and get the right x,y,z coordinates from the latitude,longitude,radius values to get the right value, but then how would this work with a 2D texture, or is this when we use Texture3D ? Never tried that object out. Might try that next to see how it works.

Managed to get a Texture3D script running ( with the help of a script and shader found online ). However, even a simple 32x32x32 texture took far too long seeing as it only works during run time mode as there is no facility to save the texture to file like there is with the Texture2D.

Looks like that option is off the books.

Well, trying to the cubesphere route using cat scripting tutorials and tweaking the one that has 3 different materials with each opposite sharing the same material. I suspect I will have to have separate material to work for me but maybe one of those texturepacks may work for me with offsets.

And then, in theory the planet (cubesphere) will have one scale of noise and the landscape will have another. EG. 1 cell on the sphere maybe 1,000 km whereas on the landscape it could become a 3x3 or 5x5 plot of land adjusting as needed… in theory.

Just thought I would post an update in case someone was reading this, although I doubt that is the case.

Well after searching and perusing 3 different examples of planetary terrain that has seamless transition the common denominator was a quadsphere with each quad having a quadtree that held a single mesh that was a quarter of the size and offset to fit into the spot reserved for it.

Still a work in progress I have managed to create a quadsphere and code a quadtree split of any side or all to a set level of detail. However, the next step is to decide which quad segment needs to be split and only split that but so far it looks like this will resolve the issue.

New avenue of exploration that made this work is the Matrix4x4 structure. I create one 128x128 mesh plane ( it may work with non power of two but I haven’t tried that bit out yet. ) that faces up and use that as the basis for every quad that makes up the quadsphere. The matrix structure for each quad determines how each vertex fits into its data, all that needs to be done is set each parent quads matrix based off of the set up of the base quad mesh and manipulate each quads vertices based on its matrix.