2012-01-31 296 views
8

您能否提出一种算法,只使用基本的plot(x,y,z)基元(可绘制单个体素)在3D空间中绘制球体?使用3D像素绘制球体(体素)

我希望得到类似于Bresenham's circle algorithm的东西,但是对于3D而不是2D。我正在研究一个使用LED三维矩阵的低分辨率3D显示器的硬件项目,所以我需要绘制一个球体,而不仅仅是一个2D投影(即圆形)。

的项目非常类似:

3D LED cube

...或者看到它在行动here

一种可能性我心目中是这样的:

  • 计算极点(给出的半径)的Y坐标(在原点为中心的球体,这些将是-r+r
  • 切割球体:对于每个水平面,在这些坐标之间,计算通过使所述平面与球体相交而获得的圆的半径=>r
  • 使用布氏算法绘制半径r的实际圈上平面p

FWIW,我使用的是.NET micro-framework microprocessor,所以编程是C#,但我不需要C#中的答案。

+0

通过LED球体判断,我们是否应该假定您需要绘制球体的内部以及以某种方式? – NominSim 2012-01-31 17:43:28

+0

@NominSim我不这么认为。在这种情况下,他不会谈论Bresenham的圆光栅化,只能使用japreiss的蛮力解决方案。 – 2012-01-31 18:09:55

+0

@Christian其实,我想有两个选择。 – 2012-01-31 20:14:28

回答

7

简单的蛮力方法是循环网格中的每个体素并计算其与球体中心的距离。如果体素的距离小于球体半径,则着色体素。通过消除平方根并将点积与半径平方进行比较,可以节省大量指令。

很不理想,当然。但是在如图所示的8x8x8网格中,您需要每个球体执行512次这样的操作。如果球体中心位于网格上,并且其半径是整数,则只需要整数数学运算。点积是3乘法和2加法。乘法很慢;假设他们每个都需要4条指令。另外你需要一个比较。加载和存储,假设每个体素需要20条指令。这是每个球体10240条指令。

以16 MHz运行的Arduino可以每秒推送1562个球体。除非你正在做其他的数学和I/O,否则这个算法应该足够好。

+0

那么,问题是他似乎并不想要一个坚实的球体。在这种情况下,您会遇到经典的光栅化问题,以防止球体表面出现空洞和隆起。尽管他仍然可以使用你的解决方案,并通过检查他们的6邻居来第二次移除所有内部体素。 – 2012-01-31 18:08:30

+0

哦,对,因为对象是隐式定义的,所以不需要第二遍去除内部体素。但是在这种情况下,您必须平均每个体素做1次以上的距离计算。 – 2012-01-31 18:18:50

+0

是的,起初我认为它被填充在演示视频中,但仔细观察后,它看起来像只有壳。我同意你的第一条评论,做一个形态操作将是最简单的。 – japreiss 2012-01-31 19:10:25

4

假设你已经有一个绘图功能像你说:

public static void DrawSphere(double r, int lats, int longs) 
    { 
     int i, j; 
     for (i = 0; i <= lats; i++) 
     { 
      double lat0 = Math.PI * (-0.5 + (double)(i - 1)/lats); 
      double z0 = Math.Sin(lat0) * r; 
      double zr0 = Math.Cos(lat0) * r; 

      double lat1 = Math.PI * (-0.5 + (double)i/lats); 
      double z1 = Math.Sin(lat1) * r; 
      double zr1 = Math.Cos(lat1) * r; 

      for (j = 0; j <= longs; j++) 
      { 
       double lng = 2 * Math.PI * (double)(j - 1)/longs; 
       double x = Math.Cos(lng); 
       double y = Math.Sin(lng); 

       plot(x * zr0, y * zr0, z0); 
       plot(x * zr1, y * zr1, z1); 
      } 
     } 
    } 

这个功能应该可以用指定的纬度和经度分辨率原点绘制球体(由立方体判断,你可能想要的东西,大约40或50作为粗略猜测)。这个算法并没有“填充”球体,所以它只会提供一个轮廓,但是使用半径时应该让你填充内部,可能会减少分辨率和长度。

+1

你不需要在剧情调用中乘以r坐标吗?因为它是未使用的,这意味着它将始终绘制半径为1的球体。 – 2013-03-22 08:57:47

1

刚刚发现一个老q &一个有关生成球网,但顶部其实答案给你一段简短的伪代码来生成你的X,Y和Z:

(x, y, z) = (sin(Pi * m/M) cos(2Pi * n/N), sin(Pi * m/M) sin(2Pi * n/N), cos(Pi * m/M))

检查这种问答&一种细节:) procedurally generate a sphere mesh

