Technical note

显示网格和求解网格为什么不是一回事

从 OCCT 的 BRepMesh_IncrementalMesh 显示剖分出发,梳理 CAE 软件中显示网格和求解网格在目标、生成方式、质量指标和自适应加密上的差异。

## 背景:同样叫 mesh,但目标并不一样

在 CAD/CAE 软件里,经常会遇到两类“网格”。

一类是显示网格。它通常来自 CAD 几何的可视化剖分,用来把 B-Rep 曲面转换成图形系统可以绘制的三角片。它的目标是让模型能够稳定显示、旋转、拾取、高亮,同时尽量控制三角形数量和渲染负载。

另一类是求解网格。它服务于有限元、有限体积、电磁场、热传导、结构力学等数值计算。它关心的不只是“看起来像不像原始几何”,还包括单元质量、材料区域、边界条件、激励、误差估计和收敛。

这两个东西都叫 mesh,但它们不是同一个工程产物。

我之前在做显示和网格相关工作时,也有过一个很自然的想法:既然求解网格最终也离散了模型,那能不能直接拿求解网格来显示?后来发现这个想法不能简单成立。

显示网格和求解网格来自同一个几何模型,但它们的设计目标差异很大。

这篇文章不展开任何内部求解流程,只从公开的 OCCT 显示剖分、TetGen 四面体网格生成,以及商业 CAE 软件中的自适应网格思想出发,梳理一下这两类网格到底有什么区别。

## 显示网格:为了把 B-Rep 稳定地画出来

CAD 模型通常不是直接由三角形表示的。

一个 TopoDS\_Shape 里面可能包含 Solid、Shell、Face、Wire、Edge、Vertex。Face 背后可能是平面、圆柱面、圆锥面、B-Spline 曲面等参数曲面;Edge 背后也可能是直线、圆弧或更复杂的参数曲线。

这些几何和拓扑结构对建模、布尔、修复、导入导出很重要,但 GPU 并不能直接绘制一个 OCCT 的 TopoDS\_Face

要在 Viewer 里显示它,通常需要把曲面离散成三角形,把边离散成折线,再交给底层图形系统。

所以显示网格的目标可以概括成一句话:


在可接受的视觉误差内,用尽量合适的离散数据把 CAD 几何画出来。

这里的关键词是“视觉误差”和“显示效率”。

如果剖分太粗,圆柱会看起来像多棱柱,曲面边界会有明显折线感。

如果剖分太细,三角形数量会膨胀,导入时间、内存占用、渲染压力、拾取成本都会上升。

所以显示剖分并不是越细越好。它本质上是一个工程取舍:在显示效果、交互性能和内存成本之间找平衡。

## 以 OCCT 的 BRepMesh_IncrementalMesh 为例

在 OCCT 里,常见的显示剖分入口是 BRepMesh\_IncrementalMesh

典型调用形式类似这样:


BRepMesh\_IncrementalMesh mesher(shape, linearDeflection, false, angularDeflection, true);

几个参数的含义大致是:


shape:

  要剖分的 TopoDS\_Shape。



linearDeflection:

  线性偏差,控制离散结果对原始曲线或曲面的逼近误差。



isRelative:

  是否按相对方式计算偏差。



angularDeflection:

  角度偏差,控制曲率变化处的离散密度。



isInParallel:

  是否启用并行剖分。

从使用方式上看,BRepMesh\_IncrementalMesh 的职责很明确:它不是在为求解器生成网格,而是在为 B-Rep 几何生成可显示的 triangulation。

你给出的源码片段里,构造函数做的事情也比较直接:


BRepMesh\_IncrementalMesh::BRepMesh\_IncrementalMesh(const TopoDS\_Shape\&    theShape,

                                                   const Standard\_Real    theLinDeflection,

                                                   const Standard\_Boolean isRelative,

                                                   const Standard\_Real    theAngDeflection,

                                                   const Standard\_Boolean isInParallel)

    : myModified(Standard\_False),

      myStatus(IMeshData\_NoError)

{

  myParameters.Deflection = theLinDeflection;

  myParameters.Angle      = theAngDeflection;

  myParameters.Relative   = isRelative;

  myParameters.InParallel = isInParallel;



  myShape = theShape;

  Perform();

}

它把 deflection、angle、relative、parallel 等参数存入 myParameters,然后设置 shape,并调用 Perform()

真正值得注意的是 Perform() 的入口:


void BRepMesh\_IncrementalMesh::Perform(const Message\_ProgressRange\& theRange)

{

  Handle(BRepMesh\_Context) aContext = new BRepMesh\_Context(myParameters.MeshAlgo);

  Perform(aContext, theRange);

}

