Jump to content
Unity Insider Forum

Problem mit Heightmap / Terrain


Elharter

Recommended Posts

Hallo,

ich baue mir ein Terrain mit Heightmal und Texturen per Script zusammen. Alles funktioniert soweit, aber es tritt folgender Bug auf > siehe Bilder.
Das gesamte Terrain, SplatPrototype, Texture und Heightmap hole ich mir aus einer Binärdatei die ich byte für byte auslese.

Wenn ich die Heightmap flatte - dann sieht alles richtig aus - aber eben ohne Höhen.
Somit vermute ich den Bug bei der Erzeugung der Heightmap.

Das Terrain ist 512x512, die Heightmap hat eine Resolution von 64 (65).

Da der Code lange ist hier die dazugehörigen Ausschnitte:

 

Deklaration:
 

public float[,] heights = new float[64, 64];

Auslesen der Binärdaten und schreiben in das Array:

 

        // Heightmap
        int heightMapStart = 20 + landMapLength * 2;
        int heightMapLength = int.Parse(data[20 + landMapLength * 2 + 1].ToString("X2") + data[20 + landMapLength * 2].ToString("X2"), System.Globalization.NumberStyles.HexNumber);

        int index = heightMapStart + 2;
        for (int x = 0; x < 64; x++)
        {
            for (int y = 0; y < 64; y++)
            {
                heights[x, y] = data[index]/255f;
                Debug.Log("Height-Position X:" + x + "/Y:" + y + " - Value:" + data[index]/255f);
                index++;
            }
        }


Erzeugen des Terrains mit Texture und Heightmap:


 

//texture on terrain
        SplatPrototype[] splats = new SplatPrototype[1];
        splats[0] = new SplatPrototype();
        splats[0].texture = data.landmap; // landmap is a list of compress pixel. it starts left/down and runs line by line to up/right. 512x512px

        //splats[0].tileSize = new Vector2(terrain.terrainData.size.x - terrain.terrainData.size.x * 8 / 512, terrain.terrainData.size.z - terrain.terrainData.size.z * 8 / 512);
        splats[0].tileSize = new Vector2(512, 512);

        terrain.terrainData.splatPrototypes = splats;
        terrain.terrainData.heightmapResolution = 64;
        terrain.terrainData.SetHeights(0, 0, data.heights);

 

Irgendwo hab ich einen Fehler und ich finde diesen nicht. Das Array ist von 0-64 auf X und Y korrekt gefüllt.

Bitte um Hilfe

danke

Capture2.JPG

Capture1.JPG

Link zu diesem Kommentar
Auf anderen Seiten teilen

Ist denke es ist kein Fehler, sondern über die Funktionsweise den Unityterrains erklärbar.

Über dein Terrain werden Flächen (Dreiecke) aufgespannt und diese Flächen bekommen jeweils Anteile (UVs) der Splatmap-Textur zugewiesen.
Wenn du eine Erhöhung definierst - wie im Bild zu sehen -, dann muss ebenfalls eine Fläche erzeugt werden vom Rand des Terrains (Höhe 0) zu der definierten Erhöhung der Hightmap, da das Terrain eine geschlossene Oberfläche bildet. Die Fläche die aufgespannt werden muss - vom Boden zum definierten Höhenprofil -  bekommt ebenfalls "Anteile" der Splatmap-Textur und dadurch wird deine Bodentextur "verschoben".

Eine mögliche Lösung wäre, die Splatmap-Textur des Terrain zu exportieren (damit meine ich nicht deine Bodentextur!).  Es gibt hier diverse Tools für:
https://mattgadient.com/2014/09/28/unity3d-a-free-script-to-convert-a-splatmap-to-a-png/

