我想与您分享我如何通过一系列教程学习如何构建所谓的“ 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着色,最后应用纹理:
单击图像以在另一个窗口中打开最终的纹理渲染。
通过正确地遵循第一个教程,您将学习如何旋转多维数据集的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或DirectX)或您可能在图中看到的三角形的概念。 我们稍后会看到。
通过阅读这些文章,您确实需要了解以这种方式进行了一系列转换:
–我们从以自身为中心的3D对象开始
–然后通过矩阵的平移,缩放或旋转操作将同一对象移到虚拟3D世界中
– 摄像机将注视位于 3D世界中的3D 对象
–将所有内容最终投影到屏幕上的2D空间中
所有这些魔术都是通过矩阵运算累计转换来完成的。 在阅读这些教程之前,您至少应该真正熟悉这些概念 。 即使您第一次阅读也不了解所有内容。 您应该先阅读它们。 您稍后可能会在编写自己的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简介 。 请同时安装对TypeScript预览和编译具有完全支持的Web Essentials 2012 。
如果选择JavaScript ,则只需要您喜欢的IDE和兼容HTML5的浏览器。 :)
请针对您要使用的语言创建一个名为“ SoftEngine ”的项目。 如果是C# ,则通过在解决方案上使用NuGet来添加“ SharpDX核心程序集 ”:
如果是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 = {}));
例如,如果要使用我们的Mesh对象描述一个多维数据集,则需要创建与该多维数据集的8个点关联的8个顶点。 这是Blender中显示的多维数据集上的坐标:
用左撇子的世界。 还要记住,创建网格时,坐标系始于网格的中心。 因此,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
– TypeScript : SoftEngineTSPart1.zip
– JavaScript : SoftEngineJSPart1.zip或直接右键单击–>在嵌入式iframe上查看源
只需查看代码,然后尝试找出您的问题所在。 :)
在下一个教程中,我们将学习如何在每个顶点与面/ 三角形的概念之间绘制线条以获得类似的内容:
在本系列的第二部分见。
最初发布: 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 。 经作者许可在此处转载。
From: https://www.sitepoint.com/write-3d-soft-engine-scratch-part-1/