From f7882875c5cbf1c0c2d0be52e7f566b7b70313a2 Mon Sep 17 00:00:00 2001 From: starmen1265 Date: Wed, 21 Jan 2026 23:21:13 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E9=A1=B9=E7=9B=AE?= =?UTF-8?q?=E6=A6=82=E8=BF=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 354 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 354 insertions(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..160dffd --- /dev/null +++ b/AGENTS.md @@ -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 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(); + // 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` instead of std::vector +- `TMap` instead of std::map +- `TSet` 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` for non-UObject smart pointers +- `TWeakObjectPtr` 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 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(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 From 8c27c82856f82adf04f3b7442303b8b635da6101 Mon Sep 17 00:00:00 2001 From: starmen1265 Date: Fri, 23 Jan 2026 00:31:21 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E8=AE=BE=E8=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .claude/settings.local.json | 3 +- CLAUDE.md | 239 ++++ ...01-20-vertex-deformation-quality-design.md | 488 -------- docs/plans/2026-01-22-刀刃形成技术细节.md | 684 +++++++++++ docs/plans/2026-01-22-物理模拟架构细节.md | 1022 +++++++++++++++++ docs/plans/2026-01-22-网格质量优化细节.md | 980 ++++++++++++++++ docs/plans/2026-01-22-表面磨损系统细节.md | 677 +++++++++++ docs/plans/2026-01-22-高真实度锻造系统设计.md | 462 ++++++++ .../plastic-deformation-voxel-system-plan.md | 723 ------------ 9 files changed, 4066 insertions(+), 1212 deletions(-) create mode 100644 CLAUDE.md delete mode 100644 docs/plans/2026-01-20-vertex-deformation-quality-design.md create mode 100644 docs/plans/2026-01-22-刀刃形成技术细节.md create mode 100644 docs/plans/2026-01-22-物理模拟架构细节.md create mode 100644 docs/plans/2026-01-22-网格质量优化细节.md create mode 100644 docs/plans/2026-01-22-表面磨损系统细节.md create mode 100644 docs/plans/2026-01-22-高真实度锻造系统设计.md delete mode 100644 docs/plans/plastic-deformation-voxel-system-plan.md diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 6fafc0a..ad4f00b 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -10,7 +10,8 @@ "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)" + "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": [] diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..fdc2656 --- /dev/null +++ b/CLAUDE.md @@ -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`, `TMap`, `TSet`, `FVector`, `FString`, `int32`, `uint8` +- Use `float` by default (not `double`) +- Smart pointers: rely on UObject garbage collection; use `TSharedPtr` 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 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(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 diff --git a/docs/plans/2026-01-20-vertex-deformation-quality-design.md b/docs/plans/2026-01-20-vertex-deformation-quality-design.md deleted file mode 100644 index 68a3636..0000000 --- a/docs/plans/2026-01-20-vertex-deformation-quality-design.md +++ /dev/null @@ -1,488 +0,0 @@ -# 顶点变形质量改善设计 - -**日期**: 2026-01-20 -**关联计划**: graceful-swimming-rainbow.md(塑性变形体素系统) -**问题**: 三角形顶点不规则位移导致布线与UV质量下降 - ---- - -## 问题陈述 - -在当前的塑性变形系统设计中,顶点直接从体素位移场采样(三线性插值)并应用到网格。这种独立采样会导致: - -1. **三角形退化**:顶点各自移动,可能导致三角形翻转、极扁平或自相交 -2. **UV拉伸/撕裂**:UV坐标固定但3D位置大幅移动,纹理严重扭曲 -3. **布线质量下降**:原本规则的网格流变得混乱 - -### 项目约束 - -- **性能预算**:<2ms 额外开销 -- **UV要求**:必须保持连续性,纹理细节(划痕、锈迹)需跟随表面变形 -- **网格精度**:0.5mm初始精度,高面数模型(~10万顶点) -- **拓扑**:优先保持原始拓扑,可选动态细分 - ---- - -## 方案选择 - -### 备选方案 - -**方案A:拉普拉斯平滑 + 边长保护** ✅ 已选择 -- 性能:~1.6ms -- UV质量:中上 -- 实现复杂度:低 - -**方案B:分区域刚性约束(Rigid Clusters)** -- 性能:~1.5-2ms -- UV质量:高 -- 实现复杂度:高 - -**方案C:简化版 - 仅边长约束** -- 性能:~0.5-0.8ms -- UV质量:中 -- 实现复杂度:最低 - -### 选择理由 - -方案A在性能、质量和复杂度之间取得最佳平衡: -- 性能可靠(远低于2ms预算) -- UV质量足够(轻微扭曲在铁匠场景中可接受) -- 实现简单,可快速验证 -- 易于扩展(未来可叠加其他约束) - ---- - -## 架构设计 - -### 整体流程 - -``` -现有流程: -体素位移场 → 三线性插值采样 → 直接应用到顶点 → 重算法线 - -新流程: -体素位移场 → 三线性插值采样 → 【质量改善阶段】 → 应用到顶点 → 重算法线 - ↓ - 1. 边长约束检查 - 2. 拉普拉斯平滑(2-3次迭代) - 3. 再次边长约束 -``` - -### 执行顺序理由 - -**"约束 → 平滑 → 约束"三明治结构:** -1. **第一次约束**:修复体素采样产生的极端拉伸/压缩 -2. **平滑**:改善整体拓扑流畅性,但可能引入新的小幅违规 -3. **第二次约束**:清理平滑阶段带来的边界效应 - ---- - -## 数据结构设计 - -### 新增成员变量(DynamicIronBlock.h) - -```cpp -// ========== 网格质量改善数据 ========== - -// 边长约束参数 -UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Deformation Quality") -float MaxEdgeStretchRatio = 1.35f; // 边最大拉伸比(135%) - -UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Deformation Quality") -float MaxEdgeCompressRatio = 0.70f; // 边最大压缩比(70%) - -UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Deformation Quality") -int32 EdgeConstraintIterations = 4; // 边长约束迭代次数 - -// 拉普拉斯平滑参数 -UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Deformation Quality") -float LaplacianSmoothingWeight = 0.45f; // 平滑强度(0-1) - -UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Deformation Quality") -int32 LaplacianIterations = 2; // 平滑迭代次数 - -// 是否启用质量改善 -UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Deformation Quality") -bool bEnableDeformationQualityImprovement = true; - -// 缓存:原始边长(用于约束检查) -TMap OriginalEdgeLengths; -``` - -### 内存开销 - -- 假设10万条边 → 100k × 8字节 = **~780KB** -- 在可接受范围内 - ---- - -## 算法实现 - -### 函数1:边长约束(ConstrainEdgeLengths) - -**职责**:遍历所有边,修复超出拉伸/压缩阈值的边 - -```cpp -void ADynamicIronBlock::ConstrainEdgeLengths( - FDynamicMesh3& EditMesh, - TArray& Positions) -{ - for (int iter = 0; iter < EdgeConstraintIterations; iter++) - { - int32 ViolationCount = 0; - - for (FEdgeID EdgeID : EditMesh.EdgeIndicesItr()) - { - FIndex2i EdgeVerts = EditMesh.GetEdgeV(EdgeID); - FVertexID V0 = EdgeVerts.A; - FVertexID V1 = EdgeVerts.B; - - FVector3d Pos0 = Positions[V0]; - FVector3d Pos1 = Positions[V1]; - float CurrentLength = (Pos1 - Pos0).Length(); - float OriginalLength = OriginalEdgeLengths[EdgeID]; - - float Ratio = CurrentLength / OriginalLength; - - // 检查是否违反约束 - if (Ratio > MaxEdgeStretchRatio || Ratio < MaxEdgeCompressRatio) - { - ViolationCount++; - - // 计算目标长度(限制在允许范围内) - float TargetLength = OriginalLength * FMath::Clamp( - Ratio, - MaxEdgeCompressRatio, - MaxEdgeStretchRatio - ); - - // 调整两端顶点(各承担50%) - FVector3d EdgeDir = (Pos1 - Pos0).GetSafeNormal(); - FVector3d EdgeCenter = (Pos0 + Pos1) * 0.5; - - Positions[V0] = EdgeCenter - EdgeDir * (TargetLength * 0.5); - Positions[V1] = EdgeCenter + EdgeDir * (TargetLength * 0.5); - } - } - - // 如果没有违反约束,提前退出 - if (ViolationCount == 0) break; - } -} -``` - -**关键设计点:** -- 从边的中心向两端调整(避免偏向某一端) -- 迭代式收敛(通常2-3次足够) -- 早停机制(无违规则提前结束) - ---- - -### 函数2:拉普拉斯平滑(LaplacianSmoothDeformation) - -**职责**:将每个顶点向其邻居的重心移动,改善拓扑流畅性 - -```cpp -void ADynamicIronBlock::LaplacianSmoothDeformation( - FDynamicMesh3& EditMesh, - TArray& Positions) -{ - TArray SmoothedPositions = Positions; // 复制避免顺序依赖 - - for (FVertexID VertexID : EditMesh.VertexIndicesItr()) - { - // 获取one-ring邻居 - TArray Neighbors; - EditMesh.GetVtxNbrVerts(VertexID, Neighbors); - - if (Neighbors.Num() == 0) continue; - - // 计算邻居重心 - FVector3d Centroid = FVector3d::Zero(); - for (FVertexID NeighborID : Neighbors) - { - Centroid += Positions[NeighborID]; - } - Centroid /= (double)Neighbors.Num(); - - // 向重心移动(加权混合) - float Weight = LaplacianSmoothingWeight; - - // 边界顶点使用更低权重(避免收缩) - if (EditMesh.IsBoundaryVertex(VertexID)) - { - Weight *= 0.5f; - } - - SmoothedPositions[VertexID] = FMath::Lerp( - Positions[VertexID], - Centroid, - Weight - ); - } - - Positions = SmoothedPositions; -} -``` - -**关键设计点:** -- 使用临时数组避免顺序依赖(雅可比迭代) -- 权重0.45是经验值(平衡平滑效果与细节保留) -- 边界顶点降低权重(防止边界收缩) - ---- - -## 集成到现有流程 - -### 修改 UpdateVertexDeformation() - -在 `DynamicIronBlock.cpp` 中修改(原计划第368-400行): - -```cpp -void ADynamicIronBlock::UpdateVertexDeformation() -{ - UDynamicMeshComponent* DynMeshComp = GetDynamicMeshComponent(); - if (!DynMeshComp) return; - - UDynamicMesh* DynMesh = DynMeshComp->GetDynamicMesh(); - if (!DynMesh) return; - - // 修改网格顶点 - DynMesh->EditMesh([&](FDynamicMesh3& EditMesh) - { - // === 阶段1:从体素场采样位移 === - TArray NewPositions; - NewPositions.SetNum(EditMesh.MaxVertexID()); - - for (int32 VertexID : EditMesh.VertexIndicesItr()) - { - FVector3d OriginalPos = FVector3d(OriginalVertexPositions[VertexID]); - FVector Displacement = SampleDisplacementAtPosition(FVector(OriginalPos)); - NewPositions[VertexID] = OriginalPos + FVector3d(Displacement); - } - - // === 阶段2:质量改善(可选开关) === - if (bEnableDeformationQualityImprovement) - { - // 2.1 第一次边长约束 - ConstrainEdgeLengths(EditMesh, NewPositions); - - // 2.2 拉普拉斯平滑(迭代2-3次) - for (int i = 0; i < LaplacianIterations; i++) - { - LaplacianSmoothDeformation(EditMesh, NewPositions); - } - - // 2.3 第二次边长约束(清理平滑引入的新违规) - ConstrainEdgeLengths(EditMesh, NewPositions); - } - - // === 阶段3:应用最终位置 === - for (int32 VertexID : EditMesh.VertexIndicesItr()) - { - EditMesh.SetVertex(VertexID, NewPositions[VertexID]); - } - - }, EDynamicMeshChangeType::GeneralEdit, EDynamicMeshAttributeChangeFlags::Unknown, false); - - // 重新计算法线 - FGeometryScriptCalculateNormalsOptions NormalOp; - UGeometryScriptLibrary_MeshNormalsFunctions::RecomputeNormals(DynMesh, NormalOp); - - DynMeshComp->NotifyMeshUpdated(); -} -``` - -### 初始化修改 - -在 `InitializeBoxMesh()` 中添加原始边长缓存: - -```cpp -// 保存原始顶点位置后,缓存原始边长 -OriginalEdgeLengths.Empty(); -DynMesh->EditMesh([&](FDynamicMesh3& EditMesh) -{ - for (FEdgeID EdgeID : EditMesh.EdgeIndicesItr()) - { - FVector3d V0 = EditMesh.GetVertex(EditMesh.GetEdgeV(EdgeID).A); - FVector3d V1 = EditMesh.GetVertex(EditMesh.GetEdgeV(EdgeID).B); - float Length = (V1 - V0).Length(); - OriginalEdgeLengths.Add(EdgeID, Length); - } -}); -``` - ---- - -## 性能优化策略 - -### 优化1:边界顶点特殊处理 ✅ 已集成 - -降低边界顶点的平滑权重,防止边界收缩。 - -**性能影响**:可忽略 - ---- - -### 优化2:脏标记系统(未来扩展) - -**当前**:每次锤击处理整个网格 -**优化**:只处理变形区域附近的顶点 - -```cpp -TSet DirtyVertices; // 受影响的顶点集合 - -void MarkDirtyVerticesInRadius(FVector ImpactCenter, float Radius) -{ - // 标记半径内的所有顶点为"脏" -} - -// 在质量改善阶段只处理脏顶点 -``` - -**预期收益**:如果变形区域<30%网格,性能提升2-3倍 - -**实现优先级**:低(等性能瓶颈出现再优化) - ---- - -### 优化3:并行化(可选) - -使用 `ParallelFor` 并行处理独立的边组。 - -**复杂度**:高(需要边着色算法分组) -**收益**:约1.5-2倍加速 -**建议**:先验证串行版本,性能不足再考虑 - ---- - -### 性能监控 - -添加分段计时日志: - -```cpp -double T0 = FPlatformTime::Seconds(); -// 采样位移 -double T1 = FPlatformTime::Seconds(); -UE_LOG(LogTemp, Warning, TEXT("Displacement Sampling: %.2f ms"), (T1-T0)*1000); - -ConstrainEdgeLengths(...); -double T2 = FPlatformTime::Seconds(); -UE_LOG(LogTemp, Warning, TEXT("Edge Constraints: %.2f ms"), (T2-T1)*1000); - -// ... 其他阶段 -``` - ---- - -## 测试验证 - -### 单元测试 - -**测试1:边长约束正确性** -- 创建简单网格,人为拉伸某条边到200% -- 验证:拉伸比被限制到135%,迭代次数<5 - -**测试2:拉普拉斯平滑效果** -- 随机扰动顶点位置(±5cm噪声) -- 验证:扰动幅度降低>60% - ---- - -### 集成测试 - -**测试3:冷态锤击(塑性=0)** -- 验证:顶点位移<0.1mm,质量改善不引入额外变形 - -**测试4:热态锤击(塑性=1)** -- 验证: - - 冲击点凹陷深度>2mm - - 边长变化率<35% - - 无三角形翻转 - - UV纹理连续 - -**测试5:连续锤击压力测试** -- 同一位置连续锤击10次 -- 验证:每次耗时<7ms,无性能衰减 - ---- - -### 视觉验证 - -**测试6:UV完整性检查** -- 使用棋盘格纹理 -- 验证:变形后棋盘格连续(无撕裂/重叠) - -**测试7:纹理细节跟随测试** -- 绘制划痕纹理 -- 验证:划痕跟随表面变形,不发生偏移 - ---- - -### 性能基准测试 - -**目标配置:** -- 网格:10万顶点,20万三角形 -- 体素分辨率:50×50×50 -- 锤击影响半径:5cm - -**目标指标:** -- 边长约束:<1.5ms -- 拉普拉斯平滑:<1.5ms -- 总耗时:<7ms(满足60fps) - -**降级策略:** -1. `LaplacianIterations` 从2降到1 -2. `EdgeConstraintIterations` 从4降到3 -3. 启用脏标记系统 - ---- - -## 实现清单 - -### 文件修改 - -**DynamicIronBlock.h** -- 添加成员变量:`OriginalEdgeLengths` 及质量改善参数 -- 添加函数声明:`ConstrainEdgeLengths()`, `LaplacianSmoothDeformation()` - -**DynamicIronBlock.cpp** -- 实现两个质量改善函数 -- 修改 `UpdateVertexDeformation()` 添加质量改善阶段 -- 修改 `InitializeBoxMesh()` 缓存原始边长 - ---- - -## 风险与缓解 - -### 风险1:性能超出预算 - -**缓解**: -- 降低迭代次数 -- 实现脏标记系统(局部处理) -- 添加性能监控日志,定位瓶颈 - ---- - -### 风险2:平滑过度导致细节丢失 - -**缓解**: -- 暴露所有参数到Blueprint,实时调整 -- 降低 `LaplacianSmoothingWeight` -- 减少 `LaplacianIterations` - ---- - -### 风险3:边界收缩或扭曲 - -**缓解**: -- 已实现边界顶点特殊处理(降低权重) -- 可选:完全跳过边界顶点的平滑 - ---- - -## 总结 - -本设计通过在体素位移采样后添加三步质量改善流程(约束→平滑→约束),在合理的性能预算(~2ms)内显著改善了顶点变形的拓扑和UV质量,满足了铁匠锻造场景中纹理细节跟随表面的需求。 - -设计优先保持实现简单性和可扩展性,未来可根据实际需求叠加更多约束(如法线保持、体积守恒等)。 diff --git a/docs/plans/2026-01-22-刀刃形成技术细节.md b/docs/plans/2026-01-22-刀刃形成技术细节.md new file mode 100644 index 0000000..d5d0fe9 --- /dev/null +++ b/docs/plans/2026-01-22-刀刃形成技术细节.md @@ -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& 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& EdgeVertices) +{ + for (int32 VID : EdgeVertices) + { + if (!Mesh.IsVertex(VID)) + continue; + + // 找到此顶点的边缘邻居 + TArray 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& 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 EdgeVertices; + MarkEdgeVertices(Mesh, EdgeVertices, EdgeThreshold); + + // 只处理受影响区域的边缘顶点 + TSet 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 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. **多重边缘**: 复杂形状(如叉状)的方向检测可能不准确 + +**未来改进方向**: +- 基于曲率张量的精确流动方向 +- 支持拓扑变化(网格切割与连接) +- 多工具系统(不同锤头形状) diff --git a/docs/plans/2026-01-22-物理模拟架构细节.md b/docs/plans/2026-01-22-物理模拟架构细节.md new file mode 100644 index 0000000..1815fd7 --- /dev/null +++ b/docs/plans/2026-01-22-物理模拟架构细节.md @@ -0,0 +1,1022 @@ +# 物理模拟架构细节 + +**所属系统**: 高真实度锻造系统 +**文档类型**: 技术实现细节 +**最后更新**: 2026-01-22 + +--- + +## 1. 系统概述 + +### 1.1 目标 + +实现游戏娱乐级的金属锻造物理模拟,包含: +- **加工硬化**: 应变累积导致材料变硬 +- **应力分布**: 不均匀形变导致内部应力 +- **淬火硬化**: 快速冷却导致的马氏体相变 +- **晶粒演化**: 温度和应变影响微观组织 + +### 1.2 架构分层 + +``` +┌────────────────────────────────────────┐ +│ 游戏逻辑层(CPU) │ +│ - 锤击输入处理 │ +│ - 形变触发 │ +│ - 参数控制 │ +└──────────────┬─────────────────────────┘ + │ +┌──────────────▼─────────────────────────┐ +│ 顶点层(CPU/GPU混合) │ +│ - 累积应变 │ +│ - 硬度系数 │ +│ - 晶粒尺寸 │ +│ - 应力值 │ +└──────────────┬─────────────────────────┘ + │ 双向采样 +┌──────────────▼─────────────────────────┐ +│ 体素层(GPU Compute Shader) │ +│ - 温度场 │ +│ - 应变率场 │ +│ - 冷却速率场 │ +│ - 相态标记 │ +└────────────────────────────────────────┘ +``` + +--- + +## 2. 数据结构 + +### 2.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; // 0-1范围,归一化的应变率 + + // 冷却速率(用于淬火判断) + UPROPERTY() + float CoolingRate; // °C/s + + // 相态标记 + UPROPERTY() + EPhaseState PhaseState; // 奥氏体/马氏体/珠光体 + + // 淬火标记(是否经历过快速冷却) + UPROPERTY() + bool bHasBeenQuenched; // 淬火状态 + + FTemperatureVoxel() + : Temperature(20.0f) + , PlasticityCoefficient(0.0f) + , Flags(0) + , AverageStrainRate(0.0f) + , CoolingRate(0.0f) + , PhaseState(EPhaseState::Austenite) // 初始为奥氏体 + , bHasBeenQuenched(false) + {} +}; + +/** + * 相态枚举 + */ +UENUM(BlueprintType) +enum class EPhaseState : uint8 +{ + Austenite UMETA(DisplayName = "奥氏体"), // 高温软相(>800°C) + Martensite UMETA(DisplayName = "马氏体"), // 淬火硬相 + Pearlite UMETA(DisplayName = "珠光体"), // 缓冷软相 + Bainite UMETA(DisplayName = "贝氏体"), // 中间相 +}; +``` + +### 2.2 顶点属性结构 + +```cpp +class ADynamicIronBlock +{ +private: + // ===== 顶点自定义属性层 ===== + + // 初始位置(用于无漂移纹理采样) + TDynamicMeshVertexAttribute* InitialPositions; + + // 累积塑性应变(0-1,归一化) + TDynamicMeshVertexAttribute* AccumulatedStrain; + + // 当前硬度系数(0-1,0=最软,1=最硬) + TDynamicMeshVertexAttribute* HardnessCoefficient; + + // 打磨度(表面状态,0=生锈,1=光亮) + TDynamicMeshVertexAttribute* PolishAmount; + + // 晶粒尺寸(0-1,归一化,0=细晶粒,1=粗晶粒) + TDynamicMeshVertexAttribute* GrainSize; + + // 应力值(简化标量,0-1,0=无应力,1=高应力) + TDynamicMeshVertexAttribute* StressValue; + + // 应力方向(可选,三维向量) + TDynamicMeshVertexAttribute* StressDirection; + +public: + // 初始化所有顶点属性 + void InitializeVertexAttributes(); +}; +``` + +--- + +## 3. 加工硬化模型 + +### 3.1 物理原理 + +加工硬化(Work Hardening): +- **现象**: 金属在塑性形变后变硬,屈服强度提高 +- **机理**: 位错密度增加,晶格畸变 +- **可逆性**: 加热到再结晶温度(约600°C)后软化 + +### 3.2 简化模型 + +```cpp +/** + * 应变-硬度映射(简化经验公式) + * 基于真实钢的应力-应变曲线拟合 + */ +float CalculateHardnessFromStrain(float AccumulatedStrain) +{ + // 参数(可调) + const float InitialHardness = 0.2f; // 初始硬度 + const float MaxHardness = 1.0f; // 最大硬度 + const float SaturationStrain = 0.5f; // 饱和应变 + + // Ludwik方程简化版:σ = σ₀ + k·εⁿ + float HardnessIncrease = MaxHardness - InitialHardness; + float StrainEffect = FMath::Pow(AccumulatedStrain / SaturationStrain, 0.5f); + + return InitialHardness + HardnessIncrease * FMath::Min(StrainEffect, 1.0f); +} + +/** + * 温度软化因子 + * 高温下硬度降低(模拟动态回复) + */ +float CalculateThermalSoftening(float Temperature, float Hardness) +{ + // 再结晶温度(简化) + const float RecrystallizationTemp = 600.0f; // °C + + if (Temperature < RecrystallizationTemp) + return Hardness; // 低温:无软化 + + // 高温:软化 + float Overheating = Temperature - RecrystallizationTemp; + float SofteningFactor = FMath::Clamp(Overheating / 400.0f, 0.0f, 0.8f); + + return Hardness * (1.0f - SofteningFactor); +} +``` + +### 3.3 实现代码 + +```cpp +/** + * 锤击时更新硬度和应变 + */ +void ADynamicIronBlock::OnHammerImpact( + FVector HitLocation, + 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) + { + // 1. 计算局部形变量(简化) + float DistanceRatio = Distance / ImpactRadius; + float LocalDeformation = FMath::Pow(1.0f - DistanceRatio, 2.0f); + float StrainIncrease = LocalDeformation * ImpactForce * 0.0001f; + + // 2. 累积应变 + float CurrentStrain = AccumulatedStrain->GetValue(VID); + float NewStrain = FMath::Min(1.0f, CurrentStrain + StrainIncrease); + AccumulatedStrain->SetValue(VID, NewStrain); + + // 3. 从温度体素采样温度 + FVector VoxelPos = VertexPos / VoxelSize; + float Temperature = SampleTemperatureFromVoxel(VoxelPos); + + // 4. 计算硬度(应变硬化) + float StrainHardness = CalculateHardnessFromStrain(NewStrain); + + // 5. 温度软化 + float FinalHardness = CalculateThermalSoftening(Temperature, StrainHardness); + + // 6. 更新硬度 + HardnessCoefficient->SetValue(VID, FinalHardness); + + // 7. 硬度影响形变阻力(可选:反馈到形变计算) + // 下一锤击时,高硬度区域形变小 + } + } + }); +} +``` + +--- + +## 4. 应力分布模型 + +### 4.1 物理原理 + +应力来源: +1. **不均匀形变**: 相邻区域形变量不同 → 内部应力 +2. **热应力**: 温度梯度导致膨胀不均 +3. **相变应力**: 奥氏体→马氏体体积膨胀4% + +应力后果: +- **弹性回弹**: 形变后部分恢复 +- **扭曲**: 不对称应力导致变形 +- **开裂**: 应力超过材料强度 + +### 4.2 简化应力计算 + +```cpp +/** + * 计算顶点应力(简化模型) + * 基于局部应变梯度 + */ +float CalculateVertexStress( + const FDynamicMesh3& Mesh, + int32 VID, + const TDynamicMeshVertexAttribute& StrainAttr) +{ + float CenterStrain = StrainAttr.GetValue(VID); + + // 获取邻居顶点 + TArray Neighbors; + for (int32 NbrVID : Mesh.VtxVerticesItr(VID)) + { + Neighbors.Add(NbrVID); + } + + if (Neighbors.Num() == 0) + return 0.0f; + + // 计算应变方差(梯度度量) + float StrainSum = 0.0f; + float StrainSumSq = 0.0f; + + for (int32 NbrVID : Neighbors) + { + float NbrStrain = StrainAttr.GetValue(NbrVID); + StrainSum += NbrStrain; + StrainSumSq += NbrStrain * NbrStrain; + } + + float MeanStrain = StrainSum / Neighbors.Num(); + float MeanStrainSq = StrainSumSq / Neighbors.Num(); + float Variance = MeanStrainSq - MeanStrain * MeanStrain; + + // 应力 = 应变梯度(简化) + float Stress = FMath::Sqrt(FMath::Max(Variance, 0.0f)); + + return Stress; +} + +/** + * 更新所有顶点的应力 + */ +void ADynamicIronBlock::UpdateStressDistribution() +{ + if (!DynamicMesh->HasMesh()) + return; + + DynamicMesh->EditMesh([&](FDynamicMesh3& Mesh) + { + for (int32 VID : Mesh.VertexIndicesItr()) + { + float Stress = CalculateVertexStress(Mesh, VID, *AccumulatedStrain); + + // 应力松弛(模拟时效) + float CurrentStress = StressValue->GetValue(VID); + float RelaxationRate = 0.01f; // 每帧松弛1% + float NewStress = FMath::Lerp(CurrentStress, Stress, RelaxationRate); + + StressValue->SetValue(VID, NewStress); + } + }); +} +``` + +### 4.3 应力导致开裂(可视化) + +```cpp +/** + * 检测高应力区域并标记裂纹 + */ +void ADynamicIronBlock::DetectCracks() +{ + const float StressThreshold = 0.8f; // 应力阈值 + + DynamicMesh->EditMesh([&](FDynamicMesh3& Mesh) + { + for (int32 TID : Mesh.TriangleIndicesItr()) + { + FIndex3i Tri = Mesh.GetTriangle(TID); + + // 计算三角形平均应力 + float AvgStress = ( + StressValue->GetValue(Tri.A) + + StressValue->GetValue(Tri.B) + + StressValue->GetValue(Tri.C) + ) / 3.0f; + + // 应力过高 → 标记为裂纹(材质中显示) + if (AvgStress > StressThreshold) + { + // 方法1: 修改顶点颜色 + FColor CrackColor = FColor::Black; + Mesh.SetVertexColor(Tri.A, CrackColor); + Mesh.SetVertexColor(Tri.B, CrackColor); + Mesh.SetVertexColor(Tri.C, CrackColor); + + // 方法2: 拓扑分裂(极端情况) + if (AvgStress > 0.95f) + { + SplitTriangleAlongCrack(Mesh, TID); + } + } + } + }); +} +``` + +--- + +## 5. 淬火硬化系统 + +### 5.1 物理原理 + +淬火(Quenching): +- **过程**: 高温奥氏体(>800°C)快速冷却(水/油) +- **结果**: 形成马氏体,硬度剧增 +- **临界**: 冷却速率 > 临界冷却速率(约100°C/s) + +### 5.2 淬火判断逻辑 + +```cpp +/** + * 判断是否发生淬火 + */ +bool ShouldTriggerQuenching( + float CurrentTemperature, + float PreviousTemperature, + float DeltaTime) +{ + // 计算冷却速率 + float CoolingRate = (PreviousTemperature - CurrentTemperature) / DeltaTime; + + // 淬火条件: + // 1. 当前温度在奥氏体区间 + // 2. 冷却速率超过临界值 + const float CriticalCoolingRate = 100.0f; // °C/s + const float AusteniteMin = 800.0f; + const float AusteniteMax = 1200.0f; + + if (CurrentTemperature >= AusteniteMin && CurrentTemperature <= AusteniteMax) + { + if (CoolingRate > CriticalCoolingRate) + { + return true; + } + } + + return false; +} + +/** + * 更新体素相态 + */ +void ADynamicIronBlock::UpdateVoxelPhaseState(float DeltaTime) +{ + // 在GPU Compute Shader中实现(异步) + // 这里展示CPU参考实现 + + for (int32 Z = 0; Z < TemperatureResolution.Z; ++Z) + { + for (int32 Y = 0; Y < TemperatureResolution.Y; ++Y) + { + for (int32 X = 0; X < TemperatureResolution.X; ++X) + { + FIntVector Coord(X, Y, Z); + int32 Index = GetTemperatureIndex(Coord); + FTemperatureVoxel& Voxel = TemperatureGrid[Index]; + + // 保存上一帧温度 + float PrevTemp = Voxel.Temperature; + + // 温度传导(已有逻辑) + UpdateTemperatureVoxel(Coord, DeltaTime); + + // 判断淬火 + if (ShouldTriggerQuenching(Voxel.Temperature, PrevTemp, DeltaTime)) + { + // 奥氏体 → 马氏体相变 + Voxel.PhaseState = EPhaseState::Martensite; + Voxel.bHasBeenQuenched = true; + + // 硬度剧增 + // (影响顶点硬度的采样) + } + // 缓冷:奥氏体 → 珠光体 + else if (Voxel.Temperature < 600.0f && + Voxel.PhaseState == EPhaseState::Austenite) + { + Voxel.PhaseState = EPhaseState::Pearlite; + } + } + } + } +} +``` + +### 5.3 相变硬度映射 + +```cpp +/** + * 相态→硬度映射 + */ +float GetPhaseHardness(EPhaseState Phase) +{ + switch (Phase) + { + case EPhaseState::Martensite: + return 1.0f; // 马氏体:最硬 + case EPhaseState::Bainite: + return 0.7f; // 贝氏体:中硬 + case EPhaseState::Pearlite: + return 0.4f; // 珠光体:软 + case EPhaseState::Austenite: + default: + return 0.2f; // 奥氏体:最软 + } +} + +/** + * 顶点从体素采样硬度 + */ +float ADynamicIronBlock::SampleHardnessFromVoxel(FVector LocalPos) +{ + // 三线性插值采样温度体素 + FIntVector VoxelCoord = WorldToVoxelCoord(LocalPos); + int32 Index = GetTemperatureIndex(VoxelCoord); + + if (Index == INDEX_NONE) + return 0.2f; // 默认硬度 + + const FTemperatureVoxel& Voxel = TemperatureGrid[Index]; + + // 基础硬度(来自相态) + float PhaseHardness = GetPhaseHardness(Voxel.PhaseState); + + // 淬火过才有额外硬度加成 + if (Voxel.bHasBeenQuenched) + { + PhaseHardness += 0.2f; // 淬火加成 + } + + return FMath::Clamp(PhaseHardness, 0.0f, 1.0f); +} +``` + +--- + +## 6. 晶粒演化模型 + +### 6.1 物理原理 + +晶粒尺寸影响材料特性: +- **细晶粒**: 高强度、高韧性(Hall-Petch关系) +- **粗晶粒**: 高温下更容易形变 + +晶粒演化因素: +- **再结晶**: 高温+应变 → 新晶粒形成 +- **晶粒长大**: 高温下晶粒合并 +- **晶粒细化**: 大应变+低温 → 细化 + +### 6.2 简化晶粒模型 + +```cpp +/** + * 晶粒尺寸演化(简化标量模型) + */ +float EvolveGrainSize( + float CurrentGrainSize, // 当前晶粒尺寸(归一化0-1) + float Temperature, // 当前温度(°C) + float Strain, // 累积应变 + float DeltaTime) // 时间步长 +{ + const float RecrystallizationTemp = 600.0f; + const float GrainGrowthTemp = 900.0f; + + // 1. 再结晶:大应变+高温 → 晶粒细化 + if (Temperature > RecrystallizationTemp && Strain > 0.3f) + { + float RecrystallizationRate = 0.1f; // 可调 + return CurrentGrainSize * (1.0f - RecrystallizationRate * DeltaTime); + } + + // 2. 晶粒长大:高温 → 晶粒粗化 + if (Temperature > GrainGrowthTemp) + { + float GrowthRate = 0.05f * (Temperature - GrainGrowthTemp) / 300.0f; + return FMath::Min(1.0f, CurrentGrainSize * (1.0f + GrowthRate * DeltaTime)); + } + + // 3. 晶粒细化:大应变 → 晶粒细化 + if (Strain > 0.5f) + { + float RefinementRate = 0.02f; + return FMath::Max(0.1f, CurrentGrainSize * (1.0f - RefinementRate * DeltaTime)); + } + + return CurrentGrainSize; +} + +/** + * 更新顶点晶粒尺寸 + */ +void ADynamicIronBlock::UpdateGrainStructure(float DeltaTime) +{ + DynamicMesh->EditMesh([&](FDynamicMesh3& Mesh) + { + for (int32 VID : Mesh.VertexIndicesItr()) + { + float CurrentGrainSize = GrainSize->GetValue(VID); + float Strain = AccumulatedStrain->GetValue(VID); + + // 采样温度 + FVector Pos = Mesh.GetVertex(VID); + float Temperature = SampleTemperatureFromVoxel(Pos); + + // 演化晶粒尺寸 + float NewGrainSize = EvolveGrainSize(CurrentGrainSize, Temperature, Strain, DeltaTime); + + GrainSize->SetValue(VID, NewGrainSize); + } + }); +} +``` + +### 6.3 大马士革花纹模拟 + +```cpp +/** + * 初始化层状材料(用于大马士革钢) + */ +void InitializeLayeredMaterialStructure() +{ + DynamicMesh->EditMesh([&](FDynamicMesh3& Mesh) + { + TDynamicMeshVertexAttribute* MaterialLayerID = + new TDynamicMeshVertexAttribute(&Mesh); + + for (int32 VID : Mesh.VertexIndicesItr()) + { + FVector Pos = Mesh.GetVertex(VID); + + // 简单层状分布:沿Z轴 + // 每层厚度2mm + int32 LayerID = (int32)FMath::Floor(Pos.Z / 2.0f) % 2; + + MaterialLayerID->SetValue(VID, LayerID); + } + }); +} + +/** + * 渲染大马士革花纹 + * 材质中基于MaterialLayerID采样不同颜色 + */ +// 材质实现: +/* +float LayerID = MaterialLayerAttribute; // 从顶点属性读取 + +float3 HighCarbonColor = float3(0.15, 0.1, 0.08); // 深色层 +float3 LowCarbonColor = float3(0.75, 0.7, 0.65); // 浅色层 + +float3 BaseColor = lerp(HighCarbonColor, LowCarbonColor, LayerID); + +// 添加扭曲(基于形变) +float Distortion = length(AccumulatedStrain) * 5.0; +float3 WarpedPattern = WarpedNoise(BaseColor, Distortion); + +FinalColor = BaseColor * (0.8 + WarpedPattern * 0.2); +*/ +``` + +--- + +## 7. GPU Compute Shader集成 + +### 7.1 Shader结构 + +``` +MiceShaders/Source/MiceShaders/Private/ +├── MetallurgyComputeShader.usf +├── MetallurgyComputeShader.h +└── MetallurgyComputeShader.cpp +``` + +### 7.2 HLSL Shader + +```hlsl +// MetallurgyComputeShader.usf + +#define VOXEL_SIZE 50 +#define THREAD_COUNT 32 + +// 输入/输出缓冲区 +RWStructuredBuffer TemperatureBufferA; // 温度 + 塑性系数 +RWStructuredBuffer TemperatureBufferB; // Ping-Pong + +RWStructuredBuffer MetallurgyBuffer; // 应变率 + 冷却速率 + 相态 + 淬火标记 + +// 参数 +cbuffer MetallurgyParams +{ + float DeltaTime; + float3 VoxelWorldMin; + float VoxelSize; + float3 Padding; +}; + +/** + * 计算 Metallurgy Shader 主函数 + */ +[numthreads(THREAD_COUNT, THREAD_COUNT, 1)] +void MetallurgyComputeShader(uint3 ThreadId : SV_DispatchThreadID) +{ + uint VoxelIndex = ThreadId.x + ThreadId.y * VoxelSize; + + // 读取温度 + float Temperature = TemperatureBufferA[VoxelIndex].r; + float Plasticity = TemperatureBufferA[VoxelIndex].g; + + // 读取应变率 + float StrainRate = MetallurgyBuffer[VoxelIndex].r; + + // 1. 应变率衰减(模拟时效) + StrainRate *= 0.99; + + // 2. 计算冷却速率 + float3 WorldPos = VoxelToWorldPosition(VoxelIndex); + float SurfaceFactor = IsSurfaceVoxel(WorldPos) ? 1.0 : 0.1; + float CoolingRate = SurfaceFactor * (Temperature - 20.0) * 0.1; + + // 3. 相态判断 + uint PhaseState = 0; // 奥氏体 + if (CoolingRate > 100.0 && Temperature > 800.0) + { + PhaseState = 1; // 马氏体(淬火) + } + else if (Temperature < 600.0) + { + PhaseState = 2; // 珠光体(缓冷) + } + + // 写回 + MetallurgyBuffer[VoxelIndex] = float4(StrainRate, CoolingRate, PhaseState, 0.0); +} + +/** + * 辅助函数:体素索引转世界坐标 + */ +float3 VoxelToWorldPosition(uint Index) +{ + uint X = Index % VoxelSize; + uint Y = (Index / VoxelSize) % VoxelSize; + uint Z = Index / (VoxelSize * VoxelSize); + + return VoxelWorldMin + float3(X, Y, Z) * VoxelSize; +} + +/** + * 辅助函数:判断是否为表面体素 + */ +bool IsSurfaceVoxel(float3 WorldPos) +{ + // 简化:边界体素为表面 + // 实际应使用法线判断 + return false; +} +``` + +### 7.3 C++调用 + +```cpp +// MetallurgyComputeShader.h +class FMetallurgyComputeShader +{ +public: + static void Dispatch( + FRHICommandListImmediate& RHICmdList, + FStructuredBufferRHIRef TemperatureBufferA, + FStructuredBufferRHIRef TemperatureBufferB, + FStructuredBufferRHIRef MetallurgyBuffer, + float DeltaTime, + FIntVector VoxelResolution); +}; + +// MetallurgyComputeShader.cpp +void FMetallurgyComputeShader::Dispatch( + FRHICommandListImmediate& RHICmdList, + FStructuredBufferRHIRef TemperatureBufferA, + FStructuredBufferRHIRef TemperatureBufferB, + FStructuredBufferRHIRef MetallurgyBuffer, + float DeltaTime, + FIntVector VoxelResolution) +{ + // 设置Shader + TShaderMapRef ComputeShader(GetGlobalShaderMap(GMaxRHIFeatureLevel)); + + // 设置参数 + RHICmdList.SetComputeShader(ComputeShader.GetComputeShader()); + ComputeShader->SetParameters(RHICmdList, TemperatureBufferA, TemperatureBufferB, + MetallurgyBuffer, DeltaTime, VoxelMin, VoxelSize); + + // Dispatch + int32 ThreadGroupCount = (VoxelResolution.X + 31) / 32; + RHICmdList.DispatchComputeShader(ThreadGroupCount, ThreadGroupCount, 1); +} +``` + +--- + +## 8. 材质可视化 + +### 8.1 硬度可视化 + +```hlsl +// 材质中:基于硬度调整颜色 +float Hardness = SampleHardnessFromVoxel(WorldPos); + +float3 SoftColor = float3(0.6, 0.5, 0.4); // 软:棕色 +float3 HardColor = float3(0.8, 0.8, 0.85); // 硬:银白色 + +float3 HardnessColor = lerp(SoftColor, HardColor, Hardness); +``` + +### 8.2 应力可视化 + +```hlsl +// 顶点颜色.A通道存储应力 +float Stress = VertexColor.a; + +float3 LowStressColor = float3(0.3, 0.5, 0.7); // 低应力:蓝色 +float3 HighStressColor = float3(1.0, 0.3, 0.1); // 高应力:红色 + +float3 StressColor = lerp(LowStressColor, HighStressColor, Stress); + +// 混合到基础颜色 +BaseColor = lerp(BaseColor, StressColor, Stress * 0.5); +``` + +### 8.3 相态可视化 + +```hlsl +// 从体素采样相态 +float PhaseState = SamplePhaseStateFromVoxel(WorldPos); + +float3 AusteniteColor = float3(1.0, 0.8, 0.6); // 奥氏体:浅橙色 +float3 MartensiteColor = float3(0.9, 0.85, 0.8); // 马氏体:银白色 +float3 PearliteColor = float3(0.7, 0.6, 0.5); // 珠光体:深灰色 + +float3 PhaseColor; +if (PhaseState < 0.5) + PhaseColor = AusteniteColor; +else if (PhaseState < 1.5) + PhaseColor = MartensiteColor; +else + PhaseColor = PearliteColor; + +// 根据温度过渡 +float Temp = SampleTemperatureFromVoxel(WorldPos); +float MixFactor = smoothstep(600.0, 800.0, Temp); + +BaseColor = lerp(PhaseColor, AusteniteColor, MixFactor); +``` + +--- + +## 9. 性能优化 + +### 9.1 GPU优化策略 + +```cpp +// 1. 使用共享内存(GroupShared) +groupshared float SharedTemperature[THREAD_COUNT * THREAD_COUNT]; + +// 2. 波前优化(Wave Intrinsics) +uint WaveSize = GetWaveSize(); +float WaveSum = WaveActiveSum(StrainRate); + +// 3. 避免分支 divergence +if (CoolingRate > 100.0) // 差:条件跳转 + PhaseState = 1.0; +PhaseState = step(100.0, CoolingRate); // 好:无分支 +``` + +### 9.2 CPU-GPU异步 + +```cpp +// 异步Dispatch +ENQUEUE_RENDER_COMMAND(FMetallurgyComputeDispatch)( + [this, DeltaTime](FRHICommandListImmediate& RHICmdList) + { + FMetallurgyComputeShader::Dispatch( + RHICmdList, + TemperatureBufferA, + TemperatureBufferB, + MetallurgyBuffer, + DeltaTime, + TemperatureResolution + ); + + // 标记完成 + bMetallurgyComputeComplete = true; + } +); + +// CPU继续其他工作,不用等待GPU +``` + +### 9.3 LOD策略 + +```cpp +// 根据摄像机距离调整计算频率 +float DistanceToCamera = FVector::Dist(GetActorLocation(), CameraLocation); + +int32 ComputeFrequency; +if (DistanceToCamera < 200.0f) + ComputeFrequency = 1; // 每帧计算 +else if (DistanceToCamera < 500.0f) + ComputeFrequency = 2; // 每2帧计算 +else + ComputeFrequency = 5; // 每5帧计算 + +static int32 FrameCounter = 0; +if (++FrameCounter % ComputeFrequency == 0) +{ + DispatchMetallurgyComputeShader(); +} +``` + +--- + +## 10. 参数配置示例 + +### 10.1 碳钢(C105) + +```cpp +// 加工硬化参数 +InitialHardness = 0.25f; +SaturationStrain = 0.6f; +WorkHardeningExponent = 0.55f; + +// 相变参数 +AustenitizingTemp = 800.0f; +QuenchingCriticalRate = 120.0f; +MartensiteHardness = 0.95f; + +// 晶粒参数 +InitialGrainSize = 0.5f; +RecrystallizationTemp = 650.0f; +``` + +### 10.2 高碳钢(C150) + +```cpp +// 更容易硬化 +InitialHardness = 0.35f; +SaturationStrain = 0.4f; + +// 淬火更敏感 +QuenchingCriticalRate = 80.0f; // 更容易淬火 +MartensiteHardness = 1.0f; // 更硬 +``` + +### 10.3 低碳钢(C20) + +```cpp +// 难硬化 +InitialHardness = 0.15f; +SaturationStrain = 0.8f; + +// 难淬火 +QuenchingCriticalRate = 200.0f; // 很难淬火 +MartensiteHardness = 0.7f; +``` + +--- + +## 11. 调试工具 + +### 11.1 体素数据可视化 + +```cpp +void ADynamicIronBlock::DebugDrawVoxelData() +{ + for (int32 Z = 0; Z < TemperatureResolution.Z; ++Z) + { + for (int32 Y = 0; Y < TemperatureResolution.Y; ++Y) + { + for (int32 X = 0; X < TemperatureResolution.X; ++X) + { + FIntVector Coord(X, Y, Z); + int32 Index = GetTemperatureIndex(Coord); + const FTemperatureVoxel& Voxel = TemperatureGrid[Index]; + + // 只绘制表面体素 + if (!(Voxel.Flags & VOXEL_FLAG_SURFACE)) + continue; + + FVector WorldPos = VoxelCoordToWorldPos(Coord); + + // 颜色:温度 + FLinearColor Color = FLinearColor( + FMath::Clamp(Voxel.Temperature / 1200.0f, 0.0f, 1.0f), + 0.0f, + 1.0f - FMath::Clamp(Voxel.Temperature / 1200.0f, 0.0f, 1.0f) + ); + + DrawDebugPoint(GetWorld(), WorldPos, 3.0f, Color.ToFColor(), false, 0.1f); + } + } + } +} +``` + +### 11.2 应力热图 + +```cpp +void ADynamicIronBlock::DebugDrawStressHeatmap() +{ + if (!DynamicMesh->HasMesh()) + return; + + for (int32 VID : DynamicMesh->VertexIndicesItr()) + { + if (VID % 50 != 0) // 稀疏采样 + continue; + + float Stress = StressValue->GetValue(VID); + FVector WorldPos = GetActorTransform().TransformPosition(DynamicMesh->GetVertex(VID)); + + // 蓝色(低应力)→ 红色(高应力) + FColor HeatColor = FLinearColor::LerpUsingHSV( + FColor::Blue, + FColor::Red, + Stress + ).ToFColor(); + + DrawDebugPoint(GetWorld(), WorldPos, 5.0f, HeatColor, false, 5.0f); + } +} +``` + +--- + +## 12. 已知限制 + +1. **简化模型**: 所有物理模型均为经验公式,非严格物理计算 +2. **无热-力耦合**: 温度场和应力场独立计算,未考虑形变生热 +3. **无磁-电效应**: 忽略了电磁场对相变的影响 +4. **各向同性**: 假设材料各向同性,真实钢有晶粒取向 + +**适用场景**: 游戏娱乐级,足够真实感但非科研级精度。 diff --git a/docs/plans/2026-01-22-网格质量优化细节.md b/docs/plans/2026-01-22-网格质量优化细节.md new file mode 100644 index 0000000..2730dc1 --- /dev/null +++ b/docs/plans/2026-01-22-网格质量优化细节.md @@ -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& AffectedTriangles, + float MinAspectRatio) +{ + // 收集相关边 + TSet 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& AffectedTriangles, + int32 Iterations, + float SmoothFactor) +{ + if (Iterations <= 0) + return; + + // 收集需要平滑的顶点 + TSet VerticesToSmooth; + for (int32 TID : AffectedTriangles) + { + FIndex3i Tri = Mesh.GetTriangle(TID); + VerticesToSmooth.Add(Tri.A); + VerticesToSmooth.Add(Tri.B); + VerticesToSmooth.Add(Tri.C); + } + + // 排除边界顶点(保持形状) + TSet InteriorVertices; + for (int32 VID : VerticesToSmooth) + { + if (!Mesh.IsBoundaryVertex(VID)) + { + InteriorVertices.Add(VID); + } + } + + // 迭代平滑 + for (int32 Iter = 0; Iter < Iterations; ++Iter) + { + TMap NewPositions; + + for (int32 VID : InteriorVertices) + { + // 获取邻接顶点 + TArray 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& AffectedTriangles, + float BaseSmoothFactor) +{ + TMap 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 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& 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& Triangles) +{ + float TotalLength = 0.0f; + int32 Count = 0; + + TSet 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 SaveVertexAttributes(const FDynamicMesh3& Mesh) +{ + TMap 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& OldAttributes) +{ + // 简化:最近邻插值 + for (int32 NewVID : Mesh.VertexIndicesItr()) + { + FVector NewPos = Mesh.GetVertex(NewVID); + + // 找最近的旧顶点 + int32 BestVID = IndexConstants::InvalidID; + float MinDist = TNumericLimits::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 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 ADynamicIronBlock::FindAffectedTriangles( + const FDynamicMesh3& Mesh, + FVector Center, + float Radius) const +{ + TSet 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. **预测性优化**: 在锤击前预测退化区域并提前优化 diff --git a/docs/plans/2026-01-22-表面磨损系统细节.md b/docs/plans/2026-01-22-表面磨损系统细节.md new file mode 100644 index 0000000..adf8a07 --- /dev/null +++ b/docs/plans/2026-01-22-表面磨损系统细节.md @@ -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* InitialPositions; // 初始局部位置 + TDynamicMeshVertexAttribute* PolishAmount; // 打磨度(0-1) + TDynamicMeshVertexAttribute* RustSeed; // 铁锈噪声种子 + TDynamicMeshVertexAttribute* 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(&Mesh); + InitialPositions->Initialize(FVector::ZeroVector); + + PolishAmount = new TDynamicMeshVertexAttribute(&Mesh); + PolishAmount->Initialize(0.0f); + + RustSeed = new TDynamicMeshVertexAttribute(&Mesh); + RustSeed->Initialize(0.0f); + + OxideThickness = new TDynamicMeshVertexAttribute(&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* MaterialLayerID = + new TDynamicMeshVertexAttribute(&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. **法线贴图**: 从打磨度生成动态法线贴图 diff --git a/docs/plans/2026-01-22-高真实度锻造系统设计.md b/docs/plans/2026-01-22-高真实度锻造系统设计.md new file mode 100644 index 0000000..4c4651c --- /dev/null +++ b/docs/plans/2026-01-22-高真实度锻造系统设计.md @@ -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* InitialPositions; // 初始位置(不变) + TDynamicMeshVertexAttribute* AccumulatedStrain; // 累积应变 + TDynamicMeshVertexAttribute* HardnessCoefficient; // 硬度系数 + TDynamicMeshVertexAttribute* PolishAmount; // 打磨度 + TDynamicMeshVertexAttribute* GrainSize; // 晶粒尺寸 + TDynamicMeshVertexAttribute* 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 +**状态**: 设计完成,待评审 diff --git a/docs/plans/plastic-deformation-voxel-system-plan.md b/docs/plans/plastic-deformation-voxel-system-plan.md deleted file mode 100644 index 19e3370..0000000 --- a/docs/plans/plastic-deformation-voxel-system-plan.md +++ /dev/null @@ -1,723 +0,0 @@ -# BladeSmith塑性变形体素系统 - 实现计划 - -## 概述 - -基于现有温度系统架构,构建一个完整的塑性变形体素系统。系统采用**两步法**: -1. **应力传播阶段**:锤击施加应力,通过GPU迭代计算应力在体素间扩散 -2. **位移计算阶段**:从应力分布计算位移场,应用到网格顶点 - -## 核心设计决策 - -- **物理模型**:简化模型(塑性流动 + 应变软化 + 体积守恒) -- **体素数据**:存储应力、应变、位移(混合方式) -- **GPU架构**:应力Ping-Pong双缓冲 + 独立位移/应变缓冲 -- **锤击响应**:两步法(应力注入 → GPU计算 → 顶点更新) -- **内存占用**:36字节/体素(20³网格 = 281KB,100³网格 = 34.4MB) - ---- - -## 一、数据结构设计 - -### 1.1 变形体素结构(添加到DynamicIronBlock.h) - -```cpp -/** - * 变形体素结构 - 存储单个体素的力学状态 - * 大小:36字节(紧凑型设计) - */ -USTRUCT(BlueprintType) -struct FDeformationVoxel -{ - GENERATED_BODY() - - // 应力张量(简化为主应力,6字节) - FFloat16 StressPrincipal; // 主应力(沿锤击方向) - 2字节 - FFloat16 StressShear; // 剪应力(切向) - 2字节 - uint16 StressFlags; // 应力状态标志 - 2字节 - - // 累积应变(12字节) - FVector3f Strain; // 累积应变向量 (εx, εy, εz) - - // 位移场(当前帧变形,12字节) - FVector3f Displacement; // 位移向量 (Δx, Δy, Δz) - - // 物理属性(4字节) - float PlasticFlow; // 塑性流动系数 [0,1],由温度决定 - - // 辅助数据(6字节) - uint16 MaterialID; // 材料ID - uint32 AuxFlags; // 辅助标志位 - - FDeformationVoxel() - : StressPrincipal(0.0f), StressShear(0.0f), StressFlags(0) - , Strain(FVector3f::ZeroVector), Displacement(FVector3f::ZeroVector) - , PlasticFlow(0.0f), MaterialID(0), AuxFlags(0) - {} -}; -``` - -### 1.2 类成员变量(添加到DynamicIronBlock.h) - -```cpp -// ========== 变形系统数据 ========== - -// 变形体素网格(CPU侧) -TArray DeformationGrid; - -// 原始网格顶点位置(未变形状态) -TArray OriginalVertexPositions; - -// 是否启用变形系统 -UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Deformation System") -bool bEnableDeformationSystem = true; - -// 变形系统参数 -UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Deformation System") -float YieldStress = 50.0f; // 屈服应力阈值 - -UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Deformation System") -float StressDiffusionRate = 0.3f; // 应力扩散速率 - -UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Deformation System") -float VolumeConservationStrength = 0.8f; // 体积守恒强度 - -UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Deformation System") -float ComplianceFactor = 0.01f; // 柔度(应力→位移转换系数) - -// ========== GPU缓冲区 ========== - -// 应力缓冲区(Ping-Pong) -FBufferRHIRef StressBufferA; -FBufferRHIRef StressBufferB; - -// 位移和应变缓冲区 -FBufferRHIRef DisplacementBuffer; // PF_R32G32B32_FLOAT(3D向量) -FBufferRHIRef StrainBuffer; // PF_R32G32B32_FLOAT - -// UAV/SRV视图 -FUnorderedAccessViewRHIRef StressUAV_A; -FUnorderedAccessViewRHIRef StressUAV_B; -FUnorderedAccessViewRHIRef DisplacementUAV; -FUnorderedAccessViewRHIRef StrainUAV; - -FShaderResourceViewRHIRef StressSRV_A; -FShaderResourceViewRHIRef StressSRV_B; -FShaderResourceViewRHIRef DisplacementSRV; -FShaderResourceViewRHIRef StrainSRV; - -bool bUseStressBufferA = true; // Ping-Pong标志 - -// GPU缓冲区是否准备好 -std::atomic bDeformationGPUBuffersReady{false}; -``` - ---- - -## 二、GPU计算着色器 - -### 2.1 应力传播着色器(阶段1) - -**文件**: `FStressPropagationShader.h` (新建) - -```cpp -class MICESHADERS_API FStressPropagationShader : public FGlobalShader -{ - DECLARE_GLOBAL_SHADER(FStressPropagationShader); - SHADER_USE_PARAMETER_STRUCT(FStressPropagationShader, FGlobalShader); - - BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) - SHADER_PARAMETER(FIntVector, VoxelResolution) - SHADER_PARAMETER(float, DeltaTime) - SHADER_PARAMETER(float, StressDiffusionRate) - SHADER_PARAMETER(float, YieldStress) - SHADER_PARAMETER(float, StrainSofteningFactor) - - SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer, InputStressBuffer) - SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer, PlasticityBuffer) - SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer, SurfaceVoxelFlagsBuffer) - SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer, OutputStressBuffer) - END_SHADER_PARAMETER_STRUCT() - - static FIntVector GetGroupSize() { return FIntVector(8, 8, 8); } -}; -``` - -**HLSL**: `StressPropagationCS.usf` (新建) - -```hlsl -[numthreads(8, 8, 8)] -void MainCompute(uint3 DispatchThreadID : SV_DispatchThreadID) -{ - int3 voxelCoord = int3(DispatchThreadID); - if (!IsValidCoord(voxelCoord)) return; - - int index = GetVoxelIndex(voxelCoord); - float currentStress = InputStressBuffer[index]; - float plasticity = PlasticityBuffer[index]; - - // === 6邻域应力扩散 === - float neighborStressSum = 0.0; - int neighborCount = 0; - const int3 neighbors[6] = { - int3(-1,0,0), int3(1,0,0), - int3(0,-1,0), int3(0,1,0), - int3(0,0,-1), int3(0,0,1) - }; - - for (int i = 0; i < 6; i++) { - int3 neighborCoord = voxelCoord + neighbors[i]; - if (IsValidCoord(neighborCoord)) { - neighborStressSum += InputStressBuffer[GetVoxelIndex(neighborCoord)]; - neighborCount++; - } - } - - float avgNeighborStress = neighborStressSum / max(neighborCount, 1); - - // === 应力传播(塑性越高,传播越快) === - float diffusionRate = StressDiffusionRate * (1.0 + plasticity * 2.0); - float stressDiffusion = diffusionRate * (avgNeighborStress - currentStress) * DeltaTime; - - // === 应力松弛(超过屈服点的应力衰减) === - float stressRelaxation = 0.0; - if (currentStress > YieldStress && plasticity > 0.1) { - float excessStress = currentStress - YieldStress; - stressRelaxation = -excessStress * plasticity * StrainSofteningFactor * DeltaTime; - } - - // === 表面应力自由释放 === - bool isSurface = (SurfaceVoxelFlagsBuffer[index] & 1) != 0; - if (isSurface) { - stressRelaxation += -currentStress * 0.1 * DeltaTime; - } - - // === 更新应力 === - float newStress = max(0.0, currentStress + stressDiffusion + stressRelaxation); - OutputStressBuffer[index] = newStress; -} -``` - -### 2.2 位移计算着色器(阶段2) - -**文件**: `FDisplacementFromStressShader.h` (新建) - -```cpp -class MICESHADERS_API FDisplacementFromStressShader : public FGlobalShader -{ - DECLARE_GLOBAL_SHADER(FDisplacementFromStressShader); - SHADER_USE_PARAMETER_STRUCT(FDisplacementFromStressShader, FGlobalShader); - - BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) - SHADER_PARAMETER(FIntVector, VoxelResolution) - SHADER_PARAMETER(float, DeltaTime) - SHADER_PARAMETER(float, VolumeConservationStrength) - SHADER_PARAMETER(float, ComplianceFactor) - - SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer, StressBuffer) - SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer, PlasticityBuffer) - SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer, PreviousStrainBuffer) - SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer, OutputDisplacementBuffer) - SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer, OutputStrainBuffer) - END_SHADER_PARAMETER_STRUCT() - - static FIntVector GetGroupSize() { return FIntVector(8, 8, 8); } -}; -``` - -**HLSL**: `DisplacementFromStressCS.usf` (新建) - -```hlsl -[numthreads(8, 8, 8)] -void MainCompute(uint3 DispatchThreadID : SV_DispatchThreadID) -{ - int3 voxelCoord = int3(DispatchThreadID); - if (!IsValidCoord(voxelCoord)) return; - - int index = GetVoxelIndex(voxelCoord); - float stress = StressBuffer[index]; - float plasticity = PlasticityBuffer[index]; - float3 previousStrain = PreviousStrainBuffer[index]; - - // === 计算应力梯度(主变形方向) === - float3 stressGradient = ComputeStressGradient(voxelCoord); - float3 deformationDirection = length(stressGradient) > 0.001 ? - normalize(stressGradient) : - float3(0, 0, -1); - - // === 计算塑性位移 === - float displacementMagnitude = stress * ComplianceFactor * plasticity; - float3 plasticDisplacement = deformationDirection * displacementMagnitude; - - // === 体积守恒修正 === - float compressionAmount = dot(plasticDisplacement, deformationDirection); - float lateralExpansion = -compressionAmount * VolumeConservationStrength * 0.5; - - float3 lateral1 = normalize(cross(deformationDirection, float3(0, 0, 1))); - float3 lateral2 = normalize(cross(deformationDirection, lateral1)); - float3 volumeCompensation = (lateral1 + lateral2) * lateralExpansion; - - // === 最终位移和应变 === - float3 totalDisplacement = plasticDisplacement + volumeCompensation; - float3 newStrain = previousStrain + totalDisplacement * DeltaTime; - - OutputDisplacementBuffer[index] = totalDisplacement; - OutputStrainBuffer[index] = newStrain; -} -``` - ---- - -## 三、锤击响应流程 - -### 3.1 修改OnHammerImpact(DynamicIronBlock.cpp) - -```cpp -void ADynamicIronBlock::OnHammerImpact( - FVector HitLocation, FVector HitNormal, - float ImpactRadius, float ImpactDepth, float ImpactForce) -{ - if (!bEnableDeformationSystem || DeformationGrid.Num() == 0) - { - // 回退到旧逻辑(直接修改顶点) - OnHammerImpact_Legacy(HitLocation, HitNormal, ImpactRadius, ImpactDepth, ImpactForce); - return; - } - - // === 步骤1:应力注入 === - ApplyImpactStressToVoxels(HitLocation, HitNormal, ImpactRadius, ImpactDepth, ImpactForce); - - // === 步骤2:GPU应力传播(迭代3次) === - for (int i = 0; i < 3; i++) - { - DispatchStressPropagationCompute(0.016f); // 16ms时间步 - } - - // === 步骤3:GPU位移计算 === - DispatchDisplacementCompute(0.016f); - - // === 步骤4:下载并应用到网格 === - DownloadDeformationDataAndApplyToMesh(); -} -``` - -### 3.2 应力注入函数 - -```cpp -void ADynamicIronBlock::ApplyImpactStressToVoxels( - FVector HitLocation, FVector HitNormal, - float ImpactRadius, float ImpactDepth, float ImpactForce) -{ - // 转换到局部坐标 - FTransform ActorTransform = GetActorTransform(); - FVector LocalHitLocation = ActorTransform.InverseTransformPosition(HitLocation); - - // 计算体素坐标 - FVector GridOrigin = -BoxSize * 0.5f; - FVector VoxelSizeCM = FVector(TemperatureVoxelSize / 10.0f); - - // 遍历影响范围 - for (int32 Z = 0; Z < TemperatureResolution.Z; ++Z) - { - for (int32 Y = 0; Y < TemperatureResolution.Y; ++Y) - { - for (int32 X = 0; X < TemperatureResolution.X; ++X) - { - FVector VoxelCenter = GridOrigin + FVector( - (X + 0.5f) * VoxelSizeCM.X, - (Y + 0.5f) * VoxelSizeCM.Y, - (Z + 0.5f) * VoxelSizeCM.Z - ); - - float Distance = FVector::Dist(VoxelCenter, LocalHitLocation); - - if (Distance < ImpactRadius) - { - int32 Index = GetTemperatureIndex(X, Y, Z); - - // 高斯衰减 - float Falloff = FMath::Exp(-FMath::Square(Distance / ImpactRadius) * 3.0f); - - // 应力大小 - float StressMagnitude = ImpactForce * ImpactDepth * Falloff * 100.0f; - - // 注入到体素 - if (DeformationGrid.IsValidIndex(Index)) - { - DeformationGrid[Index].StressPrincipal = FMath::Min( - DeformationGrid[Index].StressPrincipal.GetFloat() + StressMagnitude, - 1000.0f // 应力上限 - ); - } - } - } - } - } - - // 上传到GPU - UploadDeformationStressToGPU(); -} -``` - ---- - -## 四、顶点更新机制 - -### 4.1 从体素位移场采样到顶点 - -```cpp -void ADynamicIronBlock::UpdateVertexDeformation() -{ - UDynamicMeshComponent* DynMeshComp = GetDynamicMeshComponent(); - if (!DynMeshComp) return; - - UDynamicMesh* DynMesh = DynMeshComp->GetDynamicMesh(); - if (!DynMesh) return; - - // 修改网格顶点 - DynMesh->EditMesh([&](FDynamicMesh3& EditMesh) - { - for (int32 VertexID : EditMesh.VertexIndicesItr()) - { - // 获取原始顶点位置 - FVector3d OriginalPos = FVector3d(OriginalVertexPositions[VertexID]); - - // 采样体素位移场(三线性插值) - FVector Displacement = SampleDisplacementAtPosition(FVector(OriginalPos)); - - // 新位置 = 原始位置 + 位移 - FVector3d NewPosition = OriginalPos + FVector3d(Displacement); - - EditMesh.SetVertex(VertexID, NewPosition); - } - }, EDynamicMeshChangeType::GeneralEdit, EDynamicMeshAttributeChangeFlags::Unknown, false); - - // 重新计算法线 - FGeometryScriptCalculateNormalsOptions NormalOp; - UGeometryScriptLibrary_MeshNormalsFunctions::RecomputeNormals(DynMesh, NormalOp); - - DynMeshComp->NotifyMeshUpdated(); -} -``` - -### 4.2 三线性插值采样位移 - -```cpp -FVector ADynamicIronBlock::SampleDisplacementAtPosition(const FVector& LocalPosition) const -{ - if (!bEnableDeformationSystem || DeformationGrid.Num() == 0) - { - return FVector::ZeroVector; - } - - // 转换到体素坐标系 - FVector GridOrigin = -BoxSize * 0.5f; - FVector VoxelSizeCM = FVector(TemperatureVoxelSize / 10.0f); - FVector VoxelCoord = (LocalPosition - GridOrigin) / VoxelSizeCM; - - // 获取相邻8个体素 - int32 X0 = FMath::FloorToInt(VoxelCoord.X); - int32 Y0 = FMath::FloorToInt(VoxelCoord.Y); - int32 Z0 = FMath::FloorToInt(VoxelCoord.Z); - int32 X1 = X0 + 1, Y1 = Y0 + 1, Z1 = Z0 + 1; - - // 边界检查 - if (!IsValidTemperatureCoord(X0, Y0, Z0) || !IsValidTemperatureCoord(X1, Y1, Z1)) - { - return FVector::ZeroVector; - } - - // 插值权重 - float Fx = VoxelCoord.X - X0; - float Fy = VoxelCoord.Y - Y0; - float Fz = VoxelCoord.Z - Z0; - - // 获取8个角点的位移 - auto GetDisp = [&](int X, int Y, int Z) -> FVector - { - int32 Index = GetTemperatureIndex(X, Y, Z); - return FVector(DeformationGrid[Index].Displacement); - }; - - FVector D000 = GetDisp(X0, Y0, Z0); - FVector D100 = GetDisp(X1, Y0, Z0); - FVector D010 = GetDisp(X0, Y1, Z0); - FVector D110 = GetDisp(X1, Y1, Z0); - FVector D001 = GetDisp(X0, Y0, Z1); - FVector D101 = GetDisp(X1, Y0, Z1); - FVector D011 = GetDisp(X0, Y1, Z1); - FVector D111 = GetDisp(X1, Y1, Z1); - - // 三线性插值 - FVector D00 = FMath::Lerp(D000, D100, Fx); - FVector D10 = FMath::Lerp(D010, D110, Fx); - FVector D01 = FMath::Lerp(D001, D101, Fx); - FVector D11 = FMath::Lerp(D011, D111, Fx); - FVector D0 = FMath::Lerp(D00, D10, Fy); - FVector D1 = FMath::Lerp(D01, D11, Fy); - return FMath::Lerp(D0, D1, Fz); -} -``` - ---- - -## 五、实现步骤 - -### 阶段1:基础架构(2-3天) - -**目标**:建立数据结构和CPU侧逻辑 - -1. **添加数据结构** (DynamicIronBlock.h) - - 定义 `FDeformationVoxel` 结构体 - - 添加 `DeformationGrid`、`OriginalVertexPositions` 成员变量 - - 添加变形系统参数(YieldStress、StressDiffusionRate等) - -2. **实现初始化** (DynamicIronBlock.cpp) - - `InitializeDeformationGrid()`: 分配体素数组 - - 在 `InitializeBoxMesh()` 中保存原始顶点位置 - -3. **实现应力注入** - - `ApplyImpactStressToVoxels()`: CPU版本应力注入 - - 测试:打印应力分布,验证高斯衰减 - -4. **实现位移采样** - - `SampleDisplacementAtPosition()`: 三线性插值 - - `UpdateVertexDeformation()`: 应用到网格 - - 测试:手动设置位移值,观察网格变形 - -### 阶段2:GPU缓冲区(1-2天) - -**目标**:建立GPU缓冲区,实现CPU-GPU数据流 - -5. **创建GPU缓冲区** - - `CreateDeformationGPUBuffers()`: 创建所有RHI缓冲区 - - `ReleaseDeformationGPUBuffers()`: 清理资源 - -6. **数据传输** - - `UploadDeformationStressToGPU()`: 应力数据 CPU→GPU - - `DownloadDeformationDataAndApplyToMesh()`: 位移数据 GPU→CPU - - 测试:上传后立即下载,验证数据完整性 - -### 阶段3:GPU计算着色器(3-4天) - -**目标**:实现两阶段GPU计算 - -7. **应力传播Shader** - - 新建 `FStressPropagationShader.h/cpp` (参考FTemperatureComputeShader) - - 新建 `StressPropagationCS.usf` - - 实现6邻域扩散 + 应力松弛 - - 测试:单点应力观察传播 - -8. **位移计算Shader** - - 新建 `FDisplacementFromStressShader.h/cpp` - - 新建 `DisplacementFromStressCS.usf` - - 实现应力→位移 + 体积守恒 - - 测试:固定应力验证位移 - -9. **调度逻辑** - - `DispatchStressPropagationCompute()`: RDG调度应力传播 - - `DispatchDisplacementCompute()`: RDG调度位移计算 - - 测试:完整流程(应力→传播→位移→网格) - -### 阶段4:集成和优化(2-3天) - -**目标**:完整集成,调优参数 - -10. **修改锤击逻辑** - - 更新 `OnHammerImpact()` 使用新系统 - - 保留旧逻辑作为fallback (`OnHammerImpact_Legacy()`) - -11. **参数调优** - - 调整物理参数观察效果 - - 确保变形符合物理直觉 - -12. **性能优化** - - 添加性能日志 - - 优化迭代次数 - - 考虑局部更新 - -13. **调试工具** - - 可视化应力分布(顶点颜色) - - 可视化位移场(Debug Gizmo) - ---- - -## 六、关键文件清单 - -### 需要修改的文件 - -1. **C:\MiceCola\Indi\BladeSmith\Source\BladeSmith\DynamicIronBlock.h** - - 添加 `FDeformationVoxel` 结构体 - - 添加变形系统成员变量和参数 - - 添加新函数声明(约15个函数) - -2. **C:\MiceCola\Indi\BladeSmith\Source\BladeSmith\DynamicIronBlock.cpp** - - 实现所有变形系统函数 - - 修改 `OnHammerImpact()` 为两步法 - - 修改 `BeginPlay()`、`EndPlay()`、`InitializeBoxMesh()` - -### 需要新建的文件 - -3. **C:\MiceCola\Indi\BladeSmith\Plugins\MiceShaders\Source\MiceShaders\Public\FStressPropagationShader.h** - - 应力传播着色器类声明 - -4. **C:\MiceCola\Indi\BladeSmith\Plugins\MiceShaders\Source\MiceShaders\Private\FStressPropagationShader.cpp** - - Shader实现(IMPLEMENT_GLOBAL_SHADER) - -5. **C:\MiceCola\Indi\BladeSmith\Plugins\MiceShaders\Source\MiceShaders\Public\FDisplacementFromStressShader.h** - - 位移计算着色器类声明 - -6. **C:\MiceCola\Indi\BladeSmith\Plugins\MiceShaders\Source\MiceShaders\Private\FDisplacementFromStressShader.cpp** - - Shader实现 - -7. **C:\MiceCola\Indi\BladeSmith\Plugins\MiceShaders\Shaders\Private\StressPropagationCS.usf** - - 应力传播HLSL代码 - -8. **C:\MiceCola\Indi\BladeSmith\Plugins\MiceShaders\Shaders\Private\DisplacementFromStressCS.usf** - - 位移计算HLSL代码 - ---- - -## 七、验证和测试 - -### 单元测试 - -1. **体素索引测试** - - 验证 `GetTemperatureIndex()` 3D→1D转换正确性 - - 测试边界情况 - -2. **插值测试** - - 设置8个体素位移为已知值 - - 验证中心点插值结果精度 - -3. **应力衰减测试** - - 注入单点应力 - - 验证距离衰减符合高斯曲线 - -### 集成测试 - -4. **锤击响应测试** - - **冷态测试**(塑性=0):锤击应无明显变形 - - **热态测试**(塑性=1):锤击应有深度变形 - - **体积守恒验证**:压缩区深度 ≈ 凸起区体积 - -5. **性能测试** - - 测量每次锤击耗时(目标 <7ms) - - 验证60fps稳定性 - - GPU占用率监控 - -### 物理验证 - -6. **应力传播验证** - - 单点冲击应力向周围扩散 - - 表面体素应力快速释放 - -7. **温度-塑性耦合验证** - - 高温区域深度变形 - - 低温区域轻微变形 - -### 端到端测试 - -8. **完整流程测试** - ``` - 1. 启动游戏 → 加载DynamicIronBlock - 2. 开启自动加热 → 观察温度上升 - 3. 等待塑性系数 > 0.5 - 4. 执行锤击 → 观察变形 - 5. 验证:顶点位置改变、法线重新计算、BVH更新 - ``` - ---- - -## 八、性能预期 - -### 内存占用 - -- **小网格(20×20×20)**:约 0.5 MB -- **中网格(50×50×50)**:约 4 MB -- **大网格(100×100×100)**:约 67 MB - -### 计算延迟(一次锤击) - -- GPU应力传播:0.1ms × 3迭代 = 0.3ms -- GPU位移计算:0.1ms -- CPU下载:约 1ms(异步) -- 顶点更新:约 5ms(10万顶点) -- **总延迟**:约 6-7ms(<16.7ms/60fps)✅ - ---- - -## 九、物理模型说明 - -### 简化的应力-应变关系 - -本系统使用简化模型: -``` -位移 = 应力 × 柔度 × 塑性系数 -Δu = σ × C × P -``` - -其中: -- `σ`:应力(Pa) -- `C`:柔度系数 (ComplianceFactor) -- `P`:塑性系数(温度决定,0-1) - -### 温度对塑性的影响 - -塑性系数从温度系统读取(复用PlasticityBuffer): -``` -P = Smoothstep((T - T_min) / (T_max - T_min)) -``` -- 温度越高 → 塑性越强 → 应力更易转换为位移 - -### 体积守恒近似 - -压缩方向位移后,横向补偿: -``` -压缩量 = |位移 · 方向| -横向膨胀 = -压缩量 × 守恒强度 × 0.5 -``` - ---- - -## 十、调试建议 - -1. **日志级别** - - 使用 `UE_LOG(LogTemp, Warning, ...)` 输出关键数据 - - 每个阶段添加性能计时 - -2. **可视化工具** - - 顶点颜色显示应力分布(蓝色=低,红色=高) - - Debug Gizmo绘制体素应力箭头 - -3. **参数调优UI** - - 所有物理参数暴露到Blueprint - - 实时调整观察效果 - -4. **逐步验证** - - 先验证CPU逻辑(阶段1) - - 再验证GPU传输(阶段2) - - 最后验证GPU计算(阶段3) - ---- - -## 十一、与现有系统的集成 - -### 与温度系统协作 - -- **共享缓冲区**:`PlasticityBuffer`、`SurfaceVoxelFlagsBuffer` -- **共享分辨率**:`TemperatureResolution` 用于变形体素网格 -- **调度顺序**:温度计算(每1秒) → 变形计算(锤击触发) - -### 与BVH系统协作 - -- 每次 `UpdateVertexDeformation()` 后调用 `GenerateBVH()` -- 确保碰撞检测使用最新几何 - -### 与顶点颜色系统协作 - -- 可选:混合显示温度(红色)+ 应力(蓝色) -- 或独立显示:切换模式(温度模式 / 应力模式) - ---- - -**计划完成**。建议按照实现步骤逐步推进,每个阶段充分测试后再进入下一阶段。