也就是说,BRepMesh\_IncrementalMesh 自身不是把所有细节都写在一个函数里,而是创建一个 mesh context,再把具体流程交给后续工具链。

继续往下看:


void BRepMesh\_IncrementalMesh::Perform(const Handle(IMeshTools\_Context)\& theContext,

                                       const Message\_ProgressRange\&      theRange)

{

  initParameters();



  theContext->SetShape(Shape());

  theContext->ChangeParameters()            = myParameters;

  theContext->ChangeParameters().CleanModel = Standard\_False;



  Message\_ProgressScope  aPS(theRange, "Perform incmesh", 10);

  IMeshTools\_MeshBuilder aIncMesh(theContext);

  aIncMesh.Perform(aPS.Next(9));



  setDone();

}

这里有几个信息比较关键。

第一,initParameters() 会先整理参数。调用方传入的 deflection、angle 等参数并不是孤立使用的,内部还会根据默认值和上下文做初始化。

第二,它把 shape 和参数都放进 context。后面的剖分流程围绕这个 context 展开。

第三,真正执行的是 IMeshTools\_MeshBuilder

所以从入口层看,BRepMesh\_IncrementalMesh 更像是显示剖分流程的调度入口:准备参数、准备 context、调用 builder、收集状态。

## 从 MeshBuilder 看显示剖分的流程边界

IMeshTools\_MeshBuilder::Perform() 里面的步骤比较清楚:


if (aContext->BuildModel())

{

  if (aContext->DiscretizeEdges())

  {

    if (aContext->HealModel())

    {

      if (aContext->PreProcessModel())

      {

        if (aContext->DiscretizeFaces(aPS.Next(9)))

        {

          if (aContext->PostProcessModel())

          {

            SetStatus(Message\_Done1);

          }

        }

      }

    }

  }

}

这段流程可以概括成:


BuildModel

  -> DiscretizeEdges

  -> HealModel

  -> PreProcessModel

  -> DiscretizeFaces

  -> PostProcessModel

从这个顺序可以看出,OCCT 的显示剖分不是直接“对每个面随便撒三角形”。

它会先构建内部模型,然后离散边,再做一定的模型处理,再离散面,最后做后处理。这个顺序很符合 B-Rep 显示剖分的需求:Face 的边界由 Wire 和 Edge 限定,如果边界离散不稳定,后面的面三角化也很难稳定。

这里的 HealModel() 也容易让人误解。

它不是我们平时说的完整几何修复模块,不是要把一个坏模型修成适合求解的 watertight 几何。它更像是显示剖分流程内,为了让离散过程继续进行而做的局部处理。

所以我对这段源码的理解是:


OCCT 的 BRepMesh\_IncrementalMesh 是面向 B-Rep 显示三角化的工具链。



它关注的是如何把 CAD 拓扑和曲面边界稳定转换成显示用离散数据,

而不是生成满足数值求解要求的体网格。

这也是显示网格和求解网格分野的开始。

## 显示网格关心什么

显示网格首先要能画出来。

在 CAD/CAE Viewer 里,它还要支持很多交互能力:


点击一个三角形后,能够找回对应的 Face、Body 或 Object;

选择一个面后,可以高亮对应区域;

隐藏、透明、隔离显示时,不应该频繁重建整个场景;

大模型需要合批,但合批后还要保留拓扑语义;

边线、选中态、hover、overlay 不能互相干扰;

剖分精度要能兼顾视觉效果和渲染性能。

所以显示网格虽然看起来只是“三角形”,但在工程里它通常会和很多 Viewer 数据结构绑定在一起。

例如对象 ID、face tag、primitive range、bucket、selection layer 等。

在 CAD/CAE 软件中,显示网格不是孤立数据。它服务于“看”和“交互”。

这也是为什么显示网格不能只按三角形质量评价。

对显示系统来说,一个网格是否好用,还要看它是否方便拾取、方便局部更新、方便高亮、方便和几何树以及数据库状态同步。

## 求解网格:为了让物理问题可计算

求解网格的目标完全不同。

它不是为了让模型看起来平滑,而是为了把连续的物理问题离散成可以计算的代数问题。

以有限元为例,几何域会被划分成一系列单元。求解器在单元上构造形函数、积分、组装矩阵,最后求解线性或非线性方程。

这个过程中,网格质量会直接影响数值误差、矩阵条件数、收敛速度和结果可信度。

所以求解网格关心的问题包括:


几何域是否封闭;

体区域、材料区域是否定义清楚;

边界条件和激励是否能正确映射到节点、边、面或单元;

相邻区域是否需要共形;

