Non*_*one 8 c# 3d wpf marching-cubes volume-rendering
如果有人关心,我正在使用WPF ....
故事的开头:
我得到了一系列灰度图像(模型切片).用户输入灰度值的"范围"以构建3D模型.因此,我创建了一个3D bool数组,以便更容易地构建3D模型.此数组表示一个像素框,指示是否应构建/生成/填充每个像素.
领带:
使用a bool[,,],我想检索List<Point3D[]>每个Point3D[]长度为3的位置,并表示3D空间中的三角形.
附加信息:
生成的模型将进行3D打印.在bool[,,],true表示物质的存在,而false表明没有物质.我需要避免使用立方体模型,其中每个像素都被立方体替换(根据我的目的,这将是无用的).模型应尽可能平滑且尽可能准确.
我试图做的:
1-我实现了行进立方体算法,但似乎没有创建它来接受值的"范围".
2-我一直在努力制作自己的算法,但我部分失败了.(这真的很复杂.如果你想了解更多信息,请告知)
一些我不知道如何实现的预期解决方案:
1-修改Marching cube算法,以便使用a bool[,,]
2-修改Marching cube算法,以使其使用"范围" isolevel值的工作
3-展示如何在WPF中实现适合我的目的的另一种算法(可能是Ray-Casting算法).
4-请求我尝试实现的算法源,然后向我展示如何解决它.(它首先是将a多边形化bool[,,])
5-其他一些神奇的解决方案.
提前致谢.
编辑:
通过说
使用a
bool[,,],我想检索List<Point3D[]>每个Point3D[]长度为3的位置,并表示3D空间中的三角形.
我的意思是我想要检索一组三角形.每个三角形应表示为3 Point3D秒.如果您不知道Point3D它是什么,它struct包含3个双打(X,Y和Z),用于表示3D空间中的位置.
行进立方体算法的问题在于它有点模糊.我的意思是,通过这样做,你有什么理解?
cubeindex = 0;
if (grid.val[0] < isolevel) cubeindex |= 1;
if (grid.val[1] < isolevel) cubeindex |= 2;
if (grid.val[2] < isolevel) cubeindex |= 4;
if (grid.val[3] < isolevel) cubeindex |= 8;
if (grid.val[4] < isolevel) cubeindex |= 16;
if (grid.val[5] < isolevel) cubeindex |= 32;
if (grid.val[6] < isolevel) cubeindex |= 64;
if (grid.val[7] < isolevel) cubeindex |= 128;
Run Code Online (Sandbox Code Playgroud)
因此,我根本不知道从哪里开始修改它.
另一个编辑:
在对行进立方体算法进行了一些实验之后,我注意到多边形化a bool[,,]不会产生我期待的平滑3D模型,所以我的第一个预期解决方案和我的第四个预期解决方案都不在游戏中.经过大量的研究,我了解了体积渲染的Ray-Casting方法.它看起来真的很酷,但我不确定它是否符合我的需求.虽然我知道它是如何工作的,但我真的无法理解它是如何实现的.
还有一个编辑:
TriTable.LookupTable和EdgeTable.LookupTable是表目前在这里
这是MarchingCubes班级:
public class MarchingCubes
{
public static Point3D VertexInterp(double isolevel, Point3D p1, Point3D p2, double valp1, double valp2)
{
double mu;
Point3D p = new Point3D();
if (Math.Abs(isolevel - valp1) < 0.00001)
return (p1);
if (Math.Abs(isolevel - valp2) < 0.00001)
return (p2);
if (Math.Abs(valp1 - valp2) < 0.00001)
return (p1);
mu = (isolevel - valp1) / (valp2 - valp1);
p.X = p1.X + mu * (p2.X - p1.X);
p.Y = p1.Y + mu * (p2.Y - p1.Y);
p.Z = p1.Z + mu * (p2.Z - p1.Z);
return (p);
}
public static void Polygonise (ref List<Point3D[]> Triangles, int isolevel, GridCell gridcell)
{
int cubeindex = 0;
if (grid.val[0] < isolevel) cubeindex |= 1;
if (grid.val[1] < isolevel) cubeindex |= 2;
if (grid.val[2] < isolevel) cubeindex |= 4;
if (grid.val[3] < isolevel) cubeindex |= 8;
if (grid.val[4] < isolevel) cubeindex |= 16;
if (grid.val[5] < isolevel) cubeindex |= 32;
if (grid.val[6] < isolevel) cubeindex |= 64;
if (grid.val[7] < isolevel) cubeindex |= 128;
// Cube is entirely in/out of the surface
if (EdgeTable.LookupTable[cubeindex] == 0)
return;
Point3D[] vertlist = new Point3D[12];
// Find the vertices where the surface intersects the cube
if ((EdgeTable.LookupTable[cubeindex] & 1) > 0)
vertlist[0] = VertexInterp(isolevel, grid.p[0], grid.p[1], grid.val[0], grid.val[1]);
if ((EdgeTable.LookupTable[cubeindex] & 2) > 0)
vertlist[1] = VertexInterp(isolevel, grid.p[1], grid.p[2], grid.val[1], grid.val[2]);
if ((EdgeTable.LookupTable[cubeindex] & 4) > 0)
vertlist[2] = VertexInterp(isolevel, grid.p[2], grid.p[3], grid.val[2], grid.val[3]);
if ((EdgeTable.LookupTable[cubeindex] & 8) > 0)
vertlist[3] = VertexInterp(isolevel, grid.p[3], grid.p[0], grid.val[3], grid.val[0]);
if ((EdgeTable.LookupTable[cubeindex] & 16) > 0)
vertlist[4] = VertexInterp(isolevel, grid.p[4], grid.p[5], grid.val[4], grid.val[5]);
if ((EdgeTable.LookupTable[cubeindex] & 32) > 0)
vertlist[5] = VertexInterp(isolevel, grid.p[5], grid.p[6], grid.val[5], grid.val[6]);
if ((EdgeTable.LookupTable[cubeindex] & 64) > 0)
vertlist[6] = VertexInterp(isolevel, grid.p[6], grid.p[7], grid.val[6], grid.val[7]);
if ((EdgeTable.LookupTable[cubeindex] & 128) > 0)
vertlist[7] = VertexInterp(isolevel, grid.p[7], grid.p[4], grid.val[7], grid.val[4]);
if ((EdgeTable.LookupTable[cubeindex] & 256) > 0)
vertlist[8] = VertexInterp(isolevel, grid.p[0], grid.p[4], grid.val[0], grid.val[4]);
if ((EdgeTable.LookupTable[cubeindex] & 512) > 0)
vertlist[9] = VertexInterp(isolevel, grid.p[1], grid.p[5], grid.val[1], grid.val[5]);
if ((EdgeTable.LookupTable[cubeindex] & 1024) > 0)
vertlist[10] = VertexInterp(isolevel, grid.p[2], grid.p[6], grid.val[2], grid.val[6]);
if ((EdgeTable.LookupTable[cubeindex] & 2048) > 0)
vertlist[11] = VertexInterp(isolevel, grid.p[3], grid.p[7], grid.val[3], grid.val[7]);
// Create the triangle
for (int i = 0; TriTable.LookupTable[cubeindex, i] != -1; i += 3)
{
Point3D[] aTriangle = new Point3D[3];
aTriangle[0] = vertlist[TriTable.LookupTable[cubeindex, i]];
aTriangle[1] = vertlist[TriTable.LookupTable[cubeindex, i + 1]];
aTriangle[2] = vertlist[TriTable.LookupTable[cubeindex, i + 2]];
theTriangleList.Add(aTriangle);
}
}
}
Run Code Online (Sandbox Code Playgroud)
这是GridCell班级:
public class GridCell
{
public Point3D[] p = new Point3D[8];
public Int32[] val = new Int32[8];
}
Run Code Online (Sandbox Code Playgroud)
好的,那就是我在做的事情:
List<int[][]> Slices = new List<int[][]> (); //Each slice is a 2D jagged array. This is a list of slices, each slice is a value between about -1000 to 2300
public static void Main ()
{
List <Point3D[]> Triangles = new List<Point3D[]> ();
int RowCount;//That is my row count
int ColumnCount;//That is my column count
//Here I fill the List with my values
//Now I'll polygonise each GridCell
for (int i = 0; i < Slices.Count - 1; i++)
{
int[][] Slice1 = Slices[i];
int[][] Slice2 = Slices[i + 1];
for (int j = 0; j < RowCount - 1; j++)
{
for (int k = 0; k < ColumnCount - 1; k++)
{
GridCell currentCell = GetCurrentCell (Slice1, Slice2, j, k);
Polygonise (ref Triangles, int isoLevel, GridCell currentCell);
}
}
}
//I got the "Triangles" :D
}
//What this simply does is that it returns in 3D space from RI and CI
public static GridCell GetCurrentCell (int[][] CTSliceFront, int[][] CTSliceBack, int RI, int CI)//RI is RowIndex and CI is ColumnIndex
{
//Those are preset indicating X,Y or Z coordinates of points/edges
double X_Left_Front;
double X_Right_Front;
double X_Left_Back;
double X_Right_Back;
double Y_Top_Front;
double Y_Botton_Front;
double Y_Top_Back;
double Y_Botton_Back;
double Z_Front;
double Z_Back;
GridCell currentCell = new GridCell();
currentCell.p[0] = new Point3D(X_Left_Back, Y_Botton_Back, Z_Back);
currentCell.p[1] = new Point3D(X_Right_Back, Y_Botton_Back, Z_Back);
currentCell.p[2] = new Point3D(X_Right_Front, Y_Botton_Front, Z_Front);
currentCell.p[3] = new Point3D(X_Left_Front, Y_Botton_Front, Z_Front);
currentCell.p[4] = new Point3D(X_Left_Back, Y_Top_Back, Z_Back);
currentCell.p[5] = new Point3D(X_Right_Back, Y_Top_Back, Z_Back);
currentCell.p[6] = new Point3D(X_Right_Front, Y_Top_Front, Z_Front);
currentCell.p[7] = new Point3D(X_Left_Front, Y_Top_Front, Z_Front);
currentCell.val[] = CTSliceBack[theRowIndex + 1][theColumnIndex];
currentCell.val[] = CTSliceBack[theRowIndex + 1][theColumnIndex + 1];
currentCell.val[] = CTSliceFront[theRowIndex + 1][theColumnIndex + 1];
currentCell.val[] = CTSliceFront[theRowIndex][theColumnIndex];
currentCell.val[] = CTSliceBack[theRowIndex][theColumnIndex + 1];
currentCell.val[] = CTSliceFront[theRowIndex][theColumnIndex + 1];
currentCell.val[] = CTSliceFront[theRowIndex][theColumnIndex];
return currentCell;
}
Run Code Online (Sandbox Code Playgroud)
我从Stack-Overflow中做到了这一点,所以请指出任何错别字.这是有效的,这导致模型的外壳.我想做的是用2个整数替换函数中的isolevel变量Polygonise:isolevelMin和isolevelMax.不仅是1 int,还有一个范围.
编辑: 伙计们,请帮忙.50名代表几乎是我的声誉的1/2.我希望我能改变我的期望.现在我只是想知道如何实现我想要的,任何关于如何做到的片段,或者如果有人给出了一些关键词,我可以用google来解决我的问题,他会得到代表.我只需要一些亮光 - 这里全是黑暗的.
EDIT非常棒: 经过更多,更多的研究,我发现体积光线行进算法实际上并没有生成表面.由于我需要打印生成的3D模型,我现在只有一条出路.我必须使行进立方体算法从最大和最小等级值生成空心模型.
Marching Cube 算法的假设之一是每个立方体几何图形都是一个单独的实体,并且不依赖于其他立方体(这并不完全正确,但我们暂时坚持这一点)。
鉴于此,您可以将整个网格划分为多个立方体并分别求解每个立方体。您可能还会注意到,虽然最终的网格取决于 3D 标量场的精确值,但网格的拓扑仅取决于某个给定立方角位于网格内部还是外部的事实。
例如:假设您的isolevel设置为0,并且您的 3D 场有正数和负数。如果您现在将某个值从 更改0.1为1.0,那么您将仅更改某些三角形的比例,而不会更改其数量和/或拓扑。另一方面,如果您更改 到 的某个值,0.1那么-0.1您的三角形可能会切换到另一种排列,并且它们的数量可能会发生变化。
得益于此,我们可以使用一些巧妙的优化 - 对于 8 个角中的每一个,您检查该角是否位于网格内部或外部,然后使用这 8 位来构建一个数字 - 该数字将成为您预先计算的可能立方体解决方案表的索引,我们就这样称呼他们吧Variants。
每个Cube Variant都是用于此特定角配置的三角形列表。Marching Cubes 中的三角形总是跨越立方体边缘之间,因此我们使用边缘索引来描述它。这些索引正是您在 Paul Bourke 代码中的“神奇”表中看到的确切数据:)。
但这还不是最终的网格。您在这里得到的三角形仅基于以下事实:如果某个角位于网格内部或外部,但它距表面有多远?对结果有没有影响?你的网格值再次出现 - 当你选择你的 时Variant,得到三角形列表和每个三角形的 3 条边,然后你得到边末端的值并对它们进行插值以找到0值(我们已经设置了它是isolevel,还记得吗?)。这些最终的插值顶点应该用于您的网格。
这是一个相当长的介绍,我希望你能理解我写的内容。
我的建议: 最简单的改变是用角,而不是这样做:
if (grid.val[0] < isolevel) cubeindex |= 1;
Run Code Online (Sandbox Code Playgroud)
尝试这样改变它:
if (grid.val[0] > isolevelMin && grid.val[0] < isolevelMax) cubeindex |= 1;
// And the same for other lines...
Run Code Online (Sandbox Code Playgroud)
最终插值有点棘手,我无法测试它,但让我们尝试一下:
VertexInterp以通过两个等水平 - 最小和最大VertexInterp检查哪个 isolevel 位于 valp1 和 valp2 值之间如果它有效或者您有任何疑问,请告诉我。