+1

这不就是NominSim的解决方案吗? – 2012-01-31 18:42:43

3

我不相信运行的每个层上的中点画圆算法,一旦你达到了极点会得到期望的结果,因为你将不得不在表面差距在哪里LED未发光的。这可能会给你想要的结果,但是,这将取决于美学。这篇文章是基于使用中点圆算法来确定通过中间两个垂直八分圆的图层的半径,然后绘制每个圆时也设置极点八分的点。

我认为基于@Nick Udall的评论和回答here使用圆算法来确定您的水平切片的半径将与我在他的回答评论中提出的修改一起使用。应该修改圆算法以将初始误差作为输入,并且还为极点八分区绘制附加点。

  • y0 + y1y0 - y1绘制标准圆算法点:x0 +/- x, z0 +/- z, y0 +/- y1x0 +/- z, z0 +/- x, y0 +/- y1,总16分。这形成了球体垂直的大部分。
  • 另外画出点x0 +/- y1, z0 +/- x, y0 +/- zx0 +/- x, z0 +/- y1, y0 +/- z,总共16点,这将形成球体的极顶。

通过将外部算法的误差传递给圆形算法,它将允许每个图层圆的子体素调整。如果不将误差传递到内部算法中,则圆的赤道将近似为圆柱体,并且x,y和z轴上的每个近似球面将形成一个正方形。在包含错误的情况下,给定足够大半径的每个面将近似为实心圆。


以下代码从维基百科的Midpoint circle algorithm修改而来。 DrawCircle算法的命名法更改为在xz平面中操作,第三个初始点y0的添加,y偏移y1和初始错误error0DrawSphere从相同功能的修改,以第三初始点y0并调用DrawCircle而非DrawPixel

public static void DrawCircle(int x0, int y0, int z0, int y1, int radius, int error0) 
{ 
    int x = radius, z = 0; 
    int radiusError = error0; // Initial error state passed in, NOT 1-x 

    while(x >= z) 
    { 
    // draw the 32 points here. 
    z++; 
    if(radiusError<0) 
    { 
     radiusError+=2*z+1; 
    } 
    else 
    { 
     x--; 
     radiusError+=2*(z-x+1); 
    } 
    } 
} 

public static void DrawSphere(int x0, int y0, int z0, int radius) 
{ 
    int x = radius, y = 0; 
    int radiusError = 1-x; 

    while(x >= y) 
    { 
    // pass in base point (x0,y0,z0), this algorithm's y as y1, 
    // this algorithm's x as the radius, and pass along radius error. 
    DrawCircle(x0, y0, z0, y, x, radiusError); 
    y++; 
    if(radiusError<0) 
    { 
     radiusError+=2*y+1; 
    } 
    else 
    { 
     x--; 
     radiusError+=2*(y-x+1); 
    } 
    } 
} 

对于半径4(其实际上需要9x9x9)的球体,这将运行的三次迭代DrawCircle例程,第一次绘制一个典型的半径4个圆(三个步骤),第二个绘制一个初始误差为0(也是三个步骤)的半径为4的圆,然后第三个绘制具有初始误差0的半径3个圆三个步骤)。最终得到9个计算点,每个点绘制32个像素。 这使得32(每圈点数)×3(每点添加或减少操作)+6(每次迭代增加,减少,移位操作)= 102每个计算点的加,减或移位操作。在这个例子中,每个圆圈3点=每层306个操作。半径算法每层增加6个操作并迭代3次,因此306 + 6 * 3 = 936对于半径为4的示例的基本算术运算。 这里的成本是,您将重复设置某些像素而无需附加条件检查(即x = 0,y = 0或z = 0),所以如果你的I/O速度很慢,你可能会更适合添加条件检查。假设所有LED在开始时都被清除,示例圆将设置288个LED,而由于重复设置,实际上会点亮的LED更少。

看起来像这样会比适用于8x8x8网格的所有球体的bruteforce方法执行得更好,但是bruteforce方法会有一致的计时而不管半径如何,而当绘制大半径球体时,该方法会减慢只会显示一部分。然而,随着显示器立方体分辨率的提高,此算法时序将保持一致,而强力将增加。