2015-09-10 15 views
8

Ich versuche, in eine .obj-Datei zu laden und zeichne es mit Hilfe von glDrawElements.Map/Falte die multiplen .OBJ-Index-Puffer zu OpenGLs 1 Index Puffer

Jetzt, mit glDrawArrays funktioniert alles perfekt, aber es ist - natürlich - ineffizient.

Das Problem, das ich gerade habe ist, dass eine .obj-Datei mehrere Index-Puffer (für jedes Attribut) verwendet, während OpenGL nur eine verwenden kann. Also muss ich sie entsprechend abbilden.

Es gibt viele Pseudo-Algorithmen und ich habe sogar eine C++ - Implementierung gefunden. Ich kenne ziemlich viel C++, aber seltsamerweise hat mir keiner mit meiner Implementierung in Scala geholfen.

Mal sehen:

private def parseObj(path: String): Model = 
{ 
    val objSource: List[String] = Source.fromFile(path).getLines.toList 

    val positions: List[Vector3] = objSource.filter(_.startsWith("v ")).map(_.split(" ")).map(v => new Vector3(v(1).toFloat,v(2).toFloat,v(3).toFloat))//, 1.0f)) 
    val normals: List[Vector4] = objSource.filter(_.startsWith("vn ")).map(_.split(" ")).map(v => new Vector4(v(1)toFloat,v(2).toFloat, v(3).toFloat, 0.0f)) 
    val textureCoordinates: List[Vector2] = objSource.filter(_.startsWith("vt ")).map(_.split(" ")).map(v => new Vector2(v(1).toFloat, 1-v(2).toFloat)) // TODO 1-y because of blender 
    val faces: List[(Int, Int, Int)] = objSource.filter(_.startsWith("f ")).map(_.split(" ")).flatten.filterNot(_ == "f").map(_.split("/")).map(a => ((a(0).toInt, a(1).toInt, a(2).toInt))) 

    val vertices: List[Vertex] = for(face <- faces) yield(new Vertex(positions(face._1-1), textureCoordinates(face._2-1))) 

    val f: List[(Vector3, Vector2, Vector4)] = for(face <- faces) yield((positions(face._1-1), textureCoordinates(face._2-1), normals(face._3-1))) 
    println(f.mkString("\n")) 

    val indices: List[Int] = faces.map(f => f._1-1) // Wrong! 

    new Model(vertices.toArray, indices.toArray) 
} 

Die val indices: List[Int] mein erster naiver Ansatz war und ist natürlich falsch. Aber beginnen wir oben:

Ich lade die Datei und gehe durch sie. (Ich nehme an, Sie wissen, wie eine .obj-Datei besteht)

Ich lese in die Vertices, Textur-Koordinaten und Normalen. Dann komme ich zu den Gesichtern.

Jetzt hat jedes Gesicht in meinem Beispiel 3 Werte v_x, t_y, n_z, die vertexAtIndexX, textureCoordAtIndexY, normalAtIndexZ definieren. So definieren diese jeweils einen Vertex, während ein Tripel von ihnen (oder eine Zeile in der Datei) ein Face/Polygon/Triangle definiert.

in val vertices: List[Vertex] = for(face <- faces) yield(new Vertex(positions(face._1-1), textureCoordinates(face._2-1))) Ich versuche eigentlich Vertices (eine Fall-Klasse, die derzeit nur hält Positionen und Textur-Koordinaten und vernachlässigt Normale vorerst) zu erstellen

Das eigentliche Problem ist, diese Zeile:

val indices: List[Int] = faces.map(f => f._1-1) // Wrong!

um die realen Indizes bekommen muss ich im Grunde diese statt von tun

val vertices: List[Vertex] = for(face <- faces) yield(new Vertex(positions(face._1-1), textureCoordinates(face._2-1))) und val indices: List[Int] = faces.map(f => f._1-1) // Wrong!

Pseudo-Code:

Iterate over all faces 
    Iterate over all vertices in a face 
     Check if we already have that combination of(position, texturecoordinate, normal) in our newVertices 

     if(true) 
      indices.put(indexOfCurrentVertex) 
     else 
      create a new Vertex from the face 
      store the new vertex in the vertex list 
      indices.put(indexOfNewVertex) 

Doch bin ich total fest. Ich habe verschiedene Dinge ausprobiert, kann aber keine schöne und saubere Lösung finden, die tatsächlich funktioniert.

Dinge wie:

val f: List[(Vector3, Vector2, Vector4)] = for(face <- faces) yield((positions(face._1-1), textureCoordinates(face._2-1), normals(face._3-1))) 

