2015-09-10 28 views
8

Próbuję wczytać plik .obj i narysować go przy pomocy glDrawElements.Mapowanie/Składanie wielu buforów .OBJ-index do bufora indeksu OpenGL 1

Teraz z glDrawArrays wszystko działa idealnie, ale jest - oczywiście - nieefektywne.

Problem, który mam teraz, polega na tym, że plik .obj używa wielu buforów indeksowych (dla każdego atrybutu), podczas gdy OpenGL może używać tylko jednego. Muszę więc odpowiednio je odwzorować.

Istnieje wiele pseudo algorytmów i znalazłem nawet implementację C++. Znam sporo C++, ale o dziwo, nie pomogło mi to w implementacji w Scali.

Zobaczmy:

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) 
} 

val indices: List[Int] było moje pierwsze naiwne podejście i oczywiście jest błędne. Ale zacznijmy od początku:

Załaduję plik i przechodzę przez niego. (Zakładam, że wiesz, jak powstaje plik .obj)

Czytam w wierzchołkach, współrzędnych tekstur i normalnych. Potem podchodzę do twarzy.

Teraz każda twarz w moim przykładzie ma 3 wartości v_x, t_y, n_z określające vertexAtIndexX, textureCoordAtIndexY, normalAtIndexZ. Zatem każdy z nich definiuje jeden wierzchołek, podczas gdy potrójny z nich (lub jedna linia w pliku) definiuje twarz/wielokąt/trójkąt.

w val vertices: List[Vertex] = for(face <- faces) yield(new Vertex(positions(face._1-1), textureCoordinates(face._2-1))) I rzeczywiście starają się tworzyć wierzchołki (w indywidualnych przypadkach, klasę, która obecnie tylko utrzymuje pozycje i tekstury współrzędne i zaniedbuje normalne teraz)

prawdziwym problemem jest to linia:

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

aby uzyskać prawdziwe indeksy I w zasadzie trzeba to zrobić zamiast z

val vertices: List[Vertex] = for(face <- faces) yield(new Vertex(positions(face._1-1), textureCoordinates(face._2-1))) i 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) 

Jednak jestem całkowicie zatrzymany. Próbowałem różnych rzeczy, ale nie mogę wymyślić ładnego i czystego rozwiązania, które faktycznie działa.

miejsca jak:

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

i próbuje f.distinct nie pracuje, bo nie ma nic do odrębnych, wszystkie wpisy nie są wyjątkowe, które całkowicie sens, gdy patrzę na plik i jeszcze to, co pseudo-kod nakazuje mi sprawdzenie.

Oczywiście wtedy muszę wypełnić indeksy odpowiednio (najlepiej w jednej liniowej i z dużą ilością funkcjonalnego piękna)

Ale powinienem spróbować znaleźć duplikaty, więc ... Jestem trochę oszołomiony. Chyba mieszam różne "wierzchołki" i "pozycje" za dużo, z wszystkimi odniesieniami.

Więc, myślę źle, czy algorytm/myślenie jest właściwe, a ja po prostu muszę to zaimplementować w ładnym, czystym (i faktycznie działającym) kodzie Scala?

Proszę, oświeć mnie!

Zgodnie komentarzach, zrobiłem małą aktualizację:

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 
} 

gdzie stoi nadal jest:

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))) 

Ciekawostka ja wciąż nie rozumiejąc go oczywiście, bo to sposób, w jaki nigdy nie dostaję duplikatu!

znaczy, że combinedIndices na końcu po prostu trzyma liczby naturalne takie jak:

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

Nie znam Scala w ogóle, więc nie mogę powiedzieć, czy to już robisz. Ale kluczowa struktura danych, której potrzebujesz, to mapa, która używa krotki z ** indeksami ** pozycji, współrzędnych tekstury i normalnej z rekordu twarzy jako klucza, a indeks wierzchołka OpenGL jako wartość. Oto pseudo-kod w starszej odpowiedzi na moje pytanie, na wypadek gdyby nie było to jedno z postów, które już znalazłeś w swoich poszukiwaniach: http://stackoverflow.com/questions/23349080/opengl-index-buffers-difficult/23356738#23356738 . –

+0

@RetoKoradi: Tak więc dla każdej twarzy muszę utworzyć Map-Entry za pomocą klawisza [position (face._1-1), textureCoordinates (face._2-1), normal (face._2-1)]). następnie obliczyć indeks wierzchołka OpenGL dla wartości? – Sorona

+0

Zaczynasz od 0 i zwiększasz go za każdym razem, gdy potrzebujesz nowego wierzchołka (tzn. Klucz nie był już na mapie). Z pseudo kodu, który łączyłem, powinno być całkiem jasne. –

Odpowiedz

1

To jest javascript (przepraszam nie scala), ale jest to w komentarzu i shoul być dość łatwe do konwersji.

// 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()); 

Wyjście

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 

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

Jedyne co robię ~ inaczej jest przy użyciu kapelusz ciąg stanowi jedną z części twarzy jako klucz do mojego indexMap (np. "25/32/5").

EDIT JSFiddle http://jsfiddle.net/8q7jLvsq/2/ Ta wersja łączy powtarzające się wartości dla wierzchołka, tekstury i normalnej. Optymalizuje to pliki OBJ, które powtarzają te same wartości wartości, dzięki czemu każda twarz jest wyjątkowa.

// 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

Robisz dokładnie to samo co ja. Sprawdzasz, czy istnieje duplikat twarzy. Teraz w moim pliku .obj nie ma duplikatów. Oto mój problem! Na przykład moja implementacja daje takie same wyniki jak twoje. Wciąż nie jest to właściwy plik .obj, ponieważ nie ma zduplikowanych twarzy (dlaczego mieliby to zrobić?) – Sorona

+0

@Teolha - być może twój eksport pliku OBJ z wyłączoną opcją optymalizacji. Jeśli twój OBJ ma wiele wpisów dla V, VT lub VN, które zawierają takie same dokładne wartości ~ "v 1 1 0 \ nv 1 1 0", to jest to trochę wadliwe, zanim przejdzie do twojego algorytmu.Można to poprawić w algorytmie, tworząc Mapę dla v, vt i vn, a następnie podczas wyszukiwania indeksów z twarzy * najpierw_appearing_indx = map [vert [face_index]] *, aby poprawić indeksy twarzy przed Tobą wyszukaj indeks, aby dodać do bufora. –

+0

@Teolha - Dodałem kolejną wersję, która połączy powtarzane wartości wierzchołków, tekstur i norm w postaci ell. –