【问题标题】:How to Rasterize a Sphere如何栅格化球体
【发布时间】:2017-01-14 23:08:11
【问题描述】:

所以,我正在尝试创建一个外部有“块”的球体,有点像在 Minecraft 中构建的。 (我不知道圈外的术语是什么)。问题是,我不知道如何让像中点圆算法这样的方程适用于球体。最好在 lua 或 java 中,这样我可以更轻松地阅读任何答案。而且我不想知道如何用 trig 计算球体上的一个点,我已经知道该怎么做了。

【问题讨论】:

  • Drawing 3D sphere in C/C++,只是像立方体一样渲染体素,而不是点/像素。
  • 这最终会让我为我做数千次迭代检查某个点是否已经不在已经绘制的立方体内。这对我的口味来说太低效了。
  • 我不认为效率低下是一个太大的问题......你得到了PI/4 = ~0.78 效率意味着只有大约 22% 的迭代会浪费每个像素单个 if 和更好的简单迭代然后是中点,甚至布雷森纳姆
  • 想象某种编码语言变得非常缓慢。如果是这样的话,那么它会运行,让我们在一个小时内运行你的。但是,如果您要使用已接受的答案消除一些低效率,则它将在大约 43 分钟后完成。 17分钟和真实的速度代码相比只是很小的差别,但还是有差别的。
  • 如果你想要速度,那么你必须在没有任何优化的情况下利用你的硬件/软件架构(除了喜欢复杂性,有时甚至没有)毫无意义。在一个平台上运行得更快的东西在不同平台上可能会很慢。

标签: math


【解决方案1】:

我认为最简单的方法就是将中点圆算法扩展到 3D。

首先,让我们弄清楚我们要填充哪些块。假设原点在块(0,0,0) 的中间,半径为R

  1. 我们只想填充球体内的方框。这些正是(x,y,z) 的框,例如x²+y²+z² <= R²;和
  2. 我们只想填充显示面孔的框。如果一个盒子有一张脸,那么它的至少一个邻居在球体中,所以:(|x|+1)²+y²+z² > R² OR x²+(|y|+1)²+z² > R² OR x²+y²+(|z|+1)² > R²

第二部分让事情变得棘手,但请记住(|a|+1)² = |a|² + 2|a| + 1。例如,如果z 是球体内的一个盒子的最大 坐标,并且如果该盒子有一个面显示,那么z 面将特别显示,因为@ 987654333@,这将至少与xy 的类似值一样大。

因此,很容易计算 1) 球内的框,2) 以 z 作为它们的最大坐标,3) 具有最大可能的 z 值,即,将 1 添加到 z 会导致球体外的盒子。此外,4) 对所有 x,y,z 具有正值。

这些盒子的坐标然后可以用 24 种不同的方式反射,以生成球体表面上的所有盒子。这些是坐标符号的所有 8 个组合乘以所有 3 个选择,哪个轴具有最大坐标。

下面是如何生成具有正数的x,y,zz 最大的点:

maxR2 = floor(R*R);
zx = floor(R);
for (x=0; ;++x)
{
    //max z for this x value.
    while(x*x+zx*zx > maxR2 && zx>=x)
        --zx;
    if (zx<x)
        break; //with this x, z can't be largest

    z=zx;
    for(y=0; ;++y)
    {
        while(x*x+y*y+z*z > maxR2 && z>=x && z>=y)
            --z;
        if (z<x||z<y)
            break; //with this x and y, z can't be largest
        FILLBOX(x,y,z); //... and up to 23 reflections of it
    }
}

