Compare commits

15 Commits

Author SHA1 Message Date
zhangqing 10e831efb9 应力设计文档 2026-01-23 18:52:18 +08:00
zhangqing b079f682c9 Merge branch 'main' of http://micecola.top:8418/starmen1265/BladeSmith 2026-01-23 10:41:32 +08:00
zhangqing 1747eb7db7 更新ignore 2026-01-23 10:41:19 +08:00
starmen1265 8c27c82856 新增设计 2026-01-23 00:31:21 +08:00
starmen1265 f7882875c5 新增项目概述 2026-01-21 23:21:13 +08:00
zhangqing 61016f9c2a Add plastic deformation voxel system implementation plan to project
Move plan from user directory to project docs for cross-machine sync.
This plan describes the two-phase GPU-based deformation system:
stress propagation and displacement calculation.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-20 19:29:49 +08:00
zhangqing 5e237b8466 Add vertex deformation quality improvement design
Design document for improving mesh topology and UV quality during
plastic deformation. Uses Laplacian smoothing + edge length constraints
to prevent triangle degradation and texture tearing.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-20 19:23:27 +08:00
zhangqing de8b523c3d 顶点色设置成功,顶点从热力体素采样逻辑需要检查,部分热力区域没有采样到顶点 2025-12-22 18:45:49 +08:00
zhangqing 511f43f2c0 温度到颜色处理 2025-12-10 19:10:26 +08:00
zhangqing bba161810a 增加热辐射颜色处理 2025-12-09 19:03:17 +08:00
zhangqing e647723a7a 调整细节 2025-12-08 18:24:10 +08:00
zhangqing dd16f0123a 实现温度系统CPU-GPU双向同步及调试功能
功能改进:
- HeatRegion后自动上传温度数据到GPU
- Tick中GPU计算后自动下载温度数据到CPU
- DownloadTemperatureDataFromGPU回调中自动更新顶点颜色

调试功能:
- HeatRegion后输出距离Hit点最近的4个体素温度
- 包含体素坐标、距离和温度信息,便于调试温度计算

配置优化:
- 温度更新间隔从0.1秒调整为1.0秒,降低性能开销

技术细节:
- 使用异步任务确保线程安全
- GPU数据上传前检查缓冲区就绪状态
- 完整的CPU↔GPU温度数据同步流程

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-08 18:15:19 +08:00
zhangqing 33d0ff62de 重构温度计算:使用正确的RDG API替代SkipTracking
重构内容:
- 移除SkipTracking标志,使用FRDGPooledBuffer包装外部RHI缓冲区
- 使用RegisterExternalBuffer正确注册外部缓冲区到RDG系统
- 简化lambda捕获列表,移除不需要的SRV/UAV引用
- 添加详细的日志输出以便调试