und zu versuchen, f.distinct arbeitet nicht, weil es einzigartig nichts zu verschieden, alle Einträge gibt es, das Gefühl völlig macht, wenn ich in der Datei aussehen und doch das ist, was Der Pseudo-Code sagt mir, dass ich das überprüfen soll.

Natürlich würde ich brauchen, um die Indizes entsprechend (in einem Einzeiler bevorzugt und mit vielen funktionalen Schönheit) zu füllen

Aber ich versuchen soll, um Duplikate zu finden, so ... Ich bin ein bisschen verblüfft. Ich schätze, ich verwechsle die verschiedenen "Ecken" und "Positionen" zu sehr, mit allen Referenzen.

Also, ich denke falsch, oder ist der Algorithmus/Denken richtig und ich muss nur dies in schönen, sauberen (und tatsächlich funktioniert) Scala-Code zu implementieren?

Bitte, erleuchte mich!

Per Kommentare habe ich ein kleines Update:

var index: Int = 0 
val map: mutable.HashMap[(Int, Int, Int), Int] = new mutable.HashMap[(Int, Int, Int), Int].empty 

val combinedIndices: ListBuffer[Int] = new ListBuffer[Int] 

for(face <- faces) 
{ 
    val vID: Int = face._1-1 
    val nID: Int = face._2-1 
    val tID: Int = face._3-1 

    var combinedIndex: Int = -1 

    if(map.contains((vID, nID, tID))) 
    { 
     println("We have a duplicate, wow!") 
     combinedIndex = map.get((vID, nID, tID)).get 
    } 
    else 
    { 
     combinedIndex = index 
     map.put((vID, nID, tID), combinedIndex) 
     index += 1 
    } 

    combinedIndices += combinedIndex 
} 

wo noch steht, ist:

val faces: List[(Int, Int, Int)] = objSource.filter(_.startsWith("f ")).map(_.split(" ")).flatten.filterNot(_ == "f").map(_.split("/")).map(a => ((a(0).toInt, a(1).toInt, a(2).toInt))) 

Fun Tatsache verstehe ich immer noch nicht offensichtlich, denn das So bekomme ich nie ein Duplikat!

Was bedeutet, dass combinedIndices am Ende hält nur die natürlichen Zahlen wie:

ListBuffer(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ...) 
+1

Ich weiß nicht, Scala überhaupt, also kann ich nicht sagen, ob das ist, was Sie bereits tun. Aber die Schlüsseldatenstruktur, die Sie benötigen, ist eine Map, die ein Tupel mit den ** Indizes ** der Position, Texturkoordinaten und Normal aus dem Gesichtssatz als Schlüssel und dem Index des OpenGL-Eckpunkts als Wert verwendet. Hier ist Pseudo-Code in einer älteren Antwort von mir, falls dies nicht einer der Beiträge ist, die Sie bereits in Ihren Suchen gefunden haben: http://stackoverflow.com/questions/23349080/open-l-in-dex-buffers-difficulties/23356738#23356738 . –

+0

@RetoKoradi: Also muss ich für jedes Gesicht einen Map-Entry mit Key erstellen [positions (face._1-1), textureCoordinates (face._2-1), normals (face._2-1)]) Wie mache ich das Berechne dann den Index des OpenGL-Eckpunkts für den Wert? – Sorona

+0

Sie beginnen bei 0 und erhöhen sie jedes Mal, wenn Sie einen neuen Scheitelpunkt benötigen (d. H. Der Schlüssel war nicht bereits in der Karte). Es sollte ziemlich klar aus dem Pseudo-Code sein, den ich verlinkt habe. –

Antwort

1

Dieses Javascript ist (leider nicht scala), aber es wird kommentiert und shoul ziemlich einfach zu konvertieren.

// bow-tie 
var objString = "v 0 0 0\nv 1 1 0\nv 1 -1 0\nv -1 1 0\nv -1 -1 0\n" + 
    "vt 0 .5\nvt 1 1\nvt 1 0\n" + 
    "vn 0 0 1\n" + 
    "f 1/1/1 2/2/1 3/3/1\nf 1/1/1 4/2/1 5/3/1"; 
