Jump to content
Unity Insider Forum

Einführung - Prozedurale Meshes


damuddamc
 Share

Recommended Posts

Einstieg in die prozedurale Erzeugung von Meshes

 

Download PDF

 

Inhalt

 

1. Grundlagen

- Mesh

- Vertices

- Indices

- Normalen

- Uvs

- Bounds

 

2. Die Umsetzung in Unity und C#

 

 

 

 

1. Grundlagen

 

Mesh

 

Ein Mesh ist ein Gerüst aus Punkten, Kanten und Flächen welche zusammen ein dreidimensionales Objekt bilden. Ein Mesh ist ein komplexer Datentyp in dem alle notwendigen Daten zusammengeführt sind, dazu zählen hauptsächlich die Vertices, Indices, Normalen und die UV-Koordinaten.

In Unity ist der Typ Mesh meist in Verbindung mit einem MeshFilter und einem MeshRenderer zu verwenden um das Mesh zur Anzeige zu bringen.

 

Vertices

 

Ein Mesh besteht immer aus einer Menge an Punkten im 3D Raum welche das Grundgerüst für das 3D Objekt bilden. Ein solcher Punkt nennt sich Vertex die Mehrzahl Vertices.

Ein Vertex ist immer in Form eines Vectors angegeben mit x,y und z Koordinaten z.B. (0,0,0).

In Unity entspricht das einem Vector3.

Die Koordinaten jedes Vertex sind immer in Abhängigkeit zum Nullpunkt des Objektes nicht zum Weltkoordinatensystem, was auch nur Sinnvoll ist wenn das Objekt verschoben werden muss,

Da eine Mesh natürlich aus mehreren Punkten besteht, mindestens 3 um eine Fläche zu bilden hat ein Mesh eine Liste an Vertices zugewiesen, in Unity über mesh.vertices zu erreichen.

 

 

 

 

Indices (Triangles)

 

Die Indices oder auch oft Triangels genannt geben die Verknüpfung der einzelenen Vertices zu dem anzuzeigenden Mesh an. Die Indices sind nichts anderes als eine Liste mit Integern welche auf Positionen im Array der Vertices verweisen. Dabei beschreiben immer 3 aufeinanderfolgende Werte in dieser Liste ein Triangle also eine dreieckige Fläche. D.h. die Länge dieser Liste ist immer ein vielfaches von 3.

Ein kleines Beispiel. Wir haben eine Liste an Vertices mit 3 Punkten, also die minimale Anzahl die nötig ist um ein Mesh zu erzeugen.

Indices1.png

 

Die Zahlen im Namen der der einzelnen Vertices geben die Position in dem Array der Vertices an, dies ist wichtig um ein korrektes Verbinden zu gewährleisten.

Es gibt mehrere Möglichkeiten diese Punkte zu verbinden, so z.B. 0 → 1 → 2 oder 0 → 2 → 1.

Man könnte meinen wie wir nun diese Punkte miteinander Verbinden ist egal, das ist es aber nicht.

Denn ein Triangle wird meistens später nur in eine Richtung gerendert, die Rückseite gibt es nicht. Das nennt man Backfaceculling um zu verhindern das Triangles die nicht der Kamera zugewand sind nicht zeichnen zu müssen, mit manchen Shadern können natürlich auch die Rückseiten gerendert werden wenn Backfaceculling deaktiviert ist, das ist aber nicht die Regel daher gilt es die Reihenfolge zu beachten. Die einzelnen Punkte müssen im Uhrzeigersinn zur Kamera hin verbunden werden, wenn wir also von unser Sicht ausgehen mit der wir auf diese Punkte schauen dann wäre von den beiden oben genannten Möglichkeiten die erste die Richtige. 0 → 1 → 2 .

0 → 2 → 1 wäre falsch weil wir sie gegen den Uhrzeigersinn miteinander verbunden hätten, wir könnten das Triangle von unser Position aus also nicht sehen sondern nur von der Rückseite.

Das Ergebnis sieht also so aus.

Indices2.png

 

Jetzt hätten wir ein einzelnes Dreieck das wir zur Anzeige bringen können, ein Dreieck ist natürlich etwas wenig daher jetzt noch ein kurzes Beispiel mit 2 Dreiecken die zusammen ein Viereck bilden.

Indices3.png

 