技术改进:
- RDG系统现在能正确追踪资源依赖和状态转换
- 自动处理内存屏障和资源同步
- 符合UE5最佳实践

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-08 16:46:42 +08:00
zhangqing 8bde527782 合并 2025-12-08 10:45:13 +08:00
zhangqing 08d12c4d47 调整灯光,调整材质 2025-12-02 19:08:32 +08:00
60 changed files with 6882 additions and 66 deletions
+6 -1
View File
@@ -6,7 +6,12 @@
"Bash(\"E:\\MiceCola\\work\\UE_5_6\\Engine\\Build\\BatchFiles\\Build.bat\" BladeSmithEditor Win64 Development -Project=\"E:\\MiceCola\\work\\BladeSmith\\BladeSmith.uproject\" -WaitMutex -FromMsBuild)",
"Bash(dir:*)",
"Bash(git add:*)",
"Bash(git commit -m \"$(cat <<''EOF''\n实现DynamicIronBlock温度体素系统基础功能\n\n新增功能:\n- 添加FTemperatureVoxel结构体(温度、塑性系数、表面标志)\n- 实现自适应温度网格(5mm体素精度,基于BoxSize计算分辨率)\n- 实现三线性插值温度/塑性系数采样\n- 实现黑体辐射温度可视化(顶点颜色)\n- 添加HeatRegion蓝图测试函数\n- 添加OnHammerImpact锤击形变函数(体积守恒)\n\n技术细节:\n- Phase 1: CPU侧温度网格初始化,支持10cm-100cm+可变尺寸\n- Phase 3: 温度可视化系统(20°C灰色 → 1200°C白色)\n- 内存占用:30×30×30cm ≈ 1.7MB(60×60×60体素)\n\n待实现:Phase 2 GPU热传导计算\n\n🤖 Generated with [Claude Code](https://claude.com/claude-code)\n\nCo-Authored-By: Claude <noreply@anthropic.com>\nEOF\n)\")"
"Bash(git commit -m \"$(cat <<''EOF''\n实现DynamicIronBlock温度体素系统基础功能\n\n新增功能:\n- 添加FTemperatureVoxel结构体(温度、塑性系数、表面标志)\n- 实现自适应温度网格(5mm体素精度,基于BoxSize计算分辨率)\n- 实现三线性插值温度/塑性系数采样\n- 实现黑体辐射温度可视化(顶点颜色)\n- 添加HeatRegion蓝图测试函数\n- 添加OnHammerImpact锤击形变函数(体积守恒)\n\n技术细节:\n- Phase 1: CPU侧温度网格初始化,支持10cm-100cm+可变尺寸\n- Phase 3: 温度可视化系统(20°C灰色 → 1200°C白色)\n- 内存占用:30×30×30cm ≈ 1.7MB(60×60×60体素)\n\n待实现:Phase 2 GPU热传导计算\n\n🤖 Generated with [Claude Code](https://claude.com/claude-code)\n\nCo-Authored-By: Claude <noreply@anthropic.com>\nEOF\n)\")",
"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(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)",
"Skill(superpowers:brainstorming)"
],
"deny": [],
"ask": []
+1
View File
@@ -74,3 +74,4 @@ Plugins/**/Intermediate/*
# Cache files for the editor to use
DerivedDataCache/*
nul
+354
View File
@@ -0,0 +1,354 @@
# BladeSmith Agent Development Guide
## Project Overview
BladeSmith is a UE5.6 C++ blacksmith simulation game featuring physics-based metal forging with GPU-accelerated compute shaders and dynamic mesh deformation.
**Core Technologies:**
- Unreal Engine 5.6
- Geometry Scripting API
- Custom Compute Shaders (MiceShaders plugin)
- Dynamic Mesh System
- Enhanced Input
- Voxel-based physics simulation
---
## Build Commands
### Building the Project
```bash
# Build game target (run from UE5 engine directory)
"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"
# Alternative: Open BladeSmith.sln in Visual Studio
```
### Running Tests
UE5 uses the Automation System, not traditional test runners:
- **Editor**: Window → Developer Tools → Automation Sessions
- **Command line**: `UE5Editor.exe BladeSmith.uproject -ExecCmds="Automation RunTests"`
- **Single test**: Run specific test class from Automation Sessions panel
### Development Workflow
1. Open `BladeSmith.uproject` in UE5.6 editor
2. Use hot reload for C++ changes (Ctrl+F11 in editor)
3. Test via "Play In Editor" (Alt+P)
4. Use Visual Studio for compilation
---
## Code Style Guidelines
### File Structure
- **Header files (.h)**: Class declarations, forward declarations, USTRUCT/UCLASS definitions
- **Source files (.cpp)**: Implementations, includes in dependency order
- **Shader files (.usf)**: HLSL compute shaders in `Plugins/MiceShaders/Shaders/Private/`
### Import Ordering
**Header Files:**
```cpp
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ProjectModule.generated.h"
class UForwardDeclaredClass; // Forward declarations
```
**Source Files:**
```cpp
// Copyright Epic Games, Inc. All Rights Reserved.
#include "ClassName.h"
#include "SystemHeader1.h"
#include "SystemHeader2.h"
```
**Pattern:**
1. Epic copyright header (required)
2. `#pragma once` guard
3. Core UE5 headers (`CoreMinimal.h` first)
4. Engine framework headers
5. Project headers
6. Forward declarations (headers only)
### Naming Conventions
**Classes:** PascalCase with UE5 prefixes
- `ABladeSmithCharacter` - Actor classes (A prefix)
- `UBladeSmithWeaponComponent` - Component classes (U prefix)
- `FDeformationVoxel` - Structs (F prefix)
- `EGameStateEnum` - Enums (E prefix)
**Functions:** PascalCase
```cpp
public:
void SwingHammer();
FVector GetHammerHeadLocation() const;
private:
void PerformHammerHit(); // Private helpers use PascalCase
```
**Variables:** camelCase
```cpp
float HammerForce = 1000.0f;
FVector HammerHeadOffset;
UMaterialInstanceDynamic* DynamicMaterialInstance;
TArray<FTemperatureVoxel> TemperatureGrid;
```
**Constants:** PascalCase
```cpp
const int32 MaxResolutionPerAxis = 200;
const float DefaultTemperature = 20.0f;
```
### Formatting
**Indentation:** 4 spaces (no tabs)
**Braces:** Allman style - opening brace on new line
```cpp
void ABladeSmithCharacter::Move(const FInputActionValue& Value)
{
FVector2D MovementVector = Value.Get<FVector2D>();
// implementation
}
```
**Line Length:** Under 120 characters, wrap parameters as needed
**Spacing:** Spaces around operators, after commas
### UE5 Macros and Attributes
**UPROPERTY Specifiers:**
```cpp
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Category")
UPropertyType* VariableName;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Category|Subcategory", meta = (ClampMin = "0.0", ClampMax = "1.0"))
float PropertyWithConstraints;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Category", meta = (AllowPrivateAccess = "true"))
UComponentType* PrivateComponent;
```
**UFUNCTION Specifiers:**
```cpp
UFUNCTION(BlueprintCallable, Category = "Category Name")
ReturnType FunctionName(Parameters);
UFUNCTION(BlueprintImplementableEvent)
void BlueprintEventName();
```
**USTRUCT:**
```cpp
USTRUCT(BlueprintType)
struct FStructName
{
GENERATED_BODY()
UPROPERTY()
Type VariableName;
};
```
### Type Annotations
**UE5 Types (preferred):**
- `TArray<T>` instead of std::vector
- `TMap<TKey, TValue>` instead of std::map
- `TSet<T>` instead of std::unordered_set
- `FVector`, `FVector2D`, `FRotator`, `FQuat` for math
- `FString` for strings (TEXT() macro for string literals)
- `FName` for names, `FText` for localized text
- `float` not double (unless precision critical)
- `int32` not int, `uint8` for bytes
- `bool` for booleans
**Smart Pointers:**
- Use UE5's garbage collection for UObject-derived classes
- `TSharedPtr<T>` for non-UObject smart pointers
- `TWeakObjectPtr<T>` for weak references to UObjects
### Error Handling
**Null Checks:**
```cpp
if (DynamicMeshComp == nullptr)
{
UE_LOG(LogTemp, Error, TEXT("DynamicMeshComponent not found"));
return;
}
```
**Logging:**
```cpp
UE_LOG(LogTemp, Log, TEXT("Temperature: %.1f°C"), Temperature); // Info
UE_LOG(LogTemp, Warning, TEXT("Temperature system disabled")); // Warning
UE_LOG(LogTemp, Error, TEXT("Failed to initialize GPU buffers")); // Error
```
**Validation:**
```cpp
if (!IsValid(InputParam))
{
return false;
}
// Early return pattern
if (Condition)
{
return;
}
```
### Documentation
**Chinese Comments:** Domain-specific logic uses Chinese comments
```cpp
/** 锤子组件 */
/** 温度体素结构 - 存储单个体素的温度和塑性数据 */
```
**Function Documentation:**
```cpp
/**
* 函数简述
* @param ParameterName 参数说明
* @return 返回值说明
*/
```
### Threading and GPU Operations
**Atomic Flags:**
```cpp
std::atomic<bool> bGPUBuffersReady{false};
```
**Render Thread Operations:**
```cpp
ENQUEUE_RENDER_COMMAND(CommandName)(
[Params](FRHICommandListImmediate& RHICmdList)
{
// GPU operations here
}
);
```
### Component Structure Pattern
**Actor Template:**
```cpp
UCLASS()
class BLADESMITH_API AClassName : public AParentClass
{
GENERATED_BODY()
public:
AClassName();
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Category")
UComponentType* Component;
UFUNCTION(BlueprintCallable, Category="Category")
ReturnType PublicFunction();
protected:
virtual void BeginPlay() override;
virtual void Tick(float DeltaTime) override;
private:
void PrivateHelper();
};
```
---
## Module Dependencies
**BladeSmith.Build.cs:**
- GeometryScriptingCore, GeometryFramework, DynamicMesh
- GeometryCore, GeometryAlgorithms, ModelingComponents
- RenderCore, RHI, Renderer, PhysicsCore
- EnhancedInput, AudioExtensions, AudioMixer
- Custom: MiceShaders (plugin)
---
## Performance Guidelines
- **Deformation calculations:** <7ms per frame target
- **60 FPS:** Maintain during hammer operations
- **High-poly meshes:** ~100k vertices with GPU acceleration
- **Voxel resolution:** 50x50x50 typical (balance performance/quality)
- **Use RDG (Render Dependency Graph)** for GPU compute operations
---
## Common Patterns
**Material Parameters:**
```cpp
UMaterialInstanceDynamic* DynMaterial = UMaterialInstanceDynamic::Create(BaseMaterial, this);
DynMaterial->SetScalarParameterValue(TEXT("ParameterName"), Value);
DynMaterial->SetVectorParameterValue(TEXT("VectorParam"), FLinearColor(R,G,B,A));
DynMaterial->SetTextureParameterValue(TEXT("TextureParam"), Texture);
```
**Dynamic Mesh Editing:**
```cpp
UDynamicMesh* DynMesh = DynMeshComp->GetDynamicMesh();
DynMesh->EditMesh([&](FDynamicMesh3& EditMesh)
{
for (FVertexID VertexID : EditMesh.VertexIndicesItr())
{
EditMesh.SetVertex(VertexID, NewPosition);
}
}, EDynamicMeshChangeType::GeneralEdit);
```
**Input Binding (Enhanced Input):**
```cpp
if (UEnhancedInputComponent* EnhancedInput = Cast<UEnhancedInputComponent>(InputComponent))
{
EnhancedInput->BindAction(Action, ETriggerEvent::Triggered, this, &ClassName::Handler);
}
```
---
## Plugin System
**MiceShaders Plugin:**
- Custom compute shaders for temperature simulation and deformation
- Located in `Plugins/MiceShaders/`
- Add new shaders: Create `.h` and `.cpp` in Source/MiceShaders, HLSL in Shaders/Private/
- Use RDG for shader dispatch
---
## Git Workflow
- **Ignore:** `Binaries/`, `Intermediate/`, `DerivedDataCache/`, `Saved/`, `.vs/`, `.idea/`
- **Commit:** Source/, Content/, Plugins/, Config/, docs/
- **No binary assets** in Git (use version control for assets)
---
## Documentation
**Technical docs:** Located in `docs/plans/`
- Reference existing design docs before implementing new features
- Update plans when architecture changes
- Use markdown for design documents
+2
View File
@@ -1,4 +1,5 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=657466D1_002D2409_002D3196_002D8BA9_002D4914CBD968CC_002Fdl_003ASource_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FUE_005F5_002E6_003FEngine_003FSource_002Fd_003ARuntime_002Fd_003AEngine_002Fd_003APublic_002Fd_003AMaterials_002Ff_003AMaterialExpressionBlackBody_002Eh/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ADanyxuq_002Easm_002Fl_003AC_0021_003FUsers_003Fstarm_003FAppData_003FLocal_003FTemp_003FSandboxFiles_003FRewuwer_003FDanyxuq_002Easm/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ADycuvaq_002Easm_002Fl_003AC_0021_003FUsers_003Fstarm_003FAppData_003FLocal_003FTemp_003FSandboxFiles_003FWoqosub_003FDycuvaq_002Easm/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AFunction_002Eh_002Fl_003AD_0021_003FProgram_0020Files_003FEpic_0020Games_003FUE_005F5_002E6_003FEngine_003FSource_003FRuntime_003FCore_003FPublic_003FTemplates_003FFunction_002Eh/@EntryIndexedValue">ForceIncluded</s:String>
@@ -6,5 +7,6 @@
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ATedilij_002Easm_002Fl_003AC_0021_003FUsers_003Fstarm_003FAppData_003FLocal_003FTemp_003FSandboxFiles_003FKotyxod_003FTedilij_002Easm/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ATextureDefines_002Eh_002Fl_003AD_0021_003FProgram_0020Files_003FEpic_0020Games_003FUE_005F5_002E6_003FEngine_003FSource_003FRuntime_003FEngine_003FClasses_003FEngine_003FTextureDefines_002Eh/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AShaderParameterStruct_002Eh_002Fl_003AD_0021_003FProgram_0020Files_003FEpic_0020Games_003FUE_005F5_002E6_003FEngine_003FSource_003FRuntime_003FRenderCore_003FPublic_003FShaderParameterStruct_002Eh/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ATuvased_002Easm_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FUsers_003FAdministrator_003FAppData_003FLocal_003FTemp_003FSandboxFiles_003FKonywix_003FTuvased_002Easm/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AUnrealMathUtility_002Eh_002Fl_003AD_0021_003FProgram_0020Files_003FEpic_0020Games_003FUE_005F5_002E6_003FEngine_003FSource_003FRuntime_003FCore_003FPublic_003FMath_003FUnrealMathUtility_002Eh/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AArray_002Eh_002Fl_003AD_0021_003FProgram_0020Files_003FEpic_0020Games_003FUE_005F5_002E6_003FEngine_003FSource_003FRuntime_003FCore_003FPublic_003FContainers_003FArray_002Eh/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary>
+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
View File
@@ -38,6 +38,7 @@ r.DefaultFeature.LocalExposure.HighlightContrastScale=0.8
r.DefaultFeature.LocalExposure.ShadowContrastScale=0.8
r.DefaultFeature.AutoExposure=False
r.Lumen.TraceMeshSDFs=1
[/Script/WindowsTargetPlatform.WindowsTargetSettings]
DefaultGraphicsRHI=DefaultGraphicsRHI_DX12
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+390 -62
View File
@@ -25,6 +25,7 @@
#include "RHIStaticStates.h"
#include "ShaderCompilerCore.h"
#include "ShaderCore.h"
#include "GeometryScript/MeshVertexColorFunctions.h"
ADynamicIronBlock::ADynamicIronBlock()
{
@@ -66,6 +67,7 @@ void ADynamicIronBlock::Tick(float DeltaTime)
return;
}
// ========== 温度计算更新 ==========
// 累积时间
TimeSinceLastUpdate += DeltaTime;
@@ -78,9 +80,37 @@ void ADynamicIronBlock::Tick(float DeltaTime)
// 重置计时器
TimeSinceLastUpdate = 0.0f;
// 下载数据并更新顶点颜色(可选,性能开销较大)
// DownloadTemperatureDataFromGPU();
// UpdateVertexColors();
// 从GPU下载温度数据并更新材质参数
DownloadTemperatureDataFromGPU();
// 注意:UpdateMaterialTemperature()会在DownloadTemperatureDataFromGPU的回调中异步调用
}
// ========== 自动加热功能 ==========
if (bIsHeating)
{
// 累积加热时间
TimeSinceLastHeating += DeltaTime;
// 每隔HeatingInterval进行一次加热
if (TimeSinceLastHeating >= HeatingInterval)
{
// 获取铁块中心位置(世界坐标)
FVector WorldCenter = GetActorLocation();
// 执行加热
HeatRegion(WorldCenter, HeatingRadius, HeatingTemperatureIncrease);
// 重置加热计时器
TimeSinceLastHeating = 0.0f;
UE_LOG(LogTemp, Log, TEXT("DynamicIronBlock: Auto heating applied at center, radius: %.2f cm, temp increase: %.2f °C"),
HeatingRadius, HeatingTemperatureIncrease);
}
}
else
{
// 如果加热被关闭,重置计时器
TimeSinceLastHeating = 0.0f;
}
}
@@ -153,7 +183,17 @@ void ADynamicIronBlock::InitializeBoxMesh()
// 如果设置了材质,创建动态材质实例并应用
if (IronMaterial)
{
UMaterialInstanceDynamic* DynamicMaterialInstance = UMaterialInstanceDynamic::Create(IronMaterial, this);
// 强制设置材质的Usage标志,确保支持顶点颜色
UMaterial* BaseMaterial = IronMaterial->GetMaterial();
if (BaseMaterial)
{
BaseMaterial->bUsedWithGeometryCollections = true;
BaseMaterial->bUsedWithSkeletalMesh = true;
BaseMaterial->bUsedWithStaticLighting = true;
UE_LOG(LogTemp, Warning, TEXT("DynamicIronBlock: Material usage flags set for vertex color support"));
}
DynamicMaterialInstance = UMaterialInstanceDynamic::Create(IronMaterial, this);
if (DynamicMaterialInstance)
{
DynMeshComp->SetMaterial(0, DynamicMaterialInstance);
@@ -161,6 +201,23 @@ void ADynamicIronBlock::InitializeBoxMesh()
}
}
// 确保DynamicMeshComponent启用顶点颜色渲染
DynMeshComp->SetEnableWireframeRenderPass(false);
DynMeshComp->bExplicitShowWireframe = false;
// 这些设置确保顶点颜色被正确传递到GPU
DynMeshComp->SetColorOverrideMode(EDynamicMeshComponentColorOverrideMode::None);
// // 强制启用顶点颜色渲染
// DynMeshComp->bDrawMeshCollisionIfComplex = false;
// DynMeshComp->bDrawMeshCollisionIfSimple = false;
// 设置渲染状态
DynMeshComp->SetRenderCustomDepth(false);
DynMeshComp->MarkRenderStateDirty();
UE_LOG(LogTemp, Warning, TEXT("DynamicIronBlock: DynamicMeshComponent rendering configured for vertex colors"));
// 通知组件网格已更新
DynMeshComp->NotifyMeshUpdated();
@@ -170,6 +227,13 @@ void ADynamicIronBlock::InitializeBoxMesh()
// 初始化温度网格
InitializeTemperatureGrid();
// 初始化顶点颜色(基于初始温度)
if (bEnableTemperatureSystem && TemperatureGrid.Num() > 0)
{
UpdateVertexColors();
UE_LOG(LogTemp, Log, TEXT("DynamicIronBlock: Initial vertex colors set based on ambient temperature"));
}
UE_LOG(LogTemp, Log, TEXT("DynamicIronBlock: Box mesh initialized with size (%f, %f, %f)"),
BoxSize.X, BoxSize.Y, BoxSize.Z);
}
@@ -481,7 +545,65 @@ void ADynamicIronBlock::HeatRegion(FVector WorldCenter, float Radius, float Temp
UE_LOG(LogTemp, Log, TEXT("DynamicIronBlock: Heated %d voxels in region (center: %s, radius: %.2f)"),
AffectedVoxels, *LocalCenter.ToString(), Radius);
// 更新顶点颜色以显示温度变化
// 调试:输出距离Hit点最近的4个体素的温度
struct FVoxelDebugInfo
{
FIntVector Coord;
float Distance;
float Temperature;
};
TArray<FVoxelDebugInfo> NearestVoxels;
// 查找最近的体素
for (int32 Z = MinZ; Z <= MaxZ; ++Z)
{
for (int32 Y = MinY; Y <= MaxY; ++Y)
{
for (int32 X = MinX; X <= MaxX; ++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, LocalCenter);
int32 Index = GetTemperatureIndex(X, Y, Z);
FVoxelDebugInfo Info;
Info.Coord = FIntVector(X, Y, Z);
Info.Distance = Distance;
Info.Temperature = TemperatureGrid[Index].Temperature;
NearestVoxels.Add(Info);
}
}
}
// 按距离排序
NearestVoxels.Sort([](const FVoxelDebugInfo& A, const FVoxelDebugInfo& B) {
return A.Distance < B.Distance;
});
// 输出最近的4个体素
UE_LOG(LogTemp, Warning, TEXT("=== Nearest 4 Voxels to Hit Point ==="));
for (int32 i = 0; i < FMath::Min(4, NearestVoxels.Num()); ++i)
{
const FVoxelDebugInfo& Info = NearestVoxels[i];
UE_LOG(LogTemp, Warning, TEXT(" Voxel[%d,%d,%d] Distance=%.2fcm Temperature=%.1f°C"),
Info.Coord.X, Info.Coord.Y, Info.Coord.Z, Info.Distance, Info.Temperature);
}
// 将更新后的温度数据上传到GPU
if (bGPUBuffersReady.load(std::memory_order_acquire))
{
UploadTemperatureDataToGPU();
UE_LOG(LogTemp, Log, TEXT("DynamicIronBlock: Temperature data uploaded to GPU after HeatRegion"));
}
// 更新材质温度参数以显示温度变化
UpdateMaterialTemperature();
// 更新顶点颜色(基于体素温度)
UpdateVertexColors();
}
@@ -625,6 +747,68 @@ FLinearColor ADynamicIronBlock::TemperatureToColor(float Temperature) const
}
}
float ADynamicIronBlock::CalculateAverageTemperature() const
{
if (!bEnableTemperatureSystem || TemperatureGrid.Num() == 0)
{
return AmbientTemperature;
}
float TotalTemperature = 0.0f;
int32 ValidVoxels = 0;
// 遍历所有体素,累加温度
for (const FTemperatureVoxel& Voxel : TemperatureGrid)
{
TotalTemperature += Voxel.Temperature;
++ValidVoxels;
}
// 计算平均温度
return ValidVoxels > 0 ? (TotalTemperature / ValidVoxels) : AmbientTemperature;
}
void ADynamicIronBlock::UpdateMaterialTemperature()
{
if (!bEnableTemperatureSystem || TemperatureGrid.Num() == 0)
{
return;
}
if (!DynamicMaterialInstance)
{
UE_LOG(LogTemp, Warning, TEXT("DynamicIronBlock: Cannot update material temperature, dynamic material instance is null"));
return;
}
// 计算平均温度
float AverageTemperature = CalculateAverageTemperature();
// 将温度映射到颜色
FLinearColor TemperatureColor = TemperatureToColor(AverageTemperature);
// 设置材质参数
DynamicMaterialInstance->SetScalarParameterValue(FName("Temperature"), AverageTemperature);
DynamicMaterialInstance->SetVectorParameterValue(FName("TemperatureColor"), TemperatureColor);
UE_LOG(LogTemp, Log, TEXT("DynamicIronBlock: Material temperature updated - Avg: %.1f°C, Color: (%.2f, %.2f, %.2f)"),
AverageTemperature, TemperatureColor.R, TemperatureColor.G, TemperatureColor.B);
// 在屏幕上显示温度信息(保留3秒)
if (GEngine)
{
FString DebugMessage = FString::Printf(TEXT("Temperature: %.1f°C | Color: (R:%.2f, G:%.2f, B:%.2f)"),
AverageTemperature, TemperatureColor.R, TemperatureColor.G, TemperatureColor.B);
GEngine->AddOnScreenDebugMessage(
-1, // Key (-1 表示自动分配)
3.0f, // 显示时长(秒)
TemperatureColor.ToFColor(true), // 使用温度颜色
DebugMessage
);
}
}
void ADynamicIronBlock::UpdateVertexColors()
{
if (!bEnableTemperatureSystem || TemperatureGrid.Num() == 0)
@@ -632,47 +816,177 @@ void ADynamicIronBlock::UpdateVertexColors()
return;
}
// 获取 DynamicMeshComponent
UDynamicMeshComponent* DynMeshComp = GetDynamicMeshComponent();
if (!DynMeshComp)
{
UE_LOG(LogTemp, Warning, TEXT("DynamicIronBlock: Cannot update vertex colors, DynamicMeshComponent is null"));
return;
}
// 获取 DynamicMesh
UDynamicMesh* DynMesh = DynMeshComp->GetDynamicMesh();
if (!DynMesh)
{
UE_LOG(LogTemp, Warning, TEXT("DynamicIronBlock: Cannot update vertex colors, DynamicMesh is null"));
return;
}
// 准备颜色列表和调试信息
TArray<FLinearColor> VertexColors;
int32 VertexCount = 0;
float MinTemp = FLT_MAX;
float MaxTemp = -FLT_MAX;
FLinearColor FirstVertexColor = FLinearColor::Black;
float FirstVertexTemp = 0.0f;
FVector FirstVertexPos = FVector::ZeroVector;
// 读取网格以获取顶点信息并计算颜色
DynMesh->ProcessMesh([&](const FDynamicMesh3& ReadMesh)
{
VertexCount = ReadMesh.MaxVertexID();
// 预分配颜色数组大小
VertexColors.Reserve(VertexCount);
for (int32 VertexID = 0; VertexID < VertexCount; ++VertexID)
{
if (!ReadMesh.IsVertex(VertexID))
{
// 对于无效顶点,使用默认颜色
VertexColors.Add(FLinearColor::Black);
continue;
}
// 获取顶点位置(局部坐标)
FVector3d VertexPos = ReadMesh.GetVertex(VertexID);
FVector VertexPosFloat(VertexPos.X, VertexPos.Y, VertexPos.Z);
// 采样该位置的温度
//float Temperature = SampleTemperatureAtPosition(VertexPosFloat);
float Temperature = 900;
// 统计温度范围
MinTemp = FMath::Min(MinTemp, Temperature);
MaxTemp = FMath::Max(MaxTemp, Temperature);
// 将温度转换为黑体辐射颜色
FLinearColor TempColor = TemperatureToColor(Temperature);
VertexColors.Add(TempColor);
// 记录第一个有效顶点的信息用于调试
if (VertexID == 0)
{
FirstVertexColor = TempColor;
FirstVertexTemp = Temperature;
FirstVertexPos = VertexPosFloat;
}
}
});
// 创建 FGeometryScriptColorList 并关联颜色数组
FGeometryScriptColorList ColorList;
ColorList.List = MakeShared<TArray<FLinearColor>>(MoveTemp(VertexColors));
// 使用 GeometryScript 设置顶点颜色
UGeometryScriptLibrary_MeshVertexColorFunctions::SetMeshPerVertexColors(
DynMesh,
ColorList
);
UE_LOG(LogTemp, Warning, TEXT("=== UpdateVertexColors Debug Info ==="));
UE_LOG(LogTemp, Warning, TEXT(" Total vertices: %d"), VertexCount);
UE_LOG(LogTemp, Warning, TEXT(" Temperature range: %.1f°C - %.1f°C"), MinTemp, MaxTemp);
UE_LOG(LogTemp, Warning, TEXT(" First vertex: Pos=(%.1f, %.1f, %.1f), Temp=%.1f°C, Color=(%.3f, %.3f, %.3f)"),
FirstVertexPos.X, FirstVertexPos.Y, FirstVertexPos.Z,
FirstVertexTemp,
FirstVertexColor.R, FirstVertexColor.G, FirstVertexColor.B);
// 通知组件网格已更新
DynMeshComp->NotifyMeshUpdated();
// 强制刷新渲染状态以确保顶点颜色更新
DynMeshComp->MarkRenderStateDirty();
DynMeshComp->MarkRenderDynamicDataDirty();
}
void ADynamicIronBlock::DebugPrintVertexColors()
{
UDynamicMeshComponent* DynMeshComp = GetDynamicMeshComponent();
if (!DynMeshComp)
{
UE_LOG(LogTemp, Error, TEXT("DynamicIronBlock: DynamicMeshComponent is null"));
return;
}
UDynamicMesh* DynMesh = DynMeshComp->GetDynamicMesh();
if (!DynMesh)
{
UE_LOG(LogTemp, Error, TEXT("DynamicIronBlock: DynamicMesh is null"));
return;
}
DynMesh->EditMesh([&](FDynamicMesh3& EditMesh)
DynMesh->ProcessMesh([&](const FDynamicMesh3& ReadMesh)
{
// 启用顶点颜色(如果尚未启用)
if (!EditMesh.HasVertexColors())
bool bHasVertexColors = ReadMesh.HasVertexColors();
int32 VertexCount = ReadMesh.VertexCount();
UE_LOG(LogTemp, Warning, TEXT("========== Vertex Color Debug =========="));
UE_LOG(LogTemp, Warning, TEXT("Has Vertex Colors: %s"), bHasVertexColors ? TEXT("YES") : TEXT("NO"));
UE_LOG(LogTemp, Warning, TEXT("Total Vertices: %d"), VertexCount);
if (bHasVertexColors)
{
EditMesh.EnableVertexColors(FVector3f(0.2f, 0.2f, 0.2f));
// 采样几个顶点的颜色
int32 SampleCount = FMath::Min(10, VertexCount);
UE_LOG(LogTemp, Warning, TEXT("Sampling first %d vertices:"), SampleCount);
int32 SampledCount = 0;
for (int32 VertexID : ReadMesh.VertexIndicesItr())
{
if (SampledCount >= SampleCount)
break;
FVector3d Pos = ReadMesh.GetVertex(VertexID);
FVector3f Color = ReadMesh.GetVertexColor(VertexID);
UE_LOG(LogTemp, Warning, TEXT(" Vertex[%d]: Pos=(%.1f, %.1f, %.1f), Color=(%.3f, %.3f, %.3f)"),
VertexID, Pos.X, Pos.Y, Pos.Z, Color.X, Color.Y, Color.Z);
SampledCount++;
}
// 统计颜色范围
FVector3f MinColor(FLT_MAX, FLT_MAX, FLT_MAX);
FVector3f MaxColor(-FLT_MAX, -FLT_MAX, -FLT_MAX);
for (int32 VertexID : ReadMesh.VertexIndicesItr())
{
FVector3f Color = ReadMesh.GetVertexColor(VertexID);
MinColor.X = FMath::Min(MinColor.X, Color.X);
MinColor.Y = FMath::Min(MinColor.Y, Color.Y);
MinColor.Z = FMath::Min(MinColor.Z, Color.Z);
MaxColor.X = FMath::Max(MaxColor.X, Color.X);
MaxColor.Y = FMath::Max(MaxColor.Y, Color.Y);
MaxColor.Z = FMath::Max(MaxColor.Z, Color.Z);
}
UE_LOG(LogTemp, Warning, TEXT("Color Range: Min=(%.3f, %.3f, %.3f), Max=(%.3f, %.3f, %.3f)"),
MinColor.X, MinColor.Y, MinColor.Z, MaxColor.X, MaxColor.Y, MaxColor.Z);
}
else
{
UE_LOG(LogTemp, Error, TEXT("Mesh does NOT have vertex colors! UpdateVertexColors may not be working."));
}
// 遍历所有顶点
for (int32 VertexID : EditMesh.VertexIndicesItr())
{
// 获取顶点位置(局部坐标)
FVector3d VertexPos = EditMesh.GetVertex(VertexID);
FVector VertexPosFloat(VertexPos.X, VertexPos.Y, VertexPos.Z);
UE_LOG(LogTemp, Warning, TEXT("========================================"));
});
// 通过三线性插值获取该顶点的温度
float Temperature = SampleTemperatureAtPosition(VertexPosFloat);
// 将温度映射到颜色
FLinearColor VertexColor = TemperatureToColor(Temperature);
// 设置顶点颜色
EditMesh.SetVertexColor(VertexID, FVector3f(VertexColor.R, VertexColor.G, VertexColor.B));
}
}, EDynamicMeshChangeType::GeneralEdit, EDynamicMeshAttributeChangeFlags::Unknown, false);
DynMeshComp->NotifyMeshUpdated();
UE_LOG(LogTemp, Log, TEXT("DynamicIronBlock: Vertex colors updated based on temperature"));
// 在屏幕上显示
if (GEngine)
{
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Yellow,
TEXT("Vertex color debug info printed to log (check Output Log)"));
}
}
// ========== GPU缓冲区管理实现 ==========
@@ -848,6 +1162,12 @@ void ADynamicIronBlock::DownloadTemperatureDataFromGPU()
}
UE_LOG(LogTemp, Log, TEXT("DynamicIronBlock: Temperature data downloaded from GPU"));
// 更新材质温度参数以反映新的温度
UpdateMaterialTemperature();
// 更新顶点颜色(基于体素温度)
UpdateVertexColors();
});
});
}
@@ -856,6 +1176,8 @@ void ADynamicIronBlock::DispatchTemperatureCompute(float DeltaTime)
{
if (!TemperatureBufferA.IsValid() || !TemperatureBufferB.IsValid())
{
UE_LOG(LogTemp, Error, TEXT("DynamicIronBlock: Temperature buffers not valid! A=%d B=%d"),
TemperatureBufferA.IsValid(), TemperatureBufferB.IsValid());
return;
}
@@ -867,30 +1189,22 @@ void ADynamicIronBlock::DispatchTemperatureCompute(float DeltaTime)
float ForgeMin = ForgeTemperatureMin;
float ForgeMax = ForgeTemperatureMax;
// Ping-Pong缓冲区引用
UE_LOG(LogTemp, Log, TEXT("DynamicIronBlock: Dispatching temperature compute - Resolution=(%d,%d,%d), DeltaTime=%.4f"),
Resolution.X, Resolution.Y, Resolution.Z, DeltaTime);
// Ping-Pong缓冲区引用(捕获到lambda传递给RDG)
FBufferRHIRef InputBuffer = bUseBufferA ? TemperatureBufferA : TemperatureBufferB;
FBufferRHIRef OutputBuffer = bUseBufferA ? TemperatureBufferB : TemperatureBufferA;
FBufferRHIRef FlagsBuffer = SurfaceVoxelFlagsBuffer;
FBufferRHIRef PlasticityBuf = PlasticityBuffer;
// 创建SRV/UAV(在游戏线程创建,捕获到lambda)
FShaderResourceViewRHIRef InputSRV = TemperatureSRV_A;
FShaderResourceViewRHIRef InputSRV_B = TemperatureSRV_B;
FShaderResourceViewRHIRef FlagsSRV = SurfaceVoxelSRV;
FUnorderedAccessViewRHIRef OutputUAV_A = TemperatureUAV_A;
FUnorderedAccessViewRHIRef OutputUAV_B = TemperatureUAV_B;
FUnorderedAccessViewRHIRef PlasticityUAV_Ref = PlasticityUAV;
bool bReadFromA = bUseBufferA;
// 切换缓冲区(下次Tick使用新的缓冲区)
bUseBufferA = !bUseBufferA;
// 调度GPU计算(直接使用RHI,不用RDG)
// 调度GPU计算(使用RDG正确的资源管理)
ENQUEUE_RENDER_COMMAND(DispatchTemperatureCS)(
[Resolution, Conductivity, Cooling, Ambient, ForgeMin, ForgeMax, DeltaTime,
InputBuffer, OutputBuffer, FlagsBuffer, PlasticityBuf,
InputSRV, InputSRV_B, FlagsSRV, OutputUAV_A, OutputUAV_B, PlasticityUAV_Ref, bReadFromA]
InputBuffer, OutputBuffer, FlagsBuffer, PlasticityBuf]
(FRHICommandListImmediate& RHICmdList)
{
// 检查ShaderMap是否有效
@@ -909,21 +1223,30 @@ void ADynamicIronBlock::DispatchTemperatureCompute(float DeltaTime)
return;
}
// 使用RDG(简化版本 - 创建临时缓冲区)
UE_LOG(LogTemp, Log, TEXT("DynamicIronBlock [RenderThread]: Shader valid, creating RDG..."));
// 使用RDG
FRDGBuilder GraphBuilder(RHICmdList, RDG_EVENT_NAME("TemperatureConduction"));
// 创建RDG缓冲区描述
FRDGBufferDesc TempBufferDesc = FRDGBufferDesc::CreateBufferDesc(sizeof(float), InputBuffer->GetSize() / sizeof(float));
FRDGBufferDesc FlagsBufferDesc = FRDGBufferDesc::CreateBufferDesc(sizeof(uint32), FlagsBuffer->GetSize() / sizeof(uint32));
// 将外部RHI缓冲区正确注册到RDG系统
// 创建缓冲区描述符
const uint32 NumElements = InputBuffer->GetSize() / sizeof(float);
FRDGBufferDesc TempBufferDesc = FRDGBufferDesc::CreateBufferDesc(sizeof(float), NumElements);
FRDGBufferDesc FlagsBufferDesc = FRDGBufferDesc::CreateBufferDesc(sizeof(uint32), NumElements);
// 创建RDG缓冲区
FRDGBufferRef InputTempRDG = GraphBuilder.CreateBuffer(TempBufferDesc, TEXT("InputTemperatureBuffer"));
FRDGBufferRef OutputTempRDG = GraphBuilder.CreateBuffer(TempBufferDesc, TEXT("OutputTemperatureBuffer"));
FRDGBufferRef FlagsRDG = GraphBuilder.CreateBuffer(FlagsBufferDesc, TEXT("SurfaceVoxelFlagsBuffer"));
FRDGBufferRef PlasticityRDG = GraphBuilder.CreateBuffer(TempBufferDesc, TEXT("PlasticityBuffer"));
// 创建FRDGPooledBuffer并注册
TRefCountPtr<FRDGPooledBuffer> InputPooledBuffer = new FRDGPooledBuffer(RHICmdList, InputBuffer, TempBufferDesc, NumElements, TEXT("InputTemperatureBuffer"));
TRefCountPtr<FRDGPooledBuffer> OutputPooledBuffer = new FRDGPooledBuffer(RHICmdList, OutputBuffer, TempBufferDesc, NumElements, TEXT("OutputTemperatureBuffer"));
TRefCountPtr<FRDGPooledBuffer> FlagsPooledBuffer = new FRDGPooledBuffer(RHICmdList, FlagsBuffer, FlagsBufferDesc, NumElements, TEXT("SurfaceVoxelFlagsBuffer"));
TRefCountPtr<FRDGPooledBuffer> PlasticityPooledBuffer = new FRDGPooledBuffer(RHICmdList, PlasticityBuf, TempBufferDesc, NumElements, TEXT("PlasticityBuffer"));
// TODO: 添加数据复制Pass(从RHI到RDG)
// 暂时跳过,shader会处理未初始化数据
FRDGBufferRef InputTempRDG = GraphBuilder.RegisterExternalBuffer(InputPooledBuffer);
FRDGBufferRef OutputTempRDG = GraphBuilder.RegisterExternalBuffer(OutputPooledBuffer);
FRDGBufferRef FlagsRDG = GraphBuilder.RegisterExternalBuffer(FlagsPooledBuffer);
FRDGBufferRef PlasticityRDG = GraphBuilder.RegisterExternalBuffer(PlasticityPooledBuffer);
UE_LOG(LogTemp, Log, TEXT("DynamicIronBlock [RenderThread]: External RHI buffers registered to RDG, setting shader parameters..."));
// 设置Shader参数
FTemperatureComputeShader::FParameters* PassParameters = GraphBuilder.AllocParameters<FTemperatureComputeShader::FParameters>();
@@ -935,10 +1258,13 @@ void ADynamicIronBlock::DispatchTemperatureCompute(float DeltaTime)
PassParameters->ForgeTemperatureMax = ForgeMax;
PassParameters->DeltaTime = DeltaTime;
PassParameters->InputTemperatureBuffer = GraphBuilder.CreateSRV(InputTempRDG, PF_R32_FLOAT);
PassParameters->SurfaceVoxelFlagsBuffer = GraphBuilder.CreateSRV(FlagsRDG, PF_R32_UINT);
PassParameters->OutputTemperatureBuffer = GraphBuilder.CreateUAV(OutputTempRDG, PF_R32_FLOAT);
PassParameters->OutputPlasticityBuffer = GraphBuilder.CreateUAV(PlasticityRDG, PF_R32_FLOAT);
// 创建SRV和UAV(使用正确注册的RDG资源)
PassParameters->InputTemperatureBuffer = GraphBuilder.CreateSRV(FRDGBufferSRVDesc(InputTempRDG, PF_R32_FLOAT));
PassParameters->SurfaceVoxelFlagsBuffer = GraphBuilder.CreateSRV(FRDGBufferSRVDesc(FlagsRDG, PF_R32_UINT));
PassParameters->OutputTemperatureBuffer = GraphBuilder.CreateUAV(FRDGBufferUAVDesc(OutputTempRDG, PF_R32_FLOAT));
PassParameters->OutputPlasticityBuffer = GraphBuilder.CreateUAV(FRDGBufferUAVDesc(PlasticityRDG, PF_R32_FLOAT));
UE_LOG(LogTemp, Log, TEXT("DynamicIronBlock [RenderThread]: Shader parameters set, calculating group count..."));
// 计算线程组数量
FIntVector GroupSize = FTemperatureComputeShader::GetGroupSize();
@@ -948,6 +1274,9 @@ void ADynamicIronBlock::DispatchTemperatureCompute(float DeltaTime)
FMath::DivideAndRoundUp(Resolution.Z, GroupSize.Z)
);
UE_LOG(LogTemp, Log, TEXT("DynamicIronBlock [RenderThread]: GroupCount=(%d,%d,%d), adding compute pass..."),
GroupCount.X, GroupCount.Y, GroupCount.Z);
// 添加计算Pass
FComputeShaderUtils::AddPass(
GraphBuilder,
@@ -957,13 +1286,12 @@ void ADynamicIronBlock::DispatchTemperatureCompute(float DeltaTime)
GroupCount
);
// TODO: 添加数据复制Pass(从RDG回RHI)
// 暂时跳过,需要实现正确的缓冲区提取
UE_LOG(LogTemp, Log, TEXT("DynamicIronBlock [RenderThread]: Compute pass added, executing RDG..."));
// 执行RDG
// 执行RDG,自动处理资源转换和同步
GraphBuilder.Execute();
UE_LOG(LogTemp, Warning, TEXT("DynamicIronBlock: Temperature compute dispatched (%d x %d x %d groups) - Data copy not yet implemented"),
UE_LOG(LogTemp, Log, TEXT("DynamicIronBlock: Temperature compute dispatched successfully (%d x %d x %d groups)"),
GroupCount.X, GroupCount.Y, GroupCount.Z);
});
}
+44 -2
View File
@@ -104,6 +104,24 @@ public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Temperature System")
bool bEnableTemperatureSystem = true;
// ========== 自动加热功能 ==========
// 是否正在加热中
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Temperature System|Auto Heating")
bool bIsHeating = false;
// 自动加热间隔(秒)
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Temperature System|Auto Heating", meta = (ClampMin = "0.1", ClampMax = "10.0"))
float HeatingInterval = 1.0f;
// 自动加热半径(厘米)
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Temperature System|Auto Heating")
float HeatingRadius = 30.0f;
// 每次加热温度增量(摄氏度)
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Temperature System|Auto Heating")
float HeatingTemperatureIncrease = 100.0f;
/**
* 测试函数 - 加热指定区域
* @param WorldCenter 加热中心位置(世界坐标)
@@ -124,6 +142,12 @@ public:
UFUNCTION(BlueprintCallable, Category = "Hammer Impact")
void OnHammerImpact(FVector HitLocation, FVector HitNormal, float ImpactRadius, float ImpactDepth, float ImpactForce = 1.0f);
/**
* 调试函数 - 输出顶点颜色信息到日志和屏幕
*/
UFUNCTION(BlueprintCallable, Category = "Temperature System|Debug")
void DebugPrintVertexColors();
protected:
// 当游戏开始或生成时调用
virtual void BeginPlay() override;
@@ -156,6 +180,10 @@ private:
// 温度网格分辨率
FIntVector TemperatureResolution;
// 动态材质实例(用于更新温度参数)
UPROPERTY()
UMaterialInstanceDynamic* DynamicMaterialInstance;
// ========== 温度系统方法 ==========
/**
@@ -235,7 +263,18 @@ private:
FLinearColor TemperatureToColor(float Temperature) const;
/**
* 更新网格顶点颜色以显示温度
* 计算平均温度
* @return 体素网格的平均温度
*/
float CalculateAverageTemperature() const;
/**
* 更新动态材质的温度参数
*/
void UpdateMaterialTemperature();
/**
* 更新网格顶点颜色(基于每个顶点的温度体素采样)
*/
void UpdateVertexColors();
@@ -257,7 +296,10 @@ private:
bool bUseBufferA = true; // Ping-Pong标志
float TimeSinceLastUpdate = 0.0f; // 距离上次温度更新的时间
float TemperatureUpdateInterval = 0.1f; // 温度更新间隔(秒)
float TemperatureUpdateInterval = 1.0f; // 温度更新间隔(秒)
// 自动加热计时器
float TimeSinceLastHeating = 0.0f; // 距离上次自动加热的时间
// GPU缓冲区是否已准备好(线程安全标志)
std::atomic<bool> bGPUBuffersReady{false};
+1 -1
View File
@@ -29,7 +29,7 @@ DynMaterial(nullptr), DynCalMaterial(nullptr)
PrimaryActorTick.bCanEverTick = true;
RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("Root"));
StaticMeshComp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("StaticMeshComponent"));
StaticMeshComp->AttachToComponent(RootComponent,FAttachmentTransformRules::SnapToTargetIncludingScale);
//StaticMeshComp->AttachToComponent(RootComponent,FAttachmentTransformRules::SnapToTargetIncludingScale);
//RootComponent = StaticMeshComp;
}
void AIronBlock::PostInitProperties()
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 290 KiB

@@ -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
**状态**: 设计完成,待评审
@@ -0,0 +1,530 @@
# 应力累积系统设计
**项目**: BladeSmith
**日期**: 2026-01-23
**目标**: 模拟多来源应力累积与物理表现
**设计原则**: 真实物理表现优先,辅助功能可选
---
## 1. 应力来源分类
### 1.1 塑性形变应力
**来源**: 锤击导致材料永久形变
**计算公式**:
```
Stress_plastic = k_plastic × StrainIncrement × Hardness
```
**参数说明**:
- `k_plastic = 10.0`: 塑性应力系数(可调)
- `StrainIncrement`: 本次锤击的应变增量
- `Hardness`: 当前硬度系数
**应用范围**: 锤击影响区域内的所有顶点
### 1.2 热应力
**来源**: 温度梯度导致内部膨胀不均匀
**计算公式**:
```
Stress_thermal = ThermalStressCoeff × CoolingRate × (T_current - 20°C)
```
**参数说明**:
- `ThermalStressCoeff = 0.005`: 热应力系数(可调)
- `CoolingRate`: 冷却速率(°C/s)
- `T_current`: 当前温度(°C)
- 20°C: 环境温度
**应用范围**: 所有体素和顶点
### 1.3 相变应力
**来源**: 马氏体转变导致体积膨胀
**计算公式**:
```
Stress_phase = PhaseStressCoeff × MartensiteFraction
```
**参数说明**:
- `PhaseStressCoeff = 50.0`: 相变应力系数(可调)
- `MartensiteFraction`: 马氏体转化比例
```
MartensiteFraction = clamp(CoolingRate / 80°C/s, 0, 1)
```
**应用范围**: 淬火区域的所有顶点
### 1.4 残余应力
**来源**: 锤击时周边材料限制形变
**计算公式**:
```
Stress_residual = k_residual × (TargetStrain - ActualStrain)
```
**参数说明**:
- `k_residual = 5.0`: 残余应力系数(可调)
- `TargetStrain`: 目标应变(基于锤击强度)
- `ActualStrain`: 实际应变(受限后)
**应用范围**: 锤击区域周边的顶点
---
## 2. 应力累积规则
### 2.1 总应力计算
**综合应力公式**:
```
TotalStress = Stress_plastic + Stress_thermal + Stress_phase + Stress_residual
```
**顶点更新**:
```
InternalStress += TotalStress × DeltaTime
```
**应力限制**:
```
InternalStress = clamp(InternalStress, 0.0, MaxStress)
```
其中`MaxStress`为材料极限应力,可配置。
### 2.2 应力扩散机制
**目的**: 应力向周围顶点扩散,模拟真实物理行为
**拉普拉斯平滑**:
```
Stress_diffused = Stress_original + α × (AverageNeighborStress - Stress_original)
```
**参数说明**:
- `α`: 扩散系数(0.1-0.3,可调)
- `AverageNeighborStress`: 相邻顶点的平均应力
**实现方式**:
每帧对每个顶点执行扩散,迭代1-2次。
### 2.3 应力松弛机制
**温度驱动的松弛**:
**松弛因子**:
```
RelaxationFactor =
0.99 (T < 100°C, 常温慢松弛)
0.90 (200°C ≤ T < 400°C, 回火快松弛)
0.85 (T ≥ 400°C, 快速松弛)
```
**每帧更新**:
```
InternalStress = InternalStress × RelaxationFactor
```
**时间常数公式**:
```
InternalStress = InternalStress × exp(-t / τ)
```
其中:
- `t`: 时间(秒)
- `τ`: 松弛时间常数
- τ = 60秒(常温)
- τ = 10秒(回火温度)
---
## 3. 应力对形变阻力的影响
### 3.1 形变阻力公式
**DeformationResistance(形变阻力)**:
```
DeformationResistance = BaseResistance × HardnessFactor × StressFactor × TemperatureFactor
```
### 3.2 各因子详细公式
**基础阻力**:
```
BaseResistance = 1.0
```
**硬度因子**:
```
HardnessFactor = 1.0 + (Hardness / MaxHardness) × 2.0
```
硬度越高,形变阻力越大(最大3倍阻力)。
**应力因子**:
```
StressFactor = 1.0 + (InternalStress / StressLimit) × 1.5
```
应力越高,材料越难形变(最大2.5倍阻力)。
**温度因子**:
```
TemperatureFactor =
3.0 (T > 800°C, 高温软)
1.5 (500°C < T ≤ 800°C, 中温)
1.0 (T ≤ 500°C, 常温)
```
### 3.3 顶点位移计算
**锤击位移**:
```
Displacement = BaseDisplacement / DeformationResistance
```
**应力高的区域**:
- 更难锻打
- 需要更用力
- 或重复锤击
---
## 4. 应力物理表现
### 4.1 弯曲/扭曲机制
**弯曲角度公式**:
```
BendAngle = (StressLocal - StressAverage) × BendSensitivity
BendDirection = CrossProduct(HammerDirection, Normal)
```
**实现步骤**:
1. 计算应力梯度:
```
∇Stress = Stress_neighbor - Stress_center
```
2. 应用顶点位移:
```
VertexPosition += BendDirection × BendAngle × DistanceFromCenter
```
**扭曲公式**:
当材料两侧应力不均匀时:
```
TwistAngle = (Stress_Left - Stress_Right) × TwistSensitivity
VertexPosition = RotateAroundAxis(VertexPosition, Axis, TwistAngle)
```
**视觉效果**:
- 刀身沿应力梯度方向自然弯曲
- 应力集中区域出现扭曲
- 大幅应力差导致明显形变
### 4.2 裂纹起始判定
**临界应力阈值**:
```
CriticalStressThreshold = MaterialStrength × SafetyFactor
```
其中:
- `MaterialStrength`: 材料强度(可配置,默认100.0)
- `SafetyFactor`: 安全系数(默认1.2)
**开裂条件**:
```
if (InternalStress > CriticalStressThreshold)
{
CreateCrack(Vertex);
}
```
**概率开裂**:
为避免一次性开裂过多,引入概率:
```
CrackProbability = (InternalStress / CriticalStressThreshold) - 1.0
if (Random() < CrackProbability)
{
CreateCrack(Vertex);
}
```
### 4.3 裂纹扩展机制
**裂纹生长方向**:
优先沿应力梯度方向扩展(应力最大方向)
**每帧检查**:
```
if (Vertex.IsCracked)
{
// 裂纹扩展概率
ExtendProbability = (InternalStress / CriticalStressThreshold) - 0.8
if (Random() < ExtendProbability)
{
// 找到应力最高的相邻顶点
TargetNeighbor = FindMaxStressNeighbor();
CrackTo(TargetNeighbor);
}
}
```
**裂纹合并**:
当两条裂纹相交时:
- 大块材料脱落
- 网格拓扑断裂
- 几何处理: 删除被包围的顶点和三角形
### 4.4 裂纹视觉表现
**几何表现**:
1. **裂缝开口**:
```
CrackVertex.Position += Normal × CrackOpeningWidth
```
其中`CrackOpeningWidth`为裂缝开口宽度(可配置)
2. **边缘抖动**:
裂纹边缘顶点位置随机抖动,模拟粗糙断裂
**材质表现**:
1. **锈迹**:
- 裂纹区域显示RustTexture
- 锈迹深度基于裂纹时间
2. **内部氧化**:
- 裂缝深处更暗
- 模拟内部氧化
3. **纹理扰动**:
使用顶点法线扰动模拟裂纹纹理
**物理影响**:
- 裂纹区域的硬度系数降为0.3
- 裂纹顶点不再参与后续锻打形变(断裂)
### 4.5 应力释放事件
**开裂后应力释放**:
```
if (CrackCreated)
{
InternalStress = InternalStress × 0.3 // 应力释放
// 释放应力用于下次计算
}
```
**回火对应力的影响**:
加热至回火温度(200-400°C):
- 加速应力松弛(详见松弛机制)
- 但如果已经开裂,裂纹无法愈合
---
## 5. 应力可视化(辅助功能)
### 5.1 应力扫描模式
**触发方式**:
- 玩家按住特定键(如Alt键)切换到"应力视图"
- 或在UI中选择"应力扫描"工具
**视觉效果**:
**顶点颜色着色**(彩虹色谱):
```
// 蓝色:低应力(< 30% StressLimit)
if (Stress < 0.3 × StressLimit)
{
Color = Lerp(Blue, Green, StressRatio);
}
// 绿色:中等应力(30-60% StressLimit)
else if (Stress < 0.6 × StressLimit)
{
Color = Lerp(Green, Yellow, StressRatio);
}
// 黄色:高应力(60-90% StressLimit)
else if (Stress < 0.9 × StressLimit)
{
Color = Lerp(Yellow, Red, StressRatio);
}
// 红色:接近开裂(≥ 90% StressLimit)
else
{
Color = Red;
// 叠加闪烁效果
Emissive = Flicker();
}
```
**应力流线**(可选):
- 显示应力传导路径
- 从高应力区域流向低应力区域
- 帮助玩家理解应力分布
**UI面板**:
- 实时显示最大应力值
- 平均应力值
- 超阈值顶点数量
- 裂纹数量
### 5.2 退出可视化
**恢复正常视觉**:
释放辅助功能键后,完全恢复正常视觉表现:
- 顶点颜色恢复到正常状态(打磨度、回火色等)
- 材质参数恢复
- 无任何残留效果
---
## 6. 实现架构
### 6.1 GPU-CPU分工
**GPU Compute Shader职责**:
- 热应力计算
- 应力扩散(简化版)
**CPU职责**:
- 塑性应力计算
- 相变应力应用
- 残余应力计算
- 应力累积
- 形变阻力计算
- 弯曲/扭曲应用
- 裂纹判定与扩展
- 应力扫描可视化
### 6.2 数据结构
**顶点属性扩展**:
```cpp
TDynamicMeshVertexAttribute<float>* InternalStress; // 内部应力值
TDynamicMeshVertexAttribute<uint8>* CrackState; // 裂纹状态(0=无, 1=有)
```
**裂纹数据结构**:
```cpp
USTRUCT(BlueprintType)
struct FCrackData
{
GENERATED_BODY()
UPROPERTY()
int32 StartVertex; // 裂纹起始顶点ID
UPROPERTY()
int32 EndVertex; // 裂纹结束顶点ID
UPROPERTY()
float CrackWidth; // 裂纹宽度
UPROPERTY()
float CrackDepth; // 裂纹深度
UPROPERTY()
float CreationTime; // 创建时间
};
```
### 6.3 性能优化
**应力扩散**:
- 只在锤击后更新,每帧不需要
- 局部扩散,仅受影响区域
**裂纹判定**:
- 仅在高应力区域检查
- 使用空间分区加速
**可视化**:
- 应力扫描模式按需启用
- 使用简化着色器
---
## 7. 参数配置
### 7.1 可调参数(蓝图可配)
| 参数名 | 默认值 | 范围 | 说明 |
|-------|--------|------|------|
| `MaxStress` | 150.0 | 100-200 | 最大应力值 |
| `k_plastic` | 10.0 | 5-20 | 塑性应力系数 |
| `ThermalStressCoeff` | 0.005 | 0.001-0.01 | 热应力系数 |
| `PhaseStressCoeff` | 50.0 | 20-100 | 相变应力系数 |
| `k_residual` | 5.0 | 2-10 | 残余应力系数 |
| `DiffusionAlpha` | 0.2 | 0.1-0.3 | 扩散系数 |
| `BendSensitivity` | 0.001 | 0.0005-0.002 | 弯曲敏感度 |
| `TwistSensitivity` | 0.0005 | 0.0002-0.001 | 扭曲敏感度 |
| `CriticalStressThreshold` | 120.0 | 80-150 | 临界应力 |
| `CrackOpeningWidth` | 0.05 | 0.01-0.1 | 裂纹宽度 |
| `RelaxationTimeNormal` | 60.0 | 30-120 | 常温松弛时间 |
| `RelaxationTimeTemper` | 10.0 | 5-20 | 回火松弛时间 |
### 7.2 调试工具
- 应力热图:实时显示应力分布
- 裂纹追踪:记录裂纹生成和扩展
- 应力时间线:记录应力变化历史
- 统计面板:最大应力、平均应力、裂纹数量
---
## 8. 实施计划
### 8.1 第一阶段:应力计算(1天)
- [ ] 四种应力来源实现
- [ ] 应力累积规则
- [ ] 应力扩散机制
- [ ] 应力松弛机制
- [ ] 单元测试
### 8.2 第二阶段:形变阻力(0.5天)
- [ ] 形变阻力公式
- [ ] 顶点位移计算
- [ ] 参数调优
- [ ] 集成测试
### 8.3 第三阶段:物理表现(1.5天)
- [ ] 弯曲/扭曲机制
- [ ] 裂纹起始判定
- [ ] 裂纹扩展逻辑
- [ ] 裂纹视觉表现
- [ ] 几何处理(裂纹开口、抖动)
- [ ] 材质表现(锈迹、氧化)
### 8.4 第四阶段:辅助功能(0.5天)
- [ ] 应力扫描模式
- [ ] 顶点颜色着色
- [ ] UI面板
- [ ] 应力流线(可选)
### 8.5 第五阶段:集成测试(0.5天)
- [ ] 完整流程测试
- [ ] 性能Profiling
- [ ] 参数调优
- [ ] 文档更新
---
**文档版本**: 1.0
**最后更新**: 2026-01-23
**状态**: 设计完成,待评审
@@ -0,0 +1,424 @@
# 淬火退火相变与应力疲劳系统总览
**项目**: BladeSmith
**日期**: 2026-01-23
**目标**: 实现游戏娱乐级的高真实度冶金学物理系统
**性能目标**: 60 FPS @ 锤击操作
---
## 1. 设计目标
### 1.1 核心物理现象
实现以下冶金学物理过程:
1. **淬火硬化(马氏体相变)**
- 快速冷却改变晶体结构
- 极大提升硬度和脆性
- 温度区间与冷却速率双参数控制
2. **退火/回火机制**
- 重新加热软化材料
- 应力松弛,硬度降低
- 可逆性支持(可多次热处理)
3. **应力累积与松弛**
- 多来源应力:塑性形变、热应力、相变应力、残余应力
- 应力影响形变阻力和开裂风险
- 温度驱动的应力松弛机制
4. **疲劳累积与耐久度**
- 反复锻打导致的材料疲劳
- 裂纹起始与扩展机制
- 刀具使用中的耐久度系统
### 1.2 设计原则
- **游戏体验优先**: 简化物理模型,保留核心机制,60 FPS性能
- **三阶段可视化**: 淬火过程高辨识度→冷却过渡→真实物理表现
- **渐进式实现**: 分阶段开发,每阶段可独立运行
- **架构扩展性**: 为未来功能预留接口
- **YAGNI原则**: 避免过度设计,专注核心功能
---
## 2. 整体架构方案
### 2.1 混合体素-顶点架构
**核心思想**: 体素存储"物理场",顶点存储"材料历史"
```
┌─────────────────────────────────────────────────────────┐
│ 锤击输入 │
└─────────────────┬───────────────────────────────────────┘
│
┌────────┴────────┐
│ │
┌────▼─────┐ ┌─────▼────┐
│ 体素层 │ │ 顶点层 │
│ (GPU) │ │ (GPU) │
└────┬─────┘ └─────┬────┘
│ │
│ 双向采样通信 │
└────────┬────────┘
│
┌────────▼────────┐
│ 渲染 & 显示 │
└─────────────────┘
```
### 2.2 体素层(全局物理场)
**存储内容**:
- 温度场(已有)
- 冷却速率场(用于淬火判定)
- 相态标记(0=奥氏体, 1=马氏体, 2=珠光体, 3=开裂)
**分辨率**: 50×50×50(可配置)
**更新方式**: GPU Compute Shader(Ping-Pong双缓冲)
### 2.3 顶点层(局部材料历史)
**存储内容**:
- 硬度系数(影响形变阻力)
- 脆性系数(影响开裂风险)
- 内部应力值(标量简化)
- 疲劳值(0-1,影响耐久度)
- 相态标记(从体素采样)
- 回火颜色(用于真实视觉表现)
- 氧化层度数(用于氧化层表现)
- 打磨度(表面状态)
**数量**: 20-50万顶点(基础)
### 2.4 数据流
```
锤击事件
↓
1. 形变计算(顶点位移)
↓
2. 应力累积(塑性应力+残余应力)
↓
3. 从温度体素采样相态和材料参数
↓
4. 硬度/脆性计算(基于相变+冷却速率)
↓
5. 更新顶点属性(硬度、应力、疲劳)
↓
6. 应力扩散与松弛
↓
7. 疲劳累积与裂纹判定
↓
8. 材质可视化更新
```
---
## 3. 子系统概述
### 3.1 淬火退火相变系统
**目标**: 实现基于温度和冷却速率的真实热处理效果
**核心机制**:
- 温度区间判定(800-1200°C)
- 冷却速率计算(快速/中速/慢速)
- 硬度倍数映射(基于Sigmoid函数)
- 脆性系数增加(基于冷却速率)
- 相态标记永久保留
**可视化**:
- 三阶段可视化:过程高辨识度→冷却过渡→真实物理表现
- 真实回火色系统(基于最后回火温度)
- 氧化层表现(基于冷却介质)
**详细设计**: 见 [淬火退火相变系统设计.md](./2026-01-23-淬火退火相变系统设计.md)
### 3.2 应力累积系统
**目标**: 模拟多来源应力累积与物理表现
**应力来源**:
- 塑性形变应力(锤击)
- 热应力(温度梯度)
- 相变应力(马氏体膨胀)
- 残余应力(外部约束)
**物理表现**:
- 弯曲/扭曲(应力梯度导致)
- 裂纹起始与扩展(应力超阈值)
- 应力松弛(温度驱动)
**辅助功能**:
- 应力扫描模式(玩家主动开启)
- 彩虹色谱可视化应力分布
**详细设计**: 见 [应力累积系统设计.md](./2026-01-23-应力累积系统设计.md)
### 3.3 疲劳累积与耐久度系统
**目标**: 模拟材料疲劳和刀具耐久度
**疲劳机制**:
- 累积式疲劳值(每次锻打增加)
- 应力和温度影响疲劳率
- 疲劳裂纹起始与扩展
**耐久度系统**:
- 基于平均疲劳、裂纹数量、最大应力
- 使用中的损伤累积
- 回火部分恢复耐久度
**详细设计**: 见 [疲劳累积与耐久度系统设计.md](./2026-01-23-疲劳累积与耐久度系统设计.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 CoolingRate; // 冷却速率(°C/s)
UPROPERTY()
uint8 PhaseState; // 相态(0=奥氏体, 1=马氏体, 2=珠光体, 3=开裂)
FTemperatureVoxel()
: Temperature(20.0f)
, PlasticityCoefficient(0.0f)
, Flags(0)
, CoolingRate(0.0f)
, PhaseState(0)
{}
};
```
### 4.2 顶点自定义属性
```cpp
// 在DynamicIronBlock.h中添加
class ADynamicIronBlock : public ADynamicMeshActor
{
// 顶点属性层(使用FDynamicMesh3的扩展属性系统)
TDynamicMeshVertexAttribute<float>* HardnessCoefficient; // 硬度系数
TDynamicMeshVertexAttribute<float>* Brittleness; // 脆性系数(0-1)
TDynamicMeshVertexAttribute<float>* InternalStress; // 内部应力值
TDynamicMeshVertexAttribute<float>* FatigueValue; // 疲劳值(0-1)
TDynamicMeshVertexAttribute<uint8>* PhaseState; // 相态标记
TDynamicMeshVertexAttribute<FVector>* TemperColor; // 回火颜色
TDynamicMeshVertexAttribute<float>* OxidationLevel; // 氧化层度数
TDynamicMeshVertexAttribute<float>* PolishAmount; // 打磨度
};
```
### 4.3 顶点颜色编码方案
由于顶点颜色只有4通道(RGBA),需要精心分配:
| 通道 | 用途 | 范围 | 说明 |
|------|------|------|------|
| **R** | 温度可视化 | 0-1 | 淬火过程:温度映射,冷却后恢复 |
| **G** | 打磨度 | 0-1 | 0=生锈,1=光亮金属 |
| **B** | 噪声种子 | 0-1 | 用于无漂移纹理采样 |
| **A** | 辅助标记 | 0-3 | 0=正常, 1=裂纹区, 2=淬火区(临时) |
额外数据通过UV1通道或自定义属性传递。
---
## 5. GPU-CPU分工
### 5.1 GPU Compute Shader职责
- 体素层面热力学计算
- 温度场更新
- 冷却速率计算
- 相态判定
- 应力扩散(简化版)
### 5.2 CPU职责
- 顶点属性更新
- 锤击碰撞检测
- 形变计算
- 硬度/脆性/应力/疲劳公式应用
- 开裂判定
- 材质可视化更新
- 游戏逻辑(辅助功能触发)
---
## 6. 性能预算
### 6.1 目标帧时间分解(60 FPS = 16.67ms)
| 系统 | 预算 | 说明 |
|------|------|------|
| 渲染(UE5基础) | 10ms | 场景渲染、后处理 |
| 锤击形变计算 | 2-3ms | 顶点位移、BVH更新 |
| 网格质量优化 | 1-2ms | 边翻转、平滑 |
| GPU物理计算 | 1ms | Compute Shader(异步) |
| 应力/疲劳更新 | 0.5ms | 公式计算 |
| 材质可视化 | 0.5ms | 顶点颜色更新 |
| **总锤击开销** | **≤7ms** | 保持在预算内 |
### 6.2 内存预算
| 数据 | 大小 | 说明 |
|------|------|------|
| 网格顶点(20万) | 16.8 MB | 84字节/顶点(新增属性) |
| 网格三角形(40万) | 4.8 MB | 3×4字节索引 |
| BVH结构 | 5 MB | 加速结构 |
| 物理场体素(50³) | 0.4 MB | 扩展后的体素结构 |
| GPU缓冲区 | 3 MB | Ping-Pong缓冲 |
| **单铁块总计** | **~30 MB** | 可接受 |
---
## 7. 实施计划
### 阶段1:淬火退火相变系统(3-4天)
**目标**: 实现完整的热处理机制
- [ ] 扩展体素结构(冷却速率、相态)
- [ ] 顶点硬度/脆性属性系统
- [ ] 温度区间判定逻辑
- [ ] 冷却速率计算(滑动平均)
- [ ] 硬度/脆性公式实现
- [ ] 三阶段可视化系统
- [ ] 回火色系统
- [ ] 氧化层表现
**验收标准**:
- 淬火后硬度显著提升
- 冷却速率影响硬度和脆性
- 回火后硬度降低,应力松弛
- 视觉表现符合真实物理规律
### 阶段2:应力累积系统(2-3天)
**目标**: 实现应力累积与物理表现
- [ ] 应力来源分类与计算
- [ ] 应力累积规则
- [ ] 应力扩散机制
- [ ] 形变阻力公式
- [ ] 弯曲/扭曲机制
- [ ] 裂纹起始判定
- [ ] 裂纹扩展逻辑
- [ ] 应力扫描辅助功能
**验收标准**:
- 多来源应力正确累积
- 高应力区域形变困难
- 应力超阈值产生裂纹
- 裂纹沿应力梯度扩展
### 阶段3:疲劳累积与耐久度系统(2-3天)
**目标**: 实现材料疲劳和耐久度机制
- [ ] 疲劳值累积规则
- [ ] 应力和温度影响疲劳率
- [ ] 疲劳裂纹起始
- [ ] 裂纹扩展概率
- [ ] 耐久度计算公式
- [ ] 使用中的损伤累积
- [ ] 回火恢复机制
- [ ] 刀具失效判定
**验收标准**:
- 反复锻打疲劳累积
- 疲劳裂纹产生并扩展
- 耐久度正确计算
- 使用导致耐久度下降
- 回火部分恢复耐久度
### 阶段4:集成与优化(1-2天)
**目标**: 系统集成与性能优化
- [ ] 各子系统联动测试
- [ ] 参数调优
- [ ] 性能Profiling
- [ ] 调试工具完善
- [ ] 文档更新
**验收标准**:
- 60 FPS @ 连续锤击
- 所有功能正常运行
- 调试工具完善
- 文档齐全
---
## 8. 风险评估
### 8.1 技术风险
| 风险 | 概率 | 影响 | 缓解措施 |
|------|------|------|---------|
| 公式过于复杂,性能不足 | 中 | 高 | 简化计算,使用GPU并行 |
| 参数调优困难 | 中 | 中 | 提供调试工具,分阶段测试 |
| 可视化效果不真实 | 中 | 中 | 参考真实照片,迭代优化 |
| 网格拓扑变化导致属性丢失 | 中 | 高 | 使用持久化ID追踪顶点 |
### 8.2 设计风险
| 风险 | 概率 | 影响 | 缓解措施 |
|------|------|------|---------|
| 过度复杂化,开发周期过长 | 高 | 高 | 严格遵守YAGNI,分阶段验收 |
| 物理模型过于简化,真实感不足 | 中 | 中 | 阶段性用户测试,迭代调整 |
| 辅助功能与真实表现混淆 | 低 | 中 | 明确区分,UI提示清晰 |
---
## 9. 参考资料
### 9.1 物理基础
- 《金属塑性成形原理》(清华大学出版社)
- 《材料科学基础》(上海交通大学出版社)
- 《金属热处理原理与工艺》(机械工业出版社)
- *Metals Handbook: Heat Treating* (ASM International)
### 9.2 游戏案例
- *My Summer Car* - 机械装配真实度
- *BeamNG.drive* - 动态网格形变
- *Dwarf Fortress* - 材料属性系统
---
## 10. 文件索引
- [淬火退火相变系统设计.md](./2026-01-23-淬火退火相变系统设计.md)
- [应力累积系统设计.md](./2026-01-23-应力累积系统设计.md)
- [疲劳累积与耐久度系统设计.md](./2026-01-23-疲劳累积与耐久度系统设计.md)
---
**文档版本**: 1.0
**最后更新**: 2026-01-23
**状态**: 设计完成,待评审
@@ -0,0 +1,436 @@
# 淬火退火相变系统设计
**项目**: BladeSmith
**日期**: 2026-01-23
**目标**: 实现基于温度和冷却速率的真实热处理效果
**设计原则**: 游戏体验优先,三阶段可视化
---
## 1. 核心机制
### 1.1 温度区间判定
**三个关键温度阈值**:
| 阈值名称 | 温度范围 | 效果 |
|---------|---------|------|
| 临界下限 | < 800°C | 淬火无效,硬度无提升 |
| 黄金区间 | 900-1100°C | 最佳淬火,硬度最大提升 |
| 临界上限 | > 1200°C | 材料开裂风险,硬度提升低 |
**温度区间修正系数(TemperatureFactor)**:
```
TemperatureFactor =
3.0 (900°C ≤ T ≤ 1100°C, 黄金区间)
1.8 (800°C ≤ T < 900°C 或 1100°C < T ≤ 1200°C, 次优区间)
0.5 (T < 800°C, 温度过低)
0.2 (T > 1200°C, 温度过高且可能开裂)
```
### 1.2 冷却速率计算
**基础公式**:
```
CoolingRate = |T_current - T_previous| / Δt
```
- `T_current`: 当前帧体素温度
- `T_previous`: Δt时间前的体素温度
- `Δt`: 采样时间窗口(推荐0.5-1.0秒)
**滑动平均平滑**:
```
CoolingRate_smoothed = Σ(CoolingRate_i) / N (i=1 to N, N=10)
```
使用过去10帧的平均值,消除瞬时波动。
---
## 2. 硬度映射系统
### 2.1 硬度倍数公式
**基于冷却速率的Sigmoid函数**:
```
HardnessMultiplier = BaseMultiplier × Sigmoid(CoolingRate)
```
其中:
```
Sigmoid(CoolingRate) = 1 + 2 / (1 + exp(-k × (CoolingRate - Threshold)))
```
**参数说明**:
- `BaseMultiplier = 1.0`: 基准倍数
- `Threshold = 35°C/s`: 中点冷却速率
- `k = 0.1`: 曲线陡峭度(可调节)
**冷却速率分类**:
- 快速冷却(>50°C/s): 水淬,硬度最高
- 中速冷却(20-50°C/s): 油淬,硬度适中
- 慢速冷却(<20°C/s): 空冷,硬度低(退火)
### 2.2 最终硬度计算
**综合硬度公式**:
```
FinalHardness = InitialHardness × HardnessMultiplier × TemperatureFactor
```
**硬度系数对形变的影响**:
```
HardnessFactor = 1.0 + (Hardness / MaxHardness) × 2.0
```
硬度越高,形变阻力越大(最大3倍阻力)。
---
## 3. 脆性计算系统
### 3.1 脆性系数公式
**基础脆性公式**:
```
Brittleness = BaseBrittleness + BrittlenessIncrease × CoolingRate_normalized
```
**参数说明**:
- `BaseBrittleness = 0.1`: 基准脆性(未经淬火的钢)
- `BrittlenessIncrease = 0.6`: 最大脆性增加量
- `CoolingRate_normalized`: 归一化冷却速率
```
CoolingRate_normalized = clamp(CoolingRate / 80°C/s, 0, 1)
```
**脆性对开裂的影响**:
脆性系数与开裂概率直接相关,详见应力系统文档。
### 3.2 脆性可视化阈值
| 脆性范围 | 颜色 | 含义 |
|---------|------|------|
| < 0.3 | 绿色 | 韧性良好 |
| 0.3 - 0.6 | 黄色 | 中度脆性 |
| ≥ 0.6 | 红色 | 高度脆性 |
---
## 4. 内部应力公式
### 4.1 应力分量
**热应力分量(温度梯度)**:
```
StressThermal = ThermalStressCoeff × CoolingRate × (T_current - 20°C)
```
- `ThermalStressCoeff = 0.005`: 热应力系数(可调)
- `T_current`: 当前温度(°C)
- 20°C: 环境温度
**相变应力分量(马氏体体积膨胀)**:
```
StressPhase = PhaseStressCoeff × MartensiteFraction
```
- `PhaseStressCoeff = 50.0`: 相变应力系数
- `MartensiteFraction`: 马氏体转化比例
```
MartensiteFraction = clamp(CoolingRate / 80°C/s, 0, 1)
```
**总应力**:
```
InternalStress = StressThermal + StressPhase
```
### 4.2 应力松弛机制
**温度驱动的松弛公式**:
```
RelaxationFactor =
0.99 (T < 100°C, 常温慢松弛)
0.90 (200°C ≤ T < 400°C, 回火快松弛)
0.85 (T ≥ 400°C, 快速松弛)
```
**每帧更新**:
```
InternalStress = InternalStress × RelaxationFactor
```
---
## 5. 相态系统
### 5.1 相态枚举
每个体素维护相态标记:
```cpp
enum class EPhaseState : uint8
{
Austenite = 0, // 奥氏体(软)
Martensite = 1, // 马氏体(硬)
Pearlite = 2, // 珠光体(中)
Cracked = 3 // 开裂(永久缺陷)
};
```
### 5.2 相变判定规则
**奥氏体→马氏体(淬火)**:
```
if (Temperature > 800°C && CoolingRate > 20°C/s)
{
PhaseState = EPhaseState::Martensite;
// 硬度和脆性提升
}
```
**奥氏体→珠光体(退火)**:
```
if (Temperature > 800°C && CoolingRate < 20°C/s)
{
PhaseState = EPhaseState::Pearlite;
// 硬度适中,脆性低
}
```
**重置相态**:
```
if (Temperature > 800°C && TimeAbove800C > 5.0s)
{
PhaseState = EPhaseState::Austenite;
// 材料回到软态,可重新淬火
}
```
---
## 6. 三阶段可视化设计
### 6.1 阶段1:淬火过程(临时高辨识度)
**目的**: 给玩家实时反馈,知道淬火是否成功、哪些区域被覆盖
**顶点颜色强制覆盖**:
```
// R通道:温度的互补色
R = 1.0 - Temperature / MaxTemperature
// G通道:冷却速率(快=绿色,慢=红色)
G = clamp(1.0 - CoolingRate / 80°C/s, 0, 1)
// B通道:硬度倍数(暗色=软,亮色=硬)
B = (Hardness - MinHardness) / (MaxHardness - MinHardness)
// A通道:脆性(透明=韧性,不透明=脆)
A = Brittleness
```
**材质临时参数**:
- 自发光:启用,随温度变化
- 金属度:暂时设为1.0(全金属感)
- 粗糙度:暂时设为0.0(镜面反光)
**UI辅助**:
- 屏幕边缘显示"淬火进行中"
- 顶点统计:"淬火覆盖率:XX%"
- 品质评分实时显示
### 6.2 阶段2:冷却过渡(逐步恢复正常)
**目的**: 从游戏化反馈平滑过渡到真实物理表现
**颜色过渡**(0.5-1.0秒):
```
// 插值因子
Alpha = (CurrentTime - QuenchStartTime) / TransitionDuration
// R通道:温度回归到常温
R = Lerp(TemporaryR, 0.0, Alpha)
// G通道:恢复到打磨度
G = Lerp(TemporaryG, PolishAmount, Alpha)
// B通道:恢复到噪声种子
B = Lerp(TemporaryB, NoiseSeed, Alpha)
// A通道:保留相态标记
A = PhaseState // 0=奥氏体, 1=马氏体, 2=珠光体, 3=开裂
```
**材质过渡**:
```
// 自发光:指数衰减至0
Emissive = TemporaryEmissive × exp(-Alpha × 5.0)
// 金属度/粗糙度:恢复到打磨度驱动
Metallic = 0.5 + PolishAmount × 0.5
Roughness = 0.6 - PolishAmount × 0.4
```
### 6.3 阶段3:最终真实表现(永久保留)
**目的**: 展示真实的物理热处理效果
**回火色系统**:
回火色映射(新增TemperColor顶点属性):
| 回火温度 | 颜色 | RGB值 |
|---------|------|-------|
| 20-150°C(无回火) | 银灰色 | (0.8, 0.8, 0.8) |
| 150-200°C | 淡黄色 | (0.95, 0.9, 0.7) |
| 200-250°C | 金黄/麦秆色 | (0.95, 0.8, 0.4) |
| 250-300°C | 棕色 | (0.7, 0.5, 0.3) |
| 300-350°C | 紫色 | (0.6, 0.4, 0.7) |
| 350-400°C | 蓝色 | (0.4, 0.5, 0.8) |
| >400°C | 灰色 | (0.6, 0.6, 0.6) |
实现方式: 根据最后回火温度设置顶点TemperColor属性。
**氧化层表现**:
新增OxidationLevel顶点属性(0-1):
| 冷却介质 | OxidationLevel | 视觉效果 |
|---------|---------------|---------|
| 水淬 | 0.8-1.0 | 深色氧化层 |
| 油淬 | 0.4-0.6 | 浅棕色氧化层 |
| 空冷 | 0.1-0.3 | 轻微氧化 |
**最终材质组合**:
```
BaseColor = OxidationColor × (1 - PolishAmount) + TemperColor × PolishAmount
```
其中:
- `OxidationColor`: 基于OxidationLevel的深灰色
- `TemperColor`: 基于TemperColor的回火色
- `PolishAmount`: 打磨度(打磨后露出金属底色)
**相态的微妙影响**:
虽然马氏体组织肉眼不可见,但通过细微色调表现:
- 马氏体区域: 略微更冷色调(蓝灰倾向)
- 珠光体区域: 略微更暖色调(棕灰倾向)
- 奥氏体区域: 中性灰调
---
## 7. 实现架构
### 7.1 GPU-CPU分工
**GPU Compute Shader职责**:
- 体素层面热力学计算
- 温度场更新
- 冷却速率计算(滑动平均)
- 相态判定
- 热应力计算
**CPU职责**:
- 顶点属性更新
- 硬度/脆性公式应用
- 相态采样到顶点
- 三阶段可视化逻辑
- 材质参数更新
### 7.2 数据流
```
温度更新(GPU)
↓
冷却速率计算(GPU)
↓
相态判定(GPU)
↓
硬度/脆性公式(CPU)
↓
顶点属性更新(CPU)
↓
材质可视化(CPU)
```
### 7.3 性能优化
**冷却速率计算**:
- 使用Ping-Pong双缓冲避免冲突
- 滑动平均使用历史缓冲,减少计算量
**相态判定**:
- 只在温度变化时重新计算
- 使用标志位避免重复判定
**可视化**:
- 临时效果使用简单着色器
- 最终效果复用现有材质系统
---
## 8. 参数配置
### 8.1 可调参数(蓝图可配)
| 参数名 | 默认值 | 范围 | 说明 |
|-------|--------|------|------|
| `CriticalTempLow` | 800.0°C | 600-900 | 临界下限 |
| `CriticalTempHigh` | 1200.0°C | 1100-1400 | 临界上限 |
| `GoldTempMin` | 900.0°C | 800-1000 | 黄金区间下限 |
| `GoldTempMax` | 1100.0°C | 1000-1200 | 黄金区间上限 |
| `CoolingRateThreshold` | 35.0°C/s | 20-50 | Sigmoid中点 |
| `SigmoidK` | 0.1 | 0.05-0.2 | 曲线陡峭度 |
| `ThermalStressCoeff` | 0.005 | 0.001-0.01 | 热应力系数 |
| `PhaseStressCoeff` | 50.0 | 20-100 | 相变应力系数 |
| `RelaxationTimeNormal` | 60.0s | 30-120 | 常温松弛时间 |
| `RelaxationTimeTemper` | 10.0s | 5-20 | 回火松弛时间 |
### 8.2 调试工具
- 体素可视化:显示温度、冷却速率、相态
- 顶点属性检查:硬度、脆性、应力值
- 时间线:记录淬火-回火过程
- 统计面板:覆盖率、平均硬度、最大脆性
---
## 9. 实施计划
### 9.1 第一阶段:核心公式(1天)
- [ ] 冷却速率计算
- [ ] 硬度/脆性公式
- [ ] 相态判定逻辑
- [ ] 单元测试
### 9.2 第二阶段:顶点属性(1天)
- [ ] 顶点属性扩展
- [ ] 公式应用到顶点
- [ ] 材质参数更新
- [ ] 基础可视化
### 9.3 第三阶段:三阶段可视化(1-2天)
- [ ] 淬火过程临时效果
- [ ] 冷却过渡动画
- [ ] 回火色系统
- [ ] 氧化层表现
- [ ] UI辅助工具
### 9.4 第四阶段:集成测试(0.5天)
- [ ] 完整流程测试
- [ ] 性能Profiling
- [ ] 参数调优
- [ ] 文档更新
---
**文档版本**: 1.0
**最后更新**: 2026-01-23
**状态**: 设计完成,待评审
@@ -0,0 +1,629 @@
# 疲劳累积与耐久度系统设计
**项目**: BladeSmith
**日期**: 2026-01-23
**目标**: 模拟材料疲劳和刀具耐久度
**设计原则**: 累积式疲劳,可恢复耐久度
---
## 1. 疲劳累积机制
### 1.1 疲劳值定义
**疲劳值(Fatigue)**:
- 范围:0(完好)到 1(完全疲劳/失效)
- 每个顶点独立维护
- 累积式,不可逆(除非回火)
### 1.2 疲劳累积规则
**基础公式**:
```
Fatigue += BaseFatigueRate × StressFactor × TemperatureFactor × DeltaTime
```
### 1.3 各因子详细说明
**基础疲劳率(BaseFatigueRate)**:
| 锻打类型 | BaseFatigueRate | 说明 |
|---------|---------------|------|
| 正常锻打 | 0.001 | 标准锤击 |
| 过度锻打 | 0.005 | 形变过大(超过阈值) |
| 应力集中 | 0.010 | 高应力区域 |
判定过度锻打:
```
if (StrainIncrement > CriticalStrainThreshold)
{
BaseFatigueRate = 0.005; // 过度锻打
}
```
**应力因子**:
```
StressFactor = clamp(InternalStress / StressLimit, 1.0, 3.0)
```
应力越高,疲劳累积越快:
- 应力 < 1.0 × StressLimit: StressFactor = 1.0
- 应力 = 1.5 × StressLimit: StressFactor = 1.5
- 应力 ≥ 3.0 × StressLimit: StressFactor = 3.0
**温度因子**:
```
TemperatureFactor =
0.1 (T > 800°C, 高温疲劳慢)
1.0 (常温)
2.0 (T < 200°C, 低温疲劳快)
```
高温时材料更"柔软",疲劳较慢;低温时更"脆",疲劳快。
### 1.4 疲劳限制
```
Fatigue = clamp(Fatigue, 0.0, 1.0)
```
疲劳值达到1.0后不再增加,但可能触发失效。
---
## 2. 疲劳可视化(辅助功能)
### 2.1 疲劳扫描模式
**触发方式**:
- 玩家按住特定键(如Shift键)切换到"疲劳视图"
- 或在UI中选择"疲劳扫描"工具
### 2.2 视觉效果
**顶点颜色着色**:
```
// 蓝色:疲劳值 < 0.3
if (Fatigue < 0.3)
{
Color = Lerp(Blue, Green, Fatigue / 0.3);
}
// 绿色:疲劳值 0.3-0.7
else if (Fatigue < 0.7)
{
Color = Lerp(Green, Yellow, (Fatigue - 0.3) / 0.4);
}
// 黄色:疲劳值 0.7-0.9
else if (Fatigue < 0.9)
{
Color = Lerp(Yellow, Orange, (Fatigue - 0.7) / 0.2);
}
// 红色:疲劳值 > 0.9(危险区)
else
{
Color = Red;
// 叠加脉冲效果
Pulse = sin(Time × 5.0) × 0.5 + 0.5;
Emissive = Pulse × Red;
}
```
### 2.3 UI面板
- 平均疲劳值
- 最大疲劳值
- 危险区域数量(Fatigue > 0.9)
- 预估寿命
### 2.4 退出可视化
释放辅助功能键后,完全恢复正常视觉表现。
---
## 3. 疲劳裂纹系统
### 3.1 疲劳裂纹起始
**触发条件**:
```
if (Fatigue > 0.9 && InternalStress > 0.5 × StressLimit)
{
// 触发疲劳开裂
if (Random() < CrackProbability)
{
CreateFatigueCrack(Vertex);
}
}
```
**疲劳开裂概率**:
```
CrackProbability = (Fatigue - 0.9) × 10.0 // 最大1.0
```
疲劳值越高,开裂概率越大:
- Fatigue = 0.9: CrackProbability = 0.0
- Fatigue = 0.95: CrackProbability = 0.5
- Fatigue = 1.0: CrackProbability = 1.0
### 3.2 疲劳裂纹扩展
**每次锻打检查**:
```
if (Vertex.IsCracked && CrackType == Fatigue)
{
// 裂纹扩展概率
ExtendProbability = (Fatigue - 0.7) × 2.0
if (Random() < ExtendProbability)
{
// 找到疲劳值最高的相邻顶点
TargetNeighbor = FindMaxFatigueNeighbor();
CrackTo(TargetNeighbor);
}
}
```
**疲劳裂纹特点**:
- 沿高疲劳值方向扩展
- 扩展速度与疲劳值成正比
- 可能产生多条细小裂纹(应力裂纹通常是单条)
### 3.3 疲劳裂纹视觉表现
**几何表现**:
1. **细小裂缝**:
- 开口宽度较小(0.01-0.02)
- 多条裂纹可能交织
2. **边缘锯齿**:
- 裂纹边缘不光滑
- 顶点位置随机抖动
**材质表现**:
1. **表面氧化**:
- 显示轻微锈迹
- 氧化程度与时间相关
2. **内部损伤**:
- 深处 darker
- 模拟材料损伤
---
## 4. 耐久度系统
### 4.1 耐久度定义
**耐久度(Durability)**:
- 范围:0(损坏)到 1.0(完好)
- 基于整个刀具计算
- 影响刀具使用效果
### 4.2 耐久度计算公式
**综合耐久度**:
```
Durability = 1.0 - (AverageFatigue + CrackPenalty + StressPenalty)
```
**各分量说明**:
**平均疲劳**:
```
AverageFatigue = Σ(Fatigue_i) / VertexCount
```
**裂纹惩罚**:
```
CrackPenalty = CrackCount × 0.1 // 每条裂纹扣10%耐久
```
裂纹包括:
- 应力裂纹(由应力超阈值产生)
- 疲劳裂纹(由疲劳累积产生)
**应力惩罚**:
```
StressPenalty = (MaxStress / StressLimit) × 0.2
```
高应力区域最高扣除20%耐久度。
### 4.3 耐久度限制
```
Durability = clamp(Durability, 0.0, 1.0)
```
耐久度降到0后刀具可能失效。
---
## 5. 使用中的损伤累积
### 5.1 刀具使用场景
**切割/砍击**:
- 每次使用造成冲击
- 冲击点附近疲劳增加
**抛光/打磨**:
- 减少疲劳(回火效果)
- 恢复部分耐久度
### 5.2 冲击疲劳计算
**冲击应力**:
```
ImpactStress = ImpactForce × MaterialStiffness × (1 - ImpactAngle)
```
其中:
- `ImpactForce`: 冲击力(来自物理引擎)
- `MaterialStiffness`: 材料刚度
- `ImpactAngle`: 冲击角度(0-1,垂直=1.0)
**疲劳增量**:
```
Fatigue += ImpactStress / Durability × FatigueRate
```
其中:
- `FatigueRate`: 疲劳率(可配置,默认0.0001)
**局部更新**:
冲击影响半径内的顶点更新疲劳值。
### 5.3 耐久度实时更新
**每次使用后**:
```
// 重新计算耐久度
Durability = CalculateDurability();
// 更新UI
UpdateDurabilityUI();
```
---
## 6. 耐久度恢复机制
### 6.1 回火恢复
**加热至回火温度**(200-400°C):
**疲劳恢复**:
```
if (Temperature > 200°C && Temperature < 400°C)
{
Fatigue -= 0.1 × DeltaTime; // 慢慢消除疲劳
Fatigue = clamp(Fatigue, 0.0, 1.0);
}
```
**耐久度恢复**:
```
if (Temperature > 200°C && Temperature < 400°C)
{
Durability += 0.05 × DeltaTime;
Durability = clamp(Durability, 0.0, 1.0);
}
```
**注意事项**:
- 疲劳可部分恢复
- 但裂纹无法愈合
- 裂纹惩罚永久存在
### 6.2 打磨恢复
**打磨操作**:
**表面损伤修复**:
- 打磨可去除表面疲劳痕迹
- 减少视觉上的"老伤"
**疲劳影响**:
```
if (PolishAmount > OldPolishAmount)
{
Fatigue -= (PolishAmount - OldPolishAmount) × 0.5;
Fatigue = clamp(Fatigue, 0.0, 1.0);
}
```
**耐久度影响**:
- 打磨不恢复耐久度
- 但可以改善外观
---
## 7. 刀具失效判定
### 7.1 失效条件
**条件1: 耐久度耗尽**:
```
if (Durability < 0.0)
{
BreakWeapon();
}
```
**条件2: 主裂纹存在**:
```
if (MajorCrackExists)
{
// 主裂纹判定:长度超过阈值或穿过关键区域
if (CrackLength > MajorCrackThreshold || CrackThroughCriticalArea)
{
BreakWeapon();
}
}
```
**条件3: 瞬时过载**:
```
if (ImpactForce > UltimateStrength)
{
// 瞬间断裂
InstantBreakWeapon();
}
```
### 7.2 失效表现
**几何表现**:
1. **断裂**:
- 刀身分离成两段
- 网格拓扑断裂
- 删除断裂部分的顶点和三角形
2. **破碎**:
- 多个小碎片
- 适合薄刃刀具
**材质表现**:
1. **断口**:
- 显示粗糙断裂面
- 颜色暗淡
- 无光泽
2. **音效**:
- 断裂音效
- 碎片掉落声
**物理表现**:
- 断裂部分掉落
- 受重力影响
- 碰撞地面产生音效
---
## 8. 实现架构
### 8.1 数据结构
**顶点属性扩展**:
```cpp
TDynamicMeshVertexAttribute<float>* FatigueValue; // 疲劳值(0-1)
```
**裂纹数据结构**:
```cpp
UENUM(BlueprintType)
enum class ECrackType : uint8
{
Stress = 0, // 应力裂纹
Fatigue = 1, // 疲劳裂纹
Impact = 2 // 冲击裂纹
};
USTRUCT(BlueprintType)
struct FCrackData
{
GENERATED_BODY()
UPROPERTY()
int32 StartVertex; // 裂纹起始顶点ID
UPROPERTY()
int32 EndVertex; // 裂纹结束顶点ID
UPROPERTY()
TEnumAsByte<ECrackType> CrackType; // 裂纹类型
UPROPERTY()
float CrackWidth; // 裂纹宽度
UPROPERTY()
float CrackDepth; // 裂纹深度
UPROPERTY()
float CreationTime; // 创建时间
UPROPERTY()
bool bIsMajor; // 是否主裂纹
};
```
**刀具耐久度数据**:
```cpp
USTRUCT(BlueprintType)
struct FWeaponDurability
{
GENERATED_BODY()
UPROPERTY()
float Durability; // 耐久度(0-1)
UPROPERTY()
float AverageFatigue; // 平均疲劳
UPROPERTY()
int32 CrackCount; // 裂纹数量
UPROPERTY()
float MaxStress; // 最大应力
UPROPERTY()
int32 MajorCrackCount; // 主裂纹数量
};
```
### 8.2 GPU-CPU分工
**GPU Compute Shader职责**:
- 无(疲劳计算纯CPU)
**CPU职责**:
- 疲劳累积计算
- 疲劳裂纹判定
- 裂纹扩展逻辑
- 耐久度计算
- 使用中的损伤累积
- 回火/打磨恢复
- 刀具失效判定
- 疲劳扫描可视化
---
## 9. 性能优化
### 9.1 疲劳计算优化
**局部更新**:
- 锤击时仅更新受影响区域
- 使用空间分区加速查询
**批量更新**:
- 每帧最多更新N个顶点
- 使用优先级队列(高疲劳优先)
### 9.2 裂纹判定优化
**阈值检查**:
```
// 仅检查接近阈值的顶点
if (Fatigue > 0.8 && InternalStress > 0.5 × StressLimit)
{
// 检查裂纹
}
```
**空间分区**:
- 使用八叉树或BVH
- 快速查询相邻顶点
### 9.3 可视化优化
**疲劳扫描**:
- 按需启用
- 使用简化着色器
- 降低更新频率
---
## 10. 参数配置
### 10.1 可调参数(蓝图可配)
| 参数名 | 默认值 | 范围 | 说明 |
|-------|--------|------|------|
| `BaseFatigueRateNormal` | 0.001 | 0.0005-0.002 | 正常锻打疲劳率 |
| `BaseFatigueRateOverwork` | 0.005 | 0.002-0.01 | 过度锻打疲劳率 |
| `BaseFatigueRateStress` | 0.010 | 0.005-0.02 | 应力集中疲劳率 |
| `CriticalStrainThreshold` | 0.1 | 0.05-0.2 | 临界应变阈值 |
| `TemperatureFactorHigh` | 0.1 | 0.05-0.2 | 高温疲劳因子 |
| `TemperatureFactorNormal` | 1.0 | 0.8-1.2 | 常温疲劳因子 |
| `TemperatureFactorLow` | 2.0 | 1.5-2.5 | 低温疲劳因子 |
| `CrackPenalty` | 0.1 | 0.05-0.2 | 单条裂纹惩罚 |
| `StressPenalty` | 0.2 | 0.1-0.3 | 应力惩罚系数 |
| `FatigueRecoveryRate` | 0.1 | 0.05-0.2 | 回火疲劳恢复率 |
| `DurabilityRecoveryRate` | 0.05 | 0.02-0.1 | 回火耐久恢复率 |
| `ImpactFatigueRate` | 0.0001 | 0.00005-0.0002 | 冲击疲劳率 |
| `MajorCrackThreshold` | 10.0 | 5.0-20.0 | 主裂纹长度阈值 |
### 10.2 调试工具
- 疲劳热图:实时显示疲劳分布
- 疲劳时间线:记录疲劳变化历史
- 裂纹追踪:记录裂纹生成和扩展
- 耐久度面板:实时显示耐久度
- 预估寿命:基于当前疲劳率预估
---
## 11. 实施计划
### 11.1 第一阶段:疲劳累积(1天)
- [ ] 疲劳值属性系统
- [ ] 疲劳累积规则
- [ ] 应力和温度因子
- [ ] 疲劳限制
- [ ] 单元测试
### 11.2 第二阶段:疲劳裂纹(1天)
- [ ] 疲劳裂纹起始判定
- [ ] 疲劳裂纹扩展逻辑
- [ ] 疲劳裂纹视觉表现
- [ ] 集成测试
### 11.3 第三阶段:耐久度系统(1天)
- [ ] 耐久度计算公式
- [ ] 使用中的损伤累积
- [ ] 回火/打磨恢复
- [ ] 耐久度UI
- [ ] 集成测试
### 11.4 第四阶段:刀具失效(0.5天)
- [ ] 失效条件判定
- [ ] 失效表现(几何/材质/音效)
- [ ] 集成测试
### 11.5 第五阶段:辅助功能(0.5天)
- [ ] 疲劳扫描模式
- [ ] 疲劳热图
- [ ] UI面板
- [ ] 调试工具
### 11.6 第六阶段:集成测试(0.5天)
- [ ] 完整流程测试
- [ ] 性能Profiling
- [ ] 参数调优
- [ ] 文档更新
---
## 12. 风险评估
### 12.1 技术风险
| 风险 | 概率 | 影响 | 缓解措施 |
|------|------|------|---------|
| 疲劳计算性能不足 | 中 | 中 | 局部更新,批量处理 |
| 裂纹判定过于频繁 | 低 | 高 | 提高阈值,减少检查 |
| 耐久度平衡困难 | 中 | 中 | 参数可调,快速迭代 |
### 12.2 设计风险
| 风险 | 概率 | 影响 | 缓解措施 |
|------|------|------|---------|
| 疲劳积累过快,玩家受挫 | 中 | 高 | 降低基础疲劳率,提供恢复机制 |
| 刀具失效过于突然 | 低 | 中 | 增加预警,渐进式失效 |
| 辅助功能滥用 | 低 | 低 | 限制使用时长,增加冷却 |
---
**文档版本**: 1.0
**最后更新**: 2026-01-23
**状态**: 设计完成,待评审