基于八叉树的网格生成算法剖析

本文深入剖析基于八叉树的网格生成算法,该算法通过树结构减少共面三角形,优化SMC算法,降低网格规模。介绍了八叉树的构建、边界体元插入、Shrink操作的合并原则以及体元三角形的抽取方法,展示了算法在减少网格规模方面的效果。
摘要由CSDN通过智能技术生成

分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow

也欢迎大家转载本篇文章。分享知识,造福人民,实现我们中华民族伟大复兴!

               

基于八叉树的网格生成算法剖析

前言

  对于网格生成这个主题,之前的网格生成系列的三篇博客文章分别介绍了MC算法,SMC算法以及Cuberille算法三种方法。同时还有一篇介绍网格生成与种子点生长算法高效结合的算法。本篇文章继续这一主题,介绍采用八叉树结构来进行网格生成的算法,这个算法并不是独立于之前介绍的算法,而是基于了SMC算法,同时也采纳了一些MC算法范畴内的思想。换句话说,就是使用八叉树对上述网格生成进行改良,从而进一步减少网格的规模。

研究动机与SMC算法的网格规模

  在SMC算法那一篇中已经指出,SMC算法是相对于MC算法的简化改良,它的一大特点就是生成网格的规模相对于MC算法大大减少。但是SMC算法也毕竟是从体元中产生三角片,三角片的大小是不会超过一个体元的范围的。而三维图像往往会有很大的尺寸这就意味着体元的数量会非常大。因而即便不采用MC算法而采用SMC算法,仍然会产生大量密集的小三角片。比如下图是Engine数据的三维图像模型,可以看出三角网格是由很多小三角片构成的。

  进一步观察这样的模型,我们发现:模型中实际上存在大量的共面三角片。了解网格型的话就会意识到这不是一个好的网格模型,因为网格模型通过小三角形片去拟合模型表面的曲面。而像上图那样的平面,完全可以使用更少的三角片去表示。比如一个具有下图左方结构的Mesh用8三角片个表示一个正方形,完全等价于右图用2个三角片表示的正方形。

8个三角片组成一个正方形 2个也能组个一样的

  因此这就引入了进一步减少网格规模的主题,这也是本文使用八叉树来简化网格生成的研究动机。无论是SMC算法还是MC算法,其生成的网格都有存在大量共面三角形的可能,而这些数量众多的小三角形也有合并成较少的大三角形的可能。尤其是SMC算法,由于其小三角形的法向和形状具有良好的可枚举性,因而在局部非常容易出现能够合并的小三角形。

  为此还必须指出,本文即将讨论的方法与针对Mesh进行网格削减的一系列算法(如之前的文章提到的顶点聚簇)并不是同一类算法。后者旨在脱离图像的概念,仅从几何意义上去对Mesh进行削减。而本文的算法是在网格生成之前,通过八叉树结构进行网格生成的优化,从而直接在Mesh被构造时就减少网格的规模。所以其本质上还是算作Mesh Generation算法而不是Mesh Processing算法。 

空间划分树的分类-均等树和BON树

  熟悉计算机的都知道树这个数据结构,尤其是二叉树。在计算机科学中,树形结构经常被用来组织和检索数据。当然树的用处不止是这些。在计算机图形学中,经常使用树来对空间进行划分,也就是把空间组织成一个层次结构。这样树的每一个节点就被赋予了空间上的意义,可以用来代表一个区域,而树的父子关系也正好能表示区域之间的包含关系。一般的,一维空间可以使用二叉树来划分,而平面和空间区域分别使用四叉树和八叉树来进行划分。

  对于一维的数据,比如一个下面的范围[0-12],可以使用二叉树划分。最终使得范围内的每一个子元素都在二叉树的节点上。这样的划分方式也比较常见,简单的对范围空间除以2即可。所以扩展到平面和空间就是对各个轴坐标范围不停的除以2,这种方式与快速排序中递归树的划分方式有一点相似(注意不是完全一样的),我们把这种方式划分的树叫做均等树。比如下图展示了对0-12范围的数据进行均等划分到底所建立的树。均等树对于[A,B]这样的范围,首先找到(A+B)/2,然后就将区域分成[A,(A+B)/2],[(A+B)/2,B]。之后再继续按相同的方式划分到不能再分为止。

