从头编写操作系统_从头开始编写3D软引擎:第1部分

从头编写操作系统

我想与您分享我如何通过一系列教程学习如何构建所谓的“ 3D软引擎 ”。 “软件引擎”意味着我们将仅使用CPU以传统的方式构建3D引擎(还记得80386上的Doom吗?)。

我将与您分享代码的C#,TypeScript和JavaScript版本。 然后,在此列表中,您应该找到自己喜欢的语言,或者至少找到您喜欢的一种语言。 这个想法是为了帮助您在您喜欢的平台上转换以下示例和概念。 您还将找到Visual Studio 2012 C#/ TS / JS解决方案,也可以在末尾下载。

那么为什么要构建3D软引擎呢? 嗯,这仅仅是因为它确实有助于理解现代3D如何与我们的GPU一起工作。 的确,由于出色的David Catuhe在Microsoft内部举办的内部研讨会,我目前正在学习3D的基础知识。 他已经掌握3D多年了,矩阵运算在他的大脑中是硬编码的。 小时候,我梦想着能够写出这样的引擎,但是我觉得它对我来说太复杂了。 最后,您会发现这并不复杂。 您只需要一个可以帮助您以简单方式理解基本原理的人。

通过本系列文章,您将学习如何在2D屏幕上投影与点(顶点)关联的一些3D坐标(X,Y,Z),如何在每个点之间绘制线,如何填充一些三角形,处理灯光。 ,材料等。 第一个教程将简单地向您展示如何显示与多维数据集关联的8个点以及如何在虚拟3D世界中移动它们。

本教程是以下系列的一部分:

1 –编写相机,网格和设备对象的核心逻辑(本文)
2 – 绘制线和三角形以获得线框渲染
3 – 加载从Blender导出的JSON格式的网格
4 – 用栅格化和使用Z缓冲区填充三角形
4b – 奖金:使用技巧和并行性来提高性能
5 – 使用平面阴影和Gouraud阴影处理光
6 – 应用纹理,背面剔除和WebGL

如果您遵循完整的系列,您将知道如何构建自己的3D软件引擎 ! 然后,您的引擎将首先进行一些线框渲染,然后进行栅格化,接着进行gouraud着色,最后应用纹理:

3dSoftEngineProgression

单击图像以在另一个窗口中打开最终的纹理渲染。

通过正确地遵循此第一个教程,您将学习如何旋转多维数据集的8个点,最后获得以下结果:

免责声明:你们中有些人想知道为什么我要构建这个3D软件引擎而不是使用GPU。 确实是出于教育目的。 当然,如果您需要使用流畅的3D动画制作游戏,则需要DirectX或OpenGL / WebGL。 但是一旦您了解了如何构建3D软引擎,就会更容易理解更多“复杂”的引擎。 要走得更远,您肯定应该看看David Catuhe构建的BabylonJS WebGL引擎 。 此处有更多详细信息和教程: Babylon.js:使用HTML 5和WebGL构建3D游戏的完整JavaScript框架

阅读先决条件

我很长一段时间以来一直在思考如何编写这些教程。 最后,我决定不自己解释每个必需的原则。 网络上有很多很好的资源可以比我更好地解释这些重要原理。但是,据我自己说,我花了很多时间浏览网络供您选择,是阅读的最佳方式:

–发布了“ 世界,视图和投影矩阵”
教程3:矩阵 ,将向您介绍矩阵,模型,视图和投影矩阵。
OpenGL ES 2.x上的摄像头– ModelViewProjection矩阵 :这真的很有趣,因为它从摄像头和镜头的工作原理入手,解释了这个故事。
转换(Direct3D 9)
3D简介 :出色的PowerPoint幻灯片平台! 至少阅读第27张幻灯片。在那之后,它也与与GPU(OpenGL或DirectX)对话的技术联系在一起。
OpenGL转换

OpenGL顶点转换

阅读这些文章时,不要专注于相关技术(例如OpenGL或DirectX)或您可能在图中看到的三角形的概念。 我们稍后会看到。

通过阅读这些文章,您确实需要了解以这种方式进行了一系列转换:

