.obj-File を読み込んで、 を使用して描画しようとしていますglDrawElements
。
さて、glDrawArrays
すべてが完璧に機能しますが、もちろん非効率的です。
私が今抱えている問題は、.obj ファイルが (属性ごとに) 複数のインデックス バッファーを使用しているのに対し、OpenGL は 1 つしか使用できないことです。したがって、それに応じてそれらをマッピングする必要があります。
そこには多くの疑似アルゴリズムがあり、C++ の実装も見つけました。私はかなりの C++ を知っていますが、奇妙なことに、どちらも Scala での実装には役立ちませんでした。
どれどれ:
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]
は私の最初の単純なアプローチであり、もちろん間違っています。しかし、一番上から始めましょう:
ファイルを読み込んで調べます。(.objファイルがどのように構成されているか知っていると思います)
頂点、テクスチャ座標、法線を読み込みました。それから私は顔に来ます。
さて、私の例の各面には、 をv_x, t_y, n_z
定義する3 つの値がありvertexAtIndexX, textureCoordAtIndexY, normalAtIndexZ
ます。したがって、これらのそれぞれが 1 つの頂点を定義し、これらの 3 つ (またはファイル内の 1 行) が面/ポリゴン/三角形を定義します。
私は実際にval vertices: List[Vertex] = for(face <- faces) yield(new Vertex(positions(face._1-1), textureCoordinates(face._2-1)))
頂点を作成しようとしています (現在、位置とテクスチャ座標のみを保持し、法線を無視するケースクラス)
本当の問題は次の行です。
val indices: List[Int] = faces.map(f => f._1-1) // Wrong!
実際のインデックスを取得するには、基本的に代わりにこれを行う必要があります
val vertices: List[Vertex] = for(face <- faces) yield(new Vertex(positions(face._1-1), textureCoordinates(face._2-1)))
と
val indices: List[Int] = faces.map(f => f._1-1) // Wrong!
擬似コード:
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)
それでも私は完全に立ち往生しています。私はさまざまなことを試しましたが、実際に機能する素晴らしくクリーンなソリューションを思い付くことができません。
次のようなもの:
val f: List[(Vector3, Vector2, Vector4)] = for(face <- faces) yield((positions(face._1-1), textureCoordinates(face._2-1), normals(face._3-1)))
区別するものは何f.distinct
もないため、そこにあるすべてのエントリは一意であるため、機能していません。ファイルを見ると完全に理にかなっていますが、疑似コードが確認するように指示しています。
もちろん、それに応じてインデックスを埋める必要があります(できればワンライナーで、多くの機能的な美しさで)
しかし、重複を見つけようとする必要があるので...ちょっと困惑しています。すべての参照で、さまざまな「頂点」と「位置」を混同しすぎていると思います。
では、私の考えは間違っているのでしょうか、それともアルゴリズム/考え方が正しいのでしょうか?
教えてください!
コメントに従って、少し更新しました。
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
}
顔がまだある場所:
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)))
楽しい事実、私はまだそれを明らかに理解していません。
combinedIndices
最後に次のような自然数を保持することを意味します。
ListBuffer(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ...)