【问题标题】:Drawing 3D sphere in C/C++在 C/C++ 中绘制 3D 球体
【发布时间】:2014-09-23 07:31:50
【问题描述】:

我正在寻找一种可以在小分辨率上绘制漂亮的 3D 球体的算法。我找到了Bresenham's circle algorithm,但它是用于 2D 绘图的。我只需要球体边框(我不需要填充)。我也用谷歌搜索了这个问题的解决方案,但我没有找到任何东西。 This 文章没有帮助(什么是蛮力算法?)。我不能使用任何 OpenGL 库,我需要 vanilla C/C++ 解决方案。提前谢谢你。

【问题讨论】:

  • 恐怕你所说的问题没有解决办法。 C/C++ 不定义标准图形库。我也有点惊讶你还没有决定使用哪种语言。
  • 画圆有什么问题? 3D 球体的边界是 cricle。
  • @DavidHeffernan 我用 C 编码,但两种语言之间没有太大区别,所以即使有人用 C++ 显示代码,用 C 重写也不难。也接受其他语言,我想知道这个想法!
  • 你似乎仍然没有承认你所问的不可能
  • 请不要再嘲笑这个家伙说他正在寻找一个独立于图书馆的解决方案。显然,他的意思是,他正在寻找一种方法来生成描绘球体的体素数组,而无需使用库,因此他可以概念化该过程,然后使用标准的 openGL 库自己实现它。所以,请不要仅仅因为它的创建者没有完美地表达他的想法而对这个有效的帖子投反对票。

标签: c graphics voxel


【解决方案1】:

如果我猜对了,你想渲染球体的所有表面体素

蛮力是O(R^3)。如果您只是从平面投影光线并计算第 3 个坐标,那么您会得到 O(R^2) 但要确保没有 Voxels 丢失,您必须从所有 3 个平面进行此投影,这仍然是 @ 987654324@

看起来像这样:

在 LED 立方体 16x16x16 模拟。现在算法:

  1. 计算可见边界框

    不需要只渲染整个渲染空间,所以中心 +/- 半径...

  2. 取一个平面(例如XY)

    从边界框内的所有x,y点投射光线,该边界框只有2个for循环,并通过球体方程计算光线击中的z坐标:

    (x-x0)^2 + (y-y0)^2 + (z-z0)^2 = R^2
    

    所以

    z=z0 +/- sqrt(R^2 - (x-x0)^2 - (y-y0)^2)
    

    并渲染两个体素int sqrt(int x) 用于有限尺寸(如 LED 立方体/屏幕或体素空间)可以通过 LUT 查找表来加快处理速度。

  3. 对所有平面执行第 2 步 (xy,yz,xz)

C++ 中的代码如下所示:

//---------------------------------------------------------------------------
//--- LED cube class ver: 1.00 ----------------------------------------------
//---------------------------------------------------------------------------
#ifndef _LED_cube_h
#define _LED_cube_h
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
const int _LED_cube_size=16;
//---------------------------------------------------------------------------
class LED_cube
    {
public:
    int n,map[_LED_cube_size][_LED_cube_size][_LED_cube_size];

    LED_cube()              { n=_LED_cube_size; }
    LED_cube(LED_cube& a)   { *this=a; }
    ~LED_cube()             { }
    LED_cube* operator = (const LED_cube *a) { *this=*a; return this; }
    //LED_cube* operator = (const LED_cube &a) { /*...copy...*/ return this; }
    void cls(int col);                                  // clear cube with col 0x00BBGGRR
    void sphere(int x0,int y0,int z0,int r,int col);    // draws sphere surface with col 0x00BBGGRR
    void glDraw();                                      // render cube by OpenGL as 1x1x1 cube at 0,0,0
    };
//---------------------------------------------------------------------------
void LED_cube::cls(int col)
    {
    int x,y,z;
    for (x=0;x<n;x++)
     for (y=0;y<n;y++)
      for (z=0;z<n;z++)
       map[x][y][z]=col;
    }