// output indices should be [0, 1, 2, 0, 3, 4] 
// parse the file 
var lines = objString.split("\n"); 
var data = lines.map(function(line) { return line.split(" "); }); 
var v = []; 
var t = []; 
var n = []; 
var f = []; 
var indexMap = new Map(); // HashMap<face:string, index:integer> 
var nextIndex = 0; 
var vertices = []; 
var indices = []; 
// fill vertex, texture and normal arrays 
data.filter(function(d) { return d[0] == "v"; }).forEach(function(d) { v.push([parseFloat(d[1]), parseFloat(d[2]), parseFloat(d[3])]); }); 
data.filter(function(d) { return d[0] == "vt"; }).forEach(function(d) { t.push([parseFloat(d[1]), parseFloat(d[2])]); }); 
data.filter(function(d) { return d[0] == "vn"; }).forEach(function(d) { n.push([parseFloat(d[1]), parseFloat(d[2]), parseFloat(d[3])]); }); 
// 
console.log("V", v.toString()); 
console.log("T", t.toString()); 
console.log("N", n.toString()); 
// create vertices and indices arrays by parsing faces 
data.filter(function(d) { return d[0] == "f"; }).forEach(function(d) { 
    var f1 = d[1].split("/").map(function(d) { return parseInt(d)-1; }); 
    var f2 = d[2].split("/").map(function(d) { return parseInt(d)-1; }); 
    var f3 = d[3].split("/").map(function(d) { return parseInt(d)-1; }); 
    // 1 
    if(indexMap.has(d[1].toString())) { 
     indices.push(indexMap.get(d[1].toString())); 
    } else { 
     vertices = vertices.concat(v[f1[0]]).concat(t[f1[1]]).concat(n[f1[2]]); 
     indexMap.set(d[1].toString(), nextIndex); 
     indices.push(nextIndex++); 
    } 
    // 2 
    if(indexMap.has(d[2].toString())) { 
     indices.push(indexMap.get(d[2].toString())); 
    } else { 
     vertices = vertices.concat(v[f2[0]]).concat(t[f2[1]]).concat(n[f2[2]]); 
     indexMap.set(d[2].toString(), nextIndex); 
     indices.push(nextIndex++); 
    } 
    // 3 
    if(indexMap.has(d[3].toString())) { 
     indices.push(indexMap.get(d[3].toString())); 
    } else { 
     vertices = vertices.concat(v[f3[0]]).concat(t[f3[1]]).concat(n[f3[2]]); 
     indexMap.set(d[3].toString(), nextIndex); 
     indices.push(nextIndex++); 
    } 
}); 
// 
console.log("Vertices", vertices.toString()); 
console.log("Indices", indices.toString()); 

Der Ausgang

V 0,0,0,1,1,0,1,-1,0,-1,1,0,-1,-1,0 
T 0,0.5,1,1,1,0 
N 0,0,1 
Vertices 0,0,0,0,0.5,0,0,1,1,1,0,1,1,0,0,1,1,-1,0,1,0,0,0,1,-1,1,0,1,1,0,0,1,-1,-1,0,1,0,0,0,1 
Indices 0,1,2,0,3,4 

Die JSFiddle http://jsfiddle.net/8q7jLvsq/2

Das einzige, was ich ~ anders tue wird die Zeichenfolge Hut mit repräsentiert eines der Teile eines Gesichts als Schlüssel in meine indexMap (Beispiel: "25/32/5").

BEARBEITEN JSFiddle Diese Version kombiniert wiederholte Werte für Vertex, Textur und Normal. Dies optimiert OBJ-Dateien, die dieselben Werte wiederholen, wodurch jedes Gesicht einzigartig wird.

// bow-tie 
var objString = "v 0 0 0\nv 1 1 0\nv 1 -1 0\nv 0 0 0\nv -1 1 0\nv -1 -1 0\n" + 
    "vt 0 .5\nvt 1 1\nvt 1 0\nvt 0 .5\nvt 1 1\nvt 1 0\n" + 
    "vn 0 0 1\nvn 0 0 1\nvn 0 0 1\nvn 0 0 1\nvn 0 0 1\nvn 0 0 1\n" + 
    "f 1/1/1 2/2/2 3/3/3\nf 4/4/4 5/5/5 6/6/6"; 