–我们从以自身为中心3D对象开始
–然后通过矩阵的平移,缩放或旋转操作将同一对象移入虚拟3D世界
摄像机将注视位于 3D世界中的3D 对象
–将所有内容最终投影到屏幕上的2D空间

Diagram of how world coordinates and local coordinates are relatedOpenGL透视视锥和NDC

免费学习PHP!

全面介绍PHP和MySQL,从而实现服务器端编程的飞跃。

原价$ 11.95 您的完全免费

所有这些魔术都是通过矩阵运算累计转换来完成的。 在完成这些教程之前,您至少应该真正熟悉这些概念 。 即使您第一次阅读也不了解所有内容。 您应该先阅读它们。 您稍后可能会在编写自己的3D软件引擎版本时返回这些文章。 这完全正常,不用担心! ;)通过尝试和犯错误来学习3D的最佳方法。

我们也不会花任何时间在矩阵运算的工作方式上。 好消息是您实际上不需要了解矩阵。 只需将其视为黑匣子即可为您执行正确的操作。 我不是矩阵大师,但是我自己编写了3D软引擎。 因此,您也应该这样做。

然后,我们将使用库,会为我们做的工作:SharpDX,在DirectX的顶部的托管包装,由大卫Catuhe针对JavaScript开发人员编写C#开发人员和babylon.math.js。 我也用TypeScript重写了它。

软件先决条件

我们将使用C#和/或带有TypeScript / JavaScript的HTML5应用程序编写WinRT / XAML Windows应用商店 。 因此,如果您想按原样使用C#示例,则需要安装:

1 – Windows 8
2 –适用于Windows应用商店的Visual Studio 2012 Express。 您可以免费下载它: http : //msdn.microsoft.com/zh-CN/windows/apps/br211386

如果选择使用TypeScript示例,则需要从以下网址进行安装: http : //www.typescriptlang.org/#Download 。 所有样本均已使用TypeScript 0.9更新并成功测试。

您将找到Visual Studio 2012的插件,但还有其他可用选项: Sublime Text,Vi,Emacs:启用TypeScript! 在我这方面,我通过将代码的C#版本移植到TypeScript来学习TypeScript。 如果您还对学习TypeScript感兴趣,请观看以下网络广播的第一篇很好的介绍: Anders Hejlsberg:TypeScript简介 。 请同时安装Web Types 2012 ,它完全支持TypeScript预览和编译。

如果选择JavaScript ,则只需要您喜欢的IDE和兼容HTML5的浏览器。 :)

请针对您要使用的语言创建一个名为“ SoftEngine ”的项目。 如果是C# ,请在您的解决方案上使用NuGet添加“ SharpDX核心组件 ”:

image

如果是TypeScript ,请下载 巴比伦 。 如果是JavaScript,请下载babylon.math.js 。 在两种情况下都添加对这些文件的引用。

后台缓冲区和渲染循环

在3D引擎中,我们正在渲染每个帧期间的完整场景,希望保持最佳的每秒60帧(FPS)来保持流畅的动画。 为了完成渲染工作,我们需要所谓的后缓冲区。 这可以看作是映射屏幕/窗口大小的二维数组。 数组的每个单元格都映射到屏幕上的一个像素。

在我们的XAML Windows Store Apps中,我们将使用byte []数组作为动态后备缓冲区 。 对于在动画循环(刻度)中渲染的每一帧,此缓冲区都将受到WriteableBitmap的影响,该WriteableBitmap充当XAML图像控件的源,该控件将称为前缓冲区 。 对于渲染循环,我们将要求XAML渲染引擎针对将生成的每个帧调用我们。 通过以下代码行完成注册:

 CompositionTarget .Rendering += CompositionTarget_Rendering; 

HTML5中 ,我们将当然使用<canvas />元素。 canvas元素已经具有与其关联的后缓冲区数据数组。 您可以通过getImageData()setImageData()函数访问它。 动画循环将由requestAnimationFrame()函数处理。 这是等效于setTimeout(function(){],1000/60)的效率,因为它是由浏览器本地处理的,浏览器仅在准备绘制时才回调我们的代码。

