Files
BladeSmith/docs/_archive/plans/2026-01-22-网格质量优化细节.md
T
starmen1265 56cb314799 docs: 归档原 plans 目录,迁移实施清单到 impl/
原 docs/plans/ 下的 10 份混合文档(需求+设计混杂)归档到 docs/_archive/plans/ 作为决策考古用途。hammer-deformation-enhancement 实施清单迁移到 docs/impl/。后续工作产出统一进入 docs/requirements/ 与 docs/design/ 两个清晰分层。

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-08-11 08:57:46 +08:00

25 KiB
Raw Blame History

网格质量优化细节

所属系统: 高真实度锻造系统 文档类型: 技术实现细节 最后更新: 2026-01-22


1. 问题分析

1.1 网格退化现象

在连续锻打过程中,动态网格会逐渐退化:

初始均匀网格(良好):
  /\  /\  /\
 /__\/__\/__\
  /\  /\  /\
 /__\/__\/__\

锤击后(退化):
  /\    /|\    /\  ← 细长三角形(Sliver)
 /__\  /_|_\  /__\

      ^^ 过小 ^^  ← 重叠三角形

问题:
1. 细长三角形:长宽比>10:1
2. 过小三角形:面积<0.01mm²
3. 分布不均:密度相差100倍
4. 法线扭曲:相邻三角形法线夹角>90°

1.2 影响评估

视觉影响

  • 着色异常: 细长三角形法线插值错误 → 表面出现条纹、色块
  • 闪烁问题: 极小三角形像素覆盖不足 → 出现"空洞"或闪烁
  • 边缘锯齿: 边缘三角形拉伸 → 刀刃呈锯齿状

性能影响

  • BVH查询效率下降: 大量小三角形导致遍历深度增加,碰撞检测耗时+50-80%
  • 渲染开销: 顶点处理效率降低,Overdraw增加
  • 内存浪费: 大量无效三角形占用内存

数值稳定性

  • 浮点误差累积: 极小边长导致叉积精度丢失
  • 法线计算错误: 接近共线的三点 → 法线方向不确定
  • 碰撞检测失效: 射线-三角形交点测试不稳定

2. 解决方案:增量式局部优化

2.1 核心思想

传统方法(全局重建):
锤击 → 全网格检测 → 全网格优化 → 重建BVH
  ↓
耗时50-100ms,顶点属性可能丢失

增量优化(局部):
锤击 → 仅检测受影响区域 → 局部优化 → 局部BVH更新
  ↓
耗时5-15ms,保留所有顶点属性

2.2 优化三步法

Step 1: 边翻转(Edge Flip)
┌───────┐     ┌───────┐
│╱      │  →  │      ╲│  改善长宽比
│╱      │     │      ╲│
└───────┘     └───────┘

Step 2: 顶点平滑(Laplacian Smoothing)
●────●────●     ●────●────●
    ╰─╯              │     均匀化分布

Step 3: 自适应重网格(Remeshing)
  /\      /\     /\      /\   合并过大三角形
 /__\    /__\ → /__\    /\__/   细分过小三角形

3. 网格质量度量

3.1 质量指标

/**
 * 三角形质量指标
 */
struct FTriangleQualityMetrics
{
    float Area;              // 面积(mm²)
    float AspectRatio;       // 长宽比(0-1,1=正三角形)
    float MinAngle;          // 最小内角(度)
    float MaxAngle;          // 最大内角(度)
    float EdgeLengthVariance;// 边长方差
};

/**
 * 计算三角形质量
 */
FTriangleQualityMetrics CalculateTriangleQuality(
    const FDynamicMesh3& Mesh,
    int32 TriangleID)
{
    FTriangleQualityMetrics Metrics;

    // 获取三角形顶点
    FIndex3i Tri = Mesh.GetTriangle(TriangleID);
    FVector A = Mesh.GetVertex(Tri.A);
    FVector B = Mesh.GetVertex(Tri.B);
    FVector C = Mesh.GetVertex(Tri.C);

    // 计算边长
    float EdgeAB = FVector::Dist(A, B);
    float EdgeBC = FVector::Dist(B, C);
    float EdgeCA = FVector::Dist(C, A);

    // 计算面积
    FVector AB = B - A;
    FVector AC = C - A;
    Metrics.Area = FVector::CrossProduct(AB, AC).Size() * 0.5f;

    // 计算长宽比
    float MaxEdge = FMath::Max3(EdgeAB, EdgeBC, EdgeCA);
    float MinEdge = FMath::Min3(EdgeAB, EdgeBC, EdgeCA);
    Metrics.AspectRatio = MinEdge / (MaxEdge + 1e-6f);

    // 计算内角(余弦定理)
    Metrics.MaxAngle = FMath::RadiansToDegrees(FMath::Acos(
        FMath::Clamp((EdgeBC*EdgeBC + EdgeCA*EdgeCA - EdgeAB*EdgeAB) / (2.0f * EdgeBC * EdgeCA),
                     -1.0f, 1.0f)));

    // 边长方差
    float MeanEdge = (EdgeAB + EdgeBC + EdgeCA) / 3.0f;
    Metrics.EdgeLengthVariance = (
        FMath::Square(EdgeAB - MeanEdge) +
        FMath::Square(EdgeBC - MeanEdge) +
        FMath::Square(EdgeCA - MeanEdge)
    ) / 3.0f;

    return Metrics;
}

/**
 * 判断三角形是否退化
 */
bool IsDegenerateTriangle(const FTriangleQualityMetrics& Metrics)
{
    return
        Metrics.Area < 0.01f ||           // 面积太小
        Metrics.AspectRatio < 0.1f ||      // 长宽比>10:1
        Metrics.MaxAngle > 150.0f;         // 最大角>150度
}

3.2 质量阈值配置

class ADynamicIronBlock
{
    // 网格质量参数
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Mesh Quality")
    float MinTriangleArea = 0.01f;         // 最小面积(mm²)

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Mesh Quality")
    float MaxTriangleArea = 100.0f;        // 最大面积(mm²)

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Mesh Quality")
    float MinAspectRatio = 0.1f;           // 最小长宽比

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Mesh Quality")
    bool bAutoOptimizeMesh = true;         // 自动优化开关

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Mesh Quality")
    float OptimizationRadius = 1.5f;       // 优化半径倍数(相对于ImpactRadius)
};

4. Step 1: 边翻转优化

4.1 算法原理

翻转前(质量差):
  C
  │╲
  │ ╲
A─┼──B  ← 边AB
  │ ╱
  │╱
  D

质量指标:
- 长宽比 = 0.08(很差)
- 最大角 = 160°

翻转后(改善):
  C
  │╲
  │ ╲
  ╲  ╲   ← 新边CD
   ╲  ╲
    ╲  ╲
     D──A──B  ← 边AB分成两条

质量指标:
- 长宽比 = 0.6(良好)
- 最大角 = 80°

4.2 实现代码

/**
 * 边翻转优化
 * 通过翻转边来改善三角形长宽比
 */
void FlipEdgesToImproveQuality(
    FDynamicMesh3& Mesh,
    const TSet<int32>& AffectedTriangles,
    float MinAspectRatio)
{
    // 收集相关边
    TSet<int32> EdgesToCheck;
    for (int32 TID : AffectedTriangles)
    {
        FIndex3i TriEdges = Mesh.GetTriEdges(TID);
        EdgesToCheck.Add(TriEdges.A);
        EdgesToCheck.Add(TriEdges.B);
        EdgesToCheck.Add(TriEdges.C);
    }

    int32 FlipCount = 0;

    for (int32 EID : EdgesToCheck)
    {
        // 跳过边界边和内部边
        if (!Mesh.IsEdge(EID) || Mesh.IsBoundaryEdge(EID))
            continue;

        // 获取边的两个三角形
        FIndex2i EdgeTris = Mesh.GetEdgeT(EID);
        if (EdgeTris.B == IndexConstants::InvalidID)
            continue;  // 边界边,无法翻转

        // 计算翻转前的质量
        float QualityBefore = CalculateAspectRatio(Mesh, EdgeTris.A) +
                              CalculateAspectRatio(Mesh, EdgeTris.B);

        // 尝试翻转
        FDynamicMesh3::FEdgeFlipInfo FlipInfo;
        EMeshResult Result = Mesh.FlipEdge(EID, FlipInfo);

        if (Result == EMeshResult::Ok)
        {
            // 计算翻转后的质量
            float QualityAfter = CalculateAspectRatio(Mesh, EdgeTris.A) +
                                 CalculateAspectRatio(Mesh, EdgeTris.B);

            // 如果质量没有改善,翻转回去
            if (QualityAfter <= QualityBefore)
            {
                Mesh.FlipEdge(EID, FlipInfo);  // 撤销
            }
            else
            {
                FlipCount++;
            }
        }
    }

    //UE_LOG(LogBladeSmith, Log, TEXT("Flipped %d edges"), FlipCount);
}

/**
 * 计算单个三角形的长宽比
 */
float CalculateAspectRatio(const FDynamicMesh3& Mesh, int32 TriID)
{
    FIndex3i Tri = Mesh.GetTriangle(TriID);
    FVector A = Mesh.GetVertex(Tri.A);
    FVector B = Mesh.GetVertex(Tri.B);
    FVector C = Mesh.GetVertex(Tri.C);

    float EdgeAB = FVector::Dist(A, B);
    float EdgeBC = FVector::Dist(B, C);
    float EdgeCA = FVector::Dist(C, A);

    float MaxEdge = FMath::Max3(EdgeAB, EdgeBC, EdgeCA);
    float MinEdge = FMath::Min3(EdgeAB, EdgeBC, EdgeCA);

    return MinEdge / (MaxEdge + 1e-6f);
}

5. Step 2: 顶点平滑

5.1 Laplacian平滑原理

平滑前:
    ●
   ╱ ╲  不规则分布
  ●   ●

平滑后(每个顶点移向邻居的平均位置):
    ●
   │ ╲  均匀分布
   │  ╲
  ●───●

公式:
V_new = V_old + λ × (Σ(V_neighbor) / N - V_old)

其中:
λ = 平滑系数(0-1)
N = 邻居顶点数量

5.2 实现代码

/**
 * Laplacian顶点平滑
 * 均匀化顶点分布,改善网格质量
 */
void SmoothVertices(
    FDynamicMesh3& Mesh,
    const TSet<int32>& AffectedTriangles,
    int32 Iterations,
    float SmoothFactor)
{
    if (Iterations <= 0)
        return;

    // 收集需要平滑的顶点
    TSet<int32> VerticesToSmooth;
    for (int32 TID : AffectedTriangles)
    {
        FIndex3i Tri = Mesh.GetTriangle(TID);
        VerticesToSmooth.Add(Tri.A);
        VerticesToSmooth.Add(Tri.B);
        VerticesToSmooth.Add(Tri.C);
    }

    // 排除边界顶点(保持形状)
    TSet<int32> InteriorVertices;
    for (int32 VID : VerticesToSmooth)
    {
        if (!Mesh.IsBoundaryVertex(VID))
        {
            InteriorVertices.Add(VID);
        }
    }

    // 迭代平滑
    for (int32 Iter = 0; Iter < Iterations; ++Iter)
    {
        TMap<int32, FVector> NewPositions;

        for (int32 VID : InteriorVertices)
        {
            // 获取邻接顶点
            TArray<int32> Neighbors;
            for (int32 NbrVID : Mesh.VtxVerticesItr(VID))
            {
                Neighbors.Add(NbrVID);
            }

            if (Neighbors.Num() == 0)
                continue;

            // 计算平均位置
            FVector Sum = FVector::ZeroVector;
            for (int32 NbrVID : Neighbors)
            {
                Sum += Mesh.GetVertex(NbrVID);
            }
            FVector AvgPos = Sum / Neighbors.Num();

            // Laplacian平滑
            FVector CurrentPos = Mesh.GetVertex(VID);
            FVector NewPos = FMath::Lerp(CurrentPos, AvgPos, SmoothFactor);

            NewPositions.Add(VID, NewPos);
        }

        // 批量更新位置
        for (const auto& Pair : NewPositions)
        {
            Mesh.SetVertex(Pair.Key, Pair.Value);
        }
    }
}

5.3 加权平滑(保留特征)

/**
 * 特征保留的加权平滑
 * 根据曲率调整平滑强度:高曲率区域少平滑
 */
void WeightedSmoothVertices(
    FDynamicMesh3& Mesh,
    const TSet<int32>& AffectedTriangles,
    float BaseSmoothFactor)
{
    TMap<int32, FVector> NewPositions;

    for (int32 VID : AffectedTriangles)
    {
        if (Mesh.IsBoundaryVertex(VID))
            continue;

        // 计算局部曲率
        float Curvature = CalculateVertexCurvature(Mesh, VID);

        // 高曲率区域平滑系数小(保留锐利特征)
        float AdaptiveSmoothFactor = BaseSmoothFactor / (1.0f + Curvature * 5.0f);

        // ... 其余逻辑同Laplacian平滑
    }
}

/**
 * 计算顶点曲率(简化:基于邻面法线方差)
 */
float CalculateVertexCurvature(const FDynamicMesh3& Mesh, int32 VID)
{
    TArray<int32> Triangles;
    for (int32 TID : Mesh.VtxTrianglesItr(VID))
    {
        Triangles.Add(TID);
    }

    if (Triangles.Num() < 2)
        return 0.0f;

    // 计算平均法线
    FVector AverageNormal = FVector::ZeroVector;
    for (int32 TID : Triangles)
    {
        AverageNormal += Mesh.GetTriNormal(TID);
    }
    AverageNormal /= Triangles.Num();

    // 计算法线方差
    float Variance = 0.0f;
    for (int32 TID : Triangles)
    {
        FVector TriNormal = Mesh.GetTriNormal(TID);
        Variance += FVector::DistSquared(TriNormal, AverageNormal);
    }
    Variance /= Triangles.Num();

    return FMath::Sqrt(Variance);
}

6. Step 3: 自适应重网格

6.1 使用UE5的FRemesher

#include "MeshSimplification.h"
#include "Remesher.h"

/**
 * 局部自适应重网格
 * 使用FRemesher自动细分和合并三角形
 */
void RemeshRegion(
    FDynamicMesh3& Mesh,
    const TSet<int32>& AffectedTriangles,
    float TargetEdgeLength)
{
    // 创建Remesher
    FRemesher Remesher(&Mesh);

    // 配置Remesher
    Remesher.bEnableSplits = true;        // 允许边细分
    Remesher.bEnableCollapses = true;     // 允许边合并
    Remesher.bEnableFlips = true;         // 允许边翻转
    Remesher.bEnableSmoothing = true;     // 允许平滑
    Remesher.bPreventNormalFlips = true;  // 防止法线反转

    // 设置目标边长
    Remesher.SetTargetEdgeLength(TargetEdgeLength);
    Remesher.SetMinEdgeLength(TargetEdgeLength * 0.5f);
    Remesher.SetMaxEdgeLength(TargetEdgeLength * 2.0f);

    // 限制重网格化到受影响区域
    // 方法1: 使用投影约束
    for (int32 TID : AffectedTriangles)
    {
        Remesher.ProjectionTarget.AddTriangle(TID);
    }

    // 执行重网格化(限制迭代次数)
    int32 MaxIterations = 5;
    for (int32 i = 0; i < MaxIterations; ++i)
    {
        Remesher.BasicRemeshPass();

        // 检查是否收敛
        float AvgEdgeLength = CalculateAverageEdgeLength(Mesh, AffectedTriangles);
        if (FMath::Abs(AvgEdgeLength - TargetEdgeLength) < TargetEdgeLength * 0.1f)
        {
            break;
        }
    }
}

/**
 * 计算平均边长
 */
float CalculateAverageEdgeLength(
    const FDynamicMesh3& Mesh,
    const TSet<int32>& Triangles)
{
    float TotalLength = 0.0f;
    int32 Count = 0;

    TSet<int32> ProcessedEdges;

    for (int32 TID : Triangles)
    {
        FIndex3i Edges = Mesh.GetTriEdges(TID);

        for (int32 i = 0; i < 3; ++i)
        {
            int32 EID = Edges[i];
            if (!ProcessedEdges.Contains(EID))
            {
                ProcessedEdges.Add(EID);

                FIndex2i EdgeVerts = Mesh.GetEdgeV(EID);
                FVector V0 = Mesh.GetVertex(EdgeVerts.A);
                FVector V1 = Mesh.GetVertex(EdgeVerts.B);

                TotalLength += FVector::Dist(V0, V1);
                Count++;
            }
        }
    }

    return Count > 0 ? (TotalLength / Count) : 1.0f;
}

6.2 保留顶点属性

重网格化会改变顶点ID,需要重新插值属性:

/**
 * 保存当前顶点属性
 */
TMap<int32, FVertexAttributes> SaveVertexAttributes(const FDynamicMesh3& Mesh)
{
    TMap<int32, FVertexAttributes> AttributeMap;

    for (int32 VID : Mesh.VertexIndicesItr())
    {
        FVertexAttributes Attr;
        Attr.Position = Mesh.GetVertex(VID);
        Attr.Color = Mesh.GetVertexColor(VID);
        // ... 其他属性

        AttributeMap.Add(VID, Attr);
    }

    return AttributeMap;
}

/**
 * 重网格化后插值属性
 */
void RestoreVertexAttributes(
    FDynamicMesh3& Mesh,
    const TMap<int32, FVertexAttributes>& OldAttributes)
{
    // 简化:最近邻插值
    for (int32 NewVID : Mesh.VertexIndicesItr())
    {
        FVector NewPos = Mesh.GetVertex(NewVID);

        // 找最近的旧顶点
        int32 BestVID = IndexConstants::InvalidID;
        float MinDist = TNumericLimits<float>::Max();

        for (const auto& Pair : OldAttributes)
        {
            float Dist = FVector::Dist(NewPos, Pair.Value.Position);
            if (Dist < MinDist)
            {
                MinDist = Dist;
                BestVID = Pair.Key;
            }
        }

        if (BestVID != IndexConstants::InvalidID)
        {
            const FVertexAttributes& OldAttr = OldAttributes[BestVID];
            Mesh.SetVertexColor(NewVID, OldAttr.Color);
            // ... 其他属性
        }
    }
}

更优方案:使用重网格化回调

// FRemesher支持回调,可以在顶点创建/删除时插值属性
Remesher.OnVertexSplit.AddLambda(
    [&](int32 OriginalVID, int32 NewVID, float SplitAlpha)
    {
        // 插值属性
        float Polish0 = PolishAmount->GetValue(OriginalVID);
        float Polish1 = /* 邻接顶点的Polish */;
        PolishAmount->SetValue(NewVID, FMath::Lerp(Polish0, Polish1, SplitAlpha));
    });

7. 完整优化流程

7.1 集成到锤击事件

void ADynamicIronBlock::OnHammerImpact(
    FVector HitLocation,
    FVector HitNormal,
    float ImpactRadius,
    float ImpactDepth,
    float ImpactForce)
{
    FVector LocalHitPos = GetTransform().InverseTransformPosition(HitLocation);

    double StartTime = FPlatformTime::Seconds();

    DynamicMesh->EditMesh([&](FDynamicMesh3& Mesh)
    {
        // ===== 1. 执行形变 =====
        ApplyAnisotropicDeformation(Mesh, LocalHitPos, ImpactRadius, ...);

        // ===== 2. 网格质量优化 =====
        if (bAutoOptimizeMesh)
        {
            // 找到受影响的三角形
            TSet<int32> AffectedTriangles = FindAffectedTriangles(Mesh, LocalHitPos, ImpactRadius);

            if (AffectedTriangles.Num() > 0)
            {
                // Step 1: 边翻转
                FlipEdgesToImproveQuality(Mesh, AffectedTriangles, MinAspectRatio);

                // Step 2: 顶点平滑
                SmoothVertices(Mesh, AffectedTriangles, 2, 0.5f);

                // Step 3: 自适应重网格(每隔几次执行)
                static int32 RemeshCounter = 0;
                if (++RemeshCounter % 5 == 0)
                {
                    float TargetEdgeLength = CalculateAverageEdgeLength(Mesh, AffectedTriangles);
                    RemeshRegion(Mesh, AffectedTriangles, TargetEdgeLength);
                }
            }
        }

        // ===== 3. 更新顶点颜色(温度、打磨度)=====
        UpdateVertexColors(Mesh);
    });

    // ===== 4. 重建BVH =====
    GenerateBVH();

    double ElapsedTime = (FPlatformTime::Seconds() - StartTime) * 1000.0;
    //UE_LOG(LogBladeSmith, Log, TEXT("Hammer impact + optimization: %.2f ms"), ElapsedTime);
}

/**
 * 找到受锤击影响的三角形
 */
TSet<int32> ADynamicIronBlock::FindAffectedTriangles(
    const FDynamicMesh3& Mesh,
    FVector Center,
    float Radius) const
{
    TSet<int32> AffectedTriangles;

    for (int32 TID : Mesh.TriangleIndicesItr())
    {
        FVector Centroid = Mesh.GetTriCentroid(TID);
        if (FVector::Dist(Centroid, Center) < Radius * OptimizationRadius)
        {
            AffectedTriangles.Add(TID);
        }
    }

    return AffectedTriangles;
}

8. 性能分析

8.1 计算复杂度

操作 时间复杂度 空间复杂度 实际耗时(20万顶点)
边翻转 O(E×K) O(E) 3-5ms
顶点平滑 O(N×K×V) O(N) 2-3ms
重网格化 O(E×log E) O(E) 5-10ms
总计 - - 10-18ms

其中:

  • N = 顶点数(20万)
  • E = 边数(约3万)
  • K = 迭代次数(2-5)
  • V = 平均邻接顶点数(6-8)

8.2 优化策略

策略1: 限制优化区域

// 仅优化距离锤击中心 < OptimizationRadius × ImpactRadius 的区域
float OptimizationRadius = 1.5f;  // 仅优化150%影响半径内

// 效果:优化区域减少60%,耗时减少50%

策略2: 降低重网格频率

// 每5次锤击才重网格一次
if (++RemeshCounter % 5 == 0)
{
    RemeshRegion(...);
}

// 效果:平均耗时减少40%

策略3: 质量阈值触发

// 仅当网格质量低于阈值时才优化
float AverageQuality = CalculateAverageMeshQuality(Mesh, AffectedTriangles);
if (AverageQuality < QualityThreshold)
{
    OptimizeMeshQuality(...);
}

// 效果:仅必要时优化,平均耗时减少70%

策略4: 分帧处理

// 将重网格化分散到多帧
if (bPendingRemesh)
{
    RemeshRegion(...);  // 执行一小部分
    if (!IsRemeshComplete())
    {
        // 下一帧继续
        GetWorldTimerManager().SetTimerForNextTick(this, &ThisClass::ContinueRemesh);
    }
}

9. 调试与可视化

9.1 可视化退化三角形

void ADynamicIronBlock::DebugDrawDegenerateTriangles()
{
    if (!DynamicMesh->HasMesh())
        return;

    for (int32 TID : DynamicMesh->TriangleIndicesItr())
    {
        FTriangleQualityMetrics Quality = CalculateTriangleQuality(*DynamicMesh, TID);

        if (IsDegenerateTriangle(Quality))
        {
            // 绘制退化三角形(红色)
            FIndex3i Tri = DynamicMesh->GetTriangle(TID);
            FVector A = GetActorTransform().TransformPosition(DynamicMesh->GetVertex(Tri.A));
            FVector B = GetActorTransform().TransformPosition(DynamicMesh->GetVertex(Tri.B));
            FVector C = GetActorTransform().TransformPosition(DynamicMesh->GetVertex(Tri.C));

            DrawDebugLine(GetWorld(), A, B, FColor::Red, false, 5.0f, 0, 2.0f);
            DrawDebugLine(GetWorld(), B, C, FColor::Red, false, 5.0f, 0, 2.0f);
            DrawDebugLine(GetWorld(), C, A, FColor::Red, false, 5.0f, 0, 2.0f);
        }
    }
}

9.2 可视化网格质量热图

void ADynamicIronBlock::DebugDrawQualityHeatmap()
{
    if (!DynamicMesh->HasMesh())
        return;

    for (int32 TID : DynamicMesh->TriangleIndicesItr())
    {
        if (TID % 10 != 0)  // 稀疏采样
            continue;

        float Quality = CalculateAspectRatio(*DynamicMesh, TID);

        // 颜色映射:蓝色(好)→ 绿色(中)→ 红色(差)
        FColor HeatColor;
        if (Quality > 0.5f)
        {
            HeatColor = FLinearColor::LerpUsingHSV(FColor::Green, FColor::Blue,
                (Quality - 0.5f) / 0.5f).ToFColor();
        }
        else
        {
            HeatColor = FLinearColor::LerpUsingHSV(FColor::Red, FColor::Green,
                Quality / 0.5f).ToFColor();
        }

        FIndex3i Tri = DynamicMesh->GetTriangle(TID);
        FVector Center = GetActorTransform().TransformPosition(DynamicMesh->GetTriCentroid(TID));

        DrawDebugPoint(GetWorld(), Center, 5.0f, HeatColor, false, 5.0f);
    }
}

9.3 性能统计

void ADynamicIronBlock::LogMeshQualityStats()
{
    if (!DynamicMesh->HasMesh())
        return;

    // 统计质量指标
    int32 TotalTriangles = DynamicMesh->TriangleCount();
    int32 DegenerateCount = 0;
    float TotalAspectRatio = 0.0f;
    float TotalArea = 0.0f;

    for (int32 TID : DynamicMesh->TriangleIndicesItr())
    {
        FTriangleQualityMetrics Quality = CalculateTriangleQuality(*DynamicMesh, TID);

        TotalAspectRatio += Quality.AspectRatio;
        TotalArea += Quality.Area;

        if (IsDegenerateTriangle(Quality))
        {
            DegenerateCount++;
        }
    }

    float AvgAspectRatio = TotalAspectRatio / TotalTriangles;
    float DegenerateRatio = (float)DegenerateCount / TotalTriangles;

    UE_LOG(LogBladeSmith, Warning,
        TEXT("Mesh Quality: AvgAspectRatio=%.3f, Degenerate=%.1f%%"),
        AvgAspectRatio,
        DegenerateRatio * 100.0f
    );
}

10. 参数调优指南

10.1 不同场景的推荐参数

锻造平面(大范围形变)

MinTriangleArea = 0.5f;         // 允许较大三角形
MaxTriangleArea = 200.0f;
MinAspectRatio = 0.15f;         // 放宽长宽比要求
OptimizationRadius = 2.0f;      // 大范围优化
SmoothIterations = 2;
RemeshFrequency = 10;           // 低频重网格

锻造边缘(精细形变)

MinTriangleArea = 0.01f;        // 高质量要求
MaxTriangleArea = 50.0f;
MinAspectRatio = 0.2f;          // 严格长宽比
OptimizationRadius = 1.2f;      // 局部优化
SmoothIterations = 3;
RemeshFrequency = 5;            // 高频重网格

性能优先模式

MinTriangleArea = 1.0f;         // 放宽质量要求
MaxTriangleArea = 500.0f;
MinAspectRatio = 0.05f;         // 允许严重退化
OptimizationRadius = 1.0f;
SmoothIterations = 1;
RemeshFrequency = 20;           // 极低频重网格

10.2 自动调参

/**
 * 根据当前帧率自动调整优化强度
 */
void AutoAdjustOptimizationSettings()
{
    float CurrentFPS = GetWorld()->GetDeltaSeconds();

    if (CurrentFPS < 30.0f)
    {
        // 帧率低:降低优化频率
        RemeshFrequency = FMath::Min(RemeshFrequency + 1, 20);
        OptimizationRadius = FMath::Max(OptimizationRadius - 0.1f, 1.0f);
    }
    else if (CurrentFPS > 55.0f)
    {
        // 帧率高:提高质量
        RemeshFrequency = FMath::Max(RemeshFrequency - 1, 3);
        OptimizationRadius = FMath::Min(OptimizationRadius + 0.1f, 2.0f);
    }
}

11. 已知限制与未来改进

11.1 当前限制

  1. 属性插值精度: 重网格化后顶点属性需要插值,可能有精度损失
  2. BVH重建开销: 每次形变后重建BVH仍需5-10ms
  3. 极端形变: 当形变超过50%时,优化效果显著下降
  4. 拓扑变化: 无法处理网格撕裂、融合等拓扑变化

11.2 未来改进方向

  1. 增量式BVH更新: 仅更新受影响区域的BVH节点
  2. GPU加速优化: 将边翻转、平滑放到Compute Shader
  3. 自适应优化频率: 基于退化程度动态调整
  4. 多线程并行: 独立区域可以并行优化
  5. 预测性优化: 在锤击前预测退化区域并提前优化