小特征是否应该保留,还是应该清理;

单元形状是否过扁、过尖或接近退化;

局部网格尺寸是否满足物理场变化;

refinement 后结果是否收敛。

这和显示网格关注的问题不是一个层面。

显示网格可以接受“视觉上足够接近”。求解网格需要回答的是“这个离散化是否足以支撑当前物理问题的计算”。

## 以 TetGen 看四面体求解网格的基本目标

TetGen 是一个公开的四面体网格生成工具。

根据官方手册,它可以为三维 polyhedral domain 生成 constrained Delaunay tetrahedralization,也可以生成各向同性自适应四面体网格;它还会尽量保持输入边界中的边和面,使边界在结果网格中得到表示。

这和 OCCT 的显示三角化已经明显不是同一种目标。

TetGen 的输入域通常是 piecewise linear complex,也就是由点、线段、面片描述的三维分片线性域。它生成的是三维空间里的 tetrahedral mesh,而不是仅用于表面显示的 triangle mesh。

官方手册里还有一个很重要的边界:TetGen 不负责生成 PLC 的 surface mesh,输入的表面网格需要由用户提供。

TetGen 可以进一步细分输入表面网格,以满足 constrained Delaunay 性质和质量改善的需求;如果输入表面网格有自交,TetGen 会检测并停止;如果输入表面有洞,也可能无法得到有效的三维四面体网格。

这一点很重要。

求解网格生成不是简单地拿一个 CAD shape 就能直接生成高质量体网格。它依赖输入边界的正确性,也依赖几何修复、特征处理、区域定义和网格控制。

如果输入几何本身不封闭、有自交、有裂缝、有极小面或脏拓扑,后面的求解网格生成很可能会失败,或者生成质量很差的单元。

## 质量网格不是“看起来更细”

TetGen 的 -q 选项用于质量网格生成。

官方说明中提到,-q 会基于 Delaunay refinement 添加点,以改善四面体网格质量。默认情况下,它会控制四面体的 radius-edge ratio,也可以通过 -q 后面的数值指定更严格的约束。

radius-edge ratio 的定义可以写成:


Q = R / L

其中 R 是四面体外接球半径,L 是最短边长度。

一般来说,形状较好的四面体这个值较小,形状较差的四面体这个值较大。

但这个指标也不是万能的。

例如 sliver 这种非常扁、接近退化的四面体,可能在某些指标上并不显得特别差,但对数值计算并不友好。

这说明求解网格的“质量”不是一个单一指标能完全定义的。

常见质量指标还包括:


aspect ratio;

skewness;

minimum angle;

maximum angle;

dihedral angle;

Jacobian;

volume;

size grading;

boundary conformity;

element orientation。

更重要的是,不同求解器、不同物理场、不同离散方法,对网格质量的敏感点并不一样。

结构力学可能非常关注应力集中区域的局部加密和单元畸变;流体计算可能需要边界层网格;高频电磁计算会关注波长、介质交界、端口、金属边缘和场强变化区域;热分析又会有自己的网格尺度和材料界面要求。

所以“高质量网格”不能脱离求解目标单独讨论。

它必须绑定具体的物理问题、离散方法、误差指标和收敛准则。

## 自适应加密:求解网格通常不是一次性产物

商业 CAE 软件里的求解网格,很多时候不是一次生成后就结束。

以 HFSS 这类自适应求解流程为例,常见 adaptive mesh refinement 大致会经历这样的过程:


生成初始几何共形网格;

在初始网格上计算电磁场;

根据当前有限元解判断误差较高的区域;

对这些区域中的部分四面体进行局部细化;

使用细化后的网格重新求解;

重复“求解 -> 误差分析 -> 加密”,直到满足收敛条件或达到设定的自适应次数。

这个过程说明了一件事:求解网格本身就是求解过程的一部分。

它不是单纯的前处理结果,而是在求解、误差估计和收敛判断中不断调整的离散结构。

HFSS 一类频域电磁求解器常见的收敛判断,会关注相邻两次 adaptive pass 之间的结果变化是否足够小。换句话说,网格加密不是为了让模型“看起来更细”,而是为了让计算结果逐步稳定。

这和显示网格的思路完全不同。

显示网格通常在导入或显示更新时生成。只要视觉误差可接受、交互性能可接受,它就可以长期复用。

求解网格则可能需要根据物理场结果反复更新。

## 不同求解器对网格要求差别很大

“求解网格”这个词本身也很宽。

不同求解方法使用的网格类型和质量要求可能差别很大。

例如:


FEM 常见四面体、六面体、棱柱等体单元;

FDTD 通常有自己的空间离散网格组织;