Nun ist eine weiterer Vertex dazu gekommen in dem Vertice Array, das zweite Dreieck hat die Indices 0 → 2 → 3, unsere Liste sieht nun für dieses Viereck wie folgt aus:

0,1,2,0,2,3

zwei Triangle teilen sich somit mehrere Vertices. Man hätten diese natürlich auch klonen können, aber wenn es möglich ist einen Vertex doppelt zu verwenden sollte dies auch gemacht werden, es gibt allerdings auch Fälle in denen ein klonen von Nöten ist, z.B. dann wenn es sonst Probleme mit der anzuzeigenden Textur gibt, dazu aber später mehr.

In Unity kommt man an die Liste der Indices über mesh.triangles ran.

 

 

Normalen

 

Jeder Vertex hat immer eine Normale, diese wird vor allem zur Lichtberechnung gebraucht. Eine Normale ist ebenalls ein Vector3 und gibt die Richtung eines Punktes an. Damit ist auch klar, die Liste der Normalen ist immer genauso lang wie die Liste der Vertices.

Für die oben genannten Vertices wären alle Normalen gleich, alle würden in Unsere Richtung blicken damit ein Lichteinfall richtig dargestellt werden kann, denn die Triangles sind ja auch uns zugewandt. Die Normale für Vertex 0 und auch für alle anderen oben müsste also wie folgt lauten:

(0,0,-1)

Sie würde also direkt auf uns zeigen.

Ich lasse jetzt hier aber eine weiter Erklärung und auch Beispiele dazu weg weil die eigenehändige Zuweisung der Normalen in Unity nicht nötig ist, der Datentyp Mesh in Unity bietet nämlich eine Funktion an die die korrekte Berechnung für uns übernimmt. Diese lautet mesh.RecalculateNormals().

 

 

UV-Koordinaten

 

Eine sehr sehr wichtige Komponente eines jeden Meshes sind die UV Koordinaten, diese sind zur korrekten Anzeige einer Textur auf dem Mesh wichtig.

Jeder Vertex hat mindestens eine UV Koordinate wodurch wiederum klar ist das die Liste der Uvs ebenfalls genauso lang ist wie die Liste der Vertices.

Eine UV Koordinate wird als Vector2 angegeben mit x und y Koordinaten auf der Textur.

Der Name UV Koordinate könnte eigentlich auch XY Koordinate heißen, weil x und y aber schon im 3D Raum für die Angabe der Punkte verwendet wird nimmt man bei den Texturkoordinaten u und v statt x und y.

Ich werde dem Verständnis halber aber von x und y reden weil ja auch im Vector2 mit x und y gearbeitet wird.

 

Die Werte für x und y liegen immer zwischen 0 und 1. Für x wäre 0 also ganz links und 1 ganz rechts auf der Textur. Bei y wäre 0 ganz unten und 1 ganz oben.

Eine UV Koordinate die ganz oben links auf der Textur liegen soll muss also (0,1) lauten.

 

Hier ein Beispiel anhand des oben beschriebenen Meshes mit seinen 4 Vertices.

Wir nehmen uns eine beliebige Textur die wir komplett auf die von uns erzeugten Triangles legen möchten.

Die Textur sieht so aus:

 

uvs1.jpg

 

Nun bestimmen wir die UV-Koordinaten für jeden Vertex in unserer Liste.

Vertex 0 soll ganz unten links sein, daraus folgt die Koordinate (0,0).

Vertex 1 soll ganz oben links sein (0,1).

Vertex 2 soll ganz oben rechts sein (1,1) und

Vertex 3 ganz unten links (1,0).

 

Diese 4 zweidimensionalen Vektoren werden nun in ein Array vom Typ Vector2 gespeichert und dem Mesh übergeben.

 

UVs2.png

 

An die UV Koordinaten kommt man über mesh.uv. Ein Mesh kann auch meherer UV Koordinaten besitzen, in Unity maximal 3. an die anderen beiden kommt man über uv1 und uv2. Wir bleiben aber hier bei einem Set an UV Koordinaten.

 

Bounds

 

