我正在寻找一种算法,可以在小分辨率上绘制漂亮的3D球体.我找到了Bresenham的圆形算法,但是它用于2D绘图.我只需要球体边框(我不需要它填充).我也搜索了问题的解决方案,但我没有找到任何东西.这篇文章没有帮助(什么是强力算法?).我不能使用任何OpenGL库,我需要vanilla C/C++解决方案.先感谢您.
如果我做对了你想要渲染球体的所有表面体素
蛮力是O(R^3).如果你只是从平面投射光线并计算第3个坐标,那么你得到O(R^2)但是为了确保没有体素丢失你必须从所有3个平面做这个投影仍然是O(R^2)
它看起来像这样:

关于LED立方体16x16x16模拟.现在算法:
计算可见边界框
无需渲染整个渲染空间只是球体所以中心+/-半径...
拿一个平面(例如XY)
从x,y边界框内的所有点投射光线,只有2个用于循环,并z通过球面方程计算光线击中的坐标:
(x-x0)^2 + (y-y0)^2 + (z-z0)^2 = R^2
Run Code Online (Sandbox Code Playgroud)
所以
z=z0 +/- sqrt(R^2 - (x-x0)^2 - (y-y0)^2)
Run Code Online (Sandbox Code Playgroud)
并渲染两个体素.的int sqrt(int x)有限尺寸(如LED立方/屏幕或体元空间)可以通过完成LUT查找表以加快速度.
为所有飞机做第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
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
Run Code Online (Sandbox Code Playgroud)
课程用法:
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
Run Code Online (Sandbox Code Playgroud)
如果只想使用C,那么将class分解为全局函数和变量,并将C++运算符转换x++,--,+=,-=,*=,...为C样式x=x+1,...
根据链接,您似乎对 spheres 的体素算法而不是图形本身更感兴趣;说这样的话这个页面有帮助。你不想要一个球,而只想要表面。
中点圆算法可用于绘制 3D 体素球体:将球体视为切片的堆栈,每个切片包含一个圆。
在实践中,您使用两个嵌套的中点圆,外圈定义内圈的半径。(尽管在彼此之上绘制圆圈的幼稚算法可能会在体素中留下孔洞,但中点圆圈算法利用了对称性,如果正确实施,则不会出现此类孔洞。)
您串联构建六个帽,就像将立方体雕刻成球体一样。由于每个帽上的曲面坡度始终小于 1,因此在帽上向外移动最多会使每个坐标改变 1,因此不会出现孔洞。
这种方法的问题是复杂性:您计算的每个点可能影响多达 48 个体素单元。(在每个上限,每个点都在一个八分圆内计算,因此影响八个单元格。有六个上限,6*8=48。)
我建议采用不同的方法。
要的表面方程ř -radius球体中心位于X 0,ÿ 0,Ž 0,是
( x - x 0 ) 2 + ( y - y 0 ) 2 + ( z - z 0 ) 2 = r 2
使用整数坐标,网格点很少正好位于球体表面上,允许值范围:
RR MIN ≤ ( x - x 0 ) 2 + ( y - y 0 ) 2 + ( z - z 0 ) 2 ≤ RR MAX
其中 RR MIN和 RR MAX是常数;具体来说,最小和最大距离平方到球体中心。
我建议在一般情况下使用双坐标。这允许您选择球体的中心是以坐标为中心(意味着奇数直径),还是以两个相邻坐标为中心(意味着偶数直径)。
如果你有一个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);
}
Run Code Online (Sandbox Code Playgroud)
如果点 ( x, y, z) 位于以立方体为中心的球体的表面区域内,则返回 1 。(从技术上讲,如果从该点到size-size 立方体中心的距离在sqrt(rrmin)/2和之内,则返回sqrt(rrmax)/2。)
的“正确”的价值观rrmin和rrmax高度上下文相关的。rrmax通常在附近的某个地方size*size(请记住,该函数使用双坐标),rrmin稍微少一些。
例如,如果您有一个 3×3×3 的网格,您只希望每个面上的六个中心单元满足条件;你可以用size=3, rrmin=1,来做到这一点rrmax=4:

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

使用 5×5×5 网格,我们得到了上述算法的有趣副作用。
size=5, rrmin=8,rrmax=16产生一个非常“有角度”的球体,几乎是一个站在角落的立方体:

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

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

显然,使用也rrmin=0包括所有内部单元格,产生一个球,而不仅仅是球体的表面。
随着网格大小的增加,您可以表示的每个大小球体的变体越多。
上述函数在微控制器上特别有用,因为您可以简单地循环遍历您的格子,根据需要更新每个点的状态。此外,大多数微控制器内存不足,但具有非常快的(单时钟)加法、减法和乘法指令。(尽管 32 位结果的 16×16 位乘法通常需要两条或更多条指令。)
典型的微控制器没有 ROM/flash 功能来存储足够有趣的体素模式,只有少数具有通过 SPI 从 SD 卡读取的 DMA 功能(因此您可以从 microSD 卡加载体素模式的“帧”),但是像上面这样的函数可以用很少的输入产生有趣的形状——特别是你可以插入的输入。
上述功能也可以适用于近似antialising(通过比较rr到rrmin和rrmax),在情况下,你的体素不只是二元的,但例如PWM控制的LED。
由于可视化上述内容通常有点困难,这里是我用来生成上述图像的快速小技巧。当给定、 和作为命令行参数时size,它将 SVG 图像输出到标准输出,将 ASCII 切片输出到标准错误。rrminrrmax
#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;
}
Run Code Online (Sandbox Code Playgroud)
我没有费心去优化输出;你可以很容易地例如为每张脸选择不同的颜色,或者为背景平面添加阴影等等。
上面的图像是用这个程序创建的,然后使用 GIMP 转换为 PNG,但我建议使用您的浏览器在本地查看生成的文件。
问题?
小智 -2
该领域最常用的库是 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 这是一本令人惊叹的教科书,从头开始到高级水平