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。