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

现在,由于有了前面的教程第1部分 ,我们已经构建了3D引擎的核心,我们可以致力于增强渲染。 然后,下一步是连接点以绘制一些线,以渲染您可能称为“线框”的渲染

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

在本教程中,您将学习如何绘制线条,什么是面以及使用Bresenham算法绘制一些三角形有多酷。

因此,最后,您将知道如何编写像这样的酷代码:

是! 我们的3D旋转立方体开始真正出现在我们的屏幕上!

第一种在两点之间画线的基本算法

让我们从编码一个简单的算法开始。 为了在2个顶点之间画一条线 ,我们将使用以下逻辑:

–如果两个点(point0和point1)之间的距离小于2像素,则无事可做
–否则,我们将找到两个之间的中间点 (point0坐标+(point1坐标– point0坐标)/ 2)
–我们正在屏幕上绘制该点
–我们将在point0和中点之间以及中点和point1之间递归启动此算法

这是执行此操作的代码:

 public void  DrawLine( Vector2  point0,  Vector2  point1)
    var  dist = (point1 - point0).Length();
     // If the distance between the 2 points is less than 2 pixels
   // We're exiting
    if  (dist < 2)
        return ;
     // Find the middle point between first & second point
    Vector2  middlePoint = point0 + (point1 - point0)/2;
    // We draw this point on screen
    DrawPoint(middlePoint);
    // Recursive algorithm launched between first & middle point
   // and between middle & second point
    DrawLine(point0, middlePoint);
   DrawLine(middlePoint, point1);
 

 public  drawLine(point0: BABYLON.Vector2, point1: BABYLON.Vector2):  void  {
    var  dist = point1.subtract(point0).length();
     // If the distance between the 2 points is less than 2 pixels
   // We're exiting
    if  (dist < 2)
        return ;
     // Find the middle point between first & second point
    var  middlePoint = point0.add((point1.subtract(point0)).scale(0.5));
    // We draw this point on screen
    this .drawPoint(middlePoint);
    // Recursive algorithm launched between first & middle point
   // and between middle & second point
    this .drawLine(point0, middlePoint);
    this .drawLine(middlePoint, point1);
 

 Device.prototype.drawLine =  function  (point0, point1) {
    var  dist = point1.subtract(point0).length();
     // If the distance between the 2 points is less than 2 pixels
   // We're exiting
    if (dist < 2) {
        return ;
   }
     // Find the middle point between first & second point
    var  middlePoint = point0.add((point1.subtract(point0)).scale(0.5));
    // We draw this point on screen
    this .drawPoint(middlePoint);
    // Recursive algorithm launched between first & middle point
   // and between middle & second point
    this .drawLine(point0, middlePoint);
    this .drawLine(middlePoint, point1);
; 

您需要更新渲染循环以使用以下新代码:

 for  ( var  i = 0; i < mesh.Vertices.Length - 1; i++)
    var  point0 = Project(mesh.Vertices[i], transformMatrix);
    var  point1 = Project(mesh.Vertices[i + 1], transformMatrix);
   DrawLine(point0, point1);
 

 for  ( var  i = 0; i < cMesh.Vertices.length -1; i++){
    var  point0 =  this .project(cMesh.Vertices[i], transformMatrix);
    var  point1 =  this .project(cMesh.Vertices[i + 1], transformMatrix);
    this .drawLine(point0, point1);
 

 for  ( var  i = 0; i < cMesh.Vertices.length -1; i++){
    var  point0 =  this .project(cMesh.Vertices[i], transformMatrix);
    var  point1 =  this .project(cMesh.Vertices[i + 1], transformMatrix);
    this .drawLine(point0, point1);
 

现在您应该获得类似的信息:


我知道这看起来很奇怪,但这是预期的行为。 它应该可以帮助您开始理解显示3D网格所需的操作。 但是要获得更好的渲染,我们需要发现一个新概念。

用三角形显示面

现在我们知道如何绘制线了,我们需要一种更好的方法来渲染带有它们的网格。 最简单的2D几何形状是三角形 。 3D的想法是使用这些三角形绘制所有网格。 然后,我们需要将立方体的每一侧拆分为2个三角形。 我们将“手动”执行此操作,但是在下一个教程中,我们将看到3D建模人员现在正在为我们自动执行此步骤。

要绘制三角形,您需要有3个点/顶点。 因此,面只是一个包含3个值的结构,这些值是指向要渲染的网格的适当顶点阵列的索引。

为了理解这个概念,让我们以Blender显示的多维数据集作为上图:

image

我们在此图上显示4个顶点,并带有以下索引:0、1、2、3。要绘制立方体的上侧,我们需要绘制2个三角形。 第一个面为Face 0,将绘制3条线,从顶点0 (-1、1、1)到顶点1 (1、1、1),从顶点1 (1、1、1)到顶点2 (- 1,–1,1),最后从顶点2 (-1,–1,1)到顶点0 (-1,1,1)。 将使用从顶点1到顶点2 ,顶点2到顶点3以及顶点3到顶点1的线绘制第二个三角形Face 1

等效的代码将是这样的:

 var  mesh =  new  SoftEngine. Mesh ( "Square" , 4, 2);
eshes.Add(mesh);
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);
mesh.Faces[0] =  new  Face  { A = 0, B = 1, C = 2 };
esh.Faces[1] =  new  Face  { A = 1, B = 2, C = 3 }; 

如果要绘制整个立方体,则需要找到剩余的10 个面 ,因为要绘制立方体的6 个面12个面

现在让我们为Face对象定义代码。 这是一个非常简单的对象,因为它只是3个索引的集合 。 这是Face和新的Mesh定义的代码,现在也可以使用它:

 namespace  SoftEngine
    public struct  Face
    {
        public int  A;
        public int  B;
        public int  C;
   }
    public class  Mesh
    {
        public string  Name {  get ;  set ; }
        public  Vector3 [] Vertices {  get ;  private set ; }
        public  Face [] Faces {  get ;  set ; }
        public  Vector3  Position {  get ;  set ; }
        public  Vector3  Rotation {  get ;  set ; }
         public  Mesh( string  name,  int  verticesCount,  int  facesCount)
       {
           Vertices =  new  Vector3 [verticesCount];
           Faces =  new  Face [facesCount];
           Name = name;
       }
   }
 

 ///<reference path="babylon.math.ts"/>
 module  SoftEngine {
    export interface  Face {
       A:  number ;
       B:  number ;
       C:  number ;
   }
     export class  Mesh {
       Position: BABYLON.Vector3;
       Rotation: BABYLON.Vector3;
       Vertices: BABYLON.Vector3[];
       Faces: Face[];
         constructor ( public  name:  string , verticesCount:  number , facesCount:  number ) {
            this .Vertices =  new  Array(verticesCount);
            this .Faces =  new  Array(facesCount);
            this .Rotation =  new  BABYLON.Vector3(0, 0, 0);
            this .Position =  new  BABYLON.Vector3(0, 0, 0);
       }
   }
 

 var  SoftEngine;
 function  (SoftEngine) {
    var  Mesh = ( function  () {
        function  Mesh(name, verticesCount, facesCount) {
            this .name = name;
            this .Vertices =  new  Array(verticesCount);
            this .Faces =  new  Array(facesCount);
            this .Rotation =  new  BABYLONTS.Vector3(0, 0, 0);
            this .Position =  new  BABYLONTS.Vector3(0, 0, 0);
       }
        return  Mesh;
   })();
   SoftEngine.Mesh = Mesh;    
)(SoftEngine || (SoftEngine = {})); 

现在,我们需要更新Device对象的Render()函数/方法,以迭代定义的所有面并绘制关联的三角形。

 foreach  ( var  face  in  mesh.Faces)
    var  vertexA = mesh.Vertices[face.A];
    var  vertexB = mesh.Vertices[face.B];
    var  vertexC = mesh.Vertices[face.C];
     var  pixelA = Project(vertexA, transformMatrix);
    var  pixelB = Project(vertexB, transformMatrix);
    var  pixelC = Project(vertexC, transformMatrix);
    DrawLine(pixelA, pixelB);
   DrawLine(pixelB, pixelC);
   DrawLine(pixelC, pixelA);
 

 for  ( var  indexFaces = 0; indexFaces < cMesh.Faces.length; indexFaces++)
    var  currentFace = cMesh.Faces[indexFaces];
    var  vertexA = cMesh.Vertices[currentFace.A];
    var  vertexB = cMesh.Vertices[currentFace.B];
    var  vertexC = cMesh.Vertices[currentFace.C];
     var  pixelA =  this .project(vertexA, transformMatrix);
    var  pixelB =  this .project(vertexB, transformMatrix);
    var  pixelC =  this .project(vertexC, transformMatrix);
     this .drawLine(pixelA, pixelB);
    this .drawLine(pixelB, pixelC);
    this .drawLine(pixelC, pixelA);
 

 for  ( var  indexFaces = 0; indexFaces < cMesh.Faces.length; indexFaces++)
    var  currentFace = cMesh.Faces[indexFaces];
    var  vertexA = cMesh.Vertices[currentFace.A];
    var  vertexB = cMesh.Vertices[currentFace.B];
    var  vertexC = cMesh.Vertices[currentFace.C];
     var  pixelA =  this .project(vertexA, transformMatrix);
    var  pixelB =  this .project(vertexB, transformMatrix);
    var  pixelC =  this .project(vertexC, transformMatrix);
     this .drawLine(pixelA, pixelB);
    this .drawLine(pixelB, pixelC);
    this .drawLine(pixelC, pixelA);
 

最后,我们需要使用12个面正确声明与多维数据集关联的网格,以使此新代码按预期工作。

这是新的声明:

 var  mesh =  new  SoftEngine. Mesh ( "Cube" , 8, 12);
eshes.Add(mesh);
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);
mesh.Faces[0] =  new  Face  { A = 0, B = 1, C = 2 };
esh.Faces[1] =  new  Face  { A = 1, B = 2, C = 3 };
esh.Faces[2] =  new  Face  { A = 1, B = 3, C = 6 };
esh.Faces[3] =  new  Face  { A = 1, B = 5, C = 6 };
esh.Faces[4] =  new  Face  { A = 0, B = 1, C = 4 };
esh.Faces[5] =  new  Face  { A = 1, B = 4, C = 5 };
mesh.Faces[6] =  new  Face  { A = 2, B = 3, C = 7 };
esh.Faces[7] =  new  Face  { A = 3, B = 6, C = 7 };
esh.Faces[8] =  new  Face  { A = 0, B = 2, C = 7 };
esh.Faces[9] =  new  Face  { A = 0, B = 4, C = 7 };
esh.Faces[10] =  new  Face  { A = 4, B = 5, C = 6 };
esh.Faces[11] =  new  Face  { A = 4, B = 6, C = 7 }; 

 var  mesh =  new  SoftEngine.Mesh( "Cube" , 8, 12);
eshes.push(mesh);
esh.Vertices[0] =  new  BABYLON.Vector3(-1, 1, 1);
esh.Vertices[1] =  new  BABYLON.Vector3(1, 1, 1);
esh.Vertices[2] =  new  BABYLON.Vector3(-1, -1, 1);
esh.Vertices[3] =  new  BABYLON.Vector3(1, -1, 1);
esh.Vertices[4] =  new  BABYLON.Vector3(-1, 1, -1);
esh.Vertices[5] =  new  BABYLON.Vector3(1, 1, -1);
esh.Vertices[6] =  new  BABYLON.Vector3(1, -1, -1);
esh.Vertices[7] =  new  BABYLON.Vector3(-1, -1, -1);
mesh.Faces[0] = { A:0, B:1, C:2 };
esh.Faces[1] = { A:1, B:2, C:3 };
esh.Faces[2] = { A:1, B:3, C:6 };
esh.Faces[3] = { A:1, B:5, C:6 };
esh.Faces[4] = { A:0, B:1, C:4 };
esh.Faces[5] = { A:1, B:4, C:5 };
mesh.Faces[6] = { A:2, B:3, C:7 };
esh.Faces[7] = { A:3, B:6, C:7 };
esh.Faces[8] = { A:0, B:2, C:7 };
esh.Faces[9] = { A:0, B:4, C:7 };
esh.Faces[10] = { A:4, B:5, C:6 };
esh.Faces[11] = { A:4, B:6, C:7 }; 

 var  mesh =  new  SoftEngine.Mesh( "Cube" , 8, 12);
eshes.push(mesh);
esh.Vertices[0] =  new  BABYLON.Vector3(-1, 1, 1);
esh.Vertices[1] =  new  BABYLON.Vector3(1, 1, 1);
esh.Vertices[2] =  new  BABYLON.Vector3(-1, -1, 1);
esh.Vertices[3] =  new  BABYLON.Vector3(1, -1, 1);
esh.Vertices[4] =  new  BABYLON.Vector3(-1, 1, -1);
esh.Vertices[5] =  new  BABYLON.Vector3(1, 1, -1);
esh.Vertices[6] =  new  BABYLON.Vector3(1, -1, -1);
esh.Vertices[7] =  new  BABYLON.Vector3(-1, -1, -1);
mesh.Faces[0] = { A:0, B:1, C:2 };
esh.Faces[1] = { A:1, B:2, C:3 };
esh.Faces[2] = { A:1, B:3, C:6 };
esh.Faces[3] = { A:1, B:5, C:6 };
esh.Faces[4] = { A:0, B:1, C:4 };
esh.Faces[5] = { A:1, B:4, C:5 };
mesh.Faces[6] = { A:2, B:3, C:7 };
esh.Faces[7] = { A:3, B:6, C:7 };
esh.Faces[8] = { A:0, B:2, C:7 };
esh.Faces[9] = { A:0, B:4, C:7 };
esh.Faces[10] = { A:4, B:5, C:6 };
esh.Faces[11] = { A:4, B:6, C:7 }; 

您现在应该拥有一个漂亮的旋转立方体:


恭喜! :)

使用Bresenham增强线条绘制算法

有一种使用布雷森汉姆(Bresenham)线算法绘制线的最佳方法。 它比我们当前的简单递归版本更快更清晰。 该算法的故事很有趣。 请阅读Wikipedia对该算法的定义,以了解Bresenham如何构建该算法以及其原因。

以下是此算法在C#,TypeScript和JavaScript中的版本:

 public void  DrawBline( Vector2  point0,  Vector2  point1)
    int  x0 = ( int )point0.X;
    int  y0 = ( int )point0.Y;
    int  x1 = ( int )point1.X;
    int  y1 = ( int )point1.Y;
           
    var  dx =  Math .Abs(x1 - x0);
    var  dy =  Math .Abs(y1 - y0);
    var  sx = (x0 < x1) ? 1 : -1;
    var  sy = (y0 < y1) ? 1 : -1;
    var  err = dx - dy;
     while  ( true ) {
       DrawPoint( new  Vector2 (x0, y0));
         if  ((x0 == x1) && (y0 == y1))  break ;
        var  e2 = 2 * err;
        if  (e2 > -dy) { err -= dy; x0 += sx; }
        if  (e2 < dx) { err += dx; y0 += sy; }
   }
 

 public  drawBline(point0: BABYLON.Vector2, point1: BABYLON.Vector2):  void  {
    var  x0 = point0.x >> 0;
    var  y0 = point0.y >> 0;
    var  x1 = point1.x >> 0;
    var  y1 = point1.y >> 0;
    var  dx = Math.abs(x1 - x0);
    var  dy = Math.abs(y1 - y0);
    var  sx = (x0 < x1) ? 1 : -1;
    var  sy = (y0 < y1) ? 1 : -1;
    var  err = dx - dy;
     while  ( true ) {
        this .drawPoint( new  BABYLON.Vector2(x0, y0));
         if  ((x0 == x1) && (y0 == y1))  break ;
        var  e2 = 2 * err;
        if  (e2 > -dy) { err -= dy; x0 += sx; }
        if  (e2 < dx) { err += dx; y0 += sy; }
   }
 

 Device.prototype.drawBline =  function  (point0, point1) {
    var  x0 = point0.x >> 0;
    var  y0 = point0.y >> 0;
    var  x1 = point1.x >> 0;
    var  y1 = point1.y >> 0;
    var  dx = Math.abs(x1 - x0);
    var  dy = Math.abs(y1 - y0);
    var  sx = (x0 < x1) ? 1 : -1;
    var  sy = (y0 < y1) ? 1 : -1;
    var  err = dx - dy;
    while ( true ) {
        this .drawPoint( new  BABYLON.Vector2(x0, y0));
        if ((x0 == x1) && (y0 == y1))  break ;
        var  e2 = 2 * err;
        if (e2 > -dy) { err -= dy; x0 += sx; }
        if (e2 < dx) { err += dx; y0 += sy; }
   }
; 

在render函数中,将调用do DrawLine替换为DrawBline,您应该注意到这更加流畅并且更加清晰:

如果您注意观察它,应该看到使用Bresenham的该版本比我们的第一个算法更不连贯。

同样,您可以下载包含源代码的解决方案

C#SoftEngineCSharpPart2.zip

TypeScriptSoftEngineTSPart2.zip

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

在下一个教程中,您将学习如何从Blender (一种免费的3D建模工具)中将某些网格导出为JSON格式。 然后,我们将加载此JSON文件,以使用线框引擎显示该文件 。 确实,我们已经进行了所有设置,以显示更复杂的网格,如下所示:

image

第三部分见。

最初发布: http : //blogs.msdn.com/b/davrous/archive/2013/06/14/tutorial-part-2-learning-how-to-write-a-3d-soft-engine-from-scratch在C-TS或JS绘图线-amp-triangles.aspx中 经作者许可在此处转载。

From: https://www.sitepoint.com/write-3d-soft-engine-scratch-part-2/

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值