Zuletzt noch eine kurze Erläuterung zu den Bounds eines Meshes. Die Bounds kann man sich als Kasten um das Mesh herum vorstellen in dem das gesamte Mesh eingeschlossen ist. In den Bounds sind die minimalen und maximalen x,y und z Koordinaten aller Vertices eines Meshes hinterlegt. Damit kann schnell die ungefähre Gesamtgröße eines 3D Objektes abgefragt werden. Diese Bounds sind vorallem dafür da um für ein funktionierendes Frustumculling zu sorgen, also das ausklinken eines 3D Objektes aus derm Renderprozess wenn dieses sich außerhalb des Sichtbereiches der Kamera befindet.

Stimmen die Bounds nicht, kann es passieren dass das Objekt zufrüh aus dem Renderprozess genommen wird also schon bevor es wirklich außerhalb ist oder zuspät.

An die Bounds des Meshes kommt man in Unity über mesh.bounds.

Aber auch hier müssen wir die Bounds nicht selbst berechnen, was aber auch so nicht sonderlich schwer wäre. Aber Unity und der Datentyp Mesh stellt uns auch hier eine einfache Funktion zur Verfügung um die Bounds für ein von uns generiertes Mesh schnell und einfach berechnen zu lassen.

Das geht über mesh.RecalculateBounds().

 

 

So damit wären theoretisch alle zunächst relevanten Elemente abgehandelt, es gibt zwar noch mehr Elemente die man für ein Mesh einstellen kann aber die lasse ich jetzt bewusst weg weil sie nicht unbedingt von Nöten sind.

 

Als nächstes machen wir uns an die Umsetzung des gerade gelernten und bringen das eben beschriebene Mesh in Unity zur Anzeige.

 

 

 

2. Die Umsetzung in Unity und C#

 

Nun beginnen wir damit das oben beschriebene und vorerst noch sehr einfache Mesh in Unity zur Anzeige zu bringen.

Dazu erzeugen wir uns zunächst ein Empty GameObject und legen es auf die Position (0,0,0). Die Kamera richten wir direkt mal so aus das sie auf den Nullpunkt schaut.

Als nächstes geben wir dem GameObject einen MeshRenderer und einen MeshFilter, außerdem sollten wir uns ein neues Material erzeugen. Diesem könnt ihr nun eine eigene Textur geben oder die von mir hier genommene. Das Material ziehn wir einfach in die Material Liste des MeshRenderers.

 

Damit wäre die erste Arbeit getan, nun machen wir uns an das wesentliche, unser Script.

Dazu erzeugen wir uns ein neues C# Script, der Name ist euch überlasse.

 

In diesem Script erstellen wir uns eine neue Methode, ich nenne sie CreateMesh().

 

public void CreateMesh(){

}

 

 

 

 

Nun erzeugen wir uns in dieser Methode ein neues Mesh.

 

Mesh mesh = new Mesh();

 

Außerdem erstellen wir uns schonmal alle notwendigen Listen die wir brauchen für die Vertices, Indices und UVs . Wir könnten auch die Normalen schon anlegen aber das kann ja Unity für uns machen.

 

Vector3[] vertices;
int[] indices;
Vector2[] uvs;

 

 

Nachdem wir dies getan haben können wir bereits anfangen unser Liste mit Vertices zu füllen.

Die Werte entnehme ich dem theoretischen Teil. Ich werde hier also nicht mehr auf die Werte eingehn.

Weil wir vier Vertices haben müssen wir unser Array auch mit 4 freien Feldern initialisieren und diese anschließend zuweisen.

 

vertices = new Vector3[4];
vertices[0] = new Vector3(0,0,0);
vertices[1] = new Vector3(0,1,0);
vertices[2] = new Vector3(1,1,0);
vertices[3] = new Vector3(1,0,0);

 

anschließend kommen die Indices

 

 

indices = new int[6];
indices[0] = 0;
indices[1] = 1;
indices[2] = 2;

indices[3] = 0;
indices[4] = 2;
indices[5] = 3;

 

und dann die UV Koordinaten.

 

uvs = new Vector2[4];
uvs[0] = new Vector2(0,0);
uvs[1] = new Vector2(0,1);
uvs[2] = new Vector2(1,1);
uvs[3] = new Vector2(1,0);

 

Nun können wir alle Listen dem Mesh übergeben.

 

mesh.vertices = vertices;
mesh.triangles = indices;
mesh.uv = uvs;

 

Jetzt fehlen noch die Normalen und die Berechnung der Bounds. Das geht folgendermaßen.

 

