新增设计

This commit is contained in:
2026-01-23 00:31:21 +08:00
parent f7882875c5
commit 8c27c82856
9 changed files with 4066 additions and 1212 deletions
+2 -1
View File
@@ -10,7 +10,8 @@
"Bash(git commit:*)", "Bash(git commit:*)",
"Bash(\"E:\\MiceCola\\work\\UE_5_6\\Engine\\Build\\BatchFiles\\Build.bat\" BladeSmithEditor Win64 Development -Project=\"C:\\MiceCola\\Indi\\BladeSmith\\BladeSmith.uproject\" -WaitMutex -FromMsBuild)", "Bash(\"E:\\MiceCola\\work\\UE_5_6\\Engine\\Build\\BatchFiles\\Build.bat\" BladeSmithEditor Win64 Development -Project=\"C:\\MiceCola\\Indi\\BladeSmith\\BladeSmith.uproject\" -WaitMutex -FromMsBuild)",
"Bash(cmd /c:*)", "Bash(cmd /c:*)",
"Bash(\"C:\\MiceCola\\Indi\\UE_5.6\\Engine\\Build\\BatchFiles\\Build.bat\" BladeSmithEditor Win64 Development \"-Project=C:\\MiceCola\\Indi\\BladeSmith\\BladeSmith.uproject\" -WaitMutex -FromMsBuild)" "Bash(\"C:\\MiceCola\\Indi\\UE_5.6\\Engine\\Build\\BatchFiles\\Build.bat\" BladeSmithEditor Win64 Development \"-Project=C:\\MiceCola\\Indi\\BladeSmith\\BladeSmith.uproject\" -WaitMutex -FromMsBuild)",
"Skill(superpowers:brainstorming)"
], ],
"deny": [], "deny": [],
"ask": [] "ask": []
+239
View File
@@ -0,0 +1,239 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
BladeSmith is an Unreal Engine 5.6 C++ blacksmith simulation game where players forge bladed weapons through realistic physical processes. The core feature is GPU-accelerated plastic deformation of metal using compute shaders, combined with a voxel-based temperature system that determines material plasticity.
**Engine Version:** UE 5.6
**Language:** C++ with Blueprint integration
**Platform:** Windows (primary development target)
## Build and Development Commands
### Building the Project
From the UE5 engine directory:
```bash
# Build game target
"C:\Program Files\Epic Games\UE_5.6\Engine\Build\BatchFiles\Build.bat" BladeSmith Win64 Development "E:\MiceCola\work\BladeSmith\BladeSmith.uproject"
# Build editor target
"C:\Program Files\Epic Games\UE_5.6\Engine\Build\BatchFiles\Build.bat" BladeSmithEditor Win64 Development "E:\MiceCola\work\BladeSmith\BladeSmith.uproject"
```
Alternatively, open `BladeSmith.sln` in Visual Studio and build from there.
### Running the Project
1. Open `BladeSmith.uproject` in UE5.6 Editor
2. Press Alt+P or click Play to run in editor
3. Use Ctrl+F11 for hot reload after C++ changes
### Testing
Unreal Engine uses the Automation System:
- **In Editor:** Window → Developer Tools → Automation Sessions
- **Command Line:** `UE5Editor.exe BladeSmith.uproject -ExecCmds="Automation RunTests"`
## Architecture Overview
### Core Systems
**1. Dynamic Mesh Deformation System**
- `ADynamicIronBlock` (Source/BladeSmith/DynamicIronBlock.h/cpp) - Main iron block actor that handles all deformation
- Uses UE5's GeometryScripting API for runtime mesh manipulation
- Implements plastic deformation with volume-preserving bulge effects
- BVH acceleration structure for efficient collision detection
**2. Voxel-Based Temperature System**
- 3D temperature voxel grid (`FTemperatureVoxel`) stores temperature and plasticity coefficients
- Temperature affects material plasticity: 800-1200°C is the forging range
- Heat conduction simulation with surface cooling
- Per-vertex color visualization of temperature (black body radiation approximation)
- GPU-accelerated temperature computation using ping-pong buffers
**3. GPU Compute Pipeline (MiceShaders Plugin)**
- Custom compute shaders for physics simulation
- `FDeformComputeShader` - Plastic deformation calculations
- `FTemperatureComputeShader` - Heat conduction and cooling
- Located in `Plugins/MiceShaders/`
- Shaders written in HLSL (`.usf` files in `Shaders/Private/`)
- Uses RDG (Render Dependency Graph) for GPU dispatch
**4. Hammer Interaction System**
- `UBladeSmithWeaponComponent` (Source/BladeSmith/BladeSmithWeaponComponent.h/cpp) - First-person hammer tool
- Ray-cast based collision detection
- Enhanced Input system integration
- Triggers deformation on `ADynamicIronBlock` via `OnHammerImpact()`
### Key Classes and Their Relationships
```
ABladeSmithCharacter (Player)
└── UBladeSmithWeaponComponent (Hammer Tool)
└── OnHammerImpact()
└── ADynamicIronBlock (Iron Workpiece)
├── Temperature Voxel Grid (CPU/GPU)
├── Dynamic Mesh (GeometryScripting)
└── MiceShaders Compute Shaders
```
### Data Flow: Hammer Impact to Deformation
1. Player swings hammer (`UBladeSmithWeaponComponent::SwingHammer()`)
2. Ray-cast detects hit on `ADynamicIronBlock`
3. `OnHammerImpact()` receives: location, normal, radius, depth, force
4. Sample plasticity coefficient from temperature voxels at hit location
5. Apply plastic deformation to mesh vertices via `EditMesh()` lambda
6. Volume-preserving bulge effect applied around impact zone
7. Update vertex colors to visualize temperature
8. GPU compute shader updates temperature distribution
## Module Dependencies
**BladeSmith Module (Source/BladeSmith/BladeSmith.Build.cs):**
- GeometryScriptingCore, GeometryFramework, DynamicMesh
- GeometryCore, GeometryAlgorithms, ModelingComponents
- RenderCore, RHI, Renderer, PhysicsCore
- EnhancedInput
- AudioExtensions, AudioMixer, AudioPlatformConfiguration
- **MiceShaders** (custom plugin)
**MiceShaders Plugin:**
- Core, RenderCore, RHI
- Projects (for shader path resolution)
## Code Conventions
### File Headers
All source files must start with Epic copyright notice (as required by UE5 licensing).
### Naming Conventions
- **Actors:** `AClassName` (e.g., `ADynamicIronBlock`)
- **Components:** `UClassName` (e.g., `UBladeSmithWeaponComponent`)
- **Structs:** `FStructName` (e.g., `FTemperatureVoxel`)
- **Enums:** `EEnumName`
- **Functions:** PascalCase (e.g., `OnHammerImpact()`)
- **Variables:** PascalCase for properties, camelCase for locals
- **Constants:** PascalCase
### Type Usage
- Use UE5 types: `TArray<T>`, `TMap<K,V>`, `TSet<T>`, `FVector`, `FString`, `int32`, `uint8`
- Use `float` by default (not `double`)
- Smart pointers: rely on UObject garbage collection; use `TSharedPtr<T>` for non-UObjects
- String literals: wrap in `TEXT()` macro
### Indentation and Formatting
- **4 spaces** (no tabs)
- **Allman style braces** (opening brace on new line)
- Line length under 120 characters
### Threading and GPU Operations
```cpp
// Atomic flags for thread-safe GPU buffer status
std::atomic<bool> bGPUBuffersReady{false};
// Render thread commands
ENQUEUE_RENDER_COMMAND(CommandName)(
[Params](FRHICommandListImmediate& RHICmdList)
{
// GPU operations here
}
);
```
### Documentation
Comments use Chinese for domain-specific logic (e.g., "锤子组件", "温度体素结构").
## Important Implementation Details
### Temperature-Plasticity Relationship
- Plasticity = 0 below `ForgeTemperatureMin` (800°C by default)
- Plasticity = 1 above `ForgeTemperatureMax` (1200°C by default)
- Linear interpolation between min and max
- Deformation depth is multiplied by plasticity coefficient
### Deformation Parameters
- `FalloffExponent`: Controls edge transition smoothness (default: 2.0)
- `BulgeRatio`: Bulge radius relative to compression zone (default: 0.8)
- `BulgeStrength`: Bulge height intensity (default: 0.5)
- These simulate realistic plastic flow and volume conservation
### Vertex Color Mapping
- Each vertex samples temperature from voxel grid via trilinear interpolation
- Temperature is converted to color using black body radiation approximation
- Updated every frame when temperature changes occur
### GPU Buffer Management
- Uses ping-pong double buffering for temperature compute shader
- `TemperatureBufferA` and `TemperatureBufferB` alternate as read/write buffers
- Separate `PlasticityBuffer` stores derived plasticity coefficients
- `SurfaceVoxelFlagsBuffer` marks surface voxels for cooling calculations
- Buffers created on render thread, accessed via UAV/SRV
## Performance Targets
- **Frame time budget:** <7ms for deformation calculations
- **Target FPS:** 60 FPS during hammer operations
- **Mesh complexity:** Tested with ~100k vertices using GPU acceleration
- **Voxel resolution:** 50x50x50 typical (configurable via `TemperatureVoxelSize`)
## Adding New Compute Shaders
1. Create `.h` and `.cpp` in `Plugins/MiceShaders/Source/MiceShaders/Public|Private/`
2. Define shader class inheriting from `FGlobalShader`
3. Create corresponding `.usf` HLSL shader in `Plugins/MiceShaders/Shaders/Private/`
4. Use `IMPLEMENT_GLOBAL_SHADER()` macro to register
5. Dispatch via RDG in actor's Tick or specialized function
6. Reference existing shaders: `FDeformComputeShader`, `FTemperatureComputeShader`
## Key UE5 Patterns Used
**Dynamic Mesh Editing:**
```cpp
DynamicMesh->EditMesh([&](FDynamicMesh3& EditMesh)
{
// Modify mesh vertices/triangles here
}, EDynamicMeshChangeType::GeneralEdit);
```
**Material Parameter Updates:**
```cpp
DynamicMaterialInstance->SetScalarParameterValue(TEXT("AvgTemperature"), AvgTemp);
DynamicMaterialInstance->SetVectorParameterValue(TEXT("Color"), FLinearColor(R,G,B,A));
```
**Enhanced Input Binding:**
```cpp
if (UEnhancedInputComponent* EnhancedInput = Cast<UEnhancedInputComponent>(InputComponent))
{
EnhancedInput->BindAction(HammerAction, ETriggerEvent::Triggered, this, &ThisClass::SwingHammer);
}
```
## Plugin Structure
**Enabled Plugins (BladeSmith.uproject):**
- GeometryScripting (UE5 built-in)
- ModelingToolsEditorMode (UE5 built-in)
- HDRIBackdrop (UE5 built-in)
- RiderLink (in repository, for JetBrains Rider integration)
- MiceShaders (custom plugin)
## Content Organization
- `Content/BP/` - Blueprint actors and components
- `Content/Res/` - Game resources (meshes, materials, textures)
- `Content/Maps/` - Level maps
- `Content/Input/` - Enhanced Input assets
- `Content/FirstPersonArms/`, `Content/FPWeapon/` - First-person character assets
## Notes for AI Agents
- The AGENTS.md file contains comprehensive coding style guidelines; refer to it for detailed conventions
- When modifying deformation logic, always consider the temperature-plasticity coupling
- GPU buffer operations must be thread-safe; use atomic flags and render thread commands
- Test deformation with varying temperatures to ensure plasticity affects results correctly
- Voxel resolution trades off between accuracy and performance; adjust `TemperatureVoxelSize` accordingly
@@ -1,488 +0,0 @@
# 顶点变形质量改善设计
**日期**: 2026-01-20
**关联计划**: graceful-swimming-rainbow.md(塑性变形体素系统)
**问题**: 三角形顶点不规则位移导致布线与UV质量下降
---
## 问题陈述
在当前的塑性变形系统设计中,顶点直接从体素位移场采样(三线性插值)并应用到网格。这种独立采样会导致:
1. **三角形退化**:顶点各自移动,可能导致三角形翻转、极扁平或自相交
2. **UV拉伸/撕裂**:UV坐标固定但3D位置大幅移动,纹理严重扭曲
3. **布线质量下降**:原本规则的网格流变得混乱
### 项目约束
- **性能预算**:<2ms 额外开销
- **UV要求**:必须保持连续性,纹理细节(划痕、锈迹)需跟随表面变形
- **网格精度**:0.5mm初始精度,高面数模型(~10万顶点)
- **拓扑**:优先保持原始拓扑,可选动态细分
---
## 方案选择
### 备选方案
**方案A:拉普拉斯平滑 + 边长保护** ✅ 已选择
- 性能:~1.6ms
- UV质量:中上
- 实现复杂度:低
**方案B:分区域刚性约束(Rigid Clusters)**
- 性能:~1.5-2ms
- UV质量:高
- 实现复杂度:高
**方案C:简化版 - 仅边长约束**
- 性能:~0.5-0.8ms
- UV质量:中
- 实现复杂度:最低
### 选择理由
方案A在性能、质量和复杂度之间取得最佳平衡:
- 性能可靠(远低于2ms预算)
- UV质量足够(轻微扭曲在铁匠场景中可接受)
- 实现简单,可快速验证
- 易于扩展(未来可叠加其他约束)
---
## 架构设计
### 整体流程
```
现有流程:
体素位移场 → 三线性插值采样 → 直接应用到顶点 → 重算法线
新流程:
体素位移场 → 三线性插值采样 → 【质量改善阶段】 → 应用到顶点 → 重算法线
↓
1. 边长约束检查
2. 拉普拉斯平滑(2-3次迭代)
3. 再次边长约束
```
### 执行顺序理由
**"约束 → 平滑 → 约束"三明治结构:**
1. **第一次约束**:修复体素采样产生的极端拉伸/压缩
2. **平滑**:改善整体拓扑流畅性,但可能引入新的小幅违规
3. **第二次约束**:清理平滑阶段带来的边界效应
---
## 数据结构设计
### 新增成员变量(DynamicIronBlock.h)
```cpp
// ========== 网格质量改善数据 ==========
// 边长约束参数
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Deformation Quality")
float MaxEdgeStretchRatio = 1.35f; // 边最大拉伸比(135%)
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Deformation Quality")
float MaxEdgeCompressRatio = 0.70f; // 边最大压缩比(70%)
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Deformation Quality")
int32 EdgeConstraintIterations = 4; // 边长约束迭代次数
// 拉普拉斯平滑参数
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Deformation Quality")
float LaplacianSmoothingWeight = 0.45f; // 平滑强度(0-1)
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Deformation Quality")
int32 LaplacianIterations = 2; // 平滑迭代次数
// 是否启用质量改善
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Deformation Quality")
bool bEnableDeformationQualityImprovement = true;
// 缓存:原始边长(用于约束检查)
TMap<FEdgeID, float> OriginalEdgeLengths;
```
### 内存开销
- 假设10万条边 → 100k × 8字节 = **~780KB**
- 在可接受范围内
---
## 算法实现
### 函数1:边长约束(ConstrainEdgeLengths)
**职责**:遍历所有边,修复超出拉伸/压缩阈值的边
```cpp
void ADynamicIronBlock::ConstrainEdgeLengths(
FDynamicMesh3& EditMesh,
TArray<FVector3d>& Positions)
{
for (int iter = 0; iter < EdgeConstraintIterations; iter++)
{
int32 ViolationCount = 0;
for (FEdgeID EdgeID : EditMesh.EdgeIndicesItr())
{
FIndex2i EdgeVerts = EditMesh.GetEdgeV(EdgeID);
FVertexID V0 = EdgeVerts.A;
FVertexID V1 = EdgeVerts.B;
FVector3d Pos0 = Positions[V0];
FVector3d Pos1 = Positions[V1];
float CurrentLength = (Pos1 - Pos0).Length();
float OriginalLength = OriginalEdgeLengths[EdgeID];
float Ratio = CurrentLength / OriginalLength;
// 检查是否违反约束
if (Ratio > MaxEdgeStretchRatio || Ratio < MaxEdgeCompressRatio)
{
ViolationCount++;
// 计算目标长度(限制在允许范围内)
float TargetLength = OriginalLength * FMath::Clamp(
Ratio,
MaxEdgeCompressRatio,
MaxEdgeStretchRatio
);
// 调整两端顶点(各承担50%)
FVector3d EdgeDir = (Pos1 - Pos0).GetSafeNormal();
FVector3d EdgeCenter = (Pos0 + Pos1) * 0.5;
Positions[V0] = EdgeCenter - EdgeDir * (TargetLength * 0.5);
Positions[V1] = EdgeCenter + EdgeDir * (TargetLength * 0.5);
}
}
// 如果没有违反约束,提前退出
if (ViolationCount == 0) break;
}
}
```
**关键设计点:**
- 从边的中心向两端调整(避免偏向某一端)
- 迭代式收敛(通常2-3次足够)
- 早停机制(无违规则提前结束)
---
### 函数2:拉普拉斯平滑(LaplacianSmoothDeformation)
**职责**:将每个顶点向其邻居的重心移动,改善拓扑流畅性
```cpp
void ADynamicIronBlock::LaplacianSmoothDeformation(
FDynamicMesh3& EditMesh,
TArray<FVector3d>& Positions)
{
TArray<FVector3d> SmoothedPositions = Positions; // 复制避免顺序依赖
for (FVertexID VertexID : EditMesh.VertexIndicesItr())
{
// 获取one-ring邻居
TArray<FVertexID> Neighbors;
EditMesh.GetVtxNbrVerts(VertexID, Neighbors);
if (Neighbors.Num() == 0) continue;
// 计算邻居重心
FVector3d Centroid = FVector3d::Zero();
for (FVertexID NeighborID : Neighbors)
{
Centroid += Positions[NeighborID];
}
Centroid /= (double)Neighbors.Num();
// 向重心移动(加权混合)
float Weight = LaplacianSmoothingWeight;
// 边界顶点使用更低权重(避免收缩)
if (EditMesh.IsBoundaryVertex(VertexID))
{
Weight *= 0.5f;
}
SmoothedPositions[VertexID] = FMath::Lerp(
Positions[VertexID],
Centroid,
Weight
);
}
Positions = SmoothedPositions;
}
```
**关键设计点:**
- 使用临时数组避免顺序依赖(雅可比迭代)
- 权重0.45是经验值(平衡平滑效果与细节保留)
- 边界顶点降低权重(防止边界收缩)
---
## 集成到现有流程
### 修改 UpdateVertexDeformation()
在 `DynamicIronBlock.cpp` 中修改(原计划第368-400行):
```cpp
void ADynamicIronBlock::UpdateVertexDeformation()
{
UDynamicMeshComponent* DynMeshComp = GetDynamicMeshComponent();
if (!DynMeshComp) return;
UDynamicMesh* DynMesh = DynMeshComp->GetDynamicMesh();
if (!DynMesh) return;
// 修改网格顶点
DynMesh->EditMesh([&](FDynamicMesh3& EditMesh)
{
// === 阶段1:从体素场采样位移 ===
TArray<FVector3d> NewPositions;
NewPositions.SetNum(EditMesh.MaxVertexID());
for (int32 VertexID : EditMesh.VertexIndicesItr())
{
FVector3d OriginalPos = FVector3d(OriginalVertexPositions[VertexID]);
FVector Displacement = SampleDisplacementAtPosition(FVector(OriginalPos));
NewPositions[VertexID] = OriginalPos + FVector3d(Displacement);
}
// === 阶段2:质量改善(可选开关) ===
if (bEnableDeformationQualityImprovement)
{
// 2.1 第一次边长约束
ConstrainEdgeLengths(EditMesh, NewPositions);
// 2.2 拉普拉斯平滑(迭代2-3次)
for (int i = 0; i < LaplacianIterations; i++)
{
LaplacianSmoothDeformation(EditMesh, NewPositions);
}
// 2.3 第二次边长约束(清理平滑引入的新违规)
ConstrainEdgeLengths(EditMesh, NewPositions);
}
// === 阶段3:应用最终位置 ===
for (int32 VertexID : EditMesh.VertexIndicesItr())
{
EditMesh.SetVertex(VertexID, NewPositions[VertexID]);
}
}, EDynamicMeshChangeType::GeneralEdit, EDynamicMeshAttributeChangeFlags::Unknown, false);
// 重新计算法线
FGeometryScriptCalculateNormalsOptions NormalOp;
UGeometryScriptLibrary_MeshNormalsFunctions::RecomputeNormals(DynMesh, NormalOp);
DynMeshComp->NotifyMeshUpdated();
}
```
### 初始化修改
在 `InitializeBoxMesh()` 中添加原始边长缓存:
```cpp
// 保存原始顶点位置后,缓存原始边长
OriginalEdgeLengths.Empty();
DynMesh->EditMesh([&](FDynamicMesh3& EditMesh)
{
for (FEdgeID EdgeID : EditMesh.EdgeIndicesItr())
{
FVector3d V0 = EditMesh.GetVertex(EditMesh.GetEdgeV(EdgeID).A);
FVector3d V1 = EditMesh.GetVertex(EditMesh.GetEdgeV(EdgeID).B);
float Length = (V1 - V0).Length();
OriginalEdgeLengths.Add(EdgeID, Length);
}
});
```
---
## 性能优化策略
### 优化1:边界顶点特殊处理 ✅ 已集成
降低边界顶点的平滑权重,防止边界收缩。
**性能影响**:可忽略
---
### 优化2:脏标记系统(未来扩展)
**当前**:每次锤击处理整个网格
**优化**:只处理变形区域附近的顶点
```cpp
TSet<FVertexID> DirtyVertices; // 受影响的顶点集合
void MarkDirtyVerticesInRadius(FVector ImpactCenter, float Radius)
{
// 标记半径内的所有顶点为"脏"
}
// 在质量改善阶段只处理脏顶点
```
**预期收益**:如果变形区域<30%网格,性能提升2-3倍
**实现优先级**:低(等性能瓶颈出现再优化)
---
### 优化3:并行化(可选)
使用 `ParallelFor` 并行处理独立的边组。
**复杂度**:高(需要边着色算法分组)
**收益**:约1.5-2倍加速
**建议**:先验证串行版本,性能不足再考虑
---
### 性能监控
添加分段计时日志:
```cpp
double T0 = FPlatformTime::Seconds();
// 采样位移
double T1 = FPlatformTime::Seconds();
UE_LOG(LogTemp, Warning, TEXT("Displacement Sampling: %.2f ms"), (T1-T0)*1000);
ConstrainEdgeLengths(...);
double T2 = FPlatformTime::Seconds();
UE_LOG(LogTemp, Warning, TEXT("Edge Constraints: %.2f ms"), (T2-T1)*1000);
// ... 其他阶段
```
---
## 测试验证
### 单元测试
**测试1:边长约束正确性**
- 创建简单网格,人为拉伸某条边到200%
- 验证:拉伸比被限制到135%,迭代次数<5
**测试2:拉普拉斯平滑效果**
- 随机扰动顶点位置(±5cm噪声)
- 验证:扰动幅度降低>60%
---
### 集成测试
**测试3:冷态锤击(塑性=0)**
- 验证:顶点位移<0.1mm,质量改善不引入额外变形
**测试4:热态锤击(塑性=1)**
- 验证:
- 冲击点凹陷深度>2mm
- 边长变化率<35%
- 无三角形翻转
- UV纹理连续
**测试5:连续锤击压力测试**
- 同一位置连续锤击10次
- 验证:每次耗时<7ms,无性能衰减
---
### 视觉验证
**测试6:UV完整性检查**
- 使用棋盘格纹理
- 验证:变形后棋盘格连续(无撕裂/重叠)
**测试7:纹理细节跟随测试**
- 绘制划痕纹理
- 验证:划痕跟随表面变形,不发生偏移
---
### 性能基准测试
**目标配置:**
- 网格:10万顶点,20万三角形
- 体素分辨率:50×50×50
- 锤击影响半径:5cm
**目标指标:**
- 边长约束:<1.5ms
- 拉普拉斯平滑:<1.5ms
- 总耗时:<7ms(满足60fps)
**降级策略:**
1. `LaplacianIterations` 从2降到1
2. `EdgeConstraintIterations` 从4降到3
3. 启用脏标记系统
---
## 实现清单
### 文件修改
**DynamicIronBlock.h**
- 添加成员变量:`OriginalEdgeLengths` 及质量改善参数
- 添加函数声明:`ConstrainEdgeLengths()`, `LaplacianSmoothDeformation()`
**DynamicIronBlock.cpp**
- 实现两个质量改善函数
- 修改 `UpdateVertexDeformation()` 添加质量改善阶段
- 修改 `InitializeBoxMesh()` 缓存原始边长
---
## 风险与缓解
### 风险1:性能超出预算
**缓解**:
- 降低迭代次数
- 实现脏标记系统(局部处理)
- 添加性能监控日志,定位瓶颈
---
### 风险2:平滑过度导致细节丢失
**缓解**:
- 暴露所有参数到Blueprint,实时调整
- 降低 `LaplacianSmoothingWeight`
- 减少 `LaplacianIterations`
---
### 风险3:边界收缩或扭曲
**缓解**:
- 已实现边界顶点特殊处理(降低权重)
- 可选:完全跳过边界顶点的平滑
---
## 总结
本设计通过在体素位移采样后添加三步质量改善流程(约束→平滑→约束),在合理的性能预算(~2ms)内显著改善了顶点变形的拓扑和UV质量,满足了铁匠锻造场景中纹理细节跟随表面的需求。
设计优先保持实现简单性和可扩展性,未来可根据实际需求叠加更多约束(如法线保持、体积守恒等)。
@@ -0,0 +1,684 @@
# 刀刃形成技术细节
**所属系统**: 高真实度锻造系统
**文档类型**: 技术实现细节
**最后更新**: 2026-01-22
---
## 1. 问题分析
### 1.1 核心难点
锻造刀刃时需要:
1. **材料沿边缘方向流动**(而非均匀扩散)
2. **边缘保持平整锐利**(而非波浪状凸起)
3. **厚度逐渐减小**(形成薄片状结构)
当前系统的球形凸起(BulgeRatio)只能处理块状形变,无法满足刀刃形成的方向性需求。
### 1.2 真实锻造物理过程
```
初始状态:圆柱形铁块
███████████
███████████
第一次锻打:
▼锤击
███████████ ← 受击处变扁
█████████████ ← 材料向侧面流动
多次锻打:
██ ← 继续变薄
███████
███████████ ← 逐渐形成锥形
最终成刃:
▲ ← 边缘平整锐利
█████
████████
```
关键:材料要**优先沿着边缘方向流动**(垂直于锤击方向 + 朝向边缘)。
---
## 2. 技术方案:各向异性材料流动
### 2.1 核心思想
将凸起从**球形**改为**椭球形**,长轴沿着"最小阻力方向"(边缘方向)。
### 2.2 方向检测算法
```cpp
/**
* 查找最近的自由边界方向
* 材料会优先朝向自由边界流动(阻力最小)
*/
FVector FindNearestFreeEdgeDirection(
const FDynamicMesh3& Mesh,
const FVector& LocalPos,
const FVector& BoxHalfSize)
{
// 计算到6个面的距离
float DistToEdges[6] = {
BoxHalfSize.X + LocalPos.X, // -X面
BoxHalfSize.X - LocalPos.X, // +X面
BoxHalfSize.Y + LocalPos.Y, // -Y面
BoxHalfSize.Y - LocalPos.Y, // +Y面
BoxHalfSize.Z + LocalPos.Z, // -Z面
BoxHalfSize.Z - LocalPos.Z // +Z面
};
// 找最近的边缘
int32 NearestEdge = 0;
float MinDist = DistToEdges[0];
for (int32 i = 1; i < 6; ++i)
{
if (DistToEdges[i] < MinDist)
{
MinDist = DistToEdges[i];
NearestEdge = i;
}
}
// 返回朝向最近边缘的方向
static const FVector EdgeDirections[6] = {
FVector(-1, 0, 0), FVector(1, 0, 0),
FVector(0, -1, 0), FVector(0, 1, 0),
FVector(0, 0, -1), FVector(0, 0, 1)
};
return EdgeDirections[NearestEdge];
}
/**
* 计算材料流动的主方向
* 主方向 = 朝向边缘 + 垂直于锤击方向
*/
FVector3f CalculatePrimaryFlowDirection(
const FVector& HammerDir, // 锤击方向(通常是-Z)
const FVector& ToNearestEdge) // 朝向最近边缘的方向
{
// 移除锤击方向的分量(材料不能向锤头方向流动)
FVector3f PrimaryFlow = FVector3f(ToNearestEdge) -
FVector3f(HammerDir) * FVector::DotProduct(ToNearestEdge, HammerDir);
return PrimaryFlow.GetSafeNormal();
}
/**
* 计算次要流动方向(垂直于主方向和锤击方向)
*/
FVector3f CalculateSecondaryFlowDirection(
const FVector& HammerDir,
const FVector& PrimaryFlow)
{
return FVector3f::CrossProduct(FVector3f(HammerDir), PrimaryFlow).GetSafeNormal();
}
```
### 2.3 各向异性凸起计算
```cpp
/**
* 应用各向异性材料流动
* @param LocalHitPos 锤击位置(局部空间)
* @param ImpactRadius 锤击影响半径
* @param HammerDir 锤击方向
* @param AnisotropyRatio 各向异性比(主方向/次方向的流动强度比)
*/
void ApplyAnisotropicDeformation(
FDynamicMesh3& Mesh,
const FVector& LocalHitPos,
float ImpactRadius,
const FVector& HammerDir,
float AnisotropyRatio, // 默认3.0,主方向流动强度是次方向的3倍
float BulgeStrength)
{
// 计算流动方向
FVector ToNearestEdge = FindNearestFreeEdgeDirection(Mesh, LocalHitPos, BoxHalfSize);
FVector3f PrimaryFlow = CalculatePrimaryFlowDirection(HammerDir, ToNearestEdge);
FVector3f SecondaryFlow = CalculateSecondaryFlowDirection(HammerDir, PrimaryFlow);
// 对每个顶点应用形变
for (int32 VID : Mesh.VertexIndicesItr())
{
FVector VertexPos = Mesh.GetVertex(VID);
float Distance = FVector::Dist(VertexPos, LocalHitPos);
if (Distance < ImpactRadius)
{
// 将位置投影到流动方向
float DistAlongPrimary = FVector::DotProduct(
FVector(VertexPos - LocalHitPos), PrimaryFlow);
float DistAlongSecondary = FVector::DotProduct(
FVector(VertexPos - LocalHitPos), SecondaryFlow);
// 各向异性距离计算
// 主方向的"有效距离"更小(更容易到达)→ 凸起更大
float EffectiveDistance = FMath::Sqrt(
FMath::Square(DistAlongPrimary / AnisotropyRatio) +
FMath::Square(DistAlongSecondary)
);
float DistanceRatio = EffectiveDistance / ImpactRadius;
if (DistanceRatio < 1.0f)
{
// 使用FalloffExponent控制边缘过渡
float FalloffExponent = 2.0f;
float BulgeFactor = BulgeStrength *
FMath::Pow(1.0f - DistanceRatio, FalloffExponent);
// 凸起方向 = 锤击方向(材料被挤压向外)
FVector Displacement = HammerDir * (-BulgeFactor);
// 应用形变
Mesh.SetVertex(VID, VertexPos + Displacement);
}
}
}
}
```
### 2.4 参数说明
| 参数 | 默认值 | 范围 | 效果 |
|------|--------|------|------|
| **AnisotropyRatio** | 3.0 | 1.0-10.0 | 主方向流动强度比。越大,边缘拉伸越明显 |
| **BulgeStrength** | 0.5 | 0.1-2.0 | 凸起高度。需要配合ImpactRadius调整 |
| **ImpactRadius** | 5.0 | 1.0-20.0 | 锤击影响半径(cm) |
| **FalloffExponent** | 2.0 | 1.0-4.0 | 边缘过渡平滑度。越大,边界越锐利 |
**调参建议**:
- **锻造平面**: AnisotropyRatio = 1.0(均匀流动)
- **锻造边缘**: AnisotropyRatio = 3.0-5.0(明显方向性)
- **精细整形**: AnisotropyRatio = 8.0-10.0(极端定向)
---
## 3. 边缘平整约束(可选增强)
### 3.1 问题
即使有各向异性流动,边缘仍可能有轻微波纹:
```
理想边缘: ▲
████
██████
实际边缘: ▲▲▲
████
██████ ← 波纹状
```
### 3.2 解决方案:边缘顶点几何约束
```cpp
/**
* 检测并标记边缘顶点
* 距离任何边界< EdgeThreshold 的顶点
*/
void MarkEdgeVertices(
FDynamicMesh3& Mesh,
TSet<int32>& OutEdgeVertices,
float EdgeThreshold = 0.5f) // 5mm
{
FVector BoxHalfSize = GetBoxHalfSize();
for (int32 VID : Mesh.VertexIndicesItr())
{
FVector Pos = Mesh.GetVertex(VID);
// 检查是否接近任何边界
bool bNearEdge = false;
bNearEdge |= FMath::Abs(Pos.X - BoxHalfSize.X) < EdgeThreshold;
bNearEdge |= FMath::Abs(Pos.X + BoxHalfSize.X) < EdgeThreshold;
bNearEdge |= FMath::Abs(Pos.Y - BoxHalfSize.Y) < EdgeThreshold;
bNearEdge |= FMath::Abs(Pos.Y + BoxHalfSize.Y) < EdgeThreshold;
bNearEdge |= FMath::Abs(Pos.Z - BoxHalfSize.Z) < EdgeThreshold;
bNearEdge |= FMath::Abs(Pos.Z + BoxHalfSize.Z) < EdgeThreshold;
if (bNearEdge)
{
OutEdgeVertices.Add(VID);
}
}
}
/**
* 对边缘顶点应用线性约束
* 形变后将边缘顶点投影到拟合直线上,保持平整
*/
void ApplyEdgeLinearConstraint(
FDynamicMesh3& Mesh,
const TSet<int32>& EdgeVertices)
{
for (int32 VID : EdgeVertices)
{
if (!Mesh.IsVertex(VID))
continue;
// 找到此顶点的边缘邻居
TArray<int32> EdgeNeighbors;
for (int32 NbrVID : Mesh.VtxVerticesItr(VID))
{
if (EdgeVertices.Contains(NbrVID))
{
EdgeNeighbors.Add(NbrVID);
}
}
if (EdgeNeighbors.Num() >= 2)
{
// 使用主成分分析(PCA)拟合直线
FVector CurrentPos = Mesh.GetVertex(VID);
FVector LineOrigin, LineDirection;
FitLineToPoints(Mesh, EdgeNeighbors, LineOrigin, LineDirection);
// 投影到拟合线上
FVector ProjectedPos = ProjectPointToLine(CurrentPos, LineOrigin, LineDirection);
// 稍微平滑更新(避免突变)
FVector SmoothPos = FMath::Lerp(CurrentPos, ProjectedPos, 0.5f);
Mesh.SetVertex(VID, SmoothPos);
}
}
}
/**
* 使用PCA拟合直线到点集
*/
void FitLineToPoints(
const FDynamicMesh3& Mesh,
const TArray<int32>& VertexIDs,
FVector& OutOrigin,
FVector& OutDirection)
{
// 计算质心
FVector Centroid = FVector::ZeroVector;
for (int32 VID : VertexIDs)
{
Centroid += Mesh.GetVertex(VID);
}
Centroid /= VertexIDs.Num();
// 计算协方差矩阵
float CovXX = 0, CovYY = 0, CovZZ = 0;
float CovXY = 0, CovXZ = 0, CovYZ = 0;
for (int32 VID : VertexIDs)
{
FVector P = Mesh.GetVertex(VID) - Centroid;
CovXX += P.X * P.X;
CovYY += P.Y * P.Y;
CovZZ += P.Z * P.Z;
CovXY += P.X * P.Y;
CovXZ += P.X * P.Z;
CovYZ += P.Y * P.Z;
}
// 构建协方差矩阵
FMatrix33 CovMatrix(
FVector3f(CovXX, CovXY, CovXZ),
FVector3f(CovXY, CovYY, CovYZ),
FVector3f(CovXZ, CovYZ, CovZZ)
);
// 计算特征向量(主方向 = 最大特征值对应的向量)
FVector3f EigenValues;
FMatrix33 EigenVectors;
FMatrix33::EigenDecomposition(CovMatrix, EigenValues, EigenVectors);
// 最大特征值对应的列向量是主方向
OutOrigin = Centroid;
OutDirection = FVector(EigenVectors.GetColumn(0)); // 第一主成分
}
/**
* 将点投影到直线上
*/
FVector ProjectPointToLine(
const FVector& Point,
const FVector& LineOrigin,
const FVector& LineDirection)
{
FVector ToPoint = Point - LineOrigin;
float ProjectionLength = FVector::DotProduct(ToPoint, LineDirection);
return LineOrigin + LineDirection * ProjectionLength;
}
```
### 3.3 完整锤击流程
```cpp
void ADynamicIronBlock::OnHammerImpact(
FVector HitLocation,
FVector HitNormal,
float ImpactRadius,
float ImpactDepth,
float ImpactForce)
{
FVector LocalHitPos = GetTransform().InverseTransformPosition(HitLocation);
FVector HammerDir = GetTransform().InverseTransformPosition(HitNormal);
DynamicMesh->EditMesh([&](FDynamicMesh3& Mesh)
{
// 1. 应用各向异性形变
ApplyAnisotropicDeformation(
Mesh,
LocalHitPos,
ImpactRadius,
HammerDir,
AnisotropyRatio,
BulgeStrength
);
// 2. (可选)边缘平整约束
if (bEnableEdgeConstraint)
{
TSet<int32> EdgeVertices;
MarkEdgeVertices(Mesh, EdgeVertices, EdgeThreshold);
// 只处理受影响区域的边缘顶点
TSet<int32> AffectedEdges;
for (int32 VID : EdgeVertices)
{
FVector Pos = Mesh.GetVertex(VID);
if (FVector::Dist(Pos, LocalHitPos) < ImpactRadius * 1.5f)
{
AffectedEdges.Add(VID);
}
}
ApplyEdgeLinearConstraint(Mesh, AffectedEdges);
}
});
}
```
---
## 4. 自适应网格细分(可选高级功能)
### 4.1 目的
刀刃区域需要更高密度才能表现锐利边缘:
```
低密度(5万顶点):
▲
████ ← 锯齿状,不平整
高密度(200万顶点):
▲
████ ← 平滑锐利
██████
```
### 4.2 曲率检测与细分
```cpp
/**
* 检测高曲率边缘并细分
*/
void SubdivideHighCurvatureEdges(
FDynamicMesh3& Mesh,
float CurvatureThreshold, // 曲率阈值
float MaxEdgeLength, // 最大边长
int32 MaxSubdivisions) // 最大细分次数
{
int32 SubdivisionCount = 0;
for (int32 EID : Mesh.EdgeIndicesItr())
{
if (SubdivisionCount >= MaxSubdivisions)
break;
// 跳过边界边
if (Mesh.IsBoundaryEdge(EID))
continue;
FIndex2i EdgeVerts = Mesh.GetEdgeV(EID);
FVector V0 = Mesh.GetVertex(EdgeVerts.A);
FVector V1 = Mesh.GetVertex(EdgeVerts.B);
float EdgeLength = FVector::Dist(V0, V1);
// 边太长或曲率太高 → 细分
float EdgeCurvature = CalculateEdgeCurvature(Mesh, EID);
if (EdgeLength > MaxEdgeLength && EdgeCurvature > CurvatureThreshold)
{
// 细分此边
FDynamicMesh3::FEdgeSplitInfo SplitInfo;
EMeshResult Result = Mesh.SplitEdge(EID, SplitInfo);
if (Result == EMeshResult::Ok)
{
// 新顶点位置 = 中点
FVector MidPoint = (V0 + V1) * 0.5f;
Mesh.SetVertex(SplitInfo.NewVertex, MidPoint);
// 插值顶点属性
InterpolateVertexAttributes(Mesh, EdgeVerts.A, EdgeVerts.B, SplitInfo.NewVertex);
SubdivisionCount++;
}
}
}
}
/**
* 计算边的曲率(基于邻面法线夹角)
*/
float CalculateEdgeCurvature(const FDynamicMesh3& Mesh, int32 EdgeID)
{
FIndex2i EdgeTris = Mesh.GetEdgeT(EdgeID);
// 边界边曲率为0
if (EdgeTris.B == IndexConstants::InvalidID)
return 0.0f;
// 获取两个三角形的法线
FVector Normal0 = Mesh.GetTriNormal(EdgeTris.A);
FVector Normal1 = Mesh.GetTriNormal(EdgeTris.B);
// 法线夹角 = 曲率度量
float Dot = FVector::DotProduct(Normal0, Normal1);
return FMath::Acos(Dot); // 返回弧度
}
/**
* 插值顶点属性
* 确保新顶点继承正确的材料状态
*/
void InterpolateVertexAttributes(
FDynamicMesh3& Mesh,
int32 VID0,
int32 VID1,
int32 NewVID)
{
// 位置(已由SplitEdge处理)
// 初始位置
FVector InitPos0 = InitialPositions->GetValue(VID0);
FVector InitPos1 = InitialPositions->GetValue(VID1);
InitialPositions->SetValue(NewVID, (InitPos0 + InitPos1) * 0.5f);
// 打磨度
float Polish0 = PolishAmount->GetValue(VID0);
float Polish1 = PolishAmount->GetValue(VID1);
PolishAmount->SetValue(NewVID, (Polish0 + Polish1) * 0.5f);
// 累积应变
float Strain0 = AccumulatedStrain->GetValue(VID0);
float Strain1 = AccumulatedStrain->GetValue(VID1);
AccumulatedStrain->SetValue(NewVID, (Strain0 + Strain1) * 0.5f);
// 硬度
float Hardness0 = HardnessCoefficient->GetValue(VID0);
float Hardness1 = HardnessCoefficient->GetValue(VID1);
HardnessCoefficient->SetValue(NewVID, (Hardness0 + Hardness1) * 0.5f);
// 晶粒尺寸
float Grain0 = GrainSize->GetValue(VID0);
float Grain1 = GrainSize->GetValue(VID1);
GrainSize->SetValue(NewVID, (Grain0 + Grain1) * 0.5f);
// 顶点颜色
FColor Color0 = Mesh.GetVertexColor(VID0);
FColor Color1 = Mesh.GetVertexColor(VID1);
FLinearColor LinearColor0(Color0);
FLinearColor LinearColor1(Color1);
FLinearColor AvgColor = (LinearColor0 + LinearColor1) * 0.5f;
Mesh.SetVertexColor(NewVID, AvgColor.ToFColor());
}
```
### 4.3 集成到锤击流程
```cpp
void ADynamicIronBlock::OnHammerImpact(...)
{
// ... 形变计算 ...
// 自适应细分(每隔几次锤击执行,避免过度细分)
if (bEnableAdaptiveRefinement && HammerHitCount % 5 == 0)
{
SubdivideHighCurvatureEdges(
*DynamicMesh,
CurvatureThreshold = 0.5f, // ~30度角
MaxEdgeLength = 2.0f, // 2cm
MaxSubdivisions = 100
);
}
}
```
---
## 5. 性能考虑
### 5.1 计算复杂度
| 操作 | 复杂度 | 实际耗时(20万顶点) |
|------|--------|---------------------|
| 各向异性凸起 | O(N) | 2-3ms |
| 边缘约束(PCA) | O(N×K) | +3-5ms |
| 自适应细分 | O(E) | +2-4ms |
| 属性插值 | O(1) per顶点 | 可忽略 |
### 5.2 优化建议
1. **限制约束区域**:只对受影响的边缘顶点应用约束
2. **降低细分频率**:每5-10次锤击才细分一次
3. **LOD策略**:远距离铁块使用简化约束
4. **异步计算**:细分可以延后到帧尾执行
---
## 6. 调试工具
### 6.1 可视化流动方向
```cpp
void ADynamicIronBlock::DebugDrawFlowDirections()
{
FVector BoxHalfSize = GetBoxHalfSize();
// 在网格表面采样点
for (int32 VID : DynamicMesh->VertexIndicesItr())
{
if (VID % 100 != 0) // 稀疏采样
continue;
FVector Pos = GetActorTransform().TransformPosition(DynamicMesh->GetVertex(VID));
// 计算流动方向
FVector ToNearestEdge = FindNearestFreeEdgeDirection(*DynamicMesh,
DynamicMesh->GetVertex(VID), BoxHalfSize);
FVector PrimaryFlow = CalculatePrimaryFlowDirection(FVector::DownVector, ToNearestEdge);
// 绘制方向箭头
DrawDebugDirectionalArrow(
GetWorld(),
Pos,
Pos + GetActorTransform().TransformVector(PrimaryFlow) * 5.0f,
2.0f,
FColor::Cyan,
false,
5.0f,
0,
0.5f
);
}
}
```
### 6.2 可视化边缘顶点
```cpp
void ADynamicIronBlock::DebugDrawEdgeVertices()
{
TSet<int32> EdgeVertices;
MarkEdgeVertices(*DynamicMesh, EdgeVertices, EdgeThreshold);
for (int32 VID : EdgeVertices)
{
FVector Pos = GetActorTransform().TransformPosition(DynamicMesh->GetVertex(VID));
DrawDebugPoint(GetWorld(), Pos, 8.0f, FColor::Red, false, 5.0f);
}
}
```
---
## 7. 参数配置示例
### 7.1 锻造平面(打铁块顶面)
```cpp
AnisotropyRatio = 1.0f; // 均匀流动
ImpactRadius = 5.0f;
BulgeStrength = 0.5f;
bEnableEdgeConstraint = false;
```
### 7.2 锻造边缘(形成刀刃)
```cpp
AnisotropyRatio = 4.0f; // 强方向性
ImpactRadius = 3.0f; // 小范围
BulgeStrength = 0.3f; // 较小凸起
bEnableEdgeConstraint = true;
EdgeThreshold = 1.0f; // 1cm
```
### 7.3 精细整形(最后修整)
```cpp
AnisotropyRatio = 8.0f; // 极端定向
ImpactRadius = 1.5f; // 很小范围
BulgeStrength = 0.1f; // 微调
bEnableEdgeConstraint = true;
EdgeThreshold = 0.5f; // 5mm
```
---
## 8. 已知限制
1. **极端形变**: 当边缘厚度<1mm时,各向异性假设可能失效
2. **拓扑变化**: 当前方法无法处理网格撕裂或融合(如锻打折叠)
3. **多重边缘**: 复杂形状(如叉状)的方向检测可能不准确
**未来改进方向**:
- 基于曲率张量的精确流动方向
- 支持拓扑变化(网格切割与连接)
- 多工具系统(不同锤头形状)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,980 @@
# 网格质量优化细节
**所属系统**: 高真实度锻造系统
**文档类型**: 技术实现细节
**最后更新**: 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 质量指标
```cpp
/**
* 三角形质量指标
*/
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 质量阈值配置
```cpp
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 实现代码
```cpp
/**
* 边翻转优化
* 通过翻转边来改善三角形长宽比
*/
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 实现代码
```cpp
/**
* 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 加权平滑(保留特征)
```cpp
/**
* 特征保留的加权平滑
* 根据曲率调整平滑强度:高曲率区域少平滑
*/
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
```cpp
#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,需要重新插值属性:
```cpp
/**
* 保存当前顶点属性
*/
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);
// ... 其他属性
}
}
}
```
**更优方案**:使用重网格化回调
```cpp
// 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 集成到锤击事件
```cpp
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: 限制优化区域
```cpp
// 仅优化距离锤击中心 < OptimizationRadius × ImpactRadius 的区域
float OptimizationRadius = 1.5f; // 仅优化150%影响半径内
// 效果:优化区域减少60%,耗时减少50%
```
#### 策略2: 降低重网格频率
```cpp
// 每5次锤击才重网格一次
if (++RemeshCounter % 5 == 0)
{
RemeshRegion(...);
}
// 效果:平均耗时减少40%
```
#### 策略3: 质量阈值触发
```cpp
// 仅当网格质量低于阈值时才优化
float AverageQuality = CalculateAverageMeshQuality(Mesh, AffectedTriangles);
if (AverageQuality < QualityThreshold)
{
OptimizeMeshQuality(...);
}
// 效果:仅必要时优化,平均耗时减少70%
```
#### 策略4: 分帧处理
```cpp
// 将重网格化分散到多帧
if (bPendingRemesh)
{
RemeshRegion(...); // 执行一小部分
if (!IsRemeshComplete())
{
// 下一帧继续
GetWorldTimerManager().SetTimerForNextTick(this, &ThisClass::ContinueRemesh);
}
}
```
---
## 9. 调试与可视化
### 9.1 可视化退化三角形
```cpp
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 可视化网格质量热图
```cpp
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 性能统计
```cpp
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 不同场景的推荐参数
#### 锻造平面(大范围形变)
```cpp
MinTriangleArea = 0.5f; // 允许较大三角形
MaxTriangleArea = 200.0f;
MinAspectRatio = 0.15f; // 放宽长宽比要求
OptimizationRadius = 2.0f; // 大范围优化
SmoothIterations = 2;
RemeshFrequency = 10; // 低频重网格
```
#### 锻造边缘(精细形变)
```cpp
MinTriangleArea = 0.01f; // 高质量要求
MaxTriangleArea = 50.0f;
MinAspectRatio = 0.2f; // 严格长宽比
OptimizationRadius = 1.2f; // 局部优化
SmoothIterations = 3;
RemeshFrequency = 5; // 高频重网格
```
#### 性能优先模式
```cpp
MinTriangleArea = 1.0f; // 放宽质量要求
MaxTriangleArea = 500.0f;
MinAspectRatio = 0.05f; // 允许严重退化
OptimizationRadius = 1.0f;
SmoothIterations = 1;
RemeshFrequency = 20; // 极低频重网格
```
### 10.2 自动调参
```cpp
/**
* 根据当前帧率自动调整优化强度
*/
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. **预测性优化**: 在锤击前预测退化区域并提前优化
@@ -0,0 +1,677 @@
# 表面磨损系统细节
**所属系统**: 高真实度锻造系统
**文档类型**: 技术实现细节
**最后更新**: 2026-01-22
---
## 1. 需求概述
### 1.1 目标效果
- 锤击后表面从"生锈"变"光亮",露出金属本体
- 金属度(Metallic)增加:0.0 → 1.0
- 粗糙度(Roughness)降低:0.9 → 0.2
- 支持多种表面状态:铁锈、氧化层、酸蚀痕迹、划痕
- **关键要求**:表面特征必须随形变一起移动,不能"漂移"
### 1.2 铁锈的物理本质
真实铁锈层:
- **厚度**: 0.1-2mm(可视严重程度)
- **成分**: Fe₂O₃(红锈)、Fe₃O₄(黑锈)
- **附着强度**: 低,易被机械摩擦去除
- **颜色**: 橙红到深褐
- **粗糙度**: 高(微观凹凸不平)
锻造去锈原理:
- 锤击压力 → 摩擦力 → 铁锈层破碎脱落
- 塑性形变 → 新鲜金属表面暴露
- 高温 → 部分氧化层还原
---
## 2. 技术方案:基于顶点属性 + 程序化细节
### 2.1 方案对比回顾
| 方案 | 分辨率 | 内存 | 性能 | 漂移问题 | 推荐度 |
|------|--------|------|------|---------|--------|
| **基于顶点** | ★★★☆☆ | ★★★★★ | ★★★★★ | ✅无 | ★★★★★ |
| **基于纹理** | ★★★★★ | ★★☆☆☆ | ★★★☆☆ | ⚠️有扭曲 | ★★★☆☆ |
| **基于体素** | ★★☆☆☆ | ★★★☆☆ | ★★★★☆ | ✅无 | ★★★☆☆ |
**选择**: 基于顶点属性(方案A)+ 程序化细节补充
### 2.2 数据通道分配
顶点颜色(RGBA)+ UV1通道的完整编码方案:
| 通道 | 用途 | 范围 | 更新频率 | 说明 |
|------|------|------|---------|------|
| **R** | 温度可视化 | 0-1 | 每帧 | 从体素采样,黑体辐射颜色 |
| **G** | 打磨度(Polish) | 0-1 | 锤击时 | 0=完全生锈,1=完全光亮 |
| **B** | 噪声种子(Seed) | 0-1 | 初始化 | 用于程序化噪声,固定不变 |
| **A** | 硬度/应变 | 0-1 | 锤击时 | 可视化累积应变 |
| **UV1.x** | 初始位置X | 0-1 | 初始化 | 编码3D位置的一部分 |
| **UV1.y** | 初始位置Y | 0-1 | 初始化 | 编码3D位置的一部分 |
额外数据通过自定义顶点属性存储。
---
## 3. 数据结构定义
### 3.1 扩展顶点属性
```cpp
class ADynamicIronBlock
{
private:
// ===== 顶点自定义属性层 =====
TDynamicMeshVertexAttribute<FVector>* InitialPositions; // 初始局部位置
TDynamicMeshVertexAttribute<float>* PolishAmount; // 打磨度(0-1)
TDynamicMeshVertexAttribute<float>* RustSeed; // 铁锈噪声种子
TDynamicMeshVertexAttribute<float>* OxideThickness; // 氧化层厚度
// ===== 材质参数 =====
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Surface")
float RustRoughness = 0.9f; // 铁锈粗糙度
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Surface")
float MetalRoughness = 0.2f; // 金属粗糙度
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Surface")
FLinearColor RustBaseColor = FLinearColor(0.6f, 0.3f, 0.1f); // 铁锈基色
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Surface")
FLinearColor MetalBaseColor = FLinearColor(0.8f, 0.8f, 0.85f); // 金属基色
public:
// 初始化顶点属性
void InitializeVertexAttributes();
};
```
### 3.2 初始化逻辑
```cpp
void ADynamicIronBlock::InitializeVertexAttributes()
{
if (!DynamicMesh->HasMesh())
return;
DynamicMesh->EditMesh([&](FDynamicMesh3& Mesh)
{
// 确保属性系统已启用
if (!Mesh.HasAttributes())
{
Mesh.EnableAttributes();
}
// 创建自定义属性层
InitialPositions = new TDynamicMeshVertexAttribute<FVector>(&Mesh);
InitialPositions->Initialize(FVector::ZeroVector);
PolishAmount = new TDynamicMeshVertexAttribute<float>(&Mesh);
PolishAmount->Initialize(0.0f);
RustSeed = new TDynamicMeshVertexAttribute<float>(&Mesh);
RustSeed->Initialize(0.0f);
OxideThickness = new TDynamicMeshVertexAttribute<float>(&Mesh);
OxideThickness->Initialize(1.0f); // 初始完全氧化
// 获取或创建UV1层
FDynamicMeshUVOverlay* UV1 = Mesh.Attributes()->GetUVLayer(1);
if (!UV1)
{
Mesh.Attributes()->SetNumUVLayers(2);
UV1 = Mesh.Attributes()->GetUVLayer(1);
}
// 计算包围盒(用于位置编码)
FAABB3 Box = Mesh.GetBounds();
FVector BoxMin = Box.Min;
FVector BoxMax = Box.Max;
FVector BoxSize = Box.Max - Box.Min;
// 初始化每个顶点
for (int32 VID : Mesh.VertexIndicesItr())
{
FVector LocalPos = Mesh.GetVertex(VID);
// 1. 保存初始位置(不变)
InitialPositions->SetValue(VID, LocalPos);
// 2. 初始化打磨度为0(完全生锈)
PolishAmount->SetValue(VID, 0.0f);
// 3. 分配随机铁锈种子
RustSeed->SetValue(VID, FMath::FRand());
// 4. 编码初始位置到UV1(仅X和Y)
FVector2f EncodedPos;
EncodedPos.X = (LocalPos.X - BoxMin.X) / (BoxSize.X + 1e-6f);
EncodedPos.Y = (LocalPos.Y - BoxMin.Y) / (BoxSize.Y + 1e-6f);
UV1->SetElement(VID, EncodedPos);
// 5. 编码初始位置Z到顶点颜色.B通道(替换随机种子方案)
FColor VertexColor = Mesh.GetVertexColor(VID);
FLinearColor LinearColor(VertexColor);
LinearColor.B = (LocalPos.Z - BoxMin.Z) / (BoxSize.Z + 1e-6f); // Z坐标归一化
LinearColor.G = 0.0f; // 初始打磨度
Mesh.SetVertexColor(VID, LinearColor.ToFColor());
}
});
// 更新材质参数
if (DynamicMaterialInstance)
{
DynamicMaterialInstance->SetScalarParameterValue(TEXT("RustRoughness"), RustRoughness);
DynamicMaterialInstance->SetScalarParameterValue(TEXT("MetalRoughness"), MetalRoughness);
DynamicMaterialInstance->SetVectorParameterValue(TEXT("RustColor"), RustBaseColor);
DynamicMaterialInstance->SetVectorParameterValue(TEXT("MetalColor"), MetalBaseColor);
}
}
```
---
## 4. 锤击更新逻辑
### 4.1 打磨度更新
```cpp
void ADynamicIronBlock::OnHammerImpact(
FVector HitLocation,
FVector HitNormal,
float ImpactRadius,
float ImpactForce)
{
FVector LocalHitPos = GetTransform().InverseTransformPosition(HitLocation);
DynamicMesh->EditMesh([&](FDynamicMesh3& Mesh)
{
for (int32 VID : Mesh.VertexIndicesItr())
{
FVector VertexPos = Mesh.GetVertex(VID);
float Distance = FVector::Dist(VertexPos, LocalHitPos);
// 只更新受击区域
if (Distance < ImpactRadius)
{
// 计算打磨强度
float DistanceRatio = Distance / ImpactRadius;
float FalloffExponent = 2.0f;
float Intensity = FMath::Pow(1.0f - DistanceRatio, FalloffExponent);
// 打磨强度与力度成正比
float PolishIncrease = Intensity * ImpactForce * 0.001f; // 可调系数
// 累积打磨度(不可逆:只能增加,不能减少)
float CurrentPolish = PolishAmount->GetValue(VID);
float NewPolish = FMath::Min(1.0f, CurrentPolish + PolishIncrease);
PolishAmount->SetValue(VID, NewPolish);
// 同步到顶点颜色.G通道
FColor VertexColor = Mesh.GetVertexColor(VID);
FLinearColor LinearColor(VertexColor);
LinearColor.G = NewPolish;
Mesh.SetVertexColor(VID, LinearColor.ToFColor());
// 铁锈种子(顶点颜色.B)保持不变
// 初始位置Z(已在.B中)保持不变
}
}
});
}
```
### 4.2 氧化层更新(可选)
```cpp
void ADynamicIronBlock::UpdateOxideLayer(float DeltaTime)
{
if (!DynamicMesh->HasMesh())
return;
// 简化的氧化模型:
// - 高温(>300°C)时氧化加快
// - 打磨区域氧化较慢(新鲜表面)
DynamicMesh->EditMesh([&](FDynamicMesh3& Mesh)
{
for (int32 VID : Mesh.VertexIndicesItr())
{
float Temperature = SampleTemperatureAtVertex(Mesh, VID);
float Polish = PolishAmount->GetValue(VID);
// 氧化速率
float BaseOxidationRate = 0.01f; // 基础速率
float TemperatureFactor = FMath::Clamp((Temperature - 300.0f) / 1000.0f, 0.0f, 1.0f);
float PolishProtection = 1.0f - Polish * 0.8f; // 打磨后氧化慢
float OxidationRate = BaseOxidationRate * (1.0f + TemperatureFactor * 10.0f) * PolishProtection;
// 更新氧化层厚度
float CurrentOxide = OxideThickness->GetValue(VID);
float NewOxide = FMath::Min(1.0f, CurrentOxide + OxidationRate * DeltaTime);
OxideThickness->SetValue(VID, NewOxide);
}
});
}
```
---
## 5. 材质实现(UE5材质节点)
### 5.1 材质图表结构
```
[VertexColor] ────────┐
├─> [Polish = VertexColor.g]
[TexCoord1] ─────────┘
[Polish] ──> [Lerp]
├─> [RustColor] (Polish=0)
└─> [MetalColor] (Polish=1)
[Polish] ──> [Lerp]
├─> [RustRoughness] (0.9)
└─> [MetalRoughness] (0.2)
[Polish] ──> [Lerp]
├─> [RustMetallic] (0.0)
└─> [MetalMetallic] (1.0)
[TextureCoordinate1] ──> [重建初始位置]
──> [噪声采样]
──> [铁锈图案]
```
### 5.2 HLSL实现
创建自定义材质函数 `MF_SurfaceWear`:
```hlsl
// 输入参数
float3 WorldPosition; // GetWorldPosition()
float3 WorldNormal; // GetWorldNormal()
float4 VertexColor; // GetVertexColor()
float2 TexCoord1; // GetTexCoord(1)
// 材质参数(从材质实例传入)
float3 RustColor; // 铁锈颜色
float3 MetalColor; // 金属颜色
float RustRoughness; // 铁锈粗糙度
float MetalRoughness; // 金属粗糙度
float RustMetallic; // 铁锈金属度(通常0)
float MetalMetallic; // 金属金属度(通常1)
// ===== 主函数 =====
// 1. 读取打磨度
float Polish = VertexColor.g; // 来自G通道
// 2. 重建初始位置(用于无漂移噪声采样)
// 假设包围盒是 [-50, 50] cm
float3 BoxMin = float3(-50.0, -50.0, -50.0);
float3 BoxMax = float3(50.0, 50.0, 50.0);
float3 BoxSize = BoxMax - BoxMin;
float InitialPosX = TexCoord1.x * BoxSize.x + BoxMin.x;
float InitialPosY = TexCoord1.y * BoxSize.y + BoxMin.y;
float InitialPosZ = VertexColor.b * BoxSize.z + BoxMin.z;
float3 InitialLocalPos = float3(InitialPosX, InitialPosY, InitialPosZ);
// 3. 基于初始位置采样噪声(空间连续,无漂移)
float RustSeedNoise = frac(InitialLocalPos.x * 0.1 + InitialLocalPos.y * 0.1 + InitialLocalPos.z * 0.1);
float RustMacro = PseudoRandom(RustSeedNoise * 100.0); // 宏观铁锈斑块
float RustDetail = Noise(InitialLocalPos * 20.0); // 细节纹理
float RustMicro = Noise(InitialLocalPos * 100.0); // 微观细节
// 组合多层噪声
float RustPattern = RustMacro * 0.6 + RustDetail * 0.3 + RustMicro * 0.1;
// 4. 计算最终颜色、金属度、粗糙度
float3 FinalRustColor = RustColor * (0.8 + RustPattern * 0.4); // 铁锈颜色变化
float3 FinalBaseColor = lerp(FinalRustColor, MetalColor, Polish);
float FinalMetallic = lerp(RustMetallic, MetalMetallic, Polish);
float FinalRoughness = lerp(RustRoughness, MetalRoughness, Polish);
// 5. 可选:添加划痕细节(基于初始位置的高频噪声)
float ScratchNoise = Noise(InitialLocalPos * 200.0) * 0.1;
FinalRoughness += ScratchNoise * (1.0 - Polish); // 划痕在未打磨区域更明显
// 6. 输出
return float4(FinalBaseColor, FinalRoughness, FinalMetallic, 1.0);
```
### 5.3 材质节点连接
```
材质输出:
Base Color ──> FinalBaseColor
Roughness ──> FinalRoughness
Metallic ──> FinalMetallic
Normal ───> [默认法线]
Ambient Occlusion ──> [可选]
```
---
## 6. 无漂移保证机制
### 6.1 问题示例
**错误做法(会漂移)**:
```hlsl
// 基于世界坐标采样噪声
float3 WorldPos = GetWorldPosition();
float Rust = Noise(WorldPos * 10.0);
// 问题:
// 锻打前: 顶点A在(100, 50, 0),Noise → 0.3(铁锈)
// 锻打后: 顶点A移动到(105, 52, 2),Noise → 0.8(变光亮?)
// 铁锈"滑走"了!
```
### 6.2 正确做法(无漂移)
**方案1:初始位置采样**
```hlsl
// 用初始的、不变的位置采样噪声
float3 InitialLocalPos = ReconstructInitialPosition(TexCoord1, VertexColor.b);
float Rust = Noise(InitialLocalPos * 10.0);
// 优点:
// - 顶点移动后,InitialLocalPos不变
// - 铁锈图案"锁定"在材料上
// - 空间连续:相邻顶点采样相似的噪声值
```
**方案2:种子采样**
```hlsl
// 用每个顶点的唯一种子采样
float Seed = VertexColor.b; // 0-1范围
float Rust = PseudoRandom(Seed * 1000.0);
// 优点:
// - 完全无漂移
// - 实现简单
// 缺点:
// - 空间不连续:相邻顶点可能有跳变
// - 需要高网格密度掩盖
```
**推荐**:方案1(初始位置采样)
### 6.3 完整的无漂移采样函数
```hlsl
/**
* 从UV1和顶点颜色重建初始位置
*/
float3 ReconstructInitialPosition(float2 TexCoord1, float EncodedZ)
{
// 假设包围盒 [-50, 50] cm
float3 BoxMin = float3(-50.0, -50.0, -50.0);
float3 BoxSize = float3(100.0, 100.0, 100.0);
float InitialX = TexCoord1.x * BoxSize.x + BoxMin.x;
float InitialY = TexCoord1.y * BoxSize.y + BoxMin.y;
float InitialZ = EncodedZ * BoxSize.z + BoxMin.z;
return float3(InitialX, InitialY, InitialZ);
}
/**
* 采样铁锈图案(无漂移版本)
*/
float3 SampleRustPattern(float2 TexCoord1, float EncodedZ, float3 WorldNormal)
{
// 重建初始位置
float3 InitialLocalPos = ReconstructInitialPosition(TexCoord1, EncodedZ);
// 多层噪声
float Rust1 = Noise(InitialLocalPos * 10.0 + float3(1.0, 2.0, 3.0)); // 低频
float Rust2 = Noise(InitialLocalPos * 30.0 + float3(4.0, 5.0, 6.0)); // 中频
float Rust3 = Noise(InitialLocalPos * 100.0 + float3(7.0, 8.0, 9.0)); // 高频
// 组合
float RustPattern = Rust1 * 0.5 + Rust2 * 0.3 + Rust3 * 0.2;
// 基于法线的变化(侧面铁锈更多)
float VerticalFactor = abs(WorldNormal.z); // z=0表示侧面
RustPattern *= (0.7 + VerticalFactor * 0.3);
return RustPattern;
}
```
---
## 7. 高级特性
### 7.1 酸蚀痕迹(大马士革钢)
```cpp
/**
* 标记大马士革层
* 在初始化时分配"材料ID"
*/
void InitializeDamascusLayers(FDynamicMesh3& Mesh)
{
TDynamicMeshVertexAttribute<int32>* MaterialLayerID =
new TDynamicMeshVertexAttribute<int32>(&Mesh);
for (int32 VID : Mesh.VertexIndicesItr())
{
FVector Pos = Mesh.GetVertex(VID);
// 简化的层状分布:基于Z坐标
int32 LayerID = (int32)FMath::Floor(Pos.Z / 2.0f) % 2; // 每2cm一层
MaterialLayerID->SetValue(VID, LayerID);
}
}
// 材质中基于LayerID选择颜色
float3 GetDamascusColor(float3 InitialLocalPos, float LayerID)
{
// 高碳钢层(深色)
float3 HighCarbonColor = float3(0.15, 0.1, 0.08);
// 低碳钢层(浅色)
float3 LowCarbonColor = float3(0.75, 0.7, 0.65);
// 混合过渡
float Mix = SmoothLayerPattern(InitialLocalPos);
return lerp(HighCarbonColor, LowCarbonColor, Mix);
}
```
### 7.2 划痕系统
```hlsl
// 基于初始位置的各向异性噪声(模拟划痕)
float3 InitialPos = ReconstructInitialPosition(TexCoord1, VertexColor.b);
// 方向性划痕(沿着锤击方向)
float ScratchDirection = InitialPos.x * 50.0; // X方向划痕
float ScratchPattern = Noise(float3(ScratchDirection, InitialPos.y * 20.0, 0.0));
float3 ScratchColor = lerp(float3(1.0, 1.0, 1.0), BaseColor, ScratchPattern);
float ScratchRoughness = Roughness * (1.0 + ScratchPattern * 0.5);
// 仅在部分区域应用
float ScratchMask = SmoothNoise(InitialPos * 15.0);
FinalBaseColor = lerp(FinalBaseColor, ScratchColor, ScratchMask * 0.3);
```
### 7.3 水渍和氧化
```hlsl
// 淬火时的水渍痕迹
float WaterStainMask = SampleWaterStainTexture(WorldPos);
float3 PatinaColor = float3(0.3, 0.4, 0.5); // 蓝绿色氧化
// 仅在潮湿区域显示
float HumidityFactor = 0.3; // 可调
FinalBaseColor = lerp(FinalBaseColor, FinalBaseColor * PatinaColor,
WaterStainMask * HumidityFactor);
```
---
## 8. 性能优化
### 8.1 材质优化
```hlsl
// 早期退出优化
float Polish = VertexColor.g;
if (Polish > 0.95)
{
// 完全打磨:直接返回金属色,跳过噪声计算
return MetalColor;
}
// LOD:基于距离减少噪声层数
float DistanceToCamera = length(View.WorldPosition);
int32 NoiseOctaves = DistanceToCamera < 100.0 ? 3 : 1;
```
### 8.2 顶点属性压缩
如果内存紧张,可以压缩属性:
```cpp
// 打磨度:8位精度足够
uint8 CompressedPolish = FMath::RoundToInt(PolishAmount * 255.0f);
PolishAmount->SetValue(VID, CompressedPolish / 255.0f);
// 初始位置:16位量化
FVector QuantizedPos(
FMath::RoundToInt(LocalPos.X * 100.0f) / 100.0f,
FMath::RoundToInt(LocalPos.Y * 100.0f) / 100.0f,
FMath::RoundToInt(LocalPos.Z * 100.0f) / 100.0f
);
```
---
## 9. 调试与可视化
### 9.1 可视化打磨度
```cpp
void ADynamicIronBlock::DebugDrawPolishHeatmap()
{
if (!DynamicMesh->HasMesh())
return;
for (int32 VID : DynamicMesh->VertexIndicesItr())
{
if (VID % 50 != 0) // 稀疏采样
continue;
float Polish = PolishAmount->GetValue(VID);
FVector WorldPos = GetActorTransform().TransformPosition(DynamicMesh->GetVertex(VID));
// 颜色映射:蓝色(0)→ 绿色(0.5)→ 红色(1.0)
FColor DebugColor = FMath::Lerp(
FColor::Blue,
FColor::Red,
Polish
);
DrawDebugPoint(GetWorld(), WorldPos, 5.0f, DebugColor, false, 5.0f);
}
}
```
### 9.2 材质调试模式
在材质中添加调试开关:
```hlsl
// 材质参数
int32 DebugMode; // 0=正常, 1=显示打磨度, 2=显示铁锈图案, 3=显示初始位置
float3 DebugOutput;
switch (DebugMode)
{
case 1: // 打磨度热图
DebugOutput = float3(Polish, 0.0, 1.0 - Polish);
break;
case 2: // 铁锈图案
DebugOutput = RustPattern;
break;
case 3: // 初始位置可视化
DebugOutput = frac(InitialLocalPos * 0.1);
break;
default:
DebugOutput = FinalBaseColor;
break;
}
return DebugOutput;
```
---
## 10. 参数配置示例
### 10.1 新铁块(完全生锈)
```cpp
RoughRoughness = 0.95f;
MetalRoughness = 0.15f;
RustColor = FLinearColor(0.7f, 0.35f, 0.15f); // 橙红色
MetalColor = FLinearColor(0.75f, 0.75f, 0.8f); // 钢铁色
```
### 10.2 半旧铁块(部分打磨)
```cpp
RoughRoughness = 0.85f;
MetalRoughness = 0.25f;
RustColor = FLinearColor(0.5f, 0.25f, 0.1f); // 深褐色
MetalColor = FLinearColor(0.8f, 0.8f, 0.85f);
```
### 10.3 高碳钢(大马士革)
```cpp
RoughRoughness = 0.9f;
MetalRoughness = 0.2f;
RustColor = FLinearColor(0.3f, 0.15f, 0.1f); // 深色层
MetalColor = FLinearColor(0.6f, 0.55f, 0.5f); // 浅色层
bEnableDamascusLayers = true;
```
---
## 11. 已知限制与未来改进
### 11.1 当前限制
1. **顶点密度瓶颈**: 20-50万顶点的细节仍不及2048²纹理
2. **通道紧张**: 4个顶点颜色通道不够用,需要UV1补充
3. **无物理反射**: 打磨效果仅通过粗糙度模拟,无真实光线追踪
### 11.2 未来改进方向
1. **混合方案**: 顶点属性(大区域)+ 纹理细节(微观)
2. **动态纹理**: 运行时更新高分辨率磨损纹理
3. **程序化细节**: 材质中用噪声补充细节
4. **多层材质**: 支持油污、涂层、水渍等多层效果
5. **法线贴图**: 从打磨度生成动态法线贴图
@@ -0,0 +1,462 @@
# 高真实度锻造系统设计
**项目**: BladeSmith
**日期**: 2026-01-22
**目标**: 实现游戏娱乐级的高真实度金属锻造物理系统
**性能目标**: 60 FPS @ 锤击操作
---
## 1. 设计目标
### 1.1 核心物理现象
实现以下四大冶金学物理过程:
1. **加工硬化/应变硬化**
- 金属在锻打过程中逐渐变硬
- 需要回火(加热)才能继续加工
- 影响后续形变的阻力
2. **应力/应变分布**
- 不均匀形变导致内部应力累积
- 可能引起开裂、扭曲、回弹
- 影响武器最终性能
3. **淬火硬化(马氏体相变)**
- 快速冷却改变晶体结构
- 极大提升硬度和脆性
- 临界温度控制(800-1200°C)
4. **微观组织演化(晶粒)**
- 晶粒尺寸、形状、取向影响材料特性
- 影响强度、韧性、视觉花纹
- 支持大马士革钢花纹效果
### 1.2 设计原则
- **游戏娱乐级精度**: 简化物理模型,保持真实感,60 FPS性能
- **渐进式实现**: 分阶段开发,每阶段可独立运行
- **架构扩展性**: 为未来功能预留接口
- **YAGNI原则**: 避免过度设计,专注核心功能
---
## 2. 整体架构方案
### 2.1 技术方案:混合体素-顶点架构
**核心思想**: 体素存储"物理场",顶点存储"材料历史"
```
┌─────────────────────────────────────────────────────────┐
│ 锤击输入 │
└─────────────────┬───────────────────────────────────────┘
│
┌────────┴────────┐
│ │
┌────▼─────┐ ┌─────▼────┐
│ 体素层 │ │ 顶点层 │
│ (GPU) │ │ (GPU) │
└────┬─────┘ └─────┬────┘
│ │
│ 双向采样通信 │
└────────┬────────┘
│
┌────────▼────────┐
│ 渲染 & 显示 │
└─────────────────┘
```
#### 体素层(全局物理场)
**存储内容**:
- 温度场(已有)
- 平均应变率场(新增)
- 冷却速率场(用于淬火判断)
- 表面打磨度场(可选)
**分辨率**: 50×50×50(可配置)
**更新方式**: GPU Compute Shader(Ping-Pong双缓冲)
#### 顶点层(局部材料历史)
**存储内容**:
- 累积塑性应变(记录形变历史)
- 当前硬度系数(影响形变阻力)
- 晶粒尺寸/取向(简化标量)
- 应力张量(简化为标量应力值)
- 初始位置(用于纹理固定)
- 打磨度(表面状态)
**数量**: 20-50万顶点(基础),刀刃区域局部加密至100万顶点/cm²
### 2.2 数据流
```
锤击事件
↓
1. 形变计算(顶点位移)
↓
2. 更新顶点应变累积
↓
3. 从温度体素采样塑性系数
↓
4. 硬化计算(基于应变+温度)
↓
5. 更新顶点硬度系数
↓
6. 更新体素应变率场
↓
7. 温度/淬火/退火判断
↓
8. 材质可视化更新
```
### 2.3 关键优势
| 特性 | 优势 |
|------|------|
| **性能可控** | 体素粗糙(50³),顶点精细(20-50万),可调节 |
| **物理直觉清晰** | 全局场(温度、应力扩散)+ 局部历史(材料记忆) |
| **利用现有基础** | 复用GPU Compute、Ping-Pong、RDG基础设施 |
| **易于调试** | 体素和顶点数据可独立可视化 |
---
## 3. 核心子系统设计
### 3.1 刀刃形成系统
**目标**: 解决锻打边缘时材料流动方向性,形成平整锐利的刀刃
**技术方案**: 各向异性材料流动 + 局部边缘约束
**详细设计**: 见 [刀刃形成技术细节.md](./2026-01-22-刀刃形成技术细节.md)
**关键特性**:
- 材料优先沿边缘方向流动(非均匀扩散)
- 边缘顶点保持共线约束
- 自适应网格细分(刀刃区域高密度)
### 3.2 表面磨损系统
**目标**: 锤击在表面留下痕迹(铁锈脱落、金属光亮、划痕)
**技术方案**: 顶点属性存储 + 程序化细节 + 无漂移采样
**详细设计**: 见 [表面磨损系统细节.md](./2026-01-22-表面磨损系统细节.md)
**关键特性**:
- 顶点存储"打磨度"和"初始位置"
- 材质基于初始位置采样噪声(无漂移)
- 支持铁锈、氧化层、酸蚀痕迹等多种表面状态
- 金属度、粗糙度动态更新
### 3.3 网格质量维护系统
**目标**: 防止锻打导致的网格退化(细长三角形、分布不均)
**技术方案**: 增量式局部优化
**详细设计**: 见 [网格质量优化细节.md](./2026-01-22-网格质量优化细节.md)
**关键特性**:
- 每次锤击后局部优化(仅受影响区域)
- 边翻转 + Laplacian平滑 + 自适应重网格化
- 保留顶点属性(打磨度、应变等)
- 性能开销: +5-15ms/次
### 3.4 物理模拟系统
**目标**: 实现温度-塑性-硬化-相变的完整冶金学循环
**技术方案**: GPU Compute Shader多物理场耦合
**详细设计**: 见 [物理模拟架构细节.md](./2026-01-22-物理模拟架构细节.md)
**关键特性**:
- 温度传导与冷却(已有,需扩展)
- 加工硬化模型(基于累积应变)
- 淬火/退火相变判断
- 应力累积与松弛
---
## 4. 数据结构设计
### 4.1 扩展体素结构
```cpp
USTRUCT(BlueprintType)
struct FTemperatureVoxel
{
GENERATED_BODY()
// ===== 已有字段 =====
UPROPERTY()
float Temperature; // 温度(°C)
UPROPERTY()
float PlasticityCoefficient; // 塑性系数(0-1)
UPROPERTY()
uint8 Flags; // 标志位(bit 0: 表面体素)
// ===== 新增字段 =====
UPROPERTY()
float AverageStrainRate; // 平均应变率(记录形变活跃度)
UPROPERTY()
float CoolingRate; // 冷却速率(用于淬火判断)
UPROPERTY()
uint8 PhaseState; // 相态(0=奥氏体, 1=马氏体, 2=珠光体)
FTemperatureVoxel()
: Temperature(20.0f)
, PlasticityCoefficient(0.0f)
, Flags(0)
, AverageStrainRate(0.0f)
, CoolingRate(0.0f)
, PhaseState(0)
{}
};
```
### 4.2 顶点自定义属性
```cpp
// 在DynamicIronBlock.h中添加
class ADynamicIronBlock : public ADynamicMeshActor
{
// 顶点属性层(使用FDynamicMesh3的扩展属性系统)
TDynamicMeshVertexAttribute<FVector>* InitialPositions; // 初始位置(不变)
TDynamicMeshVertexAttribute<float>* AccumulatedStrain; // 累积应变
TDynamicMeshVertexAttribute<float>* HardnessCoefficient; // 硬度系数
TDynamicMeshVertexAttribute<float>* PolishAmount; // 打磨度
TDynamicMeshVertexAttribute<float>* GrainSize; // 晶粒尺寸
TDynamicMeshVertexAttribute<float>* StressValue; // 应力值(简化)
};
```
### 4.3 顶点颜色编码方案
由于顶点颜色只有4通道(RGBA),需要精心分配:
| 通道 | 用途 | 范围 | 说明 |
|------|------|------|------|
| **R** | 温度可视化 | 0-1 | 从体素采样,映射到颜色 |
| **G** | 打磨度 | 0-1 | 0=生锈,1=光亮金属 |
| **B** | 噪声种子 | 0-1 | 用于无漂移纹理采样 |
| **A** | 硬度/应变 | 0-1 | 可视化应变或硬化程度 |
额外数据通过UV1通道或自定义属性传递。
---
## 5. 性能预算
### 5.1 目标帧时间分解(60 FPS = 16.67ms)
| 系统 | 预算 | 说明 |
|------|------|------|
| 渲染(UE5基础) | 10ms | 场景渲染、后处理 |
| 锤击形变计算 | 2-3ms | 顶点位移、BVH更新 |
| 网格质量优化 | 1-2ms | 边翻转、平滑 |
| GPU温度计算 | 0.5ms | Compute Shader(异步) |
| 顶点属性更新 | 0.5ms | 硬化、应力计算 |
| 材质可视化 | 1ms | 顶点颜色更新 |
| **总锤击开销** | **≤7ms** | 保持在预算内 |
### 5.2 内存预算
| 数据 | 大小 | 说明 |
|------|------|------|
| 网格顶点(20万) | 13.6 MB | 68字节/顶点 |
| 网格三角形(40万) | 4.8 MB | 3×4字节索引 |
| BVH结构 | 5 MB | 加速结构 |
| 温度体素(50³) | 0.3 MB | 24字节/体素 |
| GPU缓冲区 | 2 MB | Ping-Pong温度缓冲 |
| **单铁块总计** | **~26 MB** | 可接受 |
### 5.3 可扩展性
- **网格密度**: 可配置20万-100万顶点
- **体素分辨率**: 可配置30³-80³
- **优化开关**: 各子系统可独立禁用
- **LOD支持**: 远距离降低网格密度
---
## 6. 实施计划
### 阶段1:核心形变增强(2-3天)
**目标**: 实现刀刃形成和表面磨损的基础版本
- [ ] 各向异性材料流动(刀刃方向检测)
- [ ] 顶点打磨度属性系统
- [ ] 初始位置存储(无漂移采样)
- [ ] 材质扩展(铁锈/金属混合)
- [ ] 简单Laplacian平滑(网格质量维护基础)
**验收标准**:
- 锻打边缘时材料优先沿边缘流动
- 锤击后表面铁锈脱落,露出光亮金属
- 形变后铁锈图案不"滑动"
### 阶段2:网格质量系统(1-2天)
**目标**: 防止网格退化
- [ ] 三角形质量检测(长宽比、面积)
- [ ] 边翻转优化算法
- [ ] 增量式Laplacian平滑(带权重)
- [ ] 局部自适应重网格化(FRemesher集成)
- [ ] 性能优化(仅处理受影响区域)
**验收标准**:
- 连续锻打50次后网格仍保持均匀
- 无明显细长三角形(长宽比>0.1)
- 锤击响应时间<10ms
### 阶段3:物理模拟扩展(3-4天)
**目标**: 实现完整冶金学循环
- [ ] 扩展体素结构(应变率、冷却率、相态)
- [ ] 顶点应变累积系统
- [ ] 加工硬化模型(应变→硬度映射)
- [ ] 淬火判断逻辑(温度+冷却速率→马氏体转变)
- [ ] 退火/回火逻辑(温度+时间→硬度降低)
- [ ] GPU Compute Shader扩展(多物理场耦合)
**验收标准**:
- 连续锻打后铁块变硬(形变阻力增加)
- 加热后硬度降低,可继续加工
- 淬火后硬度显著提升(快速冷却)
### 阶段4:晶粒与视觉效果(2-3天)
**目标**: 视觉真实度提升
- [ ] 晶粒尺寸标量模拟(简化模型)
- [ ] 大马士革花纹支持(多层材料ID)
- [ ] 应力可视化(顶点颜色.A通道)
- [ ] 程序化细节噪声(材质层)
- [ ] 开裂判定与可视化(应力阈值)
**验收标准**:
- 高应变区域可视化(颜色变化)
- 支持简单的大马士革花纹(层状结构)
- 应力过大时出现视觉裂纹(不破坏网格)
### 阶段5:优化与打磨(1-2天)
**目标**: 性能优化和细节调整
- [ ] 性能Profiling(Unreal Insights)
- [ ] GPU Compute优化(共享内存、波前优化)
- [ ] 参数曝光到Blueprint(方便调试)
- [ ] 调试可视化工具(体素场可视化、应力热图)
- [ ] 文档和示例场景
**验收标准**:
- 60 FPS @ 连续锤击
- 所有参数可在编辑器调节
- 提供完整的使用文档
---
## 7. 风险评估
### 7.1 技术风险
| 风险 | 概率 | 影响 | 缓解措施 |
|------|------|------|---------|
| 网格拓扑动态变化导致属性丢失 | 中 | 高 | 使用UUID或持久化ID追踪顶点 |
| GPU Compute性能不足 | 低 | 高 | 降低体素分辨率,异步计算 |
| 大马士革花纹实现困难 | 高 | 中 | 阶段4可选功能,简化模型 |
| 网格重建开销过大 | 中 | 中 | 限制重建频率,局部优化 |
### 7.2 设计风险
| 风险 | 概率 | 影响 | 缓解措施 |
|------|------|------|---------|
| 过度复杂化,开发周期过长 | 高 | 高 | 严格遵守YAGNI,分阶段验收 |
| 物理模型过于简化,真实感不足 | 中 | 中 | 阶段性用户测试,迭代调整 |
| 顶点通道不足,数据无法传递 | 低 | 中 | 使用UV1或自定义缓冲区 |
---
## 8. 技术债务与未来扩展
### 8.1 当前简化假设
1. **应力张量简化为标量**: 真实应力是3×3张量,当前仅用单值
2. **晶粒模型极度简化**: 真实晶粒有复杂的3D形状和取向
3. **相变模型经验化**: 马氏体转变实际需要CCT曲线
4. **无热-力-电-磁耦合**: 忽略了磁性、电阻率等二次效应
### 8.2 未来可扩展方向
- **多材料支持**: 当前仅钢,未来可扩展铜、青铜、钛合金
- **焊接系统**: 多块金属的连接与材料混合
- **锻造工具多样性**: 不同形状的锤头(平锤、圆锤、凿子)
- **多人协作锻造**: 网络同步动态网格
- **VR支持**: 空间交互与力反馈
---
## 9. 参考资料
### 9.1 物理基础
- 《金属塑性成形原理》(清华大学出版社)
- 《材料科学基础》(上海交通大学出版社)
- *Metals Handbook: Forging and Forming* (ASM International)
### 9.2 技术参考
- Unreal Engine 5 Geometry Scripting文档
- *Real-Time Collision Detection* by Christer Ericson (网格优化)
- *GPU Gems 3: Chapter 39* - Parallel Prefix Sum (GPU算法)
- *Siggraph 2020: Dynamic Mesh Simplification* (网格优化)
### 9.3 游戏案例
- *My Summer Car* - 机械装配真实度
- *BeamNG.drive* - 动态网格形变
- *Dwarf Fortress* - 材料属性系统
---
## 10. 附录
### 10.1 术语表
| 术语 | 英文 | 说明 |
|------|------|------|
| 塑性形变 | Plastic Deformation | 永久性的形状改变 |
| 加工硬化 | Work Hardening | 形变导致材料变硬的现象 |
| 马氏体 | Martensite | 淬火后的硬相组织 |
| 奥氏体 | Austenite | 高温下的软相组织 |
| 晶粒 | Grain | 金属微观结构的基本单元 |
| 应变 | Strain | 材料的相对变形量 |
| 应力 | Stress | 材料内部的力 |
| BVH | Bounding Volume Hierarchy | 碰撞检测加速结构 |
| RDG | Render Dependency Graph | UE5的GPU任务调度系统 |
### 10.2 文件索引
- [刀刃形成技术细节.md](./2026-01-22-刀刃形成技术细节.md)
- [表面磨损系统细节.md](./2026-01-22-表面磨损系统细节.md)
- [网格质量优化细节.md](./2026-01-22-网格质量优化细节.md)
- [物理模拟架构细节.md](./2026-01-22-物理模拟架构细节.md)
---
**文档版本**: 1.0
**最后更新**: 2026-01-22
**状态**: 设计完成,待评审
@@ -1,723 +0,0 @@
# BladeSmith塑性变形体素系统 - 实现计划
## 概述
基于现有温度系统架构,构建一个完整的塑性变形体素系统。系统采用**两步法**:
1. **应力传播阶段**:锤击施加应力,通过GPU迭代计算应力在体素间扩散
2. **位移计算阶段**:从应力分布计算位移场,应用到网格顶点
## 核心设计决策
- **物理模型**:简化模型(塑性流动 + 应变软化 + 体积守恒)
- **体素数据**:存储应力、应变、位移(混合方式)
- **GPU架构**:应力Ping-Pong双缓冲 + 独立位移/应变缓冲
- **锤击响应**:两步法(应力注入 → GPU计算 → 顶点更新)
- **内存占用**:36字节/体素(20³网格 = 281KB,100³网格 = 34.4MB)
---
## 一、数据结构设计
### 1.1 变形体素结构(添加到DynamicIronBlock.h)
```cpp
/**
* 变形体素结构 - 存储单个体素的力学状态
* 大小:36字节(紧凑型设计)
*/
USTRUCT(BlueprintType)
struct FDeformationVoxel
{
GENERATED_BODY()
// 应力张量(简化为主应力,6字节)
FFloat16 StressPrincipal; // 主应力(沿锤击方向) - 2字节
FFloat16 StressShear; // 剪应力(切向) - 2字节
uint16 StressFlags; // 应力状态标志 - 2字节
// 累积应变(12字节)
FVector3f Strain; // 累积应变向量 (εx, εy, εz)
// 位移场(当前帧变形,12字节)
FVector3f Displacement; // 位移向量 (Δx, Δy, Δz)
// 物理属性(4字节)
float PlasticFlow; // 塑性流动系数 [0,1],由温度决定
// 辅助数据(6字节)
uint16 MaterialID; // 材料ID
uint32 AuxFlags; // 辅助标志位
FDeformationVoxel()
: StressPrincipal(0.0f), StressShear(0.0f), StressFlags(0)
, Strain(FVector3f::ZeroVector), Displacement(FVector3f::ZeroVector)
, PlasticFlow(0.0f), MaterialID(0), AuxFlags(0)
{}
};
```
### 1.2 类成员变量(添加到DynamicIronBlock.h)
```cpp
// ========== 变形系统数据 ==========
// 变形体素网格(CPU侧)
TArray<FDeformationVoxel> DeformationGrid;
// 原始网格顶点位置(未变形状态)
TArray<FVector3f> OriginalVertexPositions;
// 是否启用变形系统
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Deformation System")
bool bEnableDeformationSystem = true;
// 变形系统参数
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Deformation System")
float YieldStress = 50.0f; // 屈服应力阈值
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Deformation System")
float StressDiffusionRate = 0.3f; // 应力扩散速率
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Deformation System")
float VolumeConservationStrength = 0.8f; // 体积守恒强度
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Deformation System")
float ComplianceFactor = 0.01f; // 柔度(应力→位移转换系数)
// ========== GPU缓冲区 ==========
// 应力缓冲区(Ping-Pong)
FBufferRHIRef StressBufferA;
FBufferRHIRef StressBufferB;
// 位移和应变缓冲区
FBufferRHIRef DisplacementBuffer; // PF_R32G32B32_FLOAT(3D向量)
FBufferRHIRef StrainBuffer; // PF_R32G32B32_FLOAT
// UAV/SRV视图
FUnorderedAccessViewRHIRef StressUAV_A;
FUnorderedAccessViewRHIRef StressUAV_B;
FUnorderedAccessViewRHIRef DisplacementUAV;
FUnorderedAccessViewRHIRef StrainUAV;
FShaderResourceViewRHIRef StressSRV_A;
FShaderResourceViewRHIRef StressSRV_B;
FShaderResourceViewRHIRef DisplacementSRV;
FShaderResourceViewRHIRef StrainSRV;
bool bUseStressBufferA = true; // Ping-Pong标志
// GPU缓冲区是否准备好
std::atomic<bool> bDeformationGPUBuffersReady{false};
```
---
## 二、GPU计算着色器
### 2.1 应力传播着色器(阶段1)
**文件**: `FStressPropagationShader.h` (新建)
```cpp
class MICESHADERS_API FStressPropagationShader : public FGlobalShader
{
DECLARE_GLOBAL_SHADER(FStressPropagationShader);
SHADER_USE_PARAMETER_STRUCT(FStressPropagationShader, FGlobalShader);
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER(FIntVector, VoxelResolution)
SHADER_PARAMETER(float, DeltaTime)
SHADER_PARAMETER(float, StressDiffusionRate)
SHADER_PARAMETER(float, YieldStress)
SHADER_PARAMETER(float, StrainSofteningFactor)
SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer<float>, InputStressBuffer)
SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer<float>, PlasticityBuffer)
SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer<uint>, SurfaceVoxelFlagsBuffer)
SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer<float>, OutputStressBuffer)
END_SHADER_PARAMETER_STRUCT()
static FIntVector GetGroupSize() { return FIntVector(8, 8, 8); }
};
```
**HLSL**: `StressPropagationCS.usf` (新建)
```hlsl
[numthreads(8, 8, 8)]
void MainCompute(uint3 DispatchThreadID : SV_DispatchThreadID)
{
int3 voxelCoord = int3(DispatchThreadID);
if (!IsValidCoord(voxelCoord)) return;
int index = GetVoxelIndex(voxelCoord);
float currentStress = InputStressBuffer[index];
float plasticity = PlasticityBuffer[index];
// === 6邻域应力扩散 ===
float neighborStressSum = 0.0;
int neighborCount = 0;
const int3 neighbors[6] = {
int3(-1,0,0), int3(1,0,0),
int3(0,-1,0), int3(0,1,0),
int3(0,0,-1), int3(0,0,1)
};
for (int i = 0; i < 6; i++) {
int3 neighborCoord = voxelCoord + neighbors[i];
if (IsValidCoord(neighborCoord)) {
neighborStressSum += InputStressBuffer[GetVoxelIndex(neighborCoord)];
neighborCount++;
}
}
float avgNeighborStress = neighborStressSum / max(neighborCount, 1);
// === 应力传播(塑性越高,传播越快) ===
float diffusionRate = StressDiffusionRate * (1.0 + plasticity * 2.0);
float stressDiffusion = diffusionRate * (avgNeighborStress - currentStress) * DeltaTime;
// === 应力松弛(超过屈服点的应力衰减) ===
float stressRelaxation = 0.0;
if (currentStress > YieldStress && plasticity > 0.1) {
float excessStress = currentStress - YieldStress;
stressRelaxation = -excessStress * plasticity * StrainSofteningFactor * DeltaTime;
}
// === 表面应力自由释放 ===
bool isSurface = (SurfaceVoxelFlagsBuffer[index] & 1) != 0;
if (isSurface) {
stressRelaxation += -currentStress * 0.1 * DeltaTime;
}
// === 更新应力 ===
float newStress = max(0.0, currentStress + stressDiffusion + stressRelaxation);
OutputStressBuffer[index] = newStress;
}
```
### 2.2 位移计算着色器(阶段2)
**文件**: `FDisplacementFromStressShader.h` (新建)
```cpp
class MICESHADERS_API FDisplacementFromStressShader : public FGlobalShader
{
DECLARE_GLOBAL_SHADER(FDisplacementFromStressShader);
SHADER_USE_PARAMETER_STRUCT(FDisplacementFromStressShader, FGlobalShader);
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER(FIntVector, VoxelResolution)
SHADER_PARAMETER(float, DeltaTime)
SHADER_PARAMETER(float, VolumeConservationStrength)
SHADER_PARAMETER(float, ComplianceFactor)
SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer<float>, StressBuffer)
SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer<float>, PlasticityBuffer)
SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer<float3>, PreviousStrainBuffer)
SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer<float3>, OutputDisplacementBuffer)
SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer<float3>, OutputStrainBuffer)
END_SHADER_PARAMETER_STRUCT()
static FIntVector GetGroupSize() { return FIntVector(8, 8, 8); }
};
```
**HLSL**: `DisplacementFromStressCS.usf` (新建)
```hlsl
[numthreads(8, 8, 8)]
void MainCompute(uint3 DispatchThreadID : SV_DispatchThreadID)
{
int3 voxelCoord = int3(DispatchThreadID);
if (!IsValidCoord(voxelCoord)) return;
int index = GetVoxelIndex(voxelCoord);
float stress = StressBuffer[index];
float plasticity = PlasticityBuffer[index];
float3 previousStrain = PreviousStrainBuffer[index];
// === 计算应力梯度(主变形方向) ===
float3 stressGradient = ComputeStressGradient(voxelCoord);
float3 deformationDirection = length(stressGradient) > 0.001 ?
normalize(stressGradient) :
float3(0, 0, -1);
// === 计算塑性位移 ===
float displacementMagnitude = stress * ComplianceFactor * plasticity;
float3 plasticDisplacement = deformationDirection * displacementMagnitude;
// === 体积守恒修正 ===
float compressionAmount = dot(plasticDisplacement, deformationDirection);
float lateralExpansion = -compressionAmount * VolumeConservationStrength * 0.5;
float3 lateral1 = normalize(cross(deformationDirection, float3(0, 0, 1)));
float3 lateral2 = normalize(cross(deformationDirection, lateral1));
float3 volumeCompensation = (lateral1 + lateral2) * lateralExpansion;
// === 最终位移和应变 ===
float3 totalDisplacement = plasticDisplacement + volumeCompensation;
float3 newStrain = previousStrain + totalDisplacement * DeltaTime;
OutputDisplacementBuffer[index] = totalDisplacement;
OutputStrainBuffer[index] = newStrain;
}
```
---
## 三、锤击响应流程
### 3.1 修改OnHammerImpact(DynamicIronBlock.cpp)
```cpp
void ADynamicIronBlock::OnHammerImpact(
FVector HitLocation, FVector HitNormal,
float ImpactRadius, float ImpactDepth, float ImpactForce)
{
if (!bEnableDeformationSystem || DeformationGrid.Num() == 0)
{
// 回退到旧逻辑(直接修改顶点)
OnHammerImpact_Legacy(HitLocation, HitNormal, ImpactRadius, ImpactDepth, ImpactForce);
return;
}
// === 步骤1:应力注入 ===
ApplyImpactStressToVoxels(HitLocation, HitNormal, ImpactRadius, ImpactDepth, ImpactForce);
// === 步骤2:GPU应力传播(迭代3次) ===
for (int i = 0; i < 3; i++)
{
DispatchStressPropagationCompute(0.016f); // 16ms时间步
}
// === 步骤3:GPU位移计算 ===
DispatchDisplacementCompute(0.016f);
// === 步骤4:下载并应用到网格 ===
DownloadDeformationDataAndApplyToMesh();
}
```
### 3.2 应力注入函数
```cpp
void ADynamicIronBlock::ApplyImpactStressToVoxels(
FVector HitLocation, FVector HitNormal,
float ImpactRadius, float ImpactDepth, float ImpactForce)
{
// 转换到局部坐标
FTransform ActorTransform = GetActorTransform();
FVector LocalHitLocation = ActorTransform.InverseTransformPosition(HitLocation);
// 计算体素坐标
FVector GridOrigin = -BoxSize * 0.5f;
FVector VoxelSizeCM = FVector(TemperatureVoxelSize / 10.0f);
// 遍历影响范围
for (int32 Z = 0; Z < TemperatureResolution.Z; ++Z)
{
for (int32 Y = 0; Y < TemperatureResolution.Y; ++Y)
{
for (int32 X = 0; X < TemperatureResolution.X; ++X)
{
FVector VoxelCenter = GridOrigin + FVector(
(X + 0.5f) * VoxelSizeCM.X,
(Y + 0.5f) * VoxelSizeCM.Y,
(Z + 0.5f) * VoxelSizeCM.Z
);
float Distance = FVector::Dist(VoxelCenter, LocalHitLocation);
if (Distance < ImpactRadius)
{
int32 Index = GetTemperatureIndex(X, Y, Z);
// 高斯衰减
float Falloff = FMath::Exp(-FMath::Square(Distance / ImpactRadius) * 3.0f);
// 应力大小
float StressMagnitude = ImpactForce * ImpactDepth * Falloff * 100.0f;
// 注入到体素
if (DeformationGrid.IsValidIndex(Index))
{
DeformationGrid[Index].StressPrincipal = FMath::Min(
DeformationGrid[Index].StressPrincipal.GetFloat() + StressMagnitude,
1000.0f // 应力上限
);
}
}
}
}
}
// 上传到GPU
UploadDeformationStressToGPU();
}
```
---
## 四、顶点更新机制
### 4.1 从体素位移场采样到顶点
```cpp
void ADynamicIronBlock::UpdateVertexDeformation()
{
UDynamicMeshComponent* DynMeshComp = GetDynamicMeshComponent();
if (!DynMeshComp) return;
UDynamicMesh* DynMesh = DynMeshComp->GetDynamicMesh();
if (!DynMesh) return;
// 修改网格顶点
DynMesh->EditMesh([&](FDynamicMesh3& EditMesh)
{
for (int32 VertexID : EditMesh.VertexIndicesItr())
{
// 获取原始顶点位置
FVector3d OriginalPos = FVector3d(OriginalVertexPositions[VertexID]);
// 采样体素位移场(三线性插值)
FVector Displacement = SampleDisplacementAtPosition(FVector(OriginalPos));
// 新位置 = 原始位置 + 位移
FVector3d NewPosition = OriginalPos + FVector3d(Displacement);
EditMesh.SetVertex(VertexID, NewPosition);
}
}, EDynamicMeshChangeType::GeneralEdit, EDynamicMeshAttributeChangeFlags::Unknown, false);
// 重新计算法线
FGeometryScriptCalculateNormalsOptions NormalOp;
UGeometryScriptLibrary_MeshNormalsFunctions::RecomputeNormals(DynMesh, NormalOp);
DynMeshComp->NotifyMeshUpdated();
}
```
### 4.2 三线性插值采样位移
```cpp
FVector ADynamicIronBlock::SampleDisplacementAtPosition(const FVector& LocalPosition) const
{
if (!bEnableDeformationSystem || DeformationGrid.Num() == 0)
{
return FVector::ZeroVector;
}
// 转换到体素坐标系
FVector GridOrigin = -BoxSize * 0.5f;
FVector VoxelSizeCM = FVector(TemperatureVoxelSize / 10.0f);
FVector VoxelCoord = (LocalPosition - GridOrigin) / VoxelSizeCM;
// 获取相邻8个体素
int32 X0 = FMath::FloorToInt(VoxelCoord.X);
int32 Y0 = FMath::FloorToInt(VoxelCoord.Y);
int32 Z0 = FMath::FloorToInt(VoxelCoord.Z);
int32 X1 = X0 + 1, Y1 = Y0 + 1, Z1 = Z0 + 1;
// 边界检查
if (!IsValidTemperatureCoord(X0, Y0, Z0) || !IsValidTemperatureCoord(X1, Y1, Z1))
{
return FVector::ZeroVector;
}
// 插值权重
float Fx = VoxelCoord.X - X0;
float Fy = VoxelCoord.Y - Y0;
float Fz = VoxelCoord.Z - Z0;
// 获取8个角点的位移
auto GetDisp = [&](int X, int Y, int Z) -> FVector
{
int32 Index = GetTemperatureIndex(X, Y, Z);
return FVector(DeformationGrid[Index].Displacement);
};
FVector D000 = GetDisp(X0, Y0, Z0);
FVector D100 = GetDisp(X1, Y0, Z0);
FVector D010 = GetDisp(X0, Y1, Z0);
FVector D110 = GetDisp(X1, Y1, Z0);
FVector D001 = GetDisp(X0, Y0, Z1);
FVector D101 = GetDisp(X1, Y0, Z1);
FVector D011 = GetDisp(X0, Y1, Z1);
FVector D111 = GetDisp(X1, Y1, Z1);
// 三线性插值
FVector D00 = FMath::Lerp(D000, D100, Fx);
FVector D10 = FMath::Lerp(D010, D110, Fx);
FVector D01 = FMath::Lerp(D001, D101, Fx);
FVector D11 = FMath::Lerp(D011, D111, Fx);
FVector D0 = FMath::Lerp(D00, D10, Fy);
FVector D1 = FMath::Lerp(D01, D11, Fy);
return FMath::Lerp(D0, D1, Fz);
}
```
---
## 五、实现步骤
### 阶段1:基础架构(2-3天)
**目标**:建立数据结构和CPU侧逻辑
1. **添加数据结构** (DynamicIronBlock.h)
- 定义 `FDeformationVoxel` 结构体
- 添加 `DeformationGrid`、`OriginalVertexPositions` 成员变量
- 添加变形系统参数(YieldStress、StressDiffusionRate等)
2. **实现初始化** (DynamicIronBlock.cpp)
- `InitializeDeformationGrid()`: 分配体素数组
- 在 `InitializeBoxMesh()` 中保存原始顶点位置
3. **实现应力注入**
- `ApplyImpactStressToVoxels()`: CPU版本应力注入
- 测试:打印应力分布,验证高斯衰减
4. **实现位移采样**
- `SampleDisplacementAtPosition()`: 三线性插值
- `UpdateVertexDeformation()`: 应用到网格
- 测试:手动设置位移值,观察网格变形
### 阶段2:GPU缓冲区(1-2天)
**目标**:建立GPU缓冲区,实现CPU-GPU数据流
5. **创建GPU缓冲区**
- `CreateDeformationGPUBuffers()`: 创建所有RHI缓冲区
- `ReleaseDeformationGPUBuffers()`: 清理资源
6. **数据传输**
- `UploadDeformationStressToGPU()`: 应力数据 CPU→GPU
- `DownloadDeformationDataAndApplyToMesh()`: 位移数据 GPU→CPU
- 测试:上传后立即下载,验证数据完整性
### 阶段3:GPU计算着色器(3-4天)
**目标**:实现两阶段GPU计算
7. **应力传播Shader**
- 新建 `FStressPropagationShader.h/cpp` (参考FTemperatureComputeShader)
- 新建 `StressPropagationCS.usf`
- 实现6邻域扩散 + 应力松弛
- 测试:单点应力观察传播
8. **位移计算Shader**
- 新建 `FDisplacementFromStressShader.h/cpp`
- 新建 `DisplacementFromStressCS.usf`
- 实现应力→位移 + 体积守恒
- 测试:固定应力验证位移
9. **调度逻辑**
- `DispatchStressPropagationCompute()`: RDG调度应力传播
- `DispatchDisplacementCompute()`: RDG调度位移计算
- 测试:完整流程(应力→传播→位移→网格)
### 阶段4:集成和优化(2-3天)
**目标**:完整集成,调优参数
10. **修改锤击逻辑**
- 更新 `OnHammerImpact()` 使用新系统
- 保留旧逻辑作为fallback (`OnHammerImpact_Legacy()`)
11. **参数调优**
- 调整物理参数观察效果
- 确保变形符合物理直觉
12. **性能优化**
- 添加性能日志
- 优化迭代次数
- 考虑局部更新
13. **调试工具**
- 可视化应力分布(顶点颜色)
- 可视化位移场(Debug Gizmo)
---
## 六、关键文件清单
### 需要修改的文件
1. **C:\MiceCola\Indi\BladeSmith\Source\BladeSmith\DynamicIronBlock.h**
- 添加 `FDeformationVoxel` 结构体
- 添加变形系统成员变量和参数
- 添加新函数声明(约15个函数)
2. **C:\MiceCola\Indi\BladeSmith\Source\BladeSmith\DynamicIronBlock.cpp**
- 实现所有变形系统函数
- 修改 `OnHammerImpact()` 为两步法
- 修改 `BeginPlay()`、`EndPlay()`、`InitializeBoxMesh()`
### 需要新建的文件
3. **C:\MiceCola\Indi\BladeSmith\Plugins\MiceShaders\Source\MiceShaders\Public\FStressPropagationShader.h**
- 应力传播着色器类声明
4. **C:\MiceCola\Indi\BladeSmith\Plugins\MiceShaders\Source\MiceShaders\Private\FStressPropagationShader.cpp**
- Shader实现(IMPLEMENT_GLOBAL_SHADER)
5. **C:\MiceCola\Indi\BladeSmith\Plugins\MiceShaders\Source\MiceShaders\Public\FDisplacementFromStressShader.h**
- 位移计算着色器类声明
6. **C:\MiceCola\Indi\BladeSmith\Plugins\MiceShaders\Source\MiceShaders\Private\FDisplacementFromStressShader.cpp**
- Shader实现
7. **C:\MiceCola\Indi\BladeSmith\Plugins\MiceShaders\Shaders\Private\StressPropagationCS.usf**
- 应力传播HLSL代码
8. **C:\MiceCola\Indi\BladeSmith\Plugins\MiceShaders\Shaders\Private\DisplacementFromStressCS.usf**
- 位移计算HLSL代码
---
## 七、验证和测试
### 单元测试
1. **体素索引测试**
- 验证 `GetTemperatureIndex()` 3D→1D转换正确性
- 测试边界情况
2. **插值测试**
- 设置8个体素位移为已知值
- 验证中心点插值结果精度
3. **应力衰减测试**
- 注入单点应力
- 验证距离衰减符合高斯曲线
### 集成测试
4. **锤击响应测试**
- **冷态测试**(塑性=0):锤击应无明显变形
- **热态测试**(塑性=1):锤击应有深度变形
- **体积守恒验证**:压缩区深度 ≈ 凸起区体积
5. **性能测试**
- 测量每次锤击耗时(目标 <7ms)
- 验证60fps稳定性
- GPU占用率监控
### 物理验证
6. **应力传播验证**
- 单点冲击应力向周围扩散
- 表面体素应力快速释放
7. **温度-塑性耦合验证**
- 高温区域深度变形
- 低温区域轻微变形
### 端到端测试
8. **完整流程测试**
```
1. 启动游戏 → 加载DynamicIronBlock
2. 开启自动加热 → 观察温度上升
3. 等待塑性系数 > 0.5
4. 执行锤击 → 观察变形
5. 验证:顶点位置改变、法线重新计算、BVH更新
```
---
## 八、性能预期
### 内存占用
- **小网格(20×20×20)**:约 0.5 MB
- **中网格(50×50×50)**:约 4 MB
- **大网格(100×100×100)**:约 67 MB
### 计算延迟(一次锤击)
- GPU应力传播:0.1ms × 3迭代 = 0.3ms
- GPU位移计算:0.1ms
- CPU下载:约 1ms(异步)
- 顶点更新:约 5ms(10万顶点)
- **总延迟**:约 6-7ms(<16.7ms/60fps)✅
---
## 九、物理模型说明
### 简化的应力-应变关系
本系统使用简化模型:
```
位移 = 应力 × 柔度 × 塑性系数
Δu = σ × C × P
```
其中:
- `σ`:应力(Pa)
- `C`:柔度系数 (ComplianceFactor)
- `P`:塑性系数(温度决定,0-1)
### 温度对塑性的影响
塑性系数从温度系统读取(复用PlasticityBuffer):
```
P = Smoothstep((T - T_min) / (T_max - T_min))
```
- 温度越高 → 塑性越强 → 应力更易转换为位移
### 体积守恒近似
压缩方向位移后,横向补偿:
```
压缩量 = |位移 · 方向|
横向膨胀 = -压缩量 × 守恒强度 × 0.5
```
---
## 十、调试建议
1. **日志级别**
- 使用 `UE_LOG(LogTemp, Warning, ...)` 输出关键数据
- 每个阶段添加性能计时
2. **可视化工具**
- 顶点颜色显示应力分布(蓝色=低,红色=高)
- Debug Gizmo绘制体素应力箭头
3. **参数调优UI**
- 所有物理参数暴露到Blueprint
- 实时调整观察效果
4. **逐步验证**
- 先验证CPU逻辑(阶段1)
- 再验证GPU传输(阶段2)
- 最后验证GPU计算(阶段3)
---
## 十一、与现有系统的集成
### 与温度系统协作
- **共享缓冲区**:`PlasticityBuffer`、`SurfaceVoxelFlagsBuffer`
- **共享分辨率**:`TemperatureResolution` 用于变形体素网格
- **调度顺序**:温度计算(每1秒) → 变形计算(锤击触发)
### 与BVH系统协作
- 每次 `UpdateVertexDeformation()` 后调用 `GenerateBVH()`
- 确保碰撞检测使用最新几何
### 与顶点颜色系统协作
- 可选:混合显示温度(红色)+ 应力(蓝色)
- 或独立显示:切换模式(温度模式 / 应力模式)
---
**计划完成**。建议按照实现步骤逐步推进,每个阶段充分测试后再进入下一阶段。