在上一教程中 ,我们学习了如何绘制直线和三角形,并且由于此线框渲染,我们真正开始看到了网格的3D面。 但是我们只显示了一个立方体……甚至一个简单的立方体已经有12个面! 我们是否将被迫以这种方式处理所有复杂物体的所有面孔? 希望不会。
3D建模人员帮助3D设计师和开发人员之间的协作 。 设计师可以使用自己喜欢的工具来构建场景或网格(3D Studio Max,Maya,Blender等)。 然后,他会将自己的工作导出到开发人员将加载的文件中。 开发人员最终将网格推入他的实时3D引擎中。 市场上有几种文件格式可用于序列化艺术家完成的工作。 在本例中,我们将使用JSON。 实际上, David Catuhe已经为Blender完成了导出库,该库使用JSON输出.babylon文件 。 然后,我们将了解如何解析该文件并在我们可爱的软件引擎中显示网格。
Blender是免费的3D建模器,您可以在此处下载: http : //www.blender.org/download/get-blender/
您可以使用Python编写插件。 这就是我们为出口商所做的。
通过遵循本教程系列,您将可以得到这样的结果:
您会发现您已经完成了前2个教程中的大部分工作。
安装Babylon导出器并使用Blender生成自己的场景
安装Blender后,请从此处下载我们的巴比伦出口商: io_export_babylon.py
将此文件复制到安装Blender的\ script \ addons目录中(例如,在我的特定情况下,例如“ C:\ Program Files \ Blender Foundation \ Blender \ 2.67 \ scripts \ addons ”)。
您需要在用户首选项中激活我们的插件。 转到“ 文件 ”->“ 用户首选项 ”,然后选择“ 插件 ”标签。 搜索“ 巴比伦 ”并通过检查大小写将其激活。
用Blender做任何你想做的事。 如果您像我一样,真的不擅长构建3D网格,那么这是一个很酷的选择,它会在极客聚会中给您的朋友留下深刻的印象:“ 添加 ”->“ 网格 ”->“ 猴子 ”:
然后,您应该获得一个类似的屏幕:
最后一步是将其导出为.babylon文件格式 (我们的JSON文件)。 “ 文件 ” –>“ 导出 ” –>“ Babylon.js ”
将文件命名为“ monkey.babylon ”。
注意:这只猴子叫苏珊娜(Suzanne),在3D /游戏社区中非常有名。 通过认识她,您现在是这个炫酷社区的骄傲成员! 欢迎登机! ;)
加载导出的JSON文件并显示其网格
正如我在本文开头告诉您的那样,我们已经构建了所有必需的逻辑,以显示更复杂的网格,例如Suzanne。 我们已经有了面,网格和顶点逻辑。 这就是我们现在所需要的。
巴比伦导出器以JSON格式添加了比我们当前所需更多的详细信息。 例如,它还会添加有关纹理,灯光等的潜在详细信息。这就是为什么我们要解析文件并直接跳转到我们仅感兴趣的区域:顶点和面的索引以构建我们的线框渲染。
注意:对于C#开发人员,您需要通过nuGet从Newtonsoft安装Json.NET ,就像我们在第一个教程中所做的那样添加SharpDX。 实际上,.NET本身不像在使用JavaScript的浏览器内部那样,在.NET中原生支持JSON解析。
让我们从在Device对象内添加加载逻辑开始:
// Loading the JSON file in an asynchronous manner public async Task < Mesh []> LoadJSONFileAsync( string fileName) var meshes = new List < Mesh >(); var file = await Windows.ApplicationModel. Package .Current.InstalledLocation.GetFileAsync(fileName); var data = await Windows.Storage. FileIO .ReadTextAsync(file); dynamic jsonObject = Newtonsoft.Json. JsonConvert .DeserializeObject(data); for ( var meshIndex = 0; meshIndex < jsonObject.meshes.Count; meshIndex++) { var verticesArray = jsonObject.meshes[meshIndex].vertices; // Faces var indicesArray = jsonObject.meshes[meshIndex].indices; var uvCount = jsonObject.meshes[meshIndex].uvCount.Value; var verticesStep = 1; // Depending of the number of texture's coordinates per vertex // we're jumping in the vertices array by 6, 8 & 10 windows frame switch (( int )uvCount) { case 0: verticesStep = 6; break ; case 1: verticesStep = 8; break ; case 2: verticesStep = 10; break ; } // the number of interesting vertices information for us var verticesCount = verticesArray.Count / verticesStep; // number of faces is logically the size of the array divided by 3 (A, B, C) var facesCount = indicesArray.Count / 3; var mesh = new Mesh (jsonObject.meshes[meshIndex].name.Value, verticesCount, facesCount); // Filling the Vertices array of our mesh first for ( var index = 0; index < verticesCount; index++) { var x = ( float )verticesArray[index * verticesStep].Value; var y = ( float )verticesArray[index * verticesStep + 1].Value; var z = ( float )verticesArray[index * verticesStep + 2].Value; mesh.Vertices[index] = new Vector3 (x, y, z); } // Then filling the Faces array for ( var index = 0; index < facesCount; index++) { var a = ( int )indicesArray[index * 3].Value; var b = ( int )indicesArray[index * 3 + 1].Value; var c = ( int )indicesArray[index * 3 + 2].Value; mesh.Faces[index] = new Face { A = a, B = b, C = c }; } // Getting the position you've set in Blender var position = jsonObject.meshes[meshIndex].position; mesh.Position = new Vector3 (( float )position[0].Value, ( float )position[1].Value, ( float )position[2].Value); meshes.Add(mesh); } return meshes.ToArray();
// Loading the JSON file in an asynchronous manner and / calling back with the function passed providing the array of meshes loaded public LoadJSONFileAsync(fileName: string , callback: (result: Mesh[]) => any ): void { var jsonObject = {}; var xmlhttp = new XMLHttpRequest(); xmlhttp.open( "GET" , fileName, true ); var that = this ; xmlhttp.onreadystatechange = function () { if (xmlhttp.readyState == 4 && xmlhttp.status == 200) { jsonObject = JSON.parse(xmlhttp.responseText); callback(that.CreateMeshesFromJSON(jsonObject)); } }; xmlhttp.send( null ); private CreateMeshesFromJSON(jsonObject): Mesh[] { var meshes: Mesh[] = []; for ( var meshIndex = 0; meshIndex < jsonObject.meshes.length; meshIndex++) { var verticesArray: number [] = jsonObject.meshes[meshIndex].vertices; // Faces var indicesArray: number [] = jsonObject.meshes[meshIndex].indices; var uvCount: number = jsonObject.meshes[meshIndex].uvCount; var verticesStep = 1; // Depending of the number of texture's coordinates per vertex // we're jumping in the vertices array by 6, 8 & 10 windows frame switch (uvCount) { case 0: verticesStep = 6; break ; case 1: verticesStep = 8; break ; case 2: verticesStep = 10; break ; } // the number of interesting vertices information for us var verticesCount = verticesArray.length / verticesStep; // number of faces is logically the size of the array divided by 3 (A, B, C) var facesCount = indicesArray.length / 3; var mesh = new SoftEngine.Mesh(jsonObject.meshes[meshIndex].name, verticesCount, facesCount); // Filling the Vertices array of our mesh first for ( var index = 0; index < verticesCount; index++) { var x = verticesArray[index * verticesStep]; var y = verticesArray[index * verticesStep + 1]; var z = verticesArray[index * verticesStep + 2]; mesh.Vertices[index] = new BABYLON.Vector3(x, y, z); } // Then filling the Faces array for ( var index = 0; index < facesCount; index++) { var a = indicesArray[index * 3]; var b = indicesArray[index * 3 + 1]; var c = indicesArray[index * 3 + 2]; mesh.Faces[index] = { A: a, B: b, C: c }; } // Getting the position you've set in Blender var position = jsonObject.meshes[meshIndex].position; mesh.Position = new BABYLON.Vector3(position[0], position[1], position[2]); meshes.push(mesh); } return meshes;
// Loading the JSON file in an asynchronous manner and / calling back with the function passed providing the array of meshes loaded Device.prototype.LoadJSONFileAsync = function (fileName, callback) { var jsonObject = {}; var xmlhttp = new XMLHttpRequest(); xmlhttp.open( "GET" , fileName, true ); var that = this ; xmlhttp.onreadystatechange = function () { if (xmlhttp.readyState == 4 && xmlhttp.status == 200) { jsonObject = JSON.parse(xmlhttp.responseText); callback(that.CreateMeshesFromJSON(jsonObject)); } }; xmlhttp.send( null ); ; evice.prototype.CreateMeshesFromJSON = function (jsonObject) { var meshes = []; for ( var meshIndex = 0; meshIndex < jsonObject.meshes.length; meshIndex++) { var verticesArray = jsonObject.meshes[meshIndex].vertices; // Faces var indicesArray = jsonObject.meshes[meshIndex].indices; var uvCount = jsonObject.meshes[meshIndex].uvCount; var verticesStep = 1; // Depending of the number of texture's coordinates per vertex // we're jumping in the vertices array by 6, 8 & 10 windows frame switch (uvCount) { case 0: verticesStep = 6; break ; case 1: verticesStep = 8; break ; case 2: verticesStep = 10; break ; } // the number of interesting vertices information for us var verticesCount = verticesArray.length / verticesStep; // number of faces is logically the size of the array divided by 3 (A, B, C) var facesCount = indicesArray.length / 3; var mesh = new SoftEngine.Mesh(jsonObject.meshes[meshIndex].name, verticesCount, facesCount); // Filling the Vertices array of our mesh first for ( var index = 0; index < verticesCount; index++) { var x = verticesArray[index * verticesStep]; var y = verticesArray[index * verticesStep + 1]; var z = verticesArray[index * verticesStep + 2]; mesh.Vertices[index] = new BABYLON.Vector3(x, y, z); } // Then filling the Faces array for ( var index = 0; index < facesCount; index++) { var a = indicesArray[index * 3]; var b = indicesArray[index * 3 + 1]; var c = indicesArray[index * 3 + 2]; mesh.Faces[index] = { A: a, B: b, C: c }; } // Getting the position you've set in Blender var position = jsonObject.meshes[meshIndex].position; mesh.Position = new BABYLON.Vector3(position[0], position[1], position[2]); meshes.push(mesh); } return meshes; ;
您可能会想知道为什么我们要在顶点数组中跳6,8和10以获取顶点的3D坐标(X,Y,Z)。 同样,这是因为巴比伦导出器添加了我们当前线框渲染所需的更多详细信息。 因此, 我们正在使用这种框架方法过滤那些细节 。 此逻辑特定于我们的文件格式。 如果要加载其他文件的导出文件(例如three.js文件中的文件),则只需确定在哪里可以检索另一种文件格式的顶点和面索引。
注意:为了能够加载TypeScript / JavaScript开发人员的.babylon文件,您需要定义一个新的MIME类型“ application / babylon ”,其扩展名为“ .babylon”。 在IIS中,您需要在web.config中声明它:
< system.webServer > < staticContent > < mimeMap fileExtension = " .babylon " mimeType = " application/babylon " /> </ staticContent > </ system.webServer >
C#开发人员,您需要更改将包含在解决方案中的文件的属性。 将“ Build Action ”切换到“ Content ”,并始终复制到输出目录:
否则,将找不到该文件。
最后,我们现在需要更新等效于main函数的函数,以调用此新的LoadJSONFileAsync函数,而不是手动创建多维数据集。 由于我们还可能需要设置多个网格物体进行动画处理,因此我们还需要在每个刻度期间更改每个加载的网格物体的旋转值:
private Device device; Mesh [] meshes; Camera mera = new Camera (); private async void Page_Loaded( object sender, RoutedEventArgs e) // Choose the back buffer resolution here WriteableBitmap bmp = new WriteableBitmap (640, 480); // Our Image XAML control frontBuffer.Source = bmp; device = new Device (bmp); meshes = await device.LoadJSONFileAsync( "monkey.babylon" ); mera.Position = new Vector3 (0, 0, 10.0f); mera.Target = Vector3 .Zero; // Registering to the XAML rendering loop CompositionTarget .Rendering += CompositionTarget_Rendering; // Rendering loop handler void CompositionTarget_Rendering( object sender, object e) device.Clear(0, 0, 0, 255); foreach ( var mesh in meshes) { // rotating slightly the meshes during each frame rendered mesh.Rotation = new Vector3 (mesh.Rotation.X + 0.01f, mesh.Rotation.Y + 0.01f, mesh.Rotation.Z); } // Doing the various matrix operations device.Render(mera, meshes); // Flushing the back buffer into the front buffer device.Present();
///<reference path="SoftEngine.ts"/> var canvas: HTMLCanvasElement; var device: SoftEngine.Device; var meshes: SoftEngine.Mesh[] = []; var mera: SoftEngine.Camera; document.addEventListener( "DOMContentLoaded" , init, false ); function init() { canvas = <HTMLCanvasElement> document.getElementById( "frontBuffer" ); mera = new SoftEngine.Camera(); device = new SoftEngine.Device(canvas); mera.Position = new BABYLON.Vector3(0, 0, 10); mera.Target = new BABYLON.Vector3(0, 0, 0); device.LoadJSONFileAsync( "monkey.babylon" , loadJSONCompleted) function loadJSONCompleted(meshesLoaded: SoftEngine.Mesh[]) { meshes = meshesLoaded; // Calling the HTML5 rendering loop requestAnimationFrame(drawingLoop); // Rendering loop handler function drawingLoop() { device.clear(); for ( var i = 0; i < meshes.length; i++) { // rotating slightly the mesh during each frame rendered meshes[i].Rotation.x += 0.01; meshes[i].Rotation.y += 0.01; } // Doing the various matrix operations device.render(mera, meshes); // Flushing the back buffer into the front buffer device.present(); // Calling the HTML5 rendering loop recursively requestAnimationFrame(drawingLoop);
var canvas; var device; var meshes = []; var mera; document.addEventListener( "DOMContentLoaded" , init, false ); function init() { canvas = document.getElementById( "frontBuffer" ); mera = new SoftEngine.Camera(); device = new SoftEngine.Device(canvas); mera.Position = new BABYLON.Vector3(0, 0, 10); mera.Target = new BABYLON.Vector3(0, 0, 0); device.LoadJSONFileAsync( "monkey.babylon" , loadJSONCompleted); function loadJSONCompleted(meshesLoaded) { meshes = meshesLoaded; // Calling the HTML5 rendering loop requestAnimationFrame(drawingLoop); // Rendering loop handler function drawingLoop() { device.clear(); for ( var i = 0; i < meshes.length; i++) { // rotating slightly the mesh during each frame rendered meshes[i].Rotation.x += 0.01; meshes[i].Rotation.y += 0.01; } // Doing the various matrix operations device.render(mera, meshes); // Flushing the back buffer into the front buffer device.present(); // Calling the HTML5 rendering loop recursively requestAnimationFrame(drawingLoop);
现在,您应该拥有一个3D引擎,该引擎能够加载Blender导出的网格并以线框渲染模式对其进行动画处理! 我不认识你,但是我很高兴达到这个阶段。 :)
同样,您可以下载包含源代码的解决方案 :
– C# : SoftEngineCSharpPart3.zip
– TypeScript : SoftEngineTSPart3.zip
– JavaScript : SoftEngineJSPart3.zip,或右键单击–>在第一个嵌入式iframe上查看源
那么,下一步是什么? 好吧,我们需要填充三角形 。 这称为栅格化 。 我们还将处理我们称为Z缓冲区的东西以具有适当的渲染。 在下一个教程中,您将学习如何获取类似的内容:
我们将使用随机颜色填充三角形。 在第四篇教程中见。
最初发布: http : //blogs.msdn.com/b/davrous/archive/2013/06/17/tutorial-part-3-learning-how-to-write-a-3d-soft-engine-in-c -ts或js-loading-meshes-exported-from-blender.aspx 。 经作者许可在此处转载。
From: https://www.sitepoint.com/write-3d-soft-engine-from-scratch-part-3/