均等树的划分方式

  BON树是一种不大同于上述均等树划分方式的树。从上图可以看出均等树的算法在除2的时候会遇到奇数偶数的问题。实际上在遇到奇数个元素的范围内,均等树所划分的子树实际上是不均等的,按上文说的办法,遇到奇数个元素,就会把中位数分在靠前的范围中,而靠后的范围实际上会少一个元素。这样划分到最后会出现单枝叶子节点的情况。而BON树为了保证树形,会在一开始就把最初的范围变成2的幂,从而能让划分的范围一直处于偶数个元素。例如下图展示使用BON树的分法来划分0-12的范围,首先需要找到包含这个范围的最小的2的幂,即16,然后再对这个范围进行划分,超出范围的节点不再创建。

BON树实际能表示到最近的2的幂 BON树超出部分不建立分支

  BON树的划分逻辑相比于均等树,并不更加复杂,而且相比于均等树存在一个潜在的好处。例如上图中对0-12范围所创建的均等树,可以从节点的编号上就能够获得节点的位置信息。例如9这个节点,9的二进制位为1001,正好对应这这个9的节点被BON树所分的层次信息。从图上0对应左子树,1对应右子树,这样从根节点到1001这个数就正好是右→左→左→右。这样同时提供了树节点插入的一个思路:在表示0-M范围的BON树中,若要插入A,则可以跟据A的二进制位和树的层数来确定这个A在树中的位置。同时,BON树的非叶子节点都能指代一个范围。例如9上面的父节点,就能代表100X(X为0或1)这个由两个数组成的范围,而这个父节点的父亲又能指代10XX的范围。

  以上是对两种具有不同空间划分方式的树的介绍,本文所要继续介绍的八叉树划分方法,是基于BON树的。同时在下文中会详细说明这样的树结构究竟是如何与具体的网格生成算法结合的。

 

树结构与信息的概括

  在介绍具体算法之前,还需要继续对空间划分树的一大特点进行介绍,而对这个特点的利用也是本文算法的基本思想。这个特点就是树结构具有对局部信息的概括能力。

  了解算法的人都比较清楚,像树这种具有层次的数据结构,很好的实现了对信息的概括。比如二叉排序树组织数据,相比与使用数组来说,树的每一个节点都概括了自己子树的信息,就是:左子树下面的都比自己小,右子树下面的都比自己大,这就是一种概括。那么当有数据来检索的时候,与一个节点的比较,就能判断出数组访问一个元素更多的大小关系。这就是层次结构之所以能实现高效检索的原因。而数组的每一个位置不像树那样具有概括信息的能力,访问每个位置都只能知道这一个位置的元素的信息。

  而对于本文说的空间划分树,可以通过下面的例子来说明其是如何概括信息的。例如下图中展示了一个编号为0-12的范围,这个范围分布有红绿两种颜色。也就是有的编号处是红色,有的是绿色。

  使用空间划分树来对这个范围组织一个BON树结果如下:

  假如我们想用最少的节点来表示这样一个红绿数组,那么就可以对这个树执行一个shrink操作。Shrink代表收缩是expand的反义词,类似于我们在treeview菜单上HEAD的那个节点上点击了”-”的动作一样。

Expand后的树 Shrink后的树

  这样每一个节点都会概括自己的子节点,当发现他们的颜色一致的时候,就会把这些信息概括到自己身上。下图展示了这种自底向上概括的过程,最后形成的树充分概括了这段红绿数组的信息。

原始BON树
shrink倒数第一层
shrink倒数第二层
shrink倒数第三层,不能再shrink完毕

  当对最后shrink完毕的树检索到最右的节点时,在这个节点上能够得知他所表示的范围即8-12都是绿色。

  了解了树的自底向上的shrink操作,就会在大方向上清楚了本文的算法将如何减少三角形的面片数。如下图所示,如果左图中这样八个体元中的三角形都被抽取出来,这样形成的一个大三角形平面是由4个三角形组成的,而其实如果能够将这些体元合并后再进行抽取。那么抽取出的三角形就只一个。这样实现了用更少的三角形表示相同的平面区域。这也就是本文所述的算法所要达到的直接目的。

相邻的八个体元中的三角片 体元合并在一起后再抽取,只有一个三角片了

 