mesh.RecalculateNormals();
mesh.RecalculateBounds();

 

Im Grunde sind wir nun fertig, wir müssen nur noch unserem MeshFilter das Mesh übergeben.

 

gameObject.GetComponent<MeshFilter>().mesh = mesh;

 

Das wärs, jetzt noch in der Awake Methode die von uns erzeugte Methode aufrufen und fertig.

 

void Awake(){
  CreateMesh();
}

 

Wenn wir nun das Spiel starten sollten wir ein Viereck mit unserer Textur angezeigt bekommen. Im Editor Fenster sollte es so aussehn.

 

end.png

 

Damit wären wir am Ende der Einführung. In folgenden Tutorials werde ich zeigen wie man auf die gleiche Art und Weise größere zusammenhängende Objekte erzeugt. Jeder der das Prinzip verstanden hat könnte aber theoretisch jetzt schon einen Würfel erzeugen. Dabei ist jedoch zu beachten das man bei einem Würfel für jede Seite 4 Vertices nimmt, mehrere Seiten sollten sich also keine Vertices teilen weil man sonst Probleme bei den UV Koordinaten und den Normalen bekommt.

 

Hier gehts zu Teil 2

 

Gruß Stephan

  • Like 8
Link to comment
Share on other sites

  • 1 year later...

Hallo,

 

bei mir geht irgendetwas nicht habe deinen Code mit dem von Student Game Dev vermischt.

 

function Start () {

 

mesh = GetComponent(MeshFilter).mesh;

 

var x = gameObject.transform.position.x;

var y = gameObject.transform.position.y;

var z = gameObject.transform.position.z;

 

 

newVertices.Add( new Vector3 (x , y , z ));

newVertices.Add( new Vector3 (x + 1 , y , z ));

newVertices.Add( new Vector3 (x + 1 , y-1 , z ));

newVertices.Add( new Vector3 (x , y-1 , z ));

 

newTriangles.Add(0);

newTriangles.Add(1);

newTriangles.Add(2);

newTriangles.Add(0);

newTriangles.Add(2);

newTriangles.Add(3);

 

mesh.Clear ();

mesh.vertices = newVertices.ToArray();

mesh.triangles = newTriangles.ToArray();

mesh.Optimize ();

mesh.RecalculateNormals ();

mesh.RecalculateBounds();

}

 

Habe schon mein Objekt gedreht wegen Normals aber nichts passiert ist bestimmt ein Schusselfehler.

 

Sehe Die Plane wenn ich das Mesh doppelklicke aber nicht im Spiel...

Link to comment
Share on other sites

:D bin so dämlich habe denn Mesh Renderer vergessen :D

Hier mal ne funktionierende Javascript Version:

 

#pragma strict

import System.Collections.Generic;

 

var newVertices : List.<Vector3>;

newVertices = new List.<Vector3>();

 

var newTriangles : List.<int>;

newTriangles = new List.<int>();

 

var newUV : List.<Vector2>;

newUV = new List.<Vector2>();

 

var mesh : Mesh;

 

 

function Start () {

 

mesh = GetComponent(MeshFilter).mesh;

 

var x = gameObject.transform.position.x;

var y = gameObject.transform.position.y;

var z = gameObject.transform.position.z;

 

 

newVertices.Add( new Vector3 (x , y , z ));

newVertices.Add( new Vector3 (x , y+1 , z ));

newVertices.Add( new Vector3 (x + 1 , y+1 , z ));

newVertices.Add( new Vector3 (x+1 , y , z ));

 

newTriangles.Add(0);

newTriangles.Add(1);

newTriangles.Add(2);

newTriangles.Add(0);

newTriangles.Add(2);

newTriangles.Add(3);

 

 

newUV.Add(new Vector2(0,0));

newUV.Add(new Vector2(0,1));

newUV.Add(new Vector2(1,1));

newUV.Add(new Vector2(1,0));

mesh.Clear ();

mesh.vertices = newVertices.ToArray();

mesh.triangles = newTriangles.ToArray();

mesh.uv = newUV.ToArray();

mesh.Optimize ();

mesh.RecalculateNormals ();

mesh.RecalculateBounds();

}

Link to comment
Share on other sites

  • 1 month later...

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
 Share

×
×
  • Create New...