Diesen Export muss man dann in einem externen Bildverarbeitungsprogramm bearbeiten und hier die Ränder der Splatmap korrigieren. Die korrigierte Splatmap-Textur wird dann wieder ins Terrain importiert. Über diese Korrektur kann man quasi die UVs deiner Bodentextur bezüglich des Terrains verschieben.
In deinem Fall müsste man den Rot-Kanal der Splatmap korrigieren (R = 1. Splatmap Prototyp)
Da du in deinem Fall aber die Splatmap genauso groß definierst, wie dein Splatmap-Prototyp (Bodentextur) ist diese Methode ggf. ungeeignet.

Eine andere (leichtere) Lösung wäre die Bodentextur selbst zu modifizieren und hier Ränder einzufügen.
Bedeutet, du fügst deiner Bodentextur einen leeren Rahmen hinzu. Die Breite des Rahmens muss dann so gewählt werden, daß deine Bodentextur erst in der entsprechenden Höhe startet.

g3J7dkY.png

Link zu diesem Kommentar
Auf anderen Seiten teilen

Die Antwort muss ich erstmal sacken lassen ^^ ... und mehrmals lesen um sie ordentlich zu verstehen.

Was ich sagen kann: alles wird Ingame, also on-the-fly erzeugt und verarbeitet. Ein manuelles exportieren und Rahmen hinzufügen scheidet daher aus.

Danke dir vorerstmal für die Mühe!

Link zu diesem Kommentar
Auf anderen Seiten teilen

Dein Fehler könnte hier liegen. Damit dieser Code funktioniert, muss dein Array "data" von der Dimension her passen. Ich kenne dieses Array ja nicht (kann sein daß x und y noch vertauscht werden muss, da musst du mal schauen).

Der untere Code geht davon aus, daß die Daten für die Heightmap beim Index 0 losgehen:
data[0]
...
data[4096]

      for (int y = 0; y < 64; y++)
        {
            for (int x = 0; x < 64; x++)
            {
                //heights[x, y] = data[index]/255f;
                //Debug.Log("Height-Position X:" + x + "/Y:" + y + " - Value:" + data[index]/255f);
                //index++;
                heights[y, x] = data[y * 64 + x] /255f;
            }
        }

 

Link zu diesem Kommentar
Auf anderen Seiten teilen

Auf dem Bild sieht man den Rahmen links und unten gut.....

Zu deiner Antwort:

byte[] data = File.ReadAllBytes(filePath);

in Data lese ich alle Bytes des "Trackfiles" - das einem gewissen Format folgt.

 

Danach kommt:
 

// Heightmap
        int heightMapStart = 20 + landMapLength * 2;
        int heightMapLength = int.Parse(data[20 + landMapLength * 2 + 1].ToString("X2") + data[20 + landMapLength * 2].ToString("X2"), System.Globalization.NumberStyles.HexNumber);

        int index = heightMapStart + 2;

Hier wird festgelegt an welcher Stelle begonnen wird die Bytes zu lesen.
Laut Definition ist dies wie in dem angehängten Bild. Danach wird der Index festgelegt.


data[index] liest also die einzelnen Höhenwerte aus die zwischen 0 und 255 liegen können.

Ein Problem mit dem Array/Größe oder ähnliches kann ich mir sehr gut vorstellen.

Habe auch schon jemanden gefunden der ein sehr ähnliches Problem hatte: https://answers.unity.com/questions/1236961/border-around-procedural-height-map.html
Leider 
verwendet er kein Terrain usw. und somit ist die Lösung augenscheinlich eine andere.

lg

Link zu diesem Kommentar
Auf anderen Seiten teilen

Okay ich glaube ich habe es eingrenzen können.

Das Array hat 64x64 Felder also 4069 einträge. Alle sind gefüllt und >0. Daran scheint es also nicht zu liegen.

Befülle ich die Hmap mit terrain.terrainData.SetHeightsDelayLOD(0, 0, data.heights); und lese es sofort danach wieder aus mit:

 

        int xRes = terrain.terrainData.heightmapWidth;
        int yRes = terrain.terrainData.heightmapHeight;
        Debug.LogError("Heightmap x:" + xRes);
        Debug.LogError("Heightmap y:" + yRes);