基于八叉树的网格生成

  在介绍MC算法和SMC算法的时候我们已经知道,三角形片都是从边界体元中抽取出来的。三维图像中一般都有大量的实体元和空体元,这些体元对三角形抽取是无用的,只有边界体元是我们需要的。MC和SMC算法执行过程中,对三维图像的三个轴的三重循环扫描过程实际上就是一个搜寻边界体元的过程,找出所有体元配置不为0和255的体元,就是边界体元,每找到这样的体元,就抽取其中的三角形。在种子点生长算法与网格生成算法结合的那文章里面,也是这样的方式,无非就是寻找边界体元的方式变成基于种子点生长的方式而不是全部扫描。所以我们可以把使用MC、SMC算法生成网格的步骤总结成三步:

  1. 寻找边界体元;
  2. 抽取体元三角片;
  3. 组合三角片成Mesh;

  而本文的基于八叉树的网格算法,通过树的shrink来减少需要抽取三角片的体元数量,这样在上面步骤的基础上有如下的改变:

  1. 寻找边界体元;
  2. 将边界体元插入八叉树;
  3. 对八叉树执行shrink;
  4. 抽取shrink后的各体元三角片;
  5. 组合三角片成Mesh;

  从上文的步骤可以想到,shrink之前树中存着都是同一层的等大小的体元,而shrink之后,就一部分节点被父节点吸收概括成一个超体元,那么这棵树里的体元就会由各种大小不同的体元构成,既有边长为1的单位体元,也有边长为2的幂如2、4、8等的超体元。

  上文的步骤是粗略的一个步骤,其中最为关键的是中间三步,每一步都还有具体需要关心的问题:第二步将边界体元插入八叉树,是如何的插法;第三步对树进行shrink,那么符合什么样条件的节点可以shrink,shrink到什么程度为止;第四步中提取体元的三角片,如果是单位体元,提取的方式和SMC算法一样,但如果是超体元将如何提取三角片。解决好了上述问题,就完整实现了本文所介绍的算法。

 

八叉树的创建以及边界体元的插入

  首先需要解决建树以及插入节点的问题。本算法的实现使用的是BON八叉树,空间树的建立必然关联着一个空间范围,边界体元集合是有范围的,而且至多也不会超出三维图像的范围。为了建树,首先定义树的节点类型OctreeNode,声明其为模版类型方便于携带不同的参数,更具一般性。

public class OctreeNode<T>{
        public OctreeNode<T>[] Children;//孩子指针,数组大小为8    public OctreeNode<T> Parent;//父节点指针    public T Parms;//携带的参数    public int XMin;//所代表范围的X轴下界    public int YMin;//所代表范围的Y轴下界    public int ZMin;//所代表范围的Z轴下界    public int XMax;//所代表范围的X轴下界    public int YMax;//所代表范围的Y轴下界    public int ZMax;//所代表范围的Z轴下界    public int IndexInParent;//自己在父节点孩子数组中的索引    public int LayerIndex;//自己所在的层索引    public bool IsLeaf()    {        return (XMin == XMax)&&(YMin==YMax)&&(ZMin==ZMax);    }//返回是否是叶子节点    public OctreeNode()    {    }    public override string ToString()    {        if (IsLeaf())        {            return string.Format("[{0},{1}][{2},{3}][{4},{5}] {6} Leaf", XMin, XMax, YMin, YMax, ZMin, ZMax,Parms);        }        if (Parms == null)            return string.Format("[{0},{1}][{2},{3}][{4},{5}] {6}", XMin, XMax, YMin, YMax, ZMin, ZMax, "not simple"); ;        return string.Format("[{0},{1}][{2},{3}][{4},{5}] {6}", XMin, XMax, YMin, YMax, ZMin, ZMax,Parms);    }}//BON八叉树节点

  上述定义中为了方便附加了很多信息如节点的各轴范围,节点的层次以及在父节点的索引等,这些信息有的是可以动态获取的,开辟空间记录下来是用空间换时间,本文为方便实现,附加了比较多的这些辅助信息。