MoM 更关注边界或表面积分离散;

CFD 中边界层网格很重要;

结构分析里局部应力集中和几何细节处理很重要;

高频电磁中,网格尺寸还会和波长、材料、电场变化有关。

所以不能简单说“求解网格一定比显示网格更细”,也不能说“求解网格质量一定比显示网格高”。

更准确的说法是:


求解网格的质量目标和显示网格不同。

显示网格追求视觉逼近和交互效率;求解网格追求数值稳定、误差可控和结果收敛。

有些区域,求解网格可能比显示网格细得多。比如场强变化剧烈、几何小特征影响结果、材料交界复杂的区域。

但也有些区域,求解网格未必需要比显示网格更密。因为求解网格关注的是物理场误差,而不是曲面视觉平滑程度。

## 回到工程:为什么不能简单复用

从显示系统角度看,显示网格通常需要:


表面三角形;

稳定的 face / edge / object 映射;

可控的三角形数量;

可合批;

可拾取;

可高亮;

可快速隐藏、显示、透明和隔离;

可与 Viewer 状态链路同步。

从求解系统角度看,求解网格通常需要:


满足物理域离散要求;

绑定材料、边界条件和激励;

保证单元质量;

支持局部加密;

支持误差估计;

支持结果收敛;

与具体求解器的数据结构适配。

这两套要求并不重合。

求解网格当然可以被可视化。很多 CAE 软件都会显示求解网格、网格质量、局部加密结果和求解场云图。

但这不等于它适合作为默认显示网格来驱动整个 CAD Viewer。

反过来,显示网格也不能直接拿去求解。它通常只描述模型表面,而且剖分质量主要服务视觉,不保证体域离散、边界条件映射、单元质量和求解收敛。

所以我现在更倾向于把它们分成两类工程资产:


显示网格:visual discretization

求解网格:numerical discretization

前者服务 Viewer,后者服务 Solver。

它们可以互相参考,也可以在某些场景下做可视化转换,但不应该在架构上混成一套东西。

## 显示剖分也不是“不重要”

有时候讨论求解网格时,容易把显示网格说得像是“低级网格”或“临时网格”。

这个理解也不对。

显示剖分在 CAD/CAE 软件里同样重要。

一个好的显示网格系统,需要处理:


大模型导入后的显示性能;

曲面剖分精度;

拾取语义恢复;

拓扑和三角形之间的映射;

边线显示;

高亮和透明叠加;

局部更新;

多对象合批;

显示状态缓存;

内存占用。

这些问题都很工程化,不比求解网格简单,只是它解决的问题不同。

显示网格的好坏,直接影响用户是否能顺畅查看模型、选中对象、理解几何结构和完成建模操作。

求解网格的好坏,则影响计算结果是否可靠、是否收敛、是否具有工程意义。

两者都重要,但不能混为一谈。

## 小结

这篇文章其实只想说明一个很基础但容易混淆的问题:


CAE 软件里的 mesh 不只有一种。



显示网格和求解网格都来自几何,

但它们不是同一个东西。

以 OCCT 的 BRepMesh\_IncrementalMesh 为例,它的流程更接近 B-Rep 显示三角化:准备参数,构建上下文,离散边,处理模型,离散面,生成可显示的数据。

以 TetGen 这类四面体网格工具为例,它关心的是三维 polyhedral domain 的四面体剖分、边界保持、Delaunay 性质和质量网格生成。

以 HFSS 这类自适应网格流程为例,求解网格还可能参与“求解—误差估计—局部加密—再求解”的迭代闭环。

所以,显示网格和求解网格的区别不是“粗”和“细”的区别,也不是“低质量”和“高质量”的区别,而是目标不同:


显示网格服务于看得见、选得到、转得动。



求解网格服务于算得准、能收敛、误差可控。

这个判断对 CAE 软件架构很重要。

如果把显示网格当成求解网格,容易忽略数值计算对单元质量、边界条件和误差收敛的要求。

如果把求解网格直接当成显示网格,又可能让 Viewer 背上不必要的计算负担,并破坏原本用于拾取、高亮和显示状态管理的结构。

更合理的做法,是承认它们职责不同:显示系统做好显示离散和交互映射,求解系统做好数值离散和误差控制。

两者之间可以建立转换和可视化关系,但不应该在概念上混成一个东西。

## 参考资料

* Open CASCADE Technology:BRepMesh\_IncrementalMesh / IMeshTools\_MeshBuilder 源码片段。

* TetGen, Version 1.5, User’s Manual。

* TetGen: The -q Switch: Quality mesh generation。

* Ansys HFSS: Adaptive Solution Process and its Importance to HFSS。