Darstellung der Pflanzen

Autor: Patrick Nolte

Grundlegendes

Um die Pflanzen grundlegend darzustellen werden Dreieckige Säulen verwendet. Der Stamm sowie alle Äste bestehen aus diesen Dreieckigen Säulen. Die Besonderheit dabei ist, dass es möglich ist, jede Säule beliebig zu platzieren. Dadurch können theoretisch beliebig komplexe Pflanzen erstellt werden. Jeder Stamm bzw. Ast kann in Start und Endpunkt sowie beliebig viele Zwischenpunkte unterteilt werden. Bei jedem Punkt kann sich die Dicke des Stammes/Astes ändern, sowie auch die Farbe. Die Farbe wird momentan noch genutzt, kann aber demnächst auch durch eine Textur ersetzt werden. Dabei kann dann auch darauf geachtet werden, dass mit der Textur Tessalation möglich ist, sodass nicht nur die Rundung durch erhöhte Anzahl Polygone besser wird, sondern auch die Textur immer hochauflösender. Die notwendigen Informationen, wo beispielsweise die dreieckige Säule des Stammes beginnen soll, werden bei der Generierung erstellt und hier nur übergeben. Die nur aus dreieckigen Säulen bestehenden Pflanzen werden mittels Tessalation noch ansprechend abgerundet und weiterverarbeitet.

Pflanze aus Säulen

Pflanze mit Normalen

Das Erstellen im Detail

Das Pflanzenrendern, sowie alle Informationen die dazu benötigt werden, werden in der PlantView gespeichert. Jeder Pflanzentyp bekommt also eine eigene Plantview.

Beim erstellen einer Plantview wird das Plant-Objekt, welches bei der Generierung erstellt wurde, übergeben. Es werden nun alle Branches der Pflanze (also der Stamm, sowie alle Äste) durchlaufen und die Vertices erstellt. Ebenfalls werden alle Indices entsprechend gesetzt.

Zum erstellen der Vertices werden alle Kontrollpunkte des jeweiligen Branches durchlaufen. Für jeden dieser Kontrollpunkte werden drei Vertices erstellt. Dabei wird die Position und die Normale gespeichert, die zu dem jeweiligen Vertice gehören. Insbesondere wird auch die Farbe angegeben. Theoretisch ist es also möglich, jedem Punkt eine andere Farbe zu geben. Momentan wird die Farbe aber für alle drei Vertices gleich gesetzt.

Die drei Vertices sind so angeordnet, dass sie die Ecken eines gleichseitigen Dreieckes bilden. Um diese drei Eckpunkte zu bekommen, auch wenn der Ast irgendwie im Raum steht, wird der Vektor des Astabschnittes aus den beiden Punkten berechnet. Zu diesem wird eine Orthogonale berechnet. Diese gibt die Richtung an, in der der erste Punkt liegt. Mithilfe eines Rotationsobjektes, welches an dem Vektor ausgerichtet wird, sowie der Orthogonalen werden dann die anderen beiden Vektoren im 120° Winkel berechnet, sodass die Eckpunkte des gleichseitigen Dreiecks entstehen. Die drei Vektoren werden normalisiert und verwendet, um die Punkte zu berechnen, die das Dreieck bilden, während der jeweilige Vektor als Normale des Punktes verwendet wird.

Berechnung der drei Vektoren

let ortho = random_vec_with_angle(&mut seeded_rng(0x2651aa465abded, (), ()),
                                      vector.normalize(),
                                      90.0);
    let rot = Basis3::from_axis_angle(vector.normalize(), Deg::new(120.0).into());
    let v0 = rot.rotate_vector(ortho);
    let v1 = rot.rotate_vector(v0);

    [ortho.normalize(), v0.normalize(), v1.normalize()]

Die Indices sind für jeden Abschnitt gleich, sodass immer nur das Offset (Anzahl Vertices je Abschnitt) hinzugerechnet wird, sodass mit den definierten Indices die Säulen gebildet werden. Am Ende jeden Astes wird dann nur noch das Ende geschlossen (die letzten drei Punkte als Indices gespeichert) und der Branch ist im Vertex und Indexbuffer fertig gespeichert.

Instancing

In jeder Plantview wird auch eine Liste von Positionen abgespeichert. Diese geben an, an welchen Positionen in der Welt eine Pflanze dieses Typs gerendert werden soll. Die Liste wird immer geändert, wenn neue Chunks geladen und entfernt werden, da dann ebenfalls Pflanzen hinzugefügt und entfernt werden.

Das speichern in einer solchen Liste hat den Vorteil, dass das von der Grafikkarte unterstützte Instancing genutzt werden kann. Dadurch wird für jede Pflanze nur einmal die Grafik-Pipeline abgearbeitet und dann mit einfachen übergebenen Transformationen von der Grafikkarte an die passenden Positionen gerendert. Dieser Vorgang beschleunigt das rendern der Pflanzen deutlich, sodass eine erheblich größere Anzahl Pflanzen ohne Probleme gerendert werden kann, als wenn jede Pflanze einzeln gerendert werden muss. Bei diesem Vorgang geht aber leider die Vielfalt der Pflanzen verloren, da es nun nur wenige unterschiedliche Pflanzentypen gibt, es lohnt sich aber aufgrund der schnelleren Rendergeschwindigkeit.

Das Rendern

Der Renderaufruf

surface.draw((&self.vertices, self.instance_buf.per_instance().unwrap()),
                  &self.indices,
                  &self.renderer.program(),
                  &uniforms,
                  &params)
            .unwrap();

Interessant ist hierbei, dass für alle Pflanzen dieses Typs nur dieser Renderaufruf benötigt wird. Das geht dadurch, dass die Positionen (der zweite Parameter) übergeben und von der Grafikkarte weiterverarbeitet werden. Bei den Uniforms werden insbesondere die Tessalationlevel gesetzt, welche angeben, wie detailliert die Pflanzen maximal tesseliert werden sollen.

results matching ""

    No results matching ""