Tesselation

Autor: Johann Arndt

Was ist Tesselation?

Mit OpenGL 4.0 wurde Tesselation eingeführt und sollte grobe Figuren, die aus Vertices bestehen glätten. Das wird erreicht in dem ein Objekt bestehend aus flachen Dreiecken/Quadraten in noch kleinere Dreiecke/Quadrate unterteilt wird und diese dann mit Höheninformationen angepasst werden. Das Ergebnis einer solchen Tesselation kann dann so aussehen:

Effekt der Tesselierung Quelle: nvidia.de

Was ist neu?

Als erstes muss dem IndexBuffer die Anzahl der Vertices pro Patch mit übergeben werden. Das ist die Anzahl der Vertices, die in die Shader mit übergeben werden. In unserem Fall sind das 3, da ein Dreieck aus 3 Vertices Besteht. Den IndexBuffer erstellt man dann so:

IndexBuffer::new(facade,PrimitiveType::Patches
{ vertices_per_patch: 3 },&indices).unwrap();

Die Tesselation erweitert OpenGL um zwei weitere neue veränderbare Shader:

Der Tesselation Control Shader

Hier wird der Grad der äusseren und inneren Tesselation eingestellt. Dieser shader gibt OpenGL an in wie viele kleinere Dreicke/Quadrate und unterteilt werden soll.

Bei einem Dreieck sieht der Aufruf wie folgt aus:

gl_TessLevelInner[0] = inner;
gl_TessLevelOuter[0] = outer;
gl_TessLevelOuter[1] = outer;
gl_TessLevelOuter[2] = outer;

Wobei TessLevelInner und TessLevelOuter ganzzahlige Werte sind. Aber warum werden genau diese Positionen im Array beschrieben? Das liegt daran, dass ein Dreieck im "inneren" nur aus einem Dreieck besteht und "außen" an drei weiteren Dreiecken grenzt. Somit müsste man bei einem Quadrat jeweils eine weitere Stelle im gl_TessLevelInner und gl_TessLevelOuter beschreiben. Der Effekt dieser Variablen wird hier sehr gut dargestellt: Unterschied zwischen den einzelnen TessLevel

Quelle: http://prideout.net/blog/?p=48

Der Tesselation Evaluation Shader

Hier werden die tesselierten Koordinaten verarbeitet und neue Vertices mit Höheninformationen erstellt. Die normalen werden hier auch interpoliert und dem neuen Punkt zugewiesen um möglichst realistische Darstellung des Lichts zu ermöglichen. Wie das genau geschieht wird weiter unten erkärt.

Bezier Smoothing

Um unsere Pflanzen mit zu runden benutzen wir das sogenannte Bezier Smoothing. Bei diesem verfahren wird ein bestehendes Dreieck mit Kontrollpunkten ausgestattet und mithilfe der Normalen mit Höheninformationen ausgestattet.

Die unterteilung in Plantex Quelle: google bilder

Auf dem Bild sieht man wie unsere Dreiecke in Plantex unterteilt werden. Nachdem die Unterteilung erfolgt ist benutzen wir die Koordinaten, die durch das Tesselieren errechnet wurden und nähern diese Punkte den errechneten Punkten an. Dies funktioniert genauso wie die aus den Vorlesungen bekannten Bezierkurven. Das hat einen tollen effekt auf die Pflanzen in Plantex wie man später sehen wird.

Umsetzung

Bevor die Tesselation stattfinden konnte mussten wir unsere einlese Funktion für die Shader anpassen, da diese nur für den Vertex- und Fragment Shader beschränkt war. Um ein dynamisches einlesen der Shader gewährleisten zu können haben wir das einlesen der Tesselation Shader optional gemacht. Das heißt wenn keine Tesselation Shader existieren so werden auch keine gelesen.

let tcs = load_if_present(&format!("client/shader/{}.tcs", shader)).ok();
let tes = load_if_present(&format!("client/shader/{}.tes", shader)).ok();

let source = program::SourceCode {
    vertex_shader: &vert_buf,
    tessellation_control_shader: tcs.as_ref().map(|s| s.as_str()),
    tessellation_evaluation_shader: tes.as_ref().map(|s| s.as_str()),
    geometry_shader: None,
    fragment_shader: &frag_buf,
};

let prog = Program::new(&self.facade, source);

Nach erfolgreichem einlesen der Shader müssen dann die Shader selbst angepasst werden. Der Vertex Shader ist dabei der langweiligste Shader, da er nur Variablen an den TCS (Tesselation Control Shader) übergibt.

//setting out Variables for Tesselation Controll Shader
    material_color = color;
    surfaceNormal= normal;
    vPosition = position;

Bevor man im TCS etwas machen kann muss man die Anzahl der Vertices für den Output Patch festlegen:

layout(vertices = 1) out;