Hat die Hmap aufeinmal 65x65 ....ich denke das ist der Fehler. Bloss wieso ?

Link zu diesem Kommentar
Auf anderen Seiten teilen

Siehe meine Mail, ich vermute du musst dein Array auf 65x65 Felder "strecken", da Unity bei der Heightmap-Resolution immer 2^x + 1 haben möchte.
Wenn du also bei der HMR 65 einstellst und über SetHeights nur ein 64x64 Array übergibst, dann setzt Unity die Höhe für die letzte Spalte und die letzte Zeile nicht (=Höhe 0) und damit bekommst du einen Rand.

Ich würde einfach den letzten Spalteneintrag verdoppeln und die letzte Zeile.

Link zu diesem Kommentar
Auf anderen Seiten teilen

Ich hab dir mal eine Routine gebaut, welche das Array entsprechend erweitert und die Daten am Rand dupliziert:

public float[,] heights = new float[64, 64];
...
...Daten aus deiner Binärdatei einlesen
...

float[,] heights65x65 = ResizeArray(heights); // Erweitertes Array erstellen


float[,] ResizeArray(float[,] source)
    {
        float[,] resultArray = new float[source.GetUpperBound(0) + 2, source.GetUpperBound(1) + 2];

        float lastEntry = 0;
        for (int y = 0; y <= resultArray.GetUpperBound(0); y++)
        {
            for (int x = 0; x <= resultArray.GetUpperBound(1); x++)
            {
                //print(arr[y, x]);
                if (y < resultArray.GetUpperBound(0) && x < resultArray.GetUpperBound(1))
                {
                    resultArray[y, x] = source[y, x];
                    lastEntry = source[y, x];
                } else
                {
                    resultArray[y, x] = lastEntry;
                }

            }
        }

        return resultArray;
    }

 

Link zu diesem Kommentar
Auf anderen Seiten teilen

Ich habs anders gelöst:

 

        // avoid Border Fix - copy last line/column to 64 to extend it for 1 line/column
        for (int x = 64; x < 65; x++)
        {
            for (int y = 0; y < 65; y++)
            {
                heights[x, y] = heights[x - 1, Mathf.Clamp(y - 1, 0, 64)];
            }
        }
        for (int y = 64; y < 65; y++)
        {
            for (int x = 0; x < 65; x++)
            {
                heights[x, y] = heights[Mathf.Clamp(x - 1, 0, 64), y - 1];
            }
        }

:rolleyes::rolleyes:

Link zu diesem Kommentar
Auf anderen Seiten teilen

Ja, deine Arrayerweiterung sieht besser aus, die Zeilenerweiterung war bei mir eher schlecht, da ich nicht die obere Zeile kopiere... ;)

So hier wie ich es eigentlich haben wollte:

    float[,] ResizeArray(float[,] source)
    {
        float[,] resultArray = new float[source.GetUpperBound(0) + 2, source.GetUpperBound(1) + 2];

        float lastEntry = 0;
        for (int y = 0; y <= resultArray.GetUpperBound(0); y++)
        {
            for (int x = 0; x <= resultArray.GetUpperBound(1); x++)
            {
                //print(arr[y, x]);
                if (y < resultArray.GetUpperBound(0) && x < resultArray.GetUpperBound(1))
                {
                    resultArray[y, x] = source[y, x];
                    lastEntry = source[y, x];
                } else
                {
                    // Kopiere Spalte
                    resultArray[y, x] = lastEntry;
                }

                // Kopiere Zeile
                if (y >= resultArray.GetUpperBound(0))
                {
                    resultArray[y, x] = resultArray[y-1, x];
                }

            }
        }

        return resultArray;
    }

 

Link zu diesem Kommentar
Auf anderen Seiten teilen

Archiviert

Dieses Thema ist jetzt archiviert und für weitere Antworten gesperrt.

×
×
  • Neu erstellen...