注意:在两种情况下,您都可以使用与最终窗口的实际宽度和高度不同的分辨率来渲染帧。 例如,您可以具有640×480像素的后缓冲区,而最终的显示屏(前缓冲区)将为1920×1080。 在XAML中,借助HTML5中CSS,您将受益于“ 硬件缩放 ”。 XAML和浏览器的呈现引擎甚至可以使用抗锯齿算法将后缓冲区数据扩展到前缓冲区窗口。 在两种情况下,此任务均由GPU完成。 这就是为什么我们称其为“硬件缩放”(硬件是GPU)。 您可以在此处阅读有关HTML5中解决的该主题的更多信息: 释放HTML 5 Canvas用于游戏的功能 。 例如,由于您需要处理的像素较少,因此这种方法通常用于游戏中以提高性能。

相机和网格物体

让我们开始编码。 首先,我们需要定义一些对象,这些对象将嵌入相机和网格所需的详细信息。 网格物体是描述3D对象的好名字。

我们的相机将具有2个属性:其在3D世界中的位置以及它所注视的目标。 两者均由名为Vector3的3D坐标组成。 C#将使用SharpDX.Vector3 ,TypeScript和JavaScript将使用BABYLON.Vector3

我们的网格物体将具有一组顶点(几个顶点或3D点),这些顶点将用于构建3D对象,其在3D世界中的位置及其旋转状态。 为了识别它,它还将有一个名称。

