使用Molehill渲染3D模型
发布时间:2020-12-15 18:11:51 所属栏目:百科 来源:网络整理
导读:/** * * 翻译力求准确,信达雅谈不上,如有错误或者不准确的地方欢迎指出 * @see http://ltslashgt.com/2011/08/07/rendering-models-with-molehill/ * */ 我很想接着我 上一篇文章的步伐来点高级的例子,我终于抽出时间来搞了。这是我有关在 Molehill中加载
/** * * 翻译力求准确,信达雅谈不上,如有错误或者不准确的地方欢迎指出 * @see http://ltslashgt.com/2011/08/07/rendering-models-with-molehill/ * */我很想接着我 上一篇文章的步伐来点高级的例子,我终于抽出时间来搞了。这是我有关在 Molehill中加载和渲染各种模型系列文章的第一篇(事实上也是最后一篇,之后作者就没更新过)。 模型格式可能很令人抓狂。大多数格式很古老(推出的时间比较早)或者编写时没有考虑到硬件加速。你常常需要对其数据做做调整使其可用。在随着时间的演变硬件加速渲染成为标准的过程中,看看格式的随之变迁很有趣。我们从一些较老的格式开始之后过渡到新的格式。 这是本篇文章的实战 代码,展示了一个由 Bobo the Seal制作的可爱模型 Bunker。// //演示到原网站上看看吧 // Wavefront OBJWavefront OBJ格式是为上世纪80年代Wavefront公司的Advanced Visualizer(一个3D软件)创建的。据我所知,它自从上世纪90年代以来一直没有更新(对于模型格式来说,这是常态)。今天他仍旧被用来展示静态物体,因为其格式简单所以几乎所有的建模软件都支持它。它是一个基于文本的模型格式。它支持很多高深的东西,但是(本文中)这个加载类只支持最常用的:顶点位置,法线和UV坐标以及具有3个或更多顶点的面此外还有材质组。OBJ格式不支持动画 代码[Embed(source="../res/bunker/bunker.obj",mimeType="application/octet-stream")] static protected const BUNKER_OBJ:Class; [Embed(source="../res/bunker/fidget_head.png")] static protected const BUNKER_HEAD:Class; [Embed(source="../res/bunker/fidget_body.png")] static protected const BUNKER_BODY:Class;创建之后像这样加载 // Load the model,and set the material textures _obj = new OBJ(); _obj.readBytes(new BUNKER_OBJ(),_context); _obj.setMaterial('h_head',_headTexture); _obj.setMaterial('u_torso',_bodyTexture); _obj.setMaterial('l_legs',_bodyTexture);这里你会注意到我手动设置了材质纹理,因为loader不处理MTL文件。 OBJ文件自身在OBJ.as中的readBytes()方法中被解析。为OBJ(提供方便)的字节数组(ByteArray)被传了进来,它必须被转化成文本,然后一行一行的读。任何空行或者以#开头的行都会被忽略,代码如下: var text:String = bytes.readUTFBytes(bytes.bytesAvailable); var lines:Array = text.split(/[rn]+/); for each (var line:String in lines) { // Trim whitespace from the line line = line.replace(/^s*|s*$/g,''); if (line === '' || line.charAt(0) === '#') { // Blank line or comment,ignore it continue; } // TODO: parse the line } 上段代码的TODO,你需要用空格将行拆分开,然后检测它是那种命令。你可以这样做,如下: // Split line into fields on whitespace var fields:Array=line.split(/s+/); switch (fields[0].toLowerCase()) { case 'v': // TODO: parse vertex position break; case 'vn': // TODO: parse vertex normal break; case 'vt': // TODO: parse vertex uv break; case 'f': // TODO: parse face break; case 'g': // TODO: parse group break; case 'o': // TODO: parse object break; case 'usemtl': // TODO: parse material break; }顶点位置(v命令)只是3个浮点数。字段都从字符串转化为数字,并push进了positions数组 case 'v': positions.push(parseFloat(fields[1]),parseFloat(fields[2]),parseFloat(fields[3])); break;顶点法线(vn命令)工作方式相同: case 'vn': normals.push(parseFloat(fields[1]),parseFloat(fields[3])); break;顶点UV(vt命令)只是两个浮点数。OBJ有一个翻转的V轴纹理坐标,所以你需要将其翻转回正常的: case 'vt': uvs.push(parseFloat(fields[1]),1.0 - parseFloat(fields[2])); break;对于组(g命令),创建了一个新的OBJGroup对象并将其添加到组列表中。组有几个属性(name,material和face),因此OBJGroup对象对于跟踪那些东西很有用。 case 'g': group = new OBJGroup(fields[1],materialName); groups.push(group); break;材质名称(usemtl命令)仅被保存下来并赋给当前的组(如果有的话)。默认清空下任何后续的组都将被赋予当前的材质,除非它们有自己的usemtl命令 case 'usemtl': materialName = fields[1]; if (group !== null) { group.materialName = materialName; } break;如前所述,面组(f命令)是一系列的索引元祖。创建一个新的vector来保存面的索引元祖,并且面被添加到当前的组当中。后续会处理它。 case 'f': face = new Vector.<String>(); for each (var tuple:String in fields.slice(1)) { face.push(tuple); } if (group === null) { group = new OBJGroup(null,materialName); groups.push(group); } group._faces.push(face); break; Fixing up the data(数据整理)这是所有我们需要处理的命令。这个循环将对文件中所有的行进行复制。一旦完成后我们将会有几个分开的顶点数据流(位置,法线和UV)。我们也会有组的列表,每个都有其面的列表,他们有进入这些分流的索引(indice)。这是个问题。OBJ为位置、法线和UV指定了分开的索引,但是现代的硬件渲染不知那些。我们只能有一个顶点流(index Stream)。要修正它,我们需要将这三个顶点流合并成一个顶点流。面的顶点(face indices)也需要更新以便在这个流中指定正确的偏移量。 要做到这点,每个组得到一个新的索引流(index stream)。然后对于面中的每一个索引元祖我在合并后的流中写入一个新的vertex,如果在别的面中已经有那个顶点元组,则使用已被合并进去的索引(index)。 我们所面临的的另一个问题是OBJ允许多边形面。也就是说,面不必是三角形。这就是问题所在:Context3D只支持绘制三角形。要修正这,我们需要将非三角形转化成 三角形。 这一切循环如下: for each (group in groups) { group._indices.length=0; for each (face in group._faces) { var il:int=face.length - 1; for (var i:int=1; i < il; ++i) { group._indices.push(mergeTuple(face[i],positions,normals,uvs)); group._indices.push(mergeTuple(face[0],uvs)); group._indices.push(mergeTuple(face[i + 1],uvs)); } } group.indexBuffer=context.createIndexBuffer(group._indices.length); group.indexBuffer.uploadFromVector(group._indices,group._indices.length); group._faces=null; }上述循环对面中的每一个索引元祖(mergeTuple)调用了mergeTuple方法。该方法如下: protected function mergeTuple(tuple:String,positions:Vector.<Number>,normals:Vector.<Number>,uvs:Vector.<Number>):uint { if (_tupleIndices[tuple] !== undefined) { // Already merged,return the merged index return _tupleIndices[tuple]; } else { var faceIndices:Array=tuple.split('/'); // Position index var index:uint=parseInt(faceIndices[0],10) - 1; _vertices.push(positions[index * 3 + 0],positions[index * 3 + 1],positions[index * 3 + 2]); // Normal index if (faceIndices.length > 2 && faceIndices[2].length > 0) { index=parseInt(faceIndices[2],10) - 1; _vertices.push(normals[index * 3 + 0],normals[index * 3 + 1],normals[index * 3 + 2]); } else { // Face doesn't have a normal _vertices.push(0,0); } // UV index if (faceIndices.length > 1 && faceIndices[1].length > 0) { index=parseInt(faceIndices[1],10) - 1; _vertices.push(uvs[index * 2 + 0],uvs[index * 2 + 1]); } else { // Face doesn't have a UV _vertices.push(0,0); } // Cache the merged tuple index in case it's used again return _tupleIndices[tuple]=_tupleIndex++; } }这个函数承担了OBJ 加载器的大部分工作,如果元组已经存在于我们的元组缓存中,那么返回已经被合并进去的索引(index),否则,我们将面指向的定点数据(vertex Data)拷贝到合并数组中,并返回新的索引(index)。 OBJ无需指定法线和UV,因此为了使与其之前状态一致,我们只在那里填充几个0。现在的顶点缓冲是0索引,但是OBJ不是。所以我们还需要从所有的顶点里减去1(indices)。 最后但同样重要的是,我们需要为新的流创建vertex Buffer: vertexBuffer=context.createVertexBuffer(_vertices.length / 8,8); vertexBuffer.uploadFromVector(_vertices,_vertices.length / 8); Rendering the model(渲染模型)解决了模型加载,渲染那就是一气呵成。DemoOBJ.as文件中的update()函数做了些设置,然后如下渲染OBJ:// Draw the model _context.setVertexBufferAt(0,_obj.vertexBuffer,Context3DVertexBufferFormat.FLOAT_3); _context.setVertexBufferAt(1,3,Context3DVertexBufferFormat.FLOAT_3); _context.setVertexBufferAt(2,6,Context3DVertexBufferFormat.FLOAT_2); for each (var group:OBJGroup in _obj.groups) { _context.setTextureAt(0,_obj.getMaterial(group.materialName)); _context.drawTriangles(group.indexBuffer); }这为在OBJ Buffer中的vertex Buffer设置了的位置、法线和UV。然后遍历每个组并为其设置材料并绘制与组相关的三角形。 我希望这有助于展示在Molehill(Stage3D)如何加载和渲染模型.在本文中我不能覆盖所有的代码,所以如果你有问题可以随意发表评论或者给我发邮件。如果你想找到更多的OBJ文件来折腾,我推荐你去Polycount上的SDK master thread看看 在我的下一篇文章中,我将看看Quake MDL文件。这是另一个很神秘的格式,但它是二进制的并且支持动画,所以有更多可学的东西。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |