news 2026/10/14 1:50:19

Unity硬表面模型描边断裂排查:顶点法线与网格数据的修复实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Unity硬表面模型描边断裂排查:顶点法线与网格数据的修复实战

前段时间我在处理风格化项目时碰到一个特别折磨人的问题:角色模型的描边一切正常,但凡是硬表面模型——机械臂、枪械零件、建筑外挂件——描边就像被切成一段一段的虚线,尤其在棱角转角处直接断掉,严重的地方甚至能看见模型内部裸露出来。最初我以为是描边Pass的深度写入或者剔除方向出了问题,排查了大半天后发现Shader本身完全没问题,真正的根子藏在网格顶点和法线的数据结构里。这篇文章把从现象复现、数据定位到最终修复的完整过程记录下来,包括三种能实际落地的处理方案和几个调优细节,给正在用Unity做风格化渲染、或者天天跟硬表面资产打交道的朋友提供一条可复现的排查路径。

1. 问题现场:描边像被刀切断的虚线

1.1 复现环境与描边方案

项目用的是Unity的Built-in渲染管线做主要效果验证,描边采用的是最常见也最经典的“背面外扩”方案,也就是常说的inverted hull。原理很简单:一个额外的Pass专门渲染模型的背面,在顶点着色器里把顶点沿法线方向往外推一小段距离,再输出纯色,作为描边轮廓;紧接着另一个Pass正常渲染模型正面,把描边压在底下。这样做的好处是性能开销低、观感可控,特别适合Low Poly和卡通武打风格的游戏。

基础描边Shader的骨架大概长这样:

Shader "Custom/Outline" { Properties { _OutlineWidth ("Outline Width", Range(0, 0.1)) = 0.01 _OutlineColor ("Outline Color", Color) = (0, 0, 0, 1) } SubShader { Pass { Name "OUTLINE" Cull Front CGPROGRAM #pragma vertex vert #pragma fragment frag float _OutlineWidth; fixed4 _OutlineColor; struct appdata { float4 vertex : POSITION; float3 normal : NORMAL; }; struct v2f { float4 pos : SV_POSITION; }; v2f vert (appdata v) { v2f o; float3 worldNormal = normalize(mul((float3x3)UNITY_MATRIX_IT_MV, v.normal)); float2 offset = TransformViewToProjection(worldNormal.xy); o.pos = UnityObjectToClipPos(v.vertex); o.pos.xy += offset * _OutlineWidth * o.pos.w; return o; } fixed4 frag (v2f i) : SV_Target { return _OutlineColor; } ENDCG } Pass { // 正常渲染模型表面的Pass,Cull Back } } }

这段代码在角色和普通圆形物体上效果很好,描边流畅、粗细均匀。问题是在硬表面模型上,描边立刻变样。

1.2 “断裂”的两种视觉形态

我用目标模型测试时,观察到了两种不同的异常形态。

第一种是棱角处的断线。描边沿着平面走,走到一条硬棱边时突然变细,然后完全消失,隔一小段距离又在另一侧重新出现,整体看起来就像一条被切成了几段的虚线。这种形态最典型,也最容易让人误判成Shader的剔除条件写错了。

第二种是棱角处的“外翻”和“水肿”。两个相邻平面各自的描边都向外膨胀,但是因为外扩方向不一致,棱线顶端两条描边没有汇合,中间露出一道缝隙,透过缝隙可以看到模型内部结构。这种情况在硬边密集的机械件上尤其明显,整个轮廓看起来像被撑开了一样。

这两种形态其实指向同一个底层问题:描边外扩所依赖的法线方向,在硬边处根本没有形成连贯的“轨迹”。

1.3 我最初的怀疑方向

人遇到渲染异常时第一反应都是查Shader。我当时排查的顺序是:先看描边Pass有没有写错剔除方向,再看会不会是深度写入导致描边被模型正面吃掉,然后又怀疑渲染队列是否被排到了Alpha Test阶段被提前移除。

为了隔离问题,我在场景里放了一个Unity自带的球体,把同一份描边Shader挂上去。球体全角度描边都正常,没有任何断线。接着放一个默认立方体,棱线处的描边会比其他区域细一点,但线条没有断。这两个测试基本可以下结论:渲染流程和Shader逻辑没有大问题,差异全部来自目标模型本身的网格数据。

2. 描边断裂的地基:硬表面模型顶点与法线的特殊结构

2.1 硬边、软边与顶点焊接状态

要真正理解描边为什么会在硬表面模型上断裂,得先搞清楚硬表面建模里一个非常基础的概念:顶点焊接。

在三维Mesh里,“焊接”指的是两个相邻三角形是否共享同一个顶点索引。如果两个三角形共享顶点,那么这个顶点上只能保存一个法线值,通常是相邻几个面法线的平均值,用于产生平滑的光照过渡。如果顶点没有焊接,每个三角形各自持有一份属于自己的顶点副本,法线方向就是当前面的面法线方向。

硬表面模型的特点决定了它大量使用硬边。机械、武器、载具这类模型上,棱角分明才是正常的视觉风格。在建模软件里做硬表面时,正确做法是:平面区域让顶点焊接起来,产生平滑过渡;棱角处的顶点则要沿硬边拆开,让每个面保持各自独立的面法线。这样光照才会在棱线上出现锐利的转折,模型看起来“够硬”。

但问题在于,很多模型在导入Unity后并没有达到这种理想状态。棱边上的顶点可能被焊接在一起导致法线被平均,也可能被完全拆开导致每个三角面都带着各自的法线乱飞。这两种状态都会让描边外扩的方向变得不可控。

2.2 Unity导入时的法线来源

Unity在导入模型时会根据FBX或OBJ里顶点的法线数据做处理,具体行为由Model导入面板里的Normals参数决定。Normals可以选Import或者Calculate。

如果选Import,Unity直接使用建模软件导出时写入文件的那套法线。这套法线通常是正确的,前提是建模软件里已经按硬边/软边规范处理过顶点。如果选Calculate,Unity会忽略模型文件里原有的法线,自己根据一个Smoothing Angle参数重新计算。Smoothing Angle的意思是:当两个相邻面的法线夹角小于这个角度时,Unity把它们当作平滑过渡边,顶点法线取平均值;大于这个角度时,则当作硬边。

问题往往就出在这个Smoothing Angle上。硬表面模型在建模软件里手动设置软硬边是非常常见的方式,比如一台机甲模型,45度以下的过渡边平滑,45度以上的棱线硬边。但如果Unity导入时选了Calculate,又把Smoothing Angle设得过大,原本是硬边的棱线就会被Unity强行平滑掉,顶点法线变成两侧面法线的平均值,描边外扩方向就会向棱线内部倾斜,最终导致断线。

2.3 墙角圆珠笔类比:为什么平均法线会挤出断裂

用生活场景来类比一下。假设你拿一支圆珠笔沿着两面墙的墙角画线——左墙是垂直平面,右墙是另一个方向的垂直平面。你的笔尖要同时接触两个墙面,在墙角这种突变的转棱处,如果不改变笔的倾斜角度,笔尖只能接触到一侧墙壁,另一侧的线就会断掉。想要让线条在墙角两侧都连续,必须在墙角处让笔“换一个角度”,这个角度大致是两面墙法线的某种中间方向。

描边外扩就是这支圆珠笔。顶点法线是笔的朝向,外扩距离是画线的长度。如果一个硬边顶点的法线被平均成了“左右各折一半”的方向,那么这个顶点外扩后既不在左墙面上,也不在右墙面上,而是挤在棱线内部靠里侧的位置。相邻两个面各自己外扩,棱线顶端的描边自然就接不上。

顶点分裂之后就相当于准备了两支笔,一支沿左墙面法线方向画,一支沿右墙面法线方向画,两边都走到墙角棱线顶端,线条就接上了。所以硬表面模型的描边断裂,本质上是“笔的角度”在硬边处没有准备够。

3. 排查过程:用数据和可视化定位断裂根因

3.1 第一步用测试模型隔离问题

正式的排查我分了三步走。第一步已经在前面说了,用球体和立方体做对比测试,把Shader问题排除掉。这里补充一个现象记录:立方体的棱线描边比面中心区域细,但不断开,这其实是正常情况。因为立方体棱边两侧的面法线夹角是90度,描边外扩后,棱线顶端的覆盖厚度天然小于平面区域。如果项目要求所有棱线都有和平面一样厚的描边,就得靠后续的“专用法线”方案来解决,而不是改Shader能搞定的。

3.2 用Debug工具把法线画出来看

第二步是做法线可视化。Unity的Scene视图里可以用Gizmos画线,我在场景里放了一个脚本,OnDrawGizmos里遍历Mesh的所有顶点和法线,用绿色线段把法线方向画出来。这样能肉眼看出顶点法线到底指向哪里。

using UnityEngine; public class NormalDebugger : MonoBehaviour { public float length = 0.05f; void OnDrawGizmos() { MeshFilter mf = GetComponent<MeshFilter>(); if (mf == null || mf.sharedMesh == null) return; Mesh mesh = mf.sharedMesh; Vector3[] vertices = mesh.vertices; Vector3[] normals = mesh.normals; Gizmos.color = Color.green; for (int i = 0; i < vertices.Length; i++) { Vector3 worldPos = transform.TransformPoint(vertices[i]); Vector3 worldNormal = transform.TransformDirection(normals[i]); Gizmos.DrawLine(worldPos, worldPos + worldNormal * length); } } }

把这个脚本挂到有问题的模型上,法线分布的异常立刻现出原形。如果硬边的顶点只有一个法线,且这个法线的方向明显偏向棱线内侧,那基本就能断定是顶点未分裂;如果看到棱线附近每个三角面都有三个不同方向的法线,就和“顶点完全分离”吻合。

3.3 把网格数据导出来做定点分析

可视化能看出大概,但要做精确定位还是得直接看数据。我写了一段很短的导出代码,把Mesh里每一个顶点的索引、位置、法线方向,以及它被哪些三角面引用,全部输出到文本文件里。

using UnityEngine; using System.Text; using System.IO; using System.Collections.Generic; public static class MeshDump { public static void Dump(Mesh mesh, string path) { StringBuilder sb = new StringBuilder(); Vector3[] vertices = mesh.vertices; Vector3[] normals = mesh.normals; int[] triangles = mesh.triangles; for (int i = 0; i < vertices.Length; i++) { var triRefs = new List<int>(); for (int t = 0; t < triangles.Length; t++) { if (triangles[t] == i) triRefs.Add(t / 3); } sb.AppendLine(string.Format( "vtx {0}: pos {1} normal {2} usedBy {3} tris", i, vertices[i], normals[i], triRefs.Count )); } File.WriteAllText(path, sb.ToString()); Debug.Log("Dump written to " + path); } }

拿着这个文本去对比断裂处的棱边顶点,我得到一个非常清楚的判断逻辑:如果棱边顶点的位置在文件里只出现了一次,且法线方向介于两个面之间,说明顶点被焊接了;如果位置出现了两次且法线方向分别对应两个面,说明顶点已经正确分裂。前者就是描边断裂的根因,后者说明问题出在别的环节,比如法线本身未归一化或者网格面本身不平整。

3.4 三种根因的判断表

实测下来,硬表面描边断裂的根因可以归纳成下面几类,排查的时候可以直接对着看。

网格特征判断根因推荐处理
棱边处只有一个共享顶点,法线是两侧面的平均值硬边顶点未按角度分裂按角度阈值分裂顶点
顶点已完全分离,但每个三角面的法线方向都不同CAD或矢量流程导出的全硬边网格平面区域重新焊接,棱角处保留分离
顶点和法线单独看都正常,但模型整体被非均匀缩放拉伸法线外扩方向被缩放变换破坏改描边Shader为视空间外扩
导入时选了Calculate,Smoothing Angle过大把硬边平滑掉法线计算参数不当改为Import原始法线或调低平滑角

从网格数据检查完,我基本可以断定手上的模型属于第一类:硬棱边的顶点被焊接了,法线被平均成中间方向,描边外扩时两侧面各自向棱线内侧收缩,描边自然断成一截一截。

4. 修复方案:按根因选路径

4.1 方案一:从源头修正导入设置与资产导出规范

如果项目还处在模型资源可以随时修改的阶段,最省事的办法是把锅端掉,直接在源资产里把顶点和法线弄对。

我在建模软件里的做法是:模型全部按照Auto Smooth的角度规则来处理,硬表面模型的平滑角阈值一般设在45度到60度之间,小于这个角度的过渡边保持平滑焊接,大于这个角度的棱线手工标记为Hard边并让顶点沿棱线分裂。导出FBX之前,再执行一次去重合顶点操作,确保平面区域的顶点焊接干净。

进入Unity之后,检查导入面板的Normals选项,建议直接选Import而不是Calculate。因为建模软件里已经调过软硬边,把那套法线原封不动带进来是最可靠的。如果确实需要Calculate,至少把Smoothing Angle调到和建模软件一致的数值,否则Unity重新计算的法线很可能把美术精心设置的硬边给“抹平”。

给团队内部规定一套硬表面资产的检查清单:

  • 平面区域顶点必须焊接,允许平滑光照过渡
  • 棱线处顶点必须分裂,保持面法线独立
  • Auto Smooth角度固定为45度或60度,建模与Unity导入保持一致
  • 导出FBX前清理孤立顶点和重合顶点
  • Unity导入面板使用Import模式

这套规范管住了后续新增的所有模型资产,不会再冒出新的描边断裂问题。但对于已经在项目里跑了一轮的存量模型,就得靠下面的运行时方案补救。

4.2 方案二:在Unity里按角度分裂顶点

存量模型不可能全部拿回建模软件重新导,那就在Unity里写一个工具,运行时或者Editor下自动把硬边顶点按角度阈值拆开。思路其实不复杂:把网格的所有三角形先全部拆成独立顶点,然后遍历所有顶点,找到“位置相同”的那些顶点,判断它们各自所在面的法线夹角。夹角小于阈值说明这是平滑边,可以合并为同一个顶点;夹角大于等于阈值说明这是硬边,必须保持分离。

这个工具的简化版实现大致长这样:

using UnityEngine; using System.Collections.Generic; public static class MeshSplitter { public static Mesh SplitHardEdges(Mesh mesh, float angleThreshold) { Vector3[] srcVerts = mesh.vertices; Vector3[] srcNormals = mesh.normals; Vector2[] srcUVs = mesh.uv; int[] srcTris = mesh.triangles; List<Vector3> outVerts = new List<Vector3>(); List<Vector3> outNormals = new List<Vector3>(); List<Vector2> outUVs = new List<Vector2>(); int[] outTris = new int[srcTris.Length]; // 第一步:全部拆开 for (int i = 0; i < srcTris.Length; i++) { int oldIdx = srcTris[i]; outVerts.Add(srcVerts[oldIdx]); outNormals.Add(srcNormals[oldIdx]); outUVs.Add(srcUVs.Length > 0 ? srcUVs[oldIdx] : Vector2.zero); outTris[i] = outVerts.Count - 1; } // 第二步:用位置分组,按法线夹角决定是否合并 List<int>[] groups = new List<int>[outVerts.Count]; for (int i = 0; i < outVerts.Count; i++) { if (groups[i] == null) groups[i] = new List<int>(); groups[i].Add(i); for (int j = i + 1; j < outVerts.Count; j++) { if (Vector3.Distance(outVerts[i], outVerts[j]) < 0.0001f) { float dot = Vector3.Dot(outNormals[i], outNormals[j]); float angle = Mathf.Acos(Mathf.Clamp(dot, -1f, 1f)) * Mathf.Rad2Deg; if (angle < angleThreshold) { groups[i].Add(j); outTris[j] = i; outVerts[j] = outVerts[i]; } } } } // 清理未合并的冗余顶点并重建Mesh Mesh newMesh = new Mesh(); newMesh.name = mesh.name + "_Split"; // ... 按outVerts/outNormals/outUVs/outTris填充 return newMesh; } }

这段代码是验证用的简化版本。生产环境下两个地方需要优化:一是位置分组建议用空间哈希或者字典按整数坐标分桶,避免O(n^2)的线性比对;二是角度阈值建议做成可配置参数,在Editor界面里实时调整看效果,方便不同风格的项目找到最合适的视觉平衡点。

分裂完成之后,棱边处每个面都有了自己独立的外扩方向,描边断裂基本消失。

4.3 方案三:为描边生成专用平滑法线

方案二虽然解决了断裂,但本质上是在“拆”顶点,代价是增加了顶点数量,而且棱线顶端的描边厚度还是会被压缩。如果项目对描边质量要求更高,我推荐第三种做法:在网格上额外生成一套“描边专用法线”。

描边外扩用的法线其实不需要和渲染法线完全一样。渲染法线要按照光照的软硬边规则来,但描边法线可以更自由——只要能让外扩方向在棱线两侧汇合就行。常用的做法是,对每个顶点收集所有邻接面的法线,做一个角度过滤后的加权平均。所谓过滤,就是排除掉与当前顶点原始法线夹角过大的那些面,这样硬边两侧各自保留自己那边的面法线信息,得到一套“向外张开”的平滑法线。

伪代码思路如下:

foreach triangle in mesh: faceNormal[tri] = normalize(cross(v1 - v0, v2 - v0)) for each vertex index vi: sum = (0, 0, 0) for each face f adjacent to vi: if dot(faceNormal[f], vertexNormal[vi]) > 0.2: sum += faceNormal[f] outlineNormal[vi] = normalize(sum)

这里0.2大概对应法线夹角约78度。阈值拉大,描边法线会接近原始平滑法线,棱线两侧汇合效果变弱;阈值调小,描边法线会更贴近各自面的方向,棱线顶端缝隙变小。具体数值需要按模型风格调,我目前项目里用的是夹角不超过80度这一档,既能保证棱线处外扩方向连续,又不会让平面区域法线剧烈抖动。

这套专用法线可以存在Mesh的UV3或者UV4通道里,描边Shader只需要把外扩方向从NORMAL改为读取附加通道即可,改动量不大,网格顶点数不用增加,视觉稳定性比方案二更好。

4.4 三种方案的取舍对比

方案修改范围运行时开销效果稳定性适用场景
修正源资产与导入参数美术资源无最高项目早期,模型还能回DCC重导
Unity脚本按角度分裂顶点程序工具一次性中存量模型多,不方便回DCC修改
生成描边专用法线程序加Shader加载时一次最高对棱角处描边厚度有更高要求

三种方案不是互斥的。实际项目里我通常是先跑一遍导入检查脚本,把明显有问题的模型自动做顶点分裂;然后给那些需要重点表现的硬表面模型手动生成一套专用法线;最后在DCC那边定好规范,从源头上杜绝新资产再出现同样状况。

5. 让描边更耐看的调优细节

5.1 描边改成屏幕空间恒定宽度

描边断裂修好之后,下一个常见问题是远处描边变细、近处描边过粗。这其实是因为很多描边Shader沿法线在模型空间里外扩,外扩量是一个固定模型单位,透视投影之后,远处的模型在屏幕上占的像素少,外扩量被压缩,几乎不可见。

解决方法是把外扩放到裁剪空间来做,让外扩量乘以裁剪坐标的w分量。w分量里包含了顶点到摄像机的透视深度信息,乘上去之后,描边在屏幕上的像素宽度基本恒定,不会随镜头距离剧烈变化。

o.pos = UnityObjectToClipPos(v.vertex); float3 viewNormal = normalize(mul((float3x3)UNITY_MATRIX_IT_MV, v.normal)); float2 offset = TransformViewToProjection(viewNormal.xy); o.pos.xy += normalize(offset) * _OutlineWidth * o.pos.w;

这段写法比模型空间直接外扩要多两条指令,性能影响可以忽略,但视觉稳定性提升非常明显,强烈建议所有描边Shader默认使用。

5.2 棱线处描边发虚与深度偏移

硬表面模型存在大量共面结构,比如一个长方体上有多个平贴的装饰面。描边Pass渲染背面时,如果深度写入或者深度测试没有处理好,描边会和模型表面产生深度冲突,表现为棱线处描边“发虚”、闪烁。

推荐的处理方式是在描边Pass里加上着色器指令Offset,让描边几何在深度测试时稍微靠近摄像机一点:

Pass { Name "OUTLINE" Cull Front Offset 1, 1 // 后续渲染代码 }

Offset两个参数分别是深度偏置和最小可分辨深度偏置单位,具体值需要适应项目单位尺度。一个通用做法是先用0.001量级的小数值试,如果模型很大或者很小,再按比例调整。加了Offset之后,描边闪烁和发虚的问题基本能一次解决。

5.3 抗锯齿与描边的配合问题

背面外扩描边有一个天然弱点:它是在几何层面对模型做的轮廓偏移,启用MSAA多重采样抗锯齿时,描边与模型表面的交界处会出现一半覆盖、一半未覆盖的像素,边缘锯齿很明显。

如果你的项目管线里本身就有抗锯齿,建议把描边Pass从几何阶段调整到后处理阶段再做,或者干脆换用Sobel边缘检测这类基于深度/法线的后处理描边方案。不过后处理描边的风格和几何描边不一样,它更依赖深度和法线贴图,对低多边形模型的轮廓还原度有限,不一定适合所有美术风格。实际操作时我做了一个折中:描边先用几何方案获取干净的轮廓,再交给后处理做FXAA级别的抗锯齿平滑,两者叠加后效果比较理想。

5.4 新渲染管线下的注意点

如果项目用的是URP,描边Shader的写法要稍微调整。URP下不能用UnityObjectToClipPos这类内置函数直接搞,建议改用GetVertexPositionInputs和GetVertexNormalInputs来获取顶点坐标和法线数据,然后自己拼裁剪空间偏移。

补充一个URP下的Shader Graph思路:Graph里做Unlit Shader,在主Pass前加一个Render Objects Feature,选取指定Layer,渲染时在材质中走描边Pass。这样不用动原模型的材质,描边逻辑和模型表面逻辑完全解耦,适合团队里美术不熟悉手写Shader的情况。

Shader Graph里实现外扩也很直接,用Normal Vector节点接一个Multiply节点,乘上Outline Width,加到Position节点上,最后连到顶点位置的输出。但注意Shader Graph默认生成的顶点变换里法线偏移是模型空间还是视空间,取决于具体版本和节点的连接方式,生成效果后要拉到Scene里转着相机看一下是否有宽度畸变。

最后再分享一个项目落地心得

这段排查前后花了我差不多一天半的时间,最后总结下来就一句话:硬表面模型的描边断裂问题,九成出在网格数据而不是渲染代码上。我后来在项目里定了一条规矩——所有硬表面资产进引擎之前,在建模阶段要统一平滑角阈值,导出FBX时直接把法线烧进文件里,Unity导入面板始终使用Import模式,不让引擎重新计算法线。如果你现在也正好卡在这个坑里,建议先别急着改Shader,给模型挂上法线可视化脚本看一眼,问题原因基本就暴露出来了。另一个很实用的小技巧是给团队留一个“资产体检”批处理工具,在导入时自动跑一遍顶点分裂和法线重算检查,这样以后任何同事拿新模型进来,都不会再踩同一颗雷。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/14 1:49:00

MFC集成libVLC实现视频局部放大:坐标映射与渲染优化

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/14 1:48:46

企业级定位器选型:API对接能力比硬件参数更关键

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/14 1:48:18

YOLO目标检测实战:从Faster R-CNN选型到Darknet训练避坑指南

简介&#xff1a;这份文档面向深度学习入门者与计算机视觉方向的学生、开发者&#xff0c;系统梳理基于YOLO的目标检测知识体系&#xff0c;帮助读者从算法演进到工程落地建立完整认知。内容涵盖深度学习发展脉络与目标检测关键时间节点&#xff0c;重点讲解Faster R-CNN两阶段…

作者头像 李华
网站建设 2026/10/14 1:47:45

嵌入式自学习模式超时退出机制与串口联动设计

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/14 1:45:40

SpringBoot监控功能

Spring Boot监控 1、Spring Boot actuator 它是Spring Boot 中提供了一个用于监控和管理自身应用信息的模块&#xff0c;帮助我们及时发现应用程序故障 1.初体验 通过集成 actuator 可以将 Spring Boot 应用本身的一些信息通过 HTTP 或 JMX 的方式暴露出去&#xff0c;可以让第…

作者头像 李华
网站建设 2026/10/14 1:45:25

一图带你了解人工智能简史

最原始的关于人工智能的研究&#xff0c;可以追溯到古希腊时期&#xff0c;以哲学家亚里士多德创立形式逻辑这一新学科为代表[1]。 数学家怀特海德&#xff08;Whitehead&#xff09;和罗素&#xff08;Russell&#xff09;在其名著《数学原理》中提出了数理逻辑理论&#xff0…

作者头像 李华