//---------------------------------------------------------------------------
void LED_cube::sphere(int x0,int y0,int z0,int r,int col)
    {
    int x,y,z,xa,ya,za,xb,yb,zb,xr,yr,zr,xx,yy,zz,rr=r*r;
    // bounding box
    xa=x0-r; if (xa<0) xa=0; xb=x0+r; if (xb>n) xb=n;
    ya=y0-r; if (ya<0) ya=0; yb=y0+r; if (yb>n) yb=n;
    za=z0-r; if (za<0) za=0; zb=z0+r; if (zb>n) zb=n;
    // project xy plane
    for (x=xa,xr=x-x0,xx=xr*xr;x<xb;x++,xr++,xx=xr*xr)
     for (y=ya,yr=y-y0,yy=yr*yr;y<yb;y++,yr++,yy=yr*yr)
        {
        zz=rr-xx-yy; if (zz<0) continue; zr=sqrt(zz);
        z=z0-zr; if ((z>0)&&(z<n)) map[x][y][z]=col;
        z=z0+zr; if ((z>0)&&(z<n)) map[x][y][z]=col;
        }
    // project xz plane
    for (x=xa,xr=x-x0,xx=xr*xr;x<xb;x++,xr++,xx=xr*xr)
     for (z=za,zr=z-z0,zz=zr*zr;z<zb;z++,zr++,zz=zr*zr)
        {
        yy=rr-xx-zz; if (yy<0) continue; yr=sqrt(yy);
        y=y0-yr; if ((y>0)&&(y<n)) map[x][y][z]=col;
        y=y0+yr; if ((y>0)&&(y<n)) map[x][y][z]=col;
        }
    // project yz plane
    for (y=ya,yr=y-y0,yy=yr*yr;y<yb;y++,yr++,yy=yr*yr)
     for (z=za,zr=z-z0,zz=zr*zr;z<zb;z++,zr++,zz=zr*zr)
        {
        xx=rr-zz-yy; if (xx<0) continue; xr=sqrt(xx);
        x=x0-xr; if ((x>0)&&(x<n)) map[x][y][z]=col;
        x=x0+xr; if ((x>0)&&(x<n)) map[x][y][z]=col;
        }
    }
//---------------------------------------------------------------------------
void LED_cube::glDraw()
    {
    #ifdef __gl_h_
    int x,y,z;
    float p[3],dp=1.0/float(n-1);
    glEnable(GL_BLEND);
    glBlendFunc(GL_ONE,GL_ONE);

    glPointSize(2.0);

    glBegin(GL_POINTS);

    for (p[0]=-0.5,x=0;x<n;x++,p[0]+=dp)
     for (p[1]=-0.5,y=0;y<n;y++,p[1]+=dp)
      for (p[2]=-0.5,z=0;z<n;z++,p[2]+=dp)
        {
        glColor4ubv((BYTE*)(&map[x][y][z]));
        glVertex3fv(p);
        }
    glEnd();
    glDisable(GL_BLEND);
    glPointSize(1.0);
    #endif
    }
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
#endif
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------

类用法:

LED_cube cube;
cube.cls(0x00202020); // clear space to dark gray color
int a=cube.n>>1;      // just place sphere to middle and size almost the whole space
int r=a-3;
cube.sphere(a,a,a,r,0x00FFFFFF);
cube.glDraw();        // just for mine visualization you have to rewrite it to your rendering system

如果您只想使用 C,则将类分解为仅全局函数和变量,并将 C++ 运算符 x++,--,+=,-=,*=,... 转换为 C 样式x=x+1,...