注意:如果对您很重要,请在计算反射时小心,以免绘制,例如 (0,y,z) (-0,y,z),因为这是同一个框两次.也不要交换具有相同值的轴,因为那样会再次绘制同一个框两次(例如,如果您有 (1,5,5),请不要交换 yz 并再次绘制。

还要注意R 不必是整数。加0.5会更好看。

这是一个将以上所有因素都考虑在内的示例(您需要一个支持 webgl 的浏览器)https://jsfiddle.net/mtimmerm/ay2adpwb/

【讨论】:

  • @Bedrockbreaker,查看链接的 jsfiddle 中的反射代码——结果非常酷。
  • 嘿,那里!我想知道你能否帮助我理解第 5 行和第 6 行:“while(xx+zxzx > maxR2 && zx>=x) --zx;”我不太明白 while 条件中的数学表达式是如何从您在解释中描述的数学推导出来的。
  • @retrovius 当我们开始外循环的迭代时,zx 保证 >= 正确的值,所以在第 5 行和第 6 行我们只需要担心减少正确的数量。第一个条件 xx+zxzx > maxR2 表示点 (x,0,zx) 在球体之外。如果第 7 行和第 8 行的测试要停止整个算法,则第二个条件会提前中止循环。
  • 谢谢!这很有帮助!
  • 嘿呃,我extended your example。如果你把它推得太远(和你的眼睛)可能会伤害你的电脑,因为算法是 O(n^2) 来近似表面积。
【解决方案2】:

您可以在嵌套循环中使用Midpoint Circle AlgorithmBresenham's Circle Algorithm。外循环确定圆在距原点不同z 距离处的整数值半径,而内循环计算圆的xy 元素,该圆包括垂直于Z 轴位置的球体横截面z.

【讨论】:

  • 这会在 |z| 的较大值处留下间隙其中 x-y 平面中的半径可以跳跃超过 1。
  • Bresenham 算法的死链接。简洁的代码骨架可用here
【解决方案3】:

这是用我自己的 MiniBasic 版本编写的。

10  REM Shaded textured sphere 
20 INPUT width 
30 INPUT height 
40 INPUT bwidth 
50 INPUT bheight 
60 REM Sphere radius 
70 LET r = 100 
80 REM light direction 
90 LET lx = 0 
100 LET ly = 0.2 
110 LET lz = -1 
120 LET ambient = 0.1 
130 REM camera z position 
140 LET cz = -256 
150 REM Sphere colour 
160 LET red = 255 
170 LET green =0 
180 LET blue = 100 
190 REM Normalize light  
200 LET len = SQRT(lx*lx+ly*ly+lz*lz) 
210 LET lx = -lx/len 
220 LET ly = -ly/len 
230 LET lz = -lz/len 
240 FOR i = 0 TO height -1 
250 FOR ii = 0 TO width -1 
260 LET x = ii - width /2 
270 LET y = i - height/2 
280 LET dpx = x 
290 LET dpy = y 
300 LET dpz = -cz 
310 LET a = dpx*dpx + dpy*dpy + dpz*dpz 
320 LET b = 2 * ( dpz  * cz)  
330 LET c = cz * cz 
340 LET c = c - r * r 
350 LET bb4ac = b * b - 4 * a * c 
360 IF ABS(a) < 0.001 OR bb4ac < 0 THEN 560 
370 LET mu1 = (-b + SQRT(bb4ac)) / (2 * a) 
380 LET mu2 = (-b - SQRT(bb4ac)) / (2 * a) 
390 LET spx = mu1 * x 400 LET spy = mu1 * y 
410 LET spz = mu1 * 256 - 256 
420 LET nx = spx / r 
430 LET ny = spy / r 
440 LET nz = spz / r 
450 LET costh = nx*lx + ny *ly + nz*lz 
460 IF costh > 0 THEN 480 
470 LET costh = 0 
480 LET lum = ambient + (1 - ambient) * costh 
490 LET v = ACOS(nz)/PI 495 LET nx = nx  * 0.999 
510 LET u = ACOS(nx * SIN(PI * v)) / PI   
535 LET v = -ACOS(ny)/PI + 1.0  
536 LET u = 1 - u 
540 GETPIXELB u * bwidth, v * bheight, red, green, blue 
550 SETPIXEL ii, i,  red * lum,  green *lum,  blue *lum 
560 NEXT ii 
570 NEXT i 

http://www.malcolmmclean.site11.com/www/UserPrograms.html#texsphere

【讨论】:

    【解决方案4】:

    这是一个使用 Matt Timmermans 示例的 python 示例。如果您有多个球体,则使用 numba jits 函数和并行 prange 会非常快。为任何起始坐标添加了体素。网格只是在 3d 网格中球体素所在的位置放置 1。还添加了一个可选的填充步骤。注意:这仅适用于非负坐标,因此对于使用体素坐标的所有体素,球体需要位于正空间中。

    #/usr/bin/env python
    import numpy as np
    import matplotlib.pyplot as plt
    from mpl_toolkits import mplot3d
    from numba.experimental import jitclass
    from numba import types, njit, prange
    
    
    spec = [
        ('radius', types.float64),
        ('svoxel', types.int64[:]),
        ('grid', types.b1[:,:,:])
    ]
    
    @jitclass(spec)
    class RasterizeSphere(object):
        def __init__(self, svoxel, radius, grid):
            self.grid = grid
            self.svoxel = svoxel
            self.radius = radius
            R2 = np.floor(self.radius**2)
            zx = np.int64(np.floor(self.radius))
            x = 0
            while True:
                while x**2 + zx**2 > R2 and zx >= x:
                    zx -= 1
                if zx < x:
                    break
                z = zx
                y = 0
                while True:
                    while x**2 + y**2 + z**2 > R2 and z >= x and z >= y:
                        z -= 1
                    if z < x or z < y:
                        break
                    self.fill_all(x, y, z)
                    ###### Optional fills the inside of sphere as well. #######
                    for nz in range(z):
                        self.fill_all(x, y, nz)
                    y += 1
                x += 1
    
        def fill_signs(self, x, y, z):
            self.grid[x + self.svoxel[0], y + self.svoxel[1], z + self.svoxel[2]] = True
            while True:
                z = -z
                if z >= 0:
                    y = -y
                    if y >= 0:
                        x = -x
                        if x >= 0:
                            break
                self.grid[x + self.svoxel[0], y + self.svoxel[1], z + self.svoxel[2]] = True
    
        def fill_all(self, x, y, z):
            self.fill_signs(x, y, z)
            if z > y:
                self.fill_signs(x, z, y)
            if z > x and z > y:
                self.fill_signs(z, y, x)
    
    @njit(parallel=True, cache=True)
    def parallel_spheres(grid):
        # prange just to show the insane speedup for large number of spheres disable visualization below if using large amount of prange.
        for i in prange(2):
            radius = 4.5
            svoxel = np.array([5, 5, 5])
            max = np.int64(np.ceil(radius**2))
            rs = RasterizeSphere(svoxel, radius, grid)
            points = np.where(rs.grid)
            return np.array([*points])
    
    
    def main():
        # Make max large enough to hold the spheres.
        max = 100
        points = parallel_spheres(np.zeros((max, max, max), dtype=bool))
        fig = plt.figure()
        ax = plt.axes(projection='3d')
        ax.scatter3D(*points)
        plt.show()
    
    
    if __name__ == '__main__':
        main()
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-10-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-08-28
      • 2016-12-12
      相关资源
      最近更新 更多