从头开始编写3D软引擎:第3部分

在上一教程中 ,我们学习了如何绘制直线和三角形,并且由于此线框渲染,我们真正开始看到了网格的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 ”)。

您需要在用户首选项中激活我们的插件。 转到“ 文件 ”->“ 用户首选项 ”,然后选择“ 插件 ”标签。 搜索“ 巴比伦 ”并通过检查大小写将其激活。

image

用Blender做任何你想做的事。 如果您像我一样,真的不擅长构建3D网格,那么这是一个很酷的选择,它会在极客聚会中给您的朋友留下深刻的印象:“ 添加 ”->“ 网格 ”->“ 猴子 ”:

image

然后,您应该获得一个类似的屏幕:

image

最后一步是将其导出为.babylon文件格式 (我们的JSON文件)。 “ 文件 ” –>“ 导出 ” –>“ Babylon.js

image

将文件命名为“ 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 ”,并始终复制到输出目录:

image

否则,将找不到该文件。

最后,我们现在需要更新等效于main函数的函数,以调用此新的Lo​​adJSONFileAsync函数,而不是手动创建多维数据集。 由于我们还可能需要设置多个网格物体进行动画处理,因此我们还需要在每个刻度期间更改每个加载的网格物体的旋转值:

 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

TypeScriptSoftEngineTSPart3.zip

JavaScriptSoftEngineJSPart3.zip,或右键单击–>在第一个嵌入式iframe上查看源

那么,下一步是什么? 好吧,我们需要填充三角形 。 这称为栅格化 。 我们还将处理我们称为Z缓冲区的东西以具有适当的渲染。 在下一个教程中,您将学习如何获取类似的内容:

image

我们将使用随机颜色填充三角形。 在第四篇教程中见。

最初发布: 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/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值