  那么下一步就是定义一颗BON树RegionOctree:

public class RegionOctree<T>{
        private static int GetMax2Power(int xmax,int ymax,int zmax,ref int log)    {        int max = xmax;        if (ymax > max)            max = ymax;        if (zmax > max)            max = zmax;        if ((max & (max - 1)) == 0)        {            double L = Math.Log(max, 2);            log = (int)L + 1;            return max;        }        else        {            double L = Math.Log(max, 2);            log = (int)L + 2;            return (int)Math.Pow(2, log - 1);        }    }    private int Width;//树所关联空间范围的X上界    private int Height;//树所关联空间范围的Y上界    private int Depth;//树所关联空间范围的Z上界    public OctreeNode<T> Root;//树根节点    public  int NodeCount;//所有节点总数    public  int LeafCount;//叶子节点    private int Scale;//2的幂包围盒边长    private int LayerNum;//层次数    private OctreeNode<T>[] NodeLayers;//指代一条由根通往叶子的路径    public RegionOctree(int width,int height,int depth)//使用范围构造BON树    {        this.Width = width;        this.Height = height;        this.Depth = depth;        Scale = GetMax2Power(Width,Height,Depth,ref LayerNum);        NodeCount = 0;        Root = new OctreeNode<T>();        Root.XMin = 0;        Root.XMax = Scale-1;        Root.YMin = 0;        Root.YMax = Scale-1;        Root.ZMin = 0;        Root.ZMax = Scale-1;        Root.Parent = null;        Root.IndexInParent = -1;        Root.LayerIndex = LayerNum - 1;        Root.Children = new OctreeNode<T>[8];        NodeLayers = new OctreeNode<T>[LayerNum];        NodeLayers[0] = Root;    }    public OctreeNode<T> CreateToLeafNode(int x,int y,int z)    {        LeafCount++;        for (int i = 1; i <= LayerNum - 1; i++)        {            int index = GetIndexOn(x, y, z, LayerNum - i-1);            if (NodeLayers[i - 1].Children[index] == null)            {                OctreeNode<T> node = new OctreeNode<T>();                NodeCount++;                node.Parent = NodeLayers[i - 1];                node.IndexInParent = index;                node.Children = new OctreeNode<T>[8];                node.LayerIndex = NodeLayers[i - 1].LayerIndex - 1;                InitRangeByParentAndIndex(node, NodeLayers[i - 1], index);                NodeLayers[i - 1].Children[index] = node;            }            NodeLayers[i]=NodeLayers[i-1].Children[index];        }        return NodeLayers[NodeLayers.Length - 1];    }//将关联着坐标(x,y,z)处元素一路插入到底层为叶子节点    private int GetIndexOn(int x, int y, int z, int bitindex)    {        int ret = 0;        if ((x & (1 << bitindex)) != 0)        {            ret |= 1;        }        if ((y & (1 << bitindex)) != 0)        {            ret |= 2;        }        if ((z & (1 << bitindex)) != 0)        {            ret |= 4;        }        return ret;    }    private void InitRangeByParentAndIndex(OctreeNode<T> node,OctreeNode&l
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
八叉树网格划分是一种用于将三维空间划分为多个小方块的方法。它可以在计算机图形学、计算机视觉和计算机辅助设计等领域中用于表示复杂的三维数据和场景。Open3D是一个开源的计算机视觉库,提供了许多用于处理三维点云和网格的功能。 在Open3D中,八叉树网格划分可以通过Octree类来实现。它可以将给定的三维点云数据按照一定的规则划分为多个小方块,每个小方块称为一个八叉树节点。划分过程是递归的,通过不断划分每个节点来实现对整个点云数据的划分。每个八叉树节点都存储了一定数量的点云数据,并且可以继续划分成更小的节点,直到满足某个停止条件。 八叉树网格划分在Open3D中有很多应用场景,比如点云分割、点云压缩和三维重建等。通过八叉树网格划分,可以将点云数据分割成不同的部分,从而进行更精细的处理和分析。同时,通过八叉树网格划分,可以将大规模的点云数据压缩为较小的八叉树节点存储,从而节省存储空间。此外,八叉树网格划分还可以用于三维重建中的体素化和表面重建,从而更方便地进行后续的处理和分析。 总之,八叉树网格划分是Open3D中的一个重要功能,它可以将三维点云数据划分为多个小方块,并且可以应用于点云分割、点云压缩和三维重建等场景。通过八叉树网格划分,可以更方便地进行三维数据的处理和分析,提高计算效率和准确度。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值