// output indices should be [0, 1, 2, 0, 3, 4] 
// parse the file 
var lines = objString.split("\n"); 
var data = lines.map(function(line) { return line.split(" "); }); 
var v = []; 
var t = []; 
var n = []; 
var f = []; 
var vIndexMap = new Map(); // map to earliest index in the list 
var vtIndexMap = new Map(); 
var vnIndexMap = new Map(); 
var indexMap = new Map(); // HashMap<face:string, index:integer> 
var nextIndex = 0; 
var vertices = []; 
var indices = []; 
// fill vertex, texture and normal arrays 
data.filter(function(d) { return d[0] == "v"; }).forEach(function(d, i) { 
    v[i] = [parseFloat(d[1]), parseFloat(d[2]), parseFloat(d[3])]; 
    var key = [d[1], d[2], d[3]].toString(); 
    if(!vIndexMap.has(key)) { 
     vIndexMap.set(key, i); 
    } 
}); 
data.filter(function(d) { return d[0] == "vt"; }).forEach(function(d, i) { 
    t[i] = [parseFloat(d[1]), parseFloat(d[2])]; 
    var key = [d[1], d[2]].toString(); 
    if(!vtIndexMap.has(key)) { 
     vtIndexMap.set(key, i); 
    } 
}); 
data.filter(function(d) { return d[0] == "vn"; }).forEach(function(d, i) { 
    n[i] = [parseFloat(d[1]), parseFloat(d[2]), parseFloat(d[3])]; 
    var key = [d[1], d[2], d[3]].toString(); 
    if(!vnIndexMap.has(key)) { 
     vnIndexMap.set(key, i); 
    } 
}); 
// 
console.log("V", v.toString()); 
console.log("T", t.toString()); 
console.log("N", n.toString()); 
// create vertices and indices arrays by parsing faces 
data.filter(function(d) { return d[0] == "f"; }).forEach(function(d) { 
    var f1 = d[1].split("/").map(function(d, i) { 
     var index = parseInt(d)-1; 
     if(i == 0) index = vIndexMap.get(v[index].toString()); 
     else if(i == 1) index = vtIndexMap.get(t[index].toString()); 
     else if(i == 2) index = vnIndexMap.get(n[index].toString()); 
     return index; 
    }); 
    var f2 = d[2].split("/").map(function(d, i) { 
     var index = parseInt(d)-1; 
     if(i == 0) index = vIndexMap.get(v[index].toString()); 
     else if(i == 1) index = vtIndexMap.get(t[index].toString()); 
     else if(i == 2) index = vnIndexMap.get(n[index].toString()); 
     return index; 
    }); 
    var f3 = d[3].split("/").map(function(d, i) { 
     var index = parseInt(d)-1; 
     if(i == 0) index = vIndexMap.get(v[index].toString()); 
     else if(i == 1) index = vtIndexMap.get(t[index].toString()); 
     else if(i == 2) index = vnIndexMap.get(n[index].toString()); 
     return index; 
    }); 
    // 1 
    if(indexMap.has(f1.toString())) { 
     indices.push(indexMap.get(f1.toString())); 
    } else { 
     vertices = vertices.concat(v[f1[0]]).concat(t[f1[1]]).concat(n[f1[2]]); 
     indexMap.set(f1.toString(), nextIndex); 
     indices.push(nextIndex++); 
    } 
    // 2 
    if(indexMap.has(f2.toString())) { 
     indices.push(indexMap.get(f2.toString())); 
    } else { 
     vertices = vertices.concat(v[f2[0]]).concat(t[f2[1]]).concat(n[f2[2]]); 
     indexMap.set(f2.toString(), nextIndex); 
     indices.push(nextIndex++); 
    } 
    // 3 
    if(indexMap.has(f3.toString())) { 
     indices.push(indexMap.get(f3.toString())); 
    } else { 
     vertices = vertices.concat(v[f3[0]]).concat(t[f3[1]]).concat(n[f3[2]]); 
     indexMap.set(f3.toString(), nextIndex); 
     indices.push(nextIndex++); 
    } 
}); 
// 
console.log("Vertices", vertices.toString()); 
console.log("Indices", indices.toString()); 
+0

Sie tun genau das gleiche wie ich. Sie überprüfen, ob für ein Gesicht ein Duplikat vorhanden ist. Jetzt in meiner .obj-Datei gibt es keine doppelten Gesichter. Das ist genau mein Problem! Für Ihr Beispiel liefert meine Implementierung die gleichen Ergebnisse wie Ihre. Dennoch ist es nicht das richtige für eine tatsächliche OBJ-Datei, da sie keine doppelten Gesichter zu haben scheinen (warum sollten sie?) – Sorona

+0

@Teolha - vielleicht exportieren Sie Ihre OBJ-Datei mit einer deaktivierten Optimierungsoption. Wenn Ihr OBJ mehrere Einträge für V, VT oder VN enthält, die dieselben exakten Werte enthalten - "v 1 1 0 \ nv 1 1 0", dann ist es ein bisschen fehlerhaft, bevor es überhaupt zu Ihrem Algorithmus kommt.Dies könnte im Algorithmus korrigiert werden, indem man eine Map für v, vt und vn erstellt und dann beim Suchen der Indizes von Gesichtern * first_appearing_indx = map [vert [face_index]] * vornimmt, um die Gesichtsindizes vor dir zu korrigieren Suchen Sie nach dem Index, der dem Puffer hinzugefügt werden soll. –

+0

@Teolha - Ich habe eine andere Version hinzugefügt, die wiederholte Vertex-, Textur- und Norma-Werte wie ell kombiniert. –