要恢复,我们需要以下代码:

 // Camera.cs & Mesh.cs
 using  SharpDX;
 namespace  SoftEngine
    public class  Camera
    {
        public  Vector3  Position {  get ;  set ; }
        public  Vector3  Target {  get ;  set ; }
   }
    public class  Mesh
    {
        public string  Name {  get ;  set ; }
        public  Vector3 [] Vertices {  get ;  private set ; }
        public  Vector3  Position {  get ;  set ; }
        public  Vector3  Rotation {  get ;  set ; }
         public  Mesh( string  name,  int  verticesCount)
       {
           Vertices =  new  Vector3 [verticesCount];
           Name = name;
       }
   }
 
 //<reference path="babylon.math.ts"/>
 module  SoftEngine {
    export class  Camera {
       Position: BABYLON.Vector3;
       Target: BABYLON.Vector3;
         constructor () {
            this .Position = BABYLON.Vector3.Zero();
            this .Target = BABYLON.Vector3.Zero();
       }
   }
    export class  Mesh {
       Position: BABYLON.Vector3;
       Rotation: BABYLON.Vector3;
       Vertices: BABYLON.Vector3[];
         constructor ( public  name:  string , verticesCount:  number ) {
            this .Vertices =  new  Array(verticesCount);
            this .Rotation = BABYLON.Vector3.Zero();
            this .Position = BABYLON.Vector3.Zero();
       }
   }
 
 var  SoftEngine;
 function  (SoftEngine) {
    var  Camera = ( function  () {
        function  Camera() {
            this .Position = BABYLON.Vector3.Zero();
            this .Target = BABYLON.Vector3.Zero();
       }
        return  Camera;
   })();
   SoftEngine.Camera = Camera;    
    var  Mesh = ( function  () {
        function  Mesh(name, verticesCount) {
            this .name = name;
            this .Vertices =  new  Array(verticesCount);
            this .Rotation = BABYLON.Vector3.Zero();
            this .Position = BABYLON.Vector3.Zero();
       }
        return  Mesh;
   })();
   SoftEngine.Mesh = Mesh;    
)(SoftEngine || (SoftEngine = {})); 

例如,如果要使用“网格”对象描述多维数据集,则需要创建与多维数据集的8个点关联的8个顶点。 以下是Blender中显示的多维数据集上的坐标:

image

用左撇子的世界。 还要记住,创建网格时,坐标系始于网格的中心。 因此,X = 0,Y = 0,Z = 0是立方体的中心。

可以通过以下代码创建:

 var  mesh =  new  Mesh ( "Cube" , 8);
esh.Vertices[0] =  new  Vector3 (-1, 1, 1);
esh.Vertices[1] =  new  Vector3 (1, 1, 1);
esh.Vertices[2] =  new  Vector3 (-1, -1, 1);
esh.Vertices[3] =  new  Vector3 (-1, -1, -1);
esh.Vertices[4] =  new  Vector3 (-1, 1, -1);
esh.Vertices[5] =  new  Vector3 (1, 1, -1);
esh.Vertices[6] =  new  Vector3 (1, -1, 1);
esh.Vertices[7] =  new  Vector3 (1, -1, -1); 

最重要的部分:设备对象

现在我们有了基本对象,并且知道如何构建3D网格,我们需要最重要的部分: Device对象。 这是我们3D引擎的核心

在其渲染功能中,我们将基于之前定义的相机构建视图矩阵和投影矩阵。

然后,我们将遍历每个可用的网格,以根据其当前的旋转和平移值构建其关联的世界矩阵。 最后,一旦完成,要应用的最终转换矩阵为:

 var  transformMatrix = worldMatrix * viewMatrix * projectionMatrix; 

通过阅读之前的先决条件资源,这是您绝对需要理解的概念。 否则,您可能会简单地复制/粘贴代码,而无需了解任何有关底层魔术的知识。 对于进一步的教程来说,这不是一个很大的问题,但是再次重申,最好知道您在编码什么。

使用此变换矩阵,我们将投影2D世界中每个网格的每个顶点,以从其X,Y,Z坐标获得X,Y坐标。 为了最终在屏幕上绘制,我们添加了一个小片段逻辑,仅通过PutPixel方法/函数显示可见像素。

这是Device对象的各种版本。 我尝试对代码进行注释,以帮助您尽可能地理解它。

注意: Microsoft Windows使用BGRA颜色空间(蓝色,绿色,红色,Alpha)进行绘制,而HTML5画布使用RGBA (红色,绿色,蓝色,Alpha)颜色空间进行绘制。 这就是为什么,您会注意到C#和HTML5之间的代码有些细微的差异。

 using  Windows.UI.Xaml.Media.Imaging;
 using  System.Runtime.InteropServices.WindowsRuntime;
 using  SharpDX;
 namespace  SoftEngine
    public class  Device
    {
        private byte [] backBuffer;
        private  WriteableBitmap  bmp;
         public  Device( WriteableBitmap  bmp)
       {
            this .bmp = bmp;
            // the back buffer size is equal to the number of pixels to draw
           // on screen (width*height) * 4 (R,G,B & Alpha values). 
            backBuffer =  new byte [bmp.PixelWidth * bmp.PixelHeight * 4];
       }
         // This method is called to clear the back buffer with a specific color
        public void  Clear( byte  r,  byte  g,  byte  b,  byte  a) {
            for  ( var  index = 0; index < backBuffer.Length; index += 4)
           {
                // BGRA is used by Windows instead by RGBA in HTML5
                backBuffer[index] = b;
               backBuffer[index + 1] = g;
               backBuffer[index + 2] = r;
               backBuffer[index + 3] = a;
           }
       }
         // Once everything is ready, we can flush the back buffer
       // into the front buffer. 
        public void  Present()
       {
            using  ( var  stream = bmp.PixelBuffer.AsStream())
           {
                // writing our byte[] back buffer into our WriteableBitmap stream
                stream.Write(backBuffer, 0, backBuffer.Length);
           }
            // request a redraw of the entire bitmap
            bmp.Invalidate();
       }
         // Called to put a pixel on screen at a specific X,Y coordinates
        public void  PutPixel( int  x,  int  y,  Color4  color)
       {
            // As we have a 1-D Array for our back buffer
           // we need to know the equivalent cell in 1-D based
           // on the 2D coordinates on screen
            var  index = (x + y * bmp.PixelWidth) * 4;
            backBuffer[index] = ( byte )(color.Blue * 255);
           backBuffer[index + 1] = ( byte )(color.Green * 255);
           backBuffer[index + 2] = ( byte )(color.Red * 255);
           backBuffer[index + 3] = ( byte )(color.Alpha * 255);
       }
         // Project takes some 3D coordinates and transform them
       // in 2D coordinates using the transformation matrix
        public  Vector2  Project( Vector3  coord,  Matrix  transMat)
       {
            // transforming the coordinates
            var  point =  Vector3 .TransformCoordinate(coord, transMat);
            // The transformed coordinates will be based on coordinate system
           // starting on the center of the screen. But drawing on screen normally starts
           // from top left. We then need to transform them again to have x:0, y:0 on top left.
            var  x = point.X * bmp.PixelWidth + bmp.PixelWidth / 2.0f;
            var  y = -point.Y * bmp.PixelHeight + bmp.PixelHeight / 2.0f;
            return  ( new  Vector2 (x, y));
       }
         // DrawPoint calls PutPixel but does the clipping operation before
        public void  DrawPoint( Vector2  point)
       {
            // Clipping what's visible on screen
            if  (point.X >= 0 && point.Y >= 0 && point.X < bmp.PixelWidth && point.Y < bmp.PixelHeight)
           {
                // Drawing a yellow point
                PutPixel(( int )point.X, ( int )point.Y,  new  Color4 (1.0f, 1.0f, 0.0f, 1.0f));
           }
       }
         // The main method of the engine that re-compute each vertex projection
       // during each frame
        public void  Render( Camera  camera,  params  Mesh [] meshes)
       {
            // To understand this part, please read the prerequisites resources
            var  viewMatrix =  Matrix .LookAtLH(camera.Position, camera.Target,  Vector3 .UnitY);
            var  projectionMatrix =  Matrix .PerspectiveFovRH(0.78f, 
                                                          ( float )bmp.PixelWidth / bmp.PixelHeight, 
                                                          0.01f, 1.0f);
             foreach  ( Mesh  mesh  in  meshes) 
           {
                // Beware to apply rotation before translation 
                var  worldMatrix =  Matrix .RotationYawPitchRoll(mesh.Rotation.Y, 
mesh.Rotation.X, mesh.Rotation.Z) * Matrix .Translation(mesh.Position); var transformMatrix = worldMatrix * viewMatrix * projectionMatrix; foreach ( var vertex in mesh.Vertices) { // First, we project the 3D coordinates into the 2D space var point = Project(vertex, transformMatrix); // Then we can draw on screen DrawPoint(point); } } } }
 ///<reference path="babylon.math.ts"/>
 module  SoftEngine {
      export class  Device {
        // the back buffer size is equal to the number of pixels to draw
       // on screen (width*height) * 4 (R,G,B & Alpha values). 
        private  backbuffer: ImageData;
        private  workingCanvas: HTMLCanvasElement;
        private  workingContext: CanvasRenderingContext2D;
        private  workingWidth:  number ;
        private  workingHeight:  number ;
        // equals to backbuffer.data
        private  backbufferdata;
         constructor (canvas: HTMLCanvasElement) {
            this .workingCanvas = canvas;
            this .workingWidth = canvas.width;
            this .workingHeight = canvas.height;
            this .workingContext =  this .workingCanvas.getContext( "2d" );
       }
         // This function is called to clear the back buffer with a specific color
        public  clear():  void  {
            // Clearing with black color by default
            this .workingContext.clearRect(0, 0,  this .workingWidth,  this .workingHeight);
            // once cleared with black pixels, we're getting back the associated image data to 
           // clear out back buffer
            this .backbuffer =  this .workingContext.getImageData(0, 0,  this .workingWidth,  this .workingHeight);
       }
         // Once everything is ready, we can flush the back buffer
       // into the front buffer. 
        public  present():  void  {
            this .workingContext.putImageData( this .backbuffer, 0, 0);
       }
         // Called to put a pixel on screen at a specific X,Y coordinates
        public  putPixel(x:  number , y:  number , color: BABYLON.Color4):  void  {
            this .backbufferdata =  this .backbuffer.data;
            // As we have a 1-D Array for our back buffer
           // we need to know the equivalent cell index in 1-D based
           // on the 2D coordinates of the screen
            var  index:  number  = ((x >> 0) + (y >> 0) *  this .workingWidth) * 4;
   
                    
            // RGBA color space is used by the HTML5 canvas
            this .backbufferdata[index] = color.r * 255;
            this .backbufferdata[index + 1] = color.g * 255;
            this .backbufferdata[index + 2] = color.b * 255;
            this .backbufferdata[index + 3] = color.a * 255;
       }
         // Project takes some 3D coordinates and transform them
       // in 2D coordinates using the transformation matrix
        public  project(coord: BABYLON.Vector3, transMat: BABYLON.Matrix): BABYLON.Vector2 {
            // transforming the coordinates
            var  point = BABYLON.Vector3.TransformCoordinates(coord, transMat);
            // The transformed coordinates will be based on coordinate system
           // starting on the center of the screen. But drawing on screen normally starts
           // from top left. We then need to transform them again to have x:0, y:0 on top left.
            var  x = point.x *  this .workingWidth +  this .workingWidth / 2.0 >> 0;
            var  y = -point.y *  this .workingHeight +  this .workingHeight / 2.0 >> 0;
            return  ( new  BABYLON.Vector2(x, y));
       }
         // drawPoint calls putPixel but does the clipping operation before
        public  drawPoint(point: BABYLON.Vector2):  void  {
            // Clipping what's visible on screen
            if  (point.x >= 0 && point.y >= 0 && point.x <  this .workingWidth 
&& point.y < this .workingHeight) { // Drawing a yellow point this .putPixel(point.x, point.y, new BABYLON.Color4(1, 1, 0, 1)); } } // The main method of the engine that re-compute each vertex projection // during each frame public render(camera: Camera, meshes: Mesh[]): void { // To understand this part, please read the prerequisites resources var viewMatrix = BABYLON.Matrix.LookAtLH(camera.Position, camera.Target, BABYLON.Vector3.Up()); var projectionMatrix = BABYLON.Matrix.PerspectiveFovLH(0.78,
this .workingWidth / this .workingHeight, 0.01, 1.0); for ( var index = 0; index < meshes.length; index++) { // current mesh to work on var cMesh = meshes[index]; // Beware to apply rotation before translation var worldMatrix = BABYLON.Matrix.RotationYawPitchRoll( cMesh.Rotation.y, cMesh.Rotation.x, cMesh.Rotation.z) .multiply(BABYLON.Matrix.Translation( cMesh.Position.x, cMesh.Position.y, cMesh.Position.z)); var transformMatrix = worldMatrix.multiply(viewMatrix).multiply(projectionMatrix); for ( var indexVertices = 0; indexVertices < cMesh.Vertices.length; indexVertices++) { // First, we project the 3D coordinates into the 2D space var projectedPoint = this .project(cMesh.Vertices[indexVertices], transformMatrix); // Then we can draw on screen this .drawPoint(projectedPoint); } } } }
 var  SoftEngine;
 function  (SoftEngine) {  
    var  Device = ( function  () {
        function  Device(canvas) {
            // Note: the back buffer size is equal to the number of pixels to draw
           // on screen (width*height) * 4 (R,G,B & Alpha values). 
            this .workingCanvas = canvas;
            this .workingWidth = canvas.width;
            this .workingHeight = canvas.height;
            this .workingContext =  this .workingCanvas.getContext( "2d" );
       }
         // This function is called to clear the back buffer with a specific color
        Device.prototype.clear =  function  () {
            // Clearing with black color by default
            this .workingContext.clearRect(0, 0,  this .workingWidth,  this .workingHeight);
            // once cleared with black pixels, we're getting back the associated image data to 
           // clear out back buffer
            this .backbuffer =  this .workingContext.getImageData(0, 0,  this .workingWidth,  this .workingHeight);
       };
         // Once everything is ready, we can flush the back buffer
       // into the front buffer. 
        Device.prototype.present =  function  () {
            this .workingContext.putImageData( this .backbuffer, 0, 0);
       };
         // Called to put a pixel on screen at a specific X,Y coordinates
        Device.prototype.putPixel =  function  (x, y, color) {
            this .backbufferdata =  this .backbuffer.data;
            // As we have a 1-D Array for our back buffer
           // we need to know the equivalent cell index in 1-D based
           // on the 2D coordinates of the screen
            var  index = ((x >> 0) + (y >> 0) *  this .workingWidth) * 4;
   
                    
            // RGBA color space is used by the HTML5 canvas
            this .backbufferdata[index] = color.r * 255;
            this .backbufferdata[index + 1] = color.g * 255;
            this .backbufferdata[index + 2] = color.b * 255;
            this .backbufferdata[index + 3] = color.a * 255;
       };
         // Project takes some 3D coordinates and transform them
       // in 2D coordinates using the transformation matrix
        Device.prototype.project =  function  (coord, transMat) {
            var  point = BABYLON.Vector3.TransformCoordinates(coord, transMat);
            // The transformed coordinates will be based on coordinate system
           // starting on the center of the screen. But drawing on screen normally starts
           // from top left. We then need to transform them again to have x:0, y:0 on top left.
            var  x = point.x *  this .workingWidth +  this .workingWidth / 2.0 >> 0;
            var  y = -point.y *  this .workingHeight +  this .workingHeight / 2.0 >> 0;
            return  ( new  BABYLON.Vector2(x, y));
       };
         // drawPoint calls putPixel but does the clipping operation before
        Device.prototype.drawPoint =  function  (point) {
            // Clipping what's visible on screen
            if  (point.x >= 0 && point.y >= 0 && point.x <  this .workingWidth 
&& point.y < this .workingHeight) { // Drawing a yellow point this .putPixel(point.x, point.y, new BABYLON.Color4(1, 1, 0, 1)); } }; // The main method of the engine that re-compute each vertex projection // during each frame Device.prototype.render = function (camera, meshes) { // To understand this part, please read the prerequisites resources var viewMatrix = BABYLON.Matrix.LookAtLH(camera.Position, camera.Target, BABYLON.Vector3.Up()); var projectionMatrix = BABYLON.Matrix.PerspectiveFovLH(0.78,
this .workingWidth / this .workingHeight, 0.01, 1.0); for ( var index = 0; index < meshes.length; index++) { // current mesh to work on var cMesh = meshes[index]; // Beware to apply rotation before translation var worldMatrix = BABYLON.Matrix.RotationYawPitchRoll( cMesh.Rotation.y, cMesh.Rotation.x, cMesh.Rotation.z) .multiply(BABYLON.Matrix.Translation( cMesh.Position.x, cMesh.Position.y, cMesh.Position.z)); var transformMatrix = worldMatrix.multiply(viewMatrix).multiply(projectionMatrix); for ( var indexVertices = 0; indexVertices < cMesh.Vertices.length; indexVertices++) { // First, we project the 3D coordinates into the 2D space var projectedPoint = this .project(cMesh.Vertices[indexVertices], transformMatrix); // Then we can draw on screen this .drawPoint(projectedPoint); } } }; return Device; })(); SoftEngine.Device = Device; )(SoftEngine || (SoftEngine = {}));

放在一起

最后,我们需要创建一个网格(我们的多维数据集),创建一个相机并瞄准我们的网格并实例化我们的Device对象。

完成后,我们将启动动画/渲染循环。 在最佳情况下,将每16毫秒(60 FPS)调用一次此循环。 在每次滴答(调用注册到渲染循环的处理程序)期间,我们每次都会启动以下逻辑:

1 – 清除屏幕和所有相关像素为黑色( Clear()函数)

2 – 更新网格的各种位置和旋转值

3 –通过执行所需的矩阵运算将它们渲染到后台缓冲区中( Render()函数)

4 –通过将后缓冲区数据刷新到前缓冲区( Present()函数)在屏幕上显示它们

 private  Device  device;
 Mesh  mesh =  new  Mesh ( "Cube" , 8);
 Camera  mera =  new  Camera ();
 private void  Page_Loaded( object  sender,  RoutedEventArgs  e)
    // Choose the back buffer resolution here
    WriteableBitmap  bmp =  new  WriteableBitmap (640, 480);
    device =  new  Device (bmp);
     // Our XAML Image control
    frontBuffer.Source = bmp;
    mesh.Vertices[0] =  new  Vector3 (-1, 1, 1);
   mesh.Vertices[1] =  new  Vector3 (1, 1, 1);
   mesh.Vertices[2] =  new  Vector3 (-1, -1, 1);
   mesh.Vertices[3] =  new  Vector3 (-1, -1, -1);
   mesh.Vertices[4] =  new  Vector3 (-1, 1, -1);
   mesh.Vertices[5] =  new  Vector3 (1, 1, -1);
   mesh.Vertices[6] =  new  Vector3 (1, -1, 1);
   mesh.Vertices[7] =  new  Vector3 (1, -1, -1);
    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);
     // rotating slightly the cube 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, mesh);
    // Flushing the back buffer into the front buffer
    device.Present();
 
 ///<reference path="SoftEngine.ts"/>
 var  canvas: HTMLCanvasElement; 
 var  device: SoftEngine.Device;
 var  mesh: SoftEngine.Mesh;
 var  meshes: SoftEngine.Mesh[] = [];
 var  mera: SoftEngine.Camera;
document.addEventListener( "DOMContentLoaded" , init,  false );
 function  init() {
   canvas = <HTMLCanvasElement> document.getElementById( "frontBuffer" );
   mesh =  new  SoftEngine.Mesh( "Cube" , 8);
   meshes.push(mesh);
   mera =  new  SoftEngine.Camera();
   device =  new  SoftEngine.Device(canvas);
    mesh.Vertices[0] =  new  BABYLON.Vector3(-1, 1, 1);
   mesh.Vertices[1] =  new  BABYLON.Vector3(1, 1, 1);
   mesh.Vertices[2] =  new  BABYLON.Vector3(-1, -1, 1);
   mesh.Vertices[3] =  new  BABYLON.Vector3(-1, -1, -1);
   mesh.Vertices[4] =  new  BABYLON.Vector3(-1, 1, -1);
   mesh.Vertices[5] =  new  BABYLON.Vector3(1, 1, -1);
   mesh.Vertices[6] =  new  BABYLON.Vector3(1, -1, 1);
   mesh.Vertices[7] =  new  BABYLON.Vector3(1, -1, -1);
    mera.Position =  new  BABYLON.Vector3(0, 0, 10);
   mera.Target =  new  BABYLON.Vector3(0, 0, 0);
     // Calling the HTML5 rendering loop
    requestAnimationFrame(drawingLoop);
 // Rendering loop handler
 function  drawingLoop() {
   device.clear();
     // rotating slightly the cube during each frame rendered
    mesh.Rotation.x += 0.01;
   mesh.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  mesh;
 var  meshes = [];
 var  mera;
document.addEventListener( "DOMContentLoaded" , init,  false );
 function  init() {
   canvas = document.getElementById( "frontBuffer" );
   mesh =  new  SoftEngine.Mesh( "Cube" , 8);
   meshes.push(mesh);
   mera =  new  SoftEngine.Camera();
   device =  new  SoftEngine.Device(canvas);
    mesh.Vertices[0] =  new  BABYLON.Vector3(-1, 1, 1);
   mesh.Vertices[1] =  new  BABYLON.Vector3(1, 1, 1);
   mesh.Vertices[2] =  new  BABYLON.Vector3(-1, -1, 1);
   mesh.Vertices[3] =  new  BABYLON.Vector3(-1, -1, -1);
   mesh.Vertices[4] =  new  BABYLON.Vector3(-1, 1, -1);
   mesh.Vertices[5] =  new  BABYLON.Vector3(1, 1, -1);
   mesh.Vertices[6] =  new  BABYLON.Vector3(1, -1, 1);
   mesh.Vertices[7] =  new  BABYLON.Vector3(1, -1, -1);
    mera.Position =  new  BABYLON.Vector3(0, 0, 10);
   mera.Target =  new  BABYLON.Vector3(0, 0, 0);
     // Calling the HTML5 rendering loop
    requestAnimationFrame(drawingLoop);
 // Rendering loop handler
 function  drawingLoop() {
   device.clear();
     // rotating slightly the cube during each frame rendered
    mesh.Rotation.x += 0.01;
   mesh.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);
 

如果您设法正确地遵循了第一个教程,则应该获得如下内容:


如果不是,请下载包含源代码的解决方案

C#SoftEngineCSharpPart1.zip

TypeScriptSoftEngineTSPart1.zip

JavaScriptSoftEngineJSPart1.zip或直接右键单击–>在嵌入式iframe上查看源

只需查看代码,然后尝试找出您的问题所在。 :)

在下一个教程中,我们将学习如何在每个顶点与面/ 三角形的概念之间绘制线条以获得类似的内容:

image

在本系列的第二部分见。

最初发布: http : //blogs.msdn.com/b/davrous/archive/2013/06/13/tutorial-series-learning-how-to-write-a-3d-soft-engine-from-scratch-in -c-typescript-or-javascript.aspx 经作者许可在此处转载。

翻译自: https://www.sitepoint.com/write-3d-soft-engine-scratch-part-1/

从头编写操作系统

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值