序言:
这是将 3D 三角形光栅化涵盖到体素网格中的问答,我被要求解决与 material erosion/removal 相关的不同问题。在制造过程模拟中。这个问题背后的主要思想是如何移植基于扫描线的 2D 三角形光栅化,如 this转化为 3D 体素。
所以问题是如何有效地将3D三角形光栅化为均匀体素网格表示为3D数组,体素表示为浮点值(密度或颜色(如果您愿意)。
使用C++,无需任何额外的库来进行光栅化本身。
最佳答案
所以首先更新算法:
定义
让三角形由其 3 个
int
3D 顶点和一个float
颜色给出:void triangle(int x0,int y0,int z0,int x1,int y1,int z1,int x2,int y2,int z2,float c);
目标体素网格是这样的 3D 数组:
float map[vxs][vys][vzs];
其中
vxs,vys,vzs
是网格的分辨率。扫描线算法需要左右缓冲区,但是在 3D 中我们不能使用单轴,所以我选择了主轴(具有输入三角形 AABB BBOX 最大边的轴),因此缓冲区大小vsz
应该为 3 个分辨率中的最大值。由于它只是一维数组,因此内存消耗不大,所以为了方便起见,我选择了它:const int vsz=vxs|vys|vzs; int lx[vsz],ly[vsz],lz[vsz]; // left buffers for filling int rx[vsz],ry[vsz],rz[vsz]; // right buffers for filling
计算 AABB 并选择主轴
int X0,Y0,Z0,X1,Y1,Z1,dx,dy,dz; // BBOX X0=x0; X1=x0; if (X0>x1) X0=x1; if (X1<x1) X1=x1; if (X0>x2) X0=x2; if (X1<x2) X1=x2; Y0=y0; Y1=y0; if (Y0>y1) Y0=y1; if (Y1<y1) Y1=y1; if (Y0>y2) Y0=y2; if (Y1<y2) Y1=y2; Z0=z0; Z1=z0; if (Z0>z1) Z0=z1; if (Z1<z1) Z1=z1; if (Z0>z2) Z0=z2; if (Z1<z2) Z1=z2; dx=X1-X0; dy=Y1-Y0; dz=Z1-Z0;
现在从 dx,dy,dz 的值(最大的一个)中选择长轴。例如,如果
dx
最大,那么从现在开始左右缓冲区索引将是x
坐标。将三角形边缘光栅化到左/右缓冲区
现在要选择边缘是否应该进入左侧缓冲区或右侧缓冲区,我们可以使用主要坐标差的符号。因此,选择第一个,然后按主坐标对边顶点进行排序(以避免连接三角形上出现孔洞)。
对于边缘(线)的光栅化,只需使用 integer version of DDA algortihm它很容易移植到更高的维度,并且在现代 CPU 上比 Bresenham 更快。
现在,我们只需将其渲染到左/右缓冲区中,而不是将线的体素
(x,y,z)
渲染到map
中。因此,如果长轴是x
并且目标缓冲区保留,则它将是:ly[x]=y; lz[x]=z;
由于有 3 个可能的主轴,我们需要该函数的 3 个版本...目标缓冲区也有 3 种情况(左、右、两者),但为此您可以使用指针,因此不需要每个代码两次...
此外,如果边缘可能超出体素网格范围,则需要添加剪切。我在 DDA 迭代中使用了简单的
if
条件,但是为了提高速度,最好在此之前剪辑该行...用线条填充三角形
只需遍历左/右缓冲区并在它们用线表示的相同索引处连接顶点。线条本身可能会被简化,因为我们知道主轴不会改变,但为了简单起见,我使用了普通的 3D DDA 线条(与 #3 相同,只是直接渲染到
map
中,而不是左/右缓冲区)。
将所有内容放在一起:
// voxel grid resolution
const int vxs=100;
const int vys=100;
const int vzs=100;
// helper buffers size should be max(vxs,vys,vzs)
const int vsz=vxs|vys|vzs;
float map[vxs][vys][vzs]; // voxel space map
int lx[vsz],ly[vsz],lz[vsz]; // left buffers for filling
int rx[vsz],ry[vsz],rz[vsz]; // right buffers for filling
void line(int x0,int y0,int z0,int x1,int y1,int z1,float c) // rencer line
{
int i,n,cx,cy,cz,sx,sy,sz;
// line DDA parameters
x1-=x0; sx=0; if (x1>0) sx=+1; if (x1<0) { sx=-1; x1=-x1; } if (x1) x1++; n=x1;
y1-=y0; sy=0; if (y1>0) sy=+1; if (y1<0) { sy=-1; y1=-y1; } if (y1) y1++; if (n<y1) n=y1;
z1-=z0; sz=0; if (z1>0) sz=+1; if (z1<0) { sz=-1; z1=-z1; } if (z1) z1++; if (n<z1) n=z1;
// single pixel (not a line)
if (!n)
{
if ((x0>=0)&&(x0<vxs)&&(y0>=0)&&(y0<vys)&&(z0>=0)&&(z0<vzs)) map[x0][y0][z0]=c;
return;
}
// ND DDA algo i is parameter
for (cx=cy=cz=n,i=0;i<n;i++)
{
if ((x0>=0)&&(x0<vxs)&&(y0>=0)&&(y0<vys)&&(z0>=0)&&(z0<vzs)) map[x0][y0][z0]=c;
cx-=x1; if (cx<=0){ cx+=n; x0+=sx; }
cy-=y1; if (cy<=0){ cy+=n; y0+=sy; }
cz-=z1; if (cz<=0){ cz+=n; z0+=sz; }
}
}
void _plinex(int x0,int y0,int z0,int x1,int y1,int z1) // helper edge render
{
int i,n,cx,cy,cz,sx,sy,sz,*by,*bz;
// target buffer & order points by x
if (x1>=x0)
{
i=x0; x0=x1; x0=i;
i=y0; y0=y1; y0=i;
i=z0; z0=z1; z0=i; by=ly; bz=lz;
} else { by=ry; bz=rz; }
// line DDA parameters
x1-=x0; sx=0; if (x1>0) sx=+1; if (x1<0) { sx=-1; x1=-x1; } if (x1) x1++; n=x1;
y1-=y0; sy=0; if (y1>0) sy=+1; if (y1<0) { sy=-1; y1=-y1; } if (y1) y1++; if (n<y1) n=y1;
z1-=z0; sz=0; if (z1>0) sz=+1; if (z1<0) { sz=-1; z1=-z1; } if (z1) z1++; if (n<z1) n=z1;
// single pixel (not a line)
if (!n)
{
if ((x0>=0)&&(x0<vxs))
{
ly[x0]=y0; lz[x0]=z0;
ry[x0]=y0; rz[x0]=z0;
}
return;
}
// ND DDA algo i is parameter
for (cx=cy=cz=n,i=0;i<n;i++)
{
if ((x0>=0)&&(x0<vxs)){ by[x0]=y0; bz[x0]=z0; }
cx-=x1; if (cx<=0){ cx+=n; x0+=sx; }
cy-=y1; if (cy<=0){ cy+=n; y0+=sy; }
cz-=z1; if (cz<=0){ cz+=n; z0+=sz; }
}
}
void _pliney(int x0,int y0,int z0,int x1,int y1,int z1) // helper edge render
{
int i,n,cx,cy,cz,sx,sy,sz,*bx,*bz;
// target buffer & order points by y
if (y1>=y0)
{
i=x0; x0=x1; x0=i;
i=y0; y0=y1; y0=i;
i=z0; z0=z1; z0=i; bx=lx; bz=lz;
} else { bx=rx; bz=rz; }
// line DDA parameters
x1-=x0; sx=0; if (x1>0) sx=+1; if (x1<0) { sx=-1; x1=-x1; } if (x1) x1++; n=x1;
y1-=y0; sy=0; if (y1>0) sy=+1; if (y1<0) { sy=-1; y1=-y1; } if (y1) y1++; if (n<y1) n=y1;
z1-=z0; sz=0; if (z1>0) sz=+1; if (z1<0) { sz=-1; z1=-z1; } if (z1) z1++; if (n<z1) n=z1;
// single pixel (not a line)
if (!n)
{
if ((y0>=0)&&(y0<vys))
{
lx[y0]=x0; lz[y0]=z0;
rx[y0]=x0; rz[y0]=z0;
}
return;
}
// ND DDA algo i is parameter
for (cx=cy=cz=n,i=0;i<n;i++)
{
if ((y0>=0)&&(y0<vys)){ bx[y0]=x0; bz[y0]=z0; }
cx-=x1; if (cx<=0){ cx+=n; x0+=sx; }
cy-=y1; if (cy<=0){ cy+=n; y0+=sy; }
cz-=z1; if (cz<=0){ cz+=n; z0+=sz; }
}
}
void _plinez(int x0,int y0,int z0,int x1,int y1,int z1) // helper edge render
{
int i,n,cx,cy,cz,sx,sy,sz,*bx,*by;
// target buffer & order points by z
if (z1>=z0)
{
i=x0; x0=x1; x0=i;
i=y0; y0=y1; y0=i;
i=z0; z0=z1; z0=i; bx=lx; by=ly;
} else { bx=rx; by=ry; }
// line DDA parameters
x1-=x0; sx=0; if (x1>0) sx=+1; if (x1<0) { sx=-1; x1=-x1; } if (x1) x1++; n=x1;
y1-=y0; sy=0; if (y1>0) sy=+1; if (y1<0) { sy=-1; y1=-y1; } if (y1) y1++; if (n<y1) n=y1;
z1-=z0; sz=0; if (z1>0) sz=+1; if (z1<0) { sz=-1; z1=-z1; } if (z1) z1++; if (n<z1) n=z1;
// single pixel (not a line)
if (!n)
{
if ((z0>=0)&&(z0<vzs))
{
lx[z0]=x0; ly[z0]=y0;
rx[z0]=x0; ry[z0]=y0;
}
return;
}
// ND DDA algo i is parameter
for (cx=cy=cz=n,i=0;i<n;i++)
{
if ((z0>=0)&&(z0<vzs)){ bx[z0]=x0; by[z0]=y0; }
cx-=x1; if (cx<=0){ cx+=n; x0+=sx; }
cy-=y1; if (cy<=0){ cy+=n; y0+=sy; }
cz-=z1; if (cz<=0){ cz+=n; z0+=sz; }
}
}
void triangle(int x0,int y0,int z0,int x1,int y1,int z1,int x2,int y2,int z2,float c) // render triangle
{
int X0,Y0,Z0,X1,Y1,Z1,dx,dy,dz,x,y,z;
// BBOX
X0=x0; X1=x0;
if (X0>x1) X0=x1; if (X1<x1) X1=x1;
if (X0>x2) X0=x2; if (X1<x2) X1=x2;
Y0=y0; Y1=y0;
if (Y0>y1) Y0=y1; if (Y1<y1) Y1=y1;
if (Y0>y2) Y0=y2; if (Y1<y2) Y1=y2;
Z0=z0; Z1=z0;
if (Z0>z1) Z0=z1; if (Z1<z1) Z1=z1;
if (Z0>z2) Z0=z2; if (Z1<z2) Z1=z2;
dx=X1-X0;
dy=Y1-Y0;
dz=Z1-Z0;
if ((dx>=dy)&&(dx>=dz)) // x is major axis
{
// render circumference into left/right buffers
_plinex(x0,y0,z0,x1,y1,z1);
_plinex(x1,y1,z1,x2,y2,z2);
_plinex(x2,y2,z2,x0,y0,z0);
// fill the triangle
if (X0<0) X0=0; if (X1>=vxs) X1=vxs-1;
for (x=X0;x<=X1;x++)
{
y0=ly[x]; z0=lz[x];
y1=ry[x]; z1=rz[x];
line(x,y0,z0,x,y1,z1,c);
}
}
else if ((dy>=dx)&&(dy>=dz)) // y is major axis
{
// render circumference into left/right buffers
_pliney(x0,y0,z0,x1,y1,z1);
_pliney(x1,y1,z1,x2,y2,z2);
_pliney(x2,y2,z2,x0,y0,z0);
// fill the triangle
if (Y0<0) Y0=0; if (Y1>=vys) Y1=vys-1;
for (y=Y0;y<=Y1;y++)
{
x0=lx[y]; z0=lz[y];
x1=rx[y]; z1=rz[y];
line(x0,y,z0,x1,y,z1,c);
}
}
else if ((dz>=dx)&&(dz>=dy)) // z is major axis
{
// render circumference into left/right buffers
_plinez(x0,y0,z0,x1,y1,z1);
_plinez(x1,y1,z1,x2,y2,z2);
_plinez(x2,y2,z2,x0,y0,z0);
// fill the triangle
if (Z0<0) Z0=0; if (Z1>=vzs) Z1=vzs-1;
for (z=Z0;z<=Z1;z++)
{
x0=lx[z]; y0=ly[z];
x1=rx[z]; y1=ry[z];
line(x0,y0,z,x1,y1,z,c);
}
}
}
边缘函数_pline
可以加速,如果你意识到长轴与迭代轴i
相同,你可以减少一次计算。此外,这 3 个化身可以合并为一个(只需重新排序被调用参数的 x,y,z 顺序并传递左/右缓冲区指针。将剪辑移到 DDA 迭代之外应该会提高性能也相当不错。
此处预览三角形相交圆柱体:
此处预览使用此三角形光栅化的 Material 去除:
橙色三角形不是体素一,它只是叠加,体素一不是直接渲染的,而是从体素网格中减去,而不是在动画过程中删除 Material ......
关于c++ - 3D 三角形光栅化为体素网格,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/74251620/