【讨论】:

    【解决方案2】:

    根据链接,您似乎对 球体的体素算法 更感兴趣,而不是 图形 本身;说类似this page 的帮助。你不想要一个球,只想要一个表面。

    Midpoint circle algorithm 可用于绘制 3D 体素球体:将球体视为一堆切片,每个切片包含一个圆。

    实际上,您使用两个嵌套的中点圆,外圆定义内圆的半径。 (虽然一个简单的算法在彼此之上绘制圆可能会在体素上留下空洞,但中点圆算法利用了对称性,如果正确实施,就不会出现这样的空洞。)

    您串联构建六个帽子,就像将一个立方体雕刻成一个球体。由于每个帽的表面斜率总是小于 1,所以在帽上向外移动最多会使每个坐标改变 1,因此不会出现孔。

    这种方法的问题在于复杂性:您计算的每个点可能会影响多达 48 个体素单元。 (在每个上限处,每个点都在一个八分圆内计算,因此会影响八个单元格。有六个上限,6*8=48。)

    我建议一种不同的方法。

    x0, y0为中心的r-半径球面的方程sub>,z0,是

    (x - x0)2 + (y - y0)2 + (z - z0)2 = r2

    使用整数坐标,网格点很少恰好在球面上,允许取值范围:

    RRMIN ≤ (x - x0) 2 + (y - y0)2 + (z - z0)2RRMAX

    其中 RRMIN 和 RRMAX 是常量;具体来说,就是与球心平方的最小和最大距离。

    我建议在一般情况下使用双坐标。这使您可以选择球的中心是位于某个坐标的中心(表示奇数直径),还是位于两个相邻坐标之间(表示偶数直径)。

    如果你有一个SIZE×SIZE×SIZE 体素网格(灯光、积木等),那么

    int sphere_surface(const char x, const char y, const char z,
                       const char size, const int rrmin, const int rrmax)
    {
        const int center = size - (size & 1); /* Size rounded down to even integer */
        const int dx = center - x - x,
                  dy = center - y - y,
                  dz = center - z - z;        /* Doubled coordinates */
        const int rr = dx*dx + dy*dy + dz*dz; /* Distance squared */
        return (rrmin <= rr) && (rr <= rrmax);
    }
    

    如果点 (x,y,z) 位于以立方体为中心的球体的表面区域内,则返回 1。 (从技术上讲,如果从该点到size 大小的立方体中心的距离在sqrt(rrmin)/2sqrt(rrmax)/2 之内,则返回。)

    rrminrrmax 的“正确”值高度依赖于上下文。 rrmax 通常在 size*size 附近(请记住,该函数使用双倍坐标),rrmin 稍少。

    例如,如果你有一个 3×3×3 的网格,你只希望每个面上的六个中心单元满足条件;你可以用size=3rrmin=1rrmax=4来做到这一点:

    如果你有一个 4×4×4 的网格,你希望每个面上的四个中心单元满足条件(因此总共 64 个单元中的 24 个被认为在球面上);你可以用size=4rrmin=11rrmax=11来做到这一点:

    使用 5×5×5 的网格,我们得到了上述算法的有趣副作用。

    size=5rrmin=8rrmax=16 产生一个非常“棱角分明”的球体,几乎是一个立在角落的立方体:

    size=5rrmin=12rrmax=20 产生我最喜欢的近似球体:

    size=5rrmin=16rrmax=24 生成一个圆角立方体(每个面都是一个 3×3 平面):

    显然,使用rrmin=0 也包括所有内部单元格,产生一个球,而不仅仅是球体的表面。

    随着网格大小的增加,您可以表示的每个大小球体的变体越多。

    上述函数在微控制器上特别有用,因为您可以简单地循环遍历您的晶格,根据需要更新每个点的状态。此外,大多数微控制器的内存都很紧张,但具有非常快的(单时钟)加法、减法和乘法指令。 (虽然 16×16 位乘法与 32 位结果通常需要两条或更多条指令。)

    典型的微控制器没有 ROM/闪存功能来存储足够有趣的体素模式,只有少数具有通过 SPI 从 SD 卡获取 DMA 的能力(因此您可以从 microSD 卡加载体素模式的“帧” ),但是像上面这样的函数可以用很少的输入产生有趣的形状——尤其是你可以插值的输入。

    如果您的体素不仅是二进制的,而且例如PWM 控制的 LED。


    由于可视化上述内容通常有点困难,这里是我用来生成上述图像的快速小技巧。当给定 sizerrminrrmax 作为命令行参数时,它将 SVG 图像输出到标准输出,并将 ASCII 切片输出到标准错误。

    #include <stdlib.h>
    #include <string.h>
    #include <stdio.h>
    
    #define   BORDER  2
    #define   XC(x,y,z) ((x)*16 + (y)*12)
    #define   YC(x,y,z) ((x)*6 - (y)*8 - (z)*17)
    
    static int xt = 0;
    static int yt = 0;
    
    static void fcube(FILE *out, const int x, const int y, const int z, const int fill)
    {
        const int xc = xt + XC(x,y,z);
        const int yc = yt + YC(x,y,z);
        fprintf(out, "<path d=\"M%d,%dl16,6,12,-8,0,-17,-16,-6,-12,8z\" fill=\"#%06x\" stroke=\"#000\" />\n", xc, yc, fill & 0xFFFFFF);
        fprintf(out, "<path d=\"M%d,%dl16,6,12,-8m-12,8l0,17\" fill=\"none\" stroke=\"#000\" />\n", xc, yc-17);
    }
    
    static unsigned long rrmin = 0UL;
    static unsigned long rrmax = 0UL;
    static int           center = 0;
    
    static int surface(const int x, const int y, const int z)
    {
        /* Doubled coordinates: */
        const long dx = 2*x - center,
                   dy = 2*y - center,
                   dz = 2*z - center;
        const unsigned long rr = dx*dx + dy*dy + dz*dz;
    
        return (rrmin <= rr) && (rr <= rrmax);
    }
    
    int main(int argc, char *argv[])
    {
        int  width, height;
        int  size, x, y, z;
        char dummy;
    
        if (argc != 4 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
            fprintf(stderr, "\n");
            fprintf(stderr, "Usage: %s SIZE RRMIN RRMAX\n", argv[0]);
            fprintf(stderr, "Where\n");
            fprintf(stderr, "       SIZE    is the size of the voxel cube\n");
            fprintf(stderr, "       RRMIN   is the minimum distance squared, and\n");
            fprintf(stderr, "       RRMAX   is the maximum distance squared,\n");
            fprintf(stderr, "               using doubled coordinates.\n");
            fprintf(stderr, "\n");
            fprintf(stderr, "Examples:\n");
            fprintf(stderr, "       %s 3 1 4\n", argv[0]);
            fprintf(stderr, "       %s 4 11 11\n", argv[0]);
            fprintf(stderr, "       %s 5 8 16\n", argv[0]);
            fprintf(stderr, "       %s 5 12 20\n", argv[0]);
            fprintf(stderr, "       %s 5 16 24\n", argv[0]);
            fprintf(stderr, "\n");
            return EXIT_FAILURE;
        }
    
        if (sscanf(argv[1], " %d %c", &size, &dummy) != 1 || size < 3) {
            fprintf(stderr, "%s: Invalid size.\n", argv[1]);
            return EXIT_FAILURE;
        }
    
        if (sscanf(argv[2], " %lu %c", &rrmin, &dummy) != 1) {
            fprintf(stderr, "%s: Invalid rrmin.\n", argv[2]);
            return EXIT_FAILURE;
        }
    
        if (sscanf(argv[3], " %lu %c", &rrmax, &dummy) != 1 || rrmax < rrmin) {
            fprintf(stderr, "%s: Invalid rrmax.\n", argv[3]);
            return EXIT_FAILURE;
        }
    
        /* Calculate coordinate range. */
        {   int xmin = XC(0,0,0);
            int ymin = YC(0,0,0);
            int xmax = XC(0,0,0);
            int ymax = YC(0,0,0);
    
            for (z = 0; z <= size; z++)
                for (y = 0; y <= size; y++)
                    for (x = 0; x <= size; x++) {
                        const int xc = XC(x,y,z);
                        const int yc = YC(x,y,z);
                        if (xc < xmin) xmin = xc;
                        if (xc > xmax) xmax = xc;
                        if (yc < ymin) ymin = yc;
                        if (yc > ymax) ymax = yc;
                    } 
    
            xt = BORDER - xmin;
            width = xmax - xmin + 2*BORDER;
    
            yt = BORDER - ymin;
            height = ymax - ymin + 2*BORDER;
        }
    
        center = size - 1;
    
        /* SVG preamble. */
        printf("<?xml version=\"1.0\"?>\n");
        printf("<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 %d %d\">\n", width, height);
        printf("<path d=\"M%d,%dL%d,%d,%d,%d,%d,%d,%d,%d,%d,%dz\" fill=\"#f7f7f7\" stroke=\"#666666\"/>\n",
                xt+XC(   0,   0,   0), yt+YC(   0,   0,   0),
                xt+XC(size,   0,   0), yt+YC(size,   0,   0),
                xt+XC(size,size,   0), yt+YC(size,size,   0),
                xt+XC(size,size,size), yt+YC(size,size,size),
                xt+XC(0,   size,size), yt+YC(   0,size,size),
                xt+XC(0,      0,size), yt+YC(   0,   0,size));
        printf("<path d=\"M%d,%dL%d,%d,%d,%dM%d,%dL%d,%d\" fill=\"none\" stroke=\"#666666\"/>\n",
                xt+XC(   0,   0,   0), yt+YC(   0,   0,   0),
                xt+XC(   0,size,   0), yt+YC(   0,size,   0),
                xt+XC(size,size,   0), yt+YC(size,size,   0),
                xt+XC(   0,size,   0), yt+YC(   0,size,   0),
                xt+XC(   0,size,size), yt+YC(   0,size,size));
        for (z = 0; z < size; z++)
            for (y = size - 1; y >= 0; y--)
                for (x = 0; x < size; x++)
                    if (surface(x,y,z))
                        fcube(stdout, x, y, z, 0x00CCFF);
        printf("</svg>\n");
    
        for (z=0; z < size; z++) {
            for (y = 0; y < size; y++) {
                for (x = 0; x < size; x++)
                    fputc(surface(x,y,z) ? 'X' : '.', stderr);
                fputs("   ", stderr);
                for (x = 0; x < size; x++)
                    fputc(surface(x,z,y) ? 'X' : '.', stderr);
                fputs("   ", stderr);
                for (x = 0; x < size; x++)
                    fputc(surface(y,z,x) ? 'X' : '.', stderr);
                fputc('\n', stderr);
            }
            fputc('\n', stderr);
        }
    
        return EXIT_SUCCESS;
    }
    

    我没有费心去优化输出;你可以很容易地例如为每张脸选择不同的颜色,可能为背景平面添加阴影等等。

    上面的图片是用这个程序创建的,然后用GIMP转换成PNG,但我建议用你的浏览器在本地查看生成的文件。

    问题?

    【讨论】:

      【解决方案3】:

      这个领域最常用的库是openGL 这张幻灯片向您展示如何在 IDE 上配置库 从此处下载文件 http://www.xmission.com/~nate/glut.html 然后把它们放在这个路径中 glut32.dll -> C:\Windows\System32 glut32.lib -> C:\Program Files\Microsoft Visual Studio .NET\Vc7\PlatformSDK\lib glut.h -> C:\Program Files\Microsoft Visual Studio .NET\Vc7\PlatformSDK\Include\gl

      opengl-superbible-4th 这是一本神奇的教科书,从零开始到高级

      【讨论】:

      • -1 来自问题:我不能使用任何 OpenGL 库
      【解决方案4】:

      我不能使用任何 OpenGL 库,我需要 vanilla C/C++ 解决方案。

      这是否意味着根本没有图形库?在这种情况下,非常简单的答案是:你没有。 C 和 C++ 都没有原生的图形渲染能力。您需要使用外部库和驱动程序只是为了与操作系统的帧缓冲区和/或图形卡取得联系。

      但是,至于我的非图形相关解决方案,这取决于:

      我找到了 Bresenham 的圆算法,但它是用于 2D 绘图的。我只需要球体边框。

      这是否意味着您字面意思只需要球体的边框?因为在这种情况下,您应该只使用已有的 2d 绘图算法,因为它会立即为您提供边界。

      如果这意味着您想要球体的单个体素,它会稍微复杂一些,并且需要更多的数学运算,并且可能会导致软件渲染器在某种程度上被打脸性能,取决于您的球体有多少体素和单个顶点。

      我认为您想要得到的是游戏和物理引擎开发人员所称的“边界框”或“碰撞框”,有时也称为“碰撞框”。所需要的只是绘制一个包含整个球体的立方体(通常是线框),仅此而已(换句话说,您只需绘制一个与球体具有相同宽度、高度和深度的立方体,假设我们正在使用XYZ 维度)。

      【讨论】:

      • 我看不出,如果因为某人未能完美地表达他们的想法而对你产生任何影响,那么与他们对立是如何取得任何成果的。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-12-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-11
      相关资源
      最近更新 更多