我正在尝试加载.obj文件并在 glDrawElements
的帮助下绘制它 .
现在, glDrawArrays
一切都很完美,但它当然是效率低下的 .
我现在遇到的问题是,.obj文件使用多个索引缓冲区(对于每个属性),而OpenGL只能使用一个 . 所以我需要相应地映射它们 .
那里有很多伪算法,我甚至找到了一个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文件是如何组成的)
我读了顶点,纹理坐标和法线 . 然后我来到了脸上 .
现在,我的示例中的每个面都有3个值 v_x, t_y, n_z
,用于定义 vertexAtIndexX, textureCoordAtIndexY, normalAtIndexZ
. 因此,每个定义一个顶点,而其中三个(或文件中的一行)定义一个面/多边形/三角形 .
在 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!
为了得到真正的指数,我基本上需要这样做 instead
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
不起作用,因为没有什么可以区分,所有条目都是唯一的,如果我查看文件完全有意义,但这就是伪代码告诉我检查的内容 .
当然那么我需要相应地填写指数(最好是单线并具有很多功能美)
但我应该试着找到重复的东西,所以...我有点困惑 . 我猜我把所有的参考都混合了不同的“顶点”和“位置” .
所以,我认为是错误的,还是算法/思考正确,我只需要在漂亮,干净(实际工作)的Scala代码中实现它?
拜托,赐教!
As per comments, I made a little 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
}
面孔仍然是:
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 fact 我显然还是不明白,因为这样我从来没有复制过!
意味着 combinedIndices
在最后只是拥有如下自然数:
ListBuffer(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ...)
1 回答
这是javascript(抱歉不是scala)但是它被评论并且应该很容易转换 .
输出
JSFiddle http://jsfiddle.net/8q7jLvsq/2
我唯一要做的就是〜使用字符串hat表示一个face的一部分作为我的indexMap的键(例如:“25/32/5”) .
EDIT JSFiddle http://jsfiddle.net/8q7jLvsq/2/此版本结合了顶点,纹理和法线的重复值 . 这将优化OBJ文件,这些文件重复相同的值,使每个面都独一无二 .