Da wir ein struct benutzen müssen wir die Anzahl auf eins setzen.

Das struct setzt sich wie folgt zusammen:

struct OutputPatch {
    vec3 WorldPos_B030;
    vec3 WorldPos_B021;
    vec3 WorldPos_B012;
    vec3 WorldPos_B003;
    vec3 WorldPos_B102;
    vec3 WorldPos_B201;
    vec3 WorldPos_B300;
    vec3 WorldPos_B210;
    vec3 WorldPos_B120;
    vec3 WorldPos_B111;
    vec3 Normal[3];
    vec3 Color;
};

Die Positionen der einzelnen Punkte kann man sich aus der Oberen Grafik beim Bezier Smoothing entnehmen. Die Normalen als auch die Farbe wird beim VertexBuffer mitübergeben. B300, B030 und B003 sind die Originalvertices und die anderen werden mithilfe von Vektorrechnungen errechnet.

Als nächstes wird der TES (Tesselation Evaluation Shader) für jeden neuen Punkt aufgerufen. Bei jedem Aufruf wird die normale interpoliert und zugewiesenund nachdem das geschehen ist wird unsere Bezier Smoothing Funktion aufgerufen:

//Bezier deforming
    float u = gl_TessCoord.x;
    float v = gl_TessCoord.y;
    float w = gl_TessCoord.z;

    tePosition = oPatch.WorldPos_B300 * w * w * w +
                 oPatch.WorldPos_B030 * u * u * u +
                 oPatch.WorldPos_B003 * v * v * v +
                 oPatch.WorldPos_B210 * 3.0 * w * w * u +
                 oPatch.WorldPos_B120 * 3.0 * w * u * u +
                 oPatch.WorldPos_B201 * 3.0 * w * w * v +
                 oPatch.WorldPos_B021 * 3.0 * u * u * v +
                 oPatch.WorldPos_B102 * 3.0 * w * v * v +
                 oPatch.WorldPos_B012 * 3.0 * u * v * v +
                 oPatch.WorldPos_B111 * 6.0 * w * u * v;

Die gl_TessCoord.* sind Werte von 0-1 und geben uns die Position auf dem Dreieck an. Zuletzt müssen nur noch die Koordinaten mithilfe der Matritzen transformiert werden um die Richtige Position auf dem Bildschirm zu erhalten.

Performance

Da es sehr aufwendig ist für jede gerenderte Pflanze Tesselation mit hohem Level aufzurufen wird in Plantex ein dynamisches ermitteln des Tesselationslevels benutzt.

// handle tesselation level on distance
    int len = int(length(camera_pos - offset[0]) / 3);
    float inner = tess_level_inner;
    float outer = tess_level_outer;

    if (len >= tess_level_inner-1){
        inner = 1.0;
        outer = 1.0;
    } else {
        inner = inner - len;
        outer = outer - len;
        CalcPositions();
    }

In abhängigkeit der Entfernung wird das Tesselationslevel linear angepasst. Zur Zeit wird das Tesselationlevel verringert sobald man sich von dem Tesselierten Objekt wegbewegt. Alle 3 Längeneinheiten wird das level um 1 verringert. Hier wäre wahrscheinlich eine quadratische Lösung effektiver.

Probleme

An einigen Stellen war es wirklich frustrierend die Tesselation zu ermöglichen. Das größte Problem war hierbei, dass unser Ursprügliches Pflanzenmodel auf Flatshading basierte. Es wurden 3 Normalen pro Vertice abgespeichert. Das klingt zu Anfang nicht schlimm jedoch hat das zur Folge, dass Flächen, die in eine andere Richtung zeigten nicht richtig geschlossen haben und somit die Pflanze durchscheinte. Dieser Effekt kam zustande, da die Normalen zur errechnung der Höheninformationen benutzt wurden. Das Umstellen des Models hat nicht nur dieses Problem behoben sondern hat die Pflanzen auch rundlicher erscheinen lassen. Dieser Effekt kommt durch das Gouraud Shading zustande. Leider habe ich hierzu kein Bild.

Eine weitere Fehlerquelle war der IndexBuffer. Die Vertices werden in der selben Reihenfolge übergeben wie die Indices. Das hat zur Folge, dass die Koordinaten zur Berechnung der Kontrollpunkte für das Bezier Smoothing Fehlerhaft waren.

Desweiteren gab es viele Fehler bei den Shadern durch Gleichzeitige Zugriffe auf den Speicher. Leider konnte ich den Fehler zuhause nicht reporduzieren, daher hab ich kein Bild zur verfügung. Meine Vermutung ist, dass es ein Treiber Problem gewesen sein muss.

Ergebnis

Vorher:

Vorher

Nachher:

Nachher

Durch die Tesselation sehen die Bäume runder aus und haben an den Ästen deutlich bessere Übergänge.

results matching ""

    No results matching ""