Compare commits
7 Commits
de8b523c3d
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 10e831efb9 | |||
| b079f682c9 | |||
| 1747eb7db7 | |||
| 8c27c82856 | |||
| f7882875c5 | |||
| 61016f9c2a | |||
| 5e237b8466 |
@@ -10,7 +10,8 @@
|
|||||||
"Bash(git commit:*)",
|
"Bash(git commit:*)",
|
||||||
"Bash(\"E:\\MiceCola\\work\\UE_5_6\\Engine\\Build\\BatchFiles\\Build.bat\" BladeSmithEditor Win64 Development -Project=\"C:\\MiceCola\\Indi\\BladeSmith\\BladeSmith.uproject\" -WaitMutex -FromMsBuild)",
|
"Bash(\"E:\\MiceCola\\work\\UE_5_6\\Engine\\Build\\BatchFiles\\Build.bat\" BladeSmithEditor Win64 Development -Project=\"C:\\MiceCola\\Indi\\BladeSmith\\BladeSmith.uproject\" -WaitMutex -FromMsBuild)",
|
||||||
"Bash(cmd /c:*)",
|
"Bash(cmd /c:*)",
|
||||||
"Bash(\"C:\\MiceCola\\Indi\\UE_5.6\\Engine\\Build\\BatchFiles\\Build.bat\" BladeSmithEditor Win64 Development \"-Project=C:\\MiceCola\\Indi\\BladeSmith\\BladeSmith.uproject\" -WaitMutex -FromMsBuild)"
|
"Bash(\"C:\\MiceCola\\Indi\\UE_5.6\\Engine\\Build\\BatchFiles\\Build.bat\" BladeSmithEditor Win64 Development \"-Project=C:\\MiceCola\\Indi\\BladeSmith\\BladeSmith.uproject\" -WaitMutex -FromMsBuild)",
|
||||||
|
"Skill(superpowers:brainstorming)"
|
||||||
],
|
],
|
||||||
"deny": [],
|
"deny": [],
|
||||||
"ask": []
|
"ask": []
|
||||||
|
|||||||
@@ -74,3 +74,4 @@ Plugins/**/Intermediate/*
|
|||||||
# Cache files for the editor to use
|
# Cache files for the editor to use
|
||||||
DerivedDataCache/*
|
DerivedDataCache/*
|
||||||
|
|
||||||
|
nul
|
||||||
@@ -0,0 +1,354 @@
|
|||||||
|
# BladeSmith Agent Development Guide
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
BladeSmith is a UE5.6 C++ blacksmith simulation game featuring physics-based metal forging with GPU-accelerated compute shaders and dynamic mesh deformation.
|
||||||
|
|
||||||
|
**Core Technologies:**
|
||||||
|
- Unreal Engine 5.6
|
||||||
|
- Geometry Scripting API
|
||||||
|
- Custom Compute Shaders (MiceShaders plugin)
|
||||||
|
- Dynamic Mesh System
|
||||||
|
- Enhanced Input
|
||||||
|
- Voxel-based physics simulation
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Build Commands
|
||||||
|
|
||||||
|
### Building the Project
|
||||||
|
```bash
|
||||||
|
# Build game target (run from UE5 engine directory)
|
||||||
|
"C:\Program Files\Epic Games\UE_5.6\Engine\Build\BatchFiles\Build.bat" BladeSmith Win64 Development "E:\MiceCola\work\BladeSmith\BladeSmith.uproject"
|
||||||
|
|
||||||
|
# Build editor target
|
||||||
|
"C:\Program Files\Epic Games\UE_5.6\Engine\Build\BatchFiles\Build.bat" BladeSmithEditor Win64 Development "E:\MiceCola\work\BladeSmith\BladeSmith.uproject"
|
||||||
|
|
||||||
|
# Alternative: Open BladeSmith.sln in Visual Studio
|
||||||
|
```
|
||||||
|
|
||||||
|
### Running Tests
|
||||||
|
UE5 uses the Automation System, not traditional test runners:
|
||||||
|
- **Editor**: Window → Developer Tools → Automation Sessions
|
||||||
|
- **Command line**: `UE5Editor.exe BladeSmith.uproject -ExecCmds="Automation RunTests"`
|
||||||
|
- **Single test**: Run specific test class from Automation Sessions panel
|
||||||
|
|
||||||
|
### Development Workflow
|
||||||
|
1. Open `BladeSmith.uproject` in UE5.6 editor
|
||||||
|
2. Use hot reload for C++ changes (Ctrl+F11 in editor)
|
||||||
|
3. Test via "Play In Editor" (Alt+P)
|
||||||
|
4. Use Visual Studio for compilation
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Code Style Guidelines
|
||||||
|
|
||||||
|
### File Structure
|
||||||
|
- **Header files (.h)**: Class declarations, forward declarations, USTRUCT/UCLASS definitions
|
||||||
|
- **Source files (.cpp)**: Implementations, includes in dependency order
|
||||||
|
- **Shader files (.usf)**: HLSL compute shaders in `Plugins/MiceShaders/Shaders/Private/`
|
||||||
|
|
||||||
|
### Import Ordering
|
||||||
|
|
||||||
|
**Header Files:**
|
||||||
|
```cpp
|
||||||
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "GameFramework/Actor.h"
|
||||||
|
#include "ProjectModule.generated.h"
|
||||||
|
|
||||||
|
class UForwardDeclaredClass; // Forward declarations
|
||||||
|
```
|
||||||
|
|
||||||
|
**Source Files:**
|
||||||
|
```cpp
|
||||||
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
#include "ClassName.h"
|
||||||
|
#include "SystemHeader1.h"
|
||||||
|
#include "SystemHeader2.h"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Pattern:**
|
||||||
|
1. Epic copyright header (required)
|
||||||
|
2. `#pragma once` guard
|
||||||
|
3. Core UE5 headers (`CoreMinimal.h` first)
|
||||||
|
4. Engine framework headers
|
||||||
|
5. Project headers
|
||||||
|
6. Forward declarations (headers only)
|
||||||
|
|
||||||
|
### Naming Conventions
|
||||||
|
|
||||||
|
**Classes:** PascalCase with UE5 prefixes
|
||||||
|
- `ABladeSmithCharacter` - Actor classes (A prefix)
|
||||||
|
- `UBladeSmithWeaponComponent` - Component classes (U prefix)
|
||||||
|
- `FDeformationVoxel` - Structs (F prefix)
|
||||||
|
- `EGameStateEnum` - Enums (E prefix)
|
||||||
|
|
||||||
|
**Functions:** PascalCase
|
||||||
|
```cpp
|
||||||
|
public:
|
||||||
|
void SwingHammer();
|
||||||
|
FVector GetHammerHeadLocation() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void PerformHammerHit(); // Private helpers use PascalCase
|
||||||
|
```
|
||||||
|
|
||||||
|
**Variables:** camelCase
|
||||||
|
```cpp
|
||||||
|
float HammerForce = 1000.0f;
|
||||||
|
FVector HammerHeadOffset;
|
||||||
|
UMaterialInstanceDynamic* DynamicMaterialInstance;
|
||||||
|
TArray<FTemperatureVoxel> TemperatureGrid;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Constants:** PascalCase
|
||||||
|
```cpp
|
||||||
|
const int32 MaxResolutionPerAxis = 200;
|
||||||
|
const float DefaultTemperature = 20.0f;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Formatting
|
||||||
|
|
||||||
|
**Indentation:** 4 spaces (no tabs)
|
||||||
|
**Braces:** Allman style - opening brace on new line
|
||||||
|
```cpp
|
||||||
|
void ABladeSmithCharacter::Move(const FInputActionValue& Value)
|
||||||
|
{
|
||||||
|
FVector2D MovementVector = Value.Get<FVector2D>();
|
||||||
|
// implementation
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Line Length:** Under 120 characters, wrap parameters as needed
|
||||||
|
**Spacing:** Spaces around operators, after commas
|
||||||
|
|
||||||
|
### UE5 Macros and Attributes
|
||||||
|
|
||||||
|
**UPROPERTY Specifiers:**
|
||||||
|
```cpp
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Category")
|
||||||
|
UPropertyType* VariableName;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Category|Subcategory", meta = (ClampMin = "0.0", ClampMax = "1.0"))
|
||||||
|
float PropertyWithConstraints;
|
||||||
|
|
||||||
|
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Category", meta = (AllowPrivateAccess = "true"))
|
||||||
|
UComponentType* PrivateComponent;
|
||||||
|
```
|
||||||
|
|
||||||
|
**UFUNCTION Specifiers:**
|
||||||
|
```cpp
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Category Name")
|
||||||
|
ReturnType FunctionName(Parameters);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintImplementableEvent)
|
||||||
|
void BlueprintEventName();
|
||||||
|
```
|
||||||
|
|
||||||
|
**USTRUCT:**
|
||||||
|
```cpp
|
||||||
|
USTRUCT(BlueprintType)
|
||||||
|
struct FStructName
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
Type VariableName;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Type Annotations
|
||||||
|
|
||||||
|
**UE5 Types (preferred):**
|
||||||
|
- `TArray<T>` instead of std::vector
|
||||||
|
- `TMap<TKey, TValue>` instead of std::map
|
||||||
|
- `TSet<T>` instead of std::unordered_set
|
||||||
|
- `FVector`, `FVector2D`, `FRotator`, `FQuat` for math
|
||||||
|
- `FString` for strings (TEXT() macro for string literals)
|
||||||
|
- `FName` for names, `FText` for localized text
|
||||||
|
- `float` not double (unless precision critical)
|
||||||
|
- `int32` not int, `uint8` for bytes
|
||||||
|
- `bool` for booleans
|
||||||
|
|
||||||
|
**Smart Pointers:**
|
||||||
|
- Use UE5's garbage collection for UObject-derived classes
|
||||||
|
- `TSharedPtr<T>` for non-UObject smart pointers
|
||||||
|
- `TWeakObjectPtr<T>` for weak references to UObjects
|
||||||
|
|
||||||
|
### Error Handling
|
||||||
|
|
||||||
|
**Null Checks:**
|
||||||
|
```cpp
|
||||||
|
if (DynamicMeshComp == nullptr)
|
||||||
|
{
|
||||||
|
UE_LOG(LogTemp, Error, TEXT("DynamicMeshComponent not found"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Logging:**
|
||||||
|
```cpp
|
||||||
|
UE_LOG(LogTemp, Log, TEXT("Temperature: %.1f°C"), Temperature); // Info
|
||||||
|
UE_LOG(LogTemp, Warning, TEXT("Temperature system disabled")); // Warning
|
||||||
|
UE_LOG(LogTemp, Error, TEXT("Failed to initialize GPU buffers")); // Error
|
||||||
|
```
|
||||||
|
|
||||||
|
**Validation:**
|
||||||
|
```cpp
|
||||||
|
if (!IsValid(InputParam))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Early return pattern
|
||||||
|
if (Condition)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
|
||||||
|
**Chinese Comments:** Domain-specific logic uses Chinese comments
|
||||||
|
```cpp
|
||||||
|
/** 锤子组件 */
|
||||||
|
/** 温度体素结构 - 存储单个体素的温度和塑性数据 */
|
||||||
|
```
|
||||||
|
|
||||||
|
**Function Documentation:**
|
||||||
|
```cpp
|
||||||
|
/**
|
||||||
|
* 函数简述
|
||||||
|
* @param ParameterName 参数说明
|
||||||
|
* @return 返回值说明
|
||||||
|
*/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Threading and GPU Operations
|
||||||
|
|
||||||
|
**Atomic Flags:**
|
||||||
|
```cpp
|
||||||
|
std::atomic<bool> bGPUBuffersReady{false};
|
||||||
|
```
|
||||||
|
|
||||||
|
**Render Thread Operations:**
|
||||||
|
```cpp
|
||||||
|
ENQUEUE_RENDER_COMMAND(CommandName)(
|
||||||
|
[Params](FRHICommandListImmediate& RHICmdList)
|
||||||
|
{
|
||||||
|
// GPU operations here
|
||||||
|
}
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Component Structure Pattern
|
||||||
|
|
||||||
|
**Actor Template:**
|
||||||
|
```cpp
|
||||||
|
UCLASS()
|
||||||
|
class BLADESMITH_API AClassName : public AParentClass
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
AClassName();
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Category")
|
||||||
|
UComponentType* Component;
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category="Category")
|
||||||
|
ReturnType PublicFunction();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void BeginPlay() override;
|
||||||
|
virtual void Tick(float DeltaTime) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void PrivateHelper();
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Module Dependencies
|
||||||
|
|
||||||
|
**BladeSmith.Build.cs:**
|
||||||
|
- GeometryScriptingCore, GeometryFramework, DynamicMesh
|
||||||
|
- GeometryCore, GeometryAlgorithms, ModelingComponents
|
||||||
|
- RenderCore, RHI, Renderer, PhysicsCore
|
||||||
|
- EnhancedInput, AudioExtensions, AudioMixer
|
||||||
|
- Custom: MiceShaders (plugin)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Performance Guidelines
|
||||||
|
|
||||||
|
- **Deformation calculations:** <7ms per frame target
|
||||||
|
- **60 FPS:** Maintain during hammer operations
|
||||||
|
- **High-poly meshes:** ~100k vertices with GPU acceleration
|
||||||
|
- **Voxel resolution:** 50x50x50 typical (balance performance/quality)
|
||||||
|
- **Use RDG (Render Dependency Graph)** for GPU compute operations
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Common Patterns
|
||||||
|
|
||||||
|
**Material Parameters:**
|
||||||
|
```cpp
|
||||||
|
UMaterialInstanceDynamic* DynMaterial = UMaterialInstanceDynamic::Create(BaseMaterial, this);
|
||||||
|
DynMaterial->SetScalarParameterValue(TEXT("ParameterName"), Value);
|
||||||
|
DynMaterial->SetVectorParameterValue(TEXT("VectorParam"), FLinearColor(R,G,B,A));
|
||||||
|
DynMaterial->SetTextureParameterValue(TEXT("TextureParam"), Texture);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Dynamic Mesh Editing:**
|
||||||
|
```cpp
|
||||||
|
UDynamicMesh* DynMesh = DynMeshComp->GetDynamicMesh();
|
||||||
|
DynMesh->EditMesh([&](FDynamicMesh3& EditMesh)
|
||||||
|
{
|
||||||
|
for (FVertexID VertexID : EditMesh.VertexIndicesItr())
|
||||||
|
{
|
||||||
|
EditMesh.SetVertex(VertexID, NewPosition);
|
||||||
|
}
|
||||||
|
}, EDynamicMeshChangeType::GeneralEdit);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Input Binding (Enhanced Input):**
|
||||||
|
```cpp
|
||||||
|
if (UEnhancedInputComponent* EnhancedInput = Cast<UEnhancedInputComponent>(InputComponent))
|
||||||
|
{
|
||||||
|
EnhancedInput->BindAction(Action, ETriggerEvent::Triggered, this, &ClassName::Handler);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Plugin System
|
||||||
|
|
||||||
|
**MiceShaders Plugin:**
|
||||||
|
- Custom compute shaders for temperature simulation and deformation
|
||||||
|
- Located in `Plugins/MiceShaders/`
|
||||||
|
- Add new shaders: Create `.h` and `.cpp` in Source/MiceShaders, HLSL in Shaders/Private/
|
||||||
|
- Use RDG for shader dispatch
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Git Workflow
|
||||||
|
|
||||||
|
- **Ignore:** `Binaries/`, `Intermediate/`, `DerivedDataCache/`, `Saved/`, `.vs/`, `.idea/`
|
||||||
|
- **Commit:** Source/, Content/, Plugins/, Config/, docs/
|
||||||
|
- **No binary assets** in Git (use version control for assets)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
**Technical docs:** Located in `docs/plans/`
|
||||||
|
- Reference existing design docs before implementing new features
|
||||||
|
- Update plans when architecture changes
|
||||||
|
- Use markdown for design documents
|
||||||
@@ -0,0 +1,239 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
BladeSmith is an Unreal Engine 5.6 C++ blacksmith simulation game where players forge bladed weapons through realistic physical processes. The core feature is GPU-accelerated plastic deformation of metal using compute shaders, combined with a voxel-based temperature system that determines material plasticity.
|
||||||
|
|
||||||
|
**Engine Version:** UE 5.6
|
||||||
|
**Language:** C++ with Blueprint integration
|
||||||
|
**Platform:** Windows (primary development target)
|
||||||
|
|
||||||
|
## Build and Development Commands
|
||||||
|
|
||||||
|
### Building the Project
|
||||||
|
|
||||||
|
From the UE5 engine directory:
|
||||||
|
```bash
|
||||||
|
# Build game target
|
||||||
|
"C:\Program Files\Epic Games\UE_5.6\Engine\Build\BatchFiles\Build.bat" BladeSmith Win64 Development "E:\MiceCola\work\BladeSmith\BladeSmith.uproject"
|
||||||
|
|
||||||
|
# Build editor target
|
||||||
|
"C:\Program Files\Epic Games\UE_5.6\Engine\Build\BatchFiles\Build.bat" BladeSmithEditor Win64 Development "E:\MiceCola\work\BladeSmith\BladeSmith.uproject"
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively, open `BladeSmith.sln` in Visual Studio and build from there.
|
||||||
|
|
||||||
|
### Running the Project
|
||||||
|
|
||||||
|
1. Open `BladeSmith.uproject` in UE5.6 Editor
|
||||||
|
2. Press Alt+P or click Play to run in editor
|
||||||
|
3. Use Ctrl+F11 for hot reload after C++ changes
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
|
||||||
|
Unreal Engine uses the Automation System:
|
||||||
|
- **In Editor:** Window → Developer Tools → Automation Sessions
|
||||||
|
- **Command Line:** `UE5Editor.exe BladeSmith.uproject -ExecCmds="Automation RunTests"`
|
||||||
|
|
||||||
|
## Architecture Overview
|
||||||
|
|
||||||
|
### Core Systems
|
||||||
|
|
||||||
|
**1. Dynamic Mesh Deformation System**
|
||||||
|
- `ADynamicIronBlock` (Source/BladeSmith/DynamicIronBlock.h/cpp) - Main iron block actor that handles all deformation
|
||||||
|
- Uses UE5's GeometryScripting API for runtime mesh manipulation
|
||||||
|
- Implements plastic deformation with volume-preserving bulge effects
|
||||||
|
- BVH acceleration structure for efficient collision detection
|
||||||
|
|
||||||
|
**2. Voxel-Based Temperature System**
|
||||||
|
- 3D temperature voxel grid (`FTemperatureVoxel`) stores temperature and plasticity coefficients
|
||||||
|
- Temperature affects material plasticity: 800-1200°C is the forging range
|
||||||
|
- Heat conduction simulation with surface cooling
|
||||||
|
- Per-vertex color visualization of temperature (black body radiation approximation)
|
||||||
|
- GPU-accelerated temperature computation using ping-pong buffers
|
||||||
|
|
||||||
|
**3. GPU Compute Pipeline (MiceShaders Plugin)**
|
||||||
|
- Custom compute shaders for physics simulation
|
||||||
|
- `FDeformComputeShader` - Plastic deformation calculations
|
||||||
|
- `FTemperatureComputeShader` - Heat conduction and cooling
|
||||||
|
- Located in `Plugins/MiceShaders/`
|
||||||
|
- Shaders written in HLSL (`.usf` files in `Shaders/Private/`)
|
||||||
|
- Uses RDG (Render Dependency Graph) for GPU dispatch
|
||||||
|
|
||||||
|
**4. Hammer Interaction System**
|
||||||
|
- `UBladeSmithWeaponComponent` (Source/BladeSmith/BladeSmithWeaponComponent.h/cpp) - First-person hammer tool
|
||||||
|
- Ray-cast based collision detection
|
||||||
|
- Enhanced Input system integration
|
||||||
|
- Triggers deformation on `ADynamicIronBlock` via `OnHammerImpact()`
|
||||||
|
|
||||||
|
### Key Classes and Their Relationships
|
||||||
|
|
||||||
|
```
|
||||||
|
ABladeSmithCharacter (Player)
|
||||||
|
└── UBladeSmithWeaponComponent (Hammer Tool)
|
||||||
|
└── OnHammerImpact()
|
||||||
|
└── ADynamicIronBlock (Iron Workpiece)
|
||||||
|
├── Temperature Voxel Grid (CPU/GPU)
|
||||||
|
├── Dynamic Mesh (GeometryScripting)
|
||||||
|
└── MiceShaders Compute Shaders
|
||||||
|
```
|
||||||
|
|
||||||
|
### Data Flow: Hammer Impact to Deformation
|
||||||
|
|
||||||
|
1. Player swings hammer (`UBladeSmithWeaponComponent::SwingHammer()`)
|
||||||
|
2. Ray-cast detects hit on `ADynamicIronBlock`
|
||||||
|
3. `OnHammerImpact()` receives: location, normal, radius, depth, force
|
||||||
|
4. Sample plasticity coefficient from temperature voxels at hit location
|
||||||
|
5. Apply plastic deformation to mesh vertices via `EditMesh()` lambda
|
||||||
|
6. Volume-preserving bulge effect applied around impact zone
|
||||||
|
7. Update vertex colors to visualize temperature
|
||||||
|
8. GPU compute shader updates temperature distribution
|
||||||
|
|
||||||
|
## Module Dependencies
|
||||||
|
|
||||||
|
**BladeSmith Module (Source/BladeSmith/BladeSmith.Build.cs):**
|
||||||
|
- GeometryScriptingCore, GeometryFramework, DynamicMesh
|
||||||
|
- GeometryCore, GeometryAlgorithms, ModelingComponents
|
||||||
|
- RenderCore, RHI, Renderer, PhysicsCore
|
||||||
|
- EnhancedInput
|
||||||
|
- AudioExtensions, AudioMixer, AudioPlatformConfiguration
|
||||||
|
- **MiceShaders** (custom plugin)
|
||||||
|
|
||||||
|
**MiceShaders Plugin:**
|
||||||
|
- Core, RenderCore, RHI
|
||||||
|
- Projects (for shader path resolution)
|
||||||
|
|
||||||
|
## Code Conventions
|
||||||
|
|
||||||
|
### File Headers
|
||||||
|
All source files must start with Epic copyright notice (as required by UE5 licensing).
|
||||||
|
|
||||||
|
### Naming Conventions
|
||||||
|
- **Actors:** `AClassName` (e.g., `ADynamicIronBlock`)
|
||||||
|
- **Components:** `UClassName` (e.g., `UBladeSmithWeaponComponent`)
|
||||||
|
- **Structs:** `FStructName` (e.g., `FTemperatureVoxel`)
|
||||||
|
- **Enums:** `EEnumName`
|
||||||
|
- **Functions:** PascalCase (e.g., `OnHammerImpact()`)
|
||||||
|
- **Variables:** PascalCase for properties, camelCase for locals
|
||||||
|
- **Constants:** PascalCase
|
||||||
|
|
||||||
|
### Type Usage
|
||||||
|
- Use UE5 types: `TArray<T>`, `TMap<K,V>`, `TSet<T>`, `FVector`, `FString`, `int32`, `uint8`
|
||||||
|
- Use `float` by default (not `double`)
|
||||||
|
- Smart pointers: rely on UObject garbage collection; use `TSharedPtr<T>` for non-UObjects
|
||||||
|
- String literals: wrap in `TEXT()` macro
|
||||||
|
|
||||||
|
### Indentation and Formatting
|
||||||
|
- **4 spaces** (no tabs)
|
||||||
|
- **Allman style braces** (opening brace on new line)
|
||||||
|
- Line length under 120 characters
|
||||||
|
|
||||||
|
### Threading and GPU Operations
|
||||||
|
```cpp
|
||||||
|
// Atomic flags for thread-safe GPU buffer status
|
||||||
|
std::atomic<bool> bGPUBuffersReady{false};
|
||||||
|
|
||||||
|
// Render thread commands
|
||||||
|
ENQUEUE_RENDER_COMMAND(CommandName)(
|
||||||
|
[Params](FRHICommandListImmediate& RHICmdList)
|
||||||
|
{
|
||||||
|
// GPU operations here
|
||||||
|
}
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
Comments use Chinese for domain-specific logic (e.g., "锤子组件", "温度体素结构").
|
||||||
|
|
||||||
|
## Important Implementation Details
|
||||||
|
|
||||||
|
### Temperature-Plasticity Relationship
|
||||||
|
- Plasticity = 0 below `ForgeTemperatureMin` (800°C by default)
|
||||||
|
- Plasticity = 1 above `ForgeTemperatureMax` (1200°C by default)
|
||||||
|
- Linear interpolation between min and max
|
||||||
|
- Deformation depth is multiplied by plasticity coefficient
|
||||||
|
|
||||||
|
### Deformation Parameters
|
||||||
|
- `FalloffExponent`: Controls edge transition smoothness (default: 2.0)
|
||||||
|
- `BulgeRatio`: Bulge radius relative to compression zone (default: 0.8)
|
||||||
|
- `BulgeStrength`: Bulge height intensity (default: 0.5)
|
||||||
|
- These simulate realistic plastic flow and volume conservation
|
||||||
|
|
||||||
|
### Vertex Color Mapping
|
||||||
|
- Each vertex samples temperature from voxel grid via trilinear interpolation
|
||||||
|
- Temperature is converted to color using black body radiation approximation
|
||||||
|
- Updated every frame when temperature changes occur
|
||||||
|
|
||||||
|
### GPU Buffer Management
|
||||||
|
- Uses ping-pong double buffering for temperature compute shader
|
||||||
|
- `TemperatureBufferA` and `TemperatureBufferB` alternate as read/write buffers
|
||||||
|
- Separate `PlasticityBuffer` stores derived plasticity coefficients
|
||||||
|
- `SurfaceVoxelFlagsBuffer` marks surface voxels for cooling calculations
|
||||||
|
- Buffers created on render thread, accessed via UAV/SRV
|
||||||
|
|
||||||
|
## Performance Targets
|
||||||
|
|
||||||
|
- **Frame time budget:** <7ms for deformation calculations
|
||||||
|
- **Target FPS:** 60 FPS during hammer operations
|
||||||
|
- **Mesh complexity:** Tested with ~100k vertices using GPU acceleration
|
||||||
|
- **Voxel resolution:** 50x50x50 typical (configurable via `TemperatureVoxelSize`)
|
||||||
|
|
||||||
|
## Adding New Compute Shaders
|
||||||
|
|
||||||
|
1. Create `.h` and `.cpp` in `Plugins/MiceShaders/Source/MiceShaders/Public|Private/`
|
||||||
|
2. Define shader class inheriting from `FGlobalShader`
|
||||||
|
3. Create corresponding `.usf` HLSL shader in `Plugins/MiceShaders/Shaders/Private/`
|
||||||
|
4. Use `IMPLEMENT_GLOBAL_SHADER()` macro to register
|
||||||
|
5. Dispatch via RDG in actor's Tick or specialized function
|
||||||
|
6. Reference existing shaders: `FDeformComputeShader`, `FTemperatureComputeShader`
|
||||||
|
|
||||||
|
## Key UE5 Patterns Used
|
||||||
|
|
||||||
|
**Dynamic Mesh Editing:**
|
||||||
|
```cpp
|
||||||
|
DynamicMesh->EditMesh([&](FDynamicMesh3& EditMesh)
|
||||||
|
{
|
||||||
|
// Modify mesh vertices/triangles here
|
||||||
|
}, EDynamicMeshChangeType::GeneralEdit);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Material Parameter Updates:**
|
||||||
|
```cpp
|
||||||
|
DynamicMaterialInstance->SetScalarParameterValue(TEXT("AvgTemperature"), AvgTemp);
|
||||||
|
DynamicMaterialInstance->SetVectorParameterValue(TEXT("Color"), FLinearColor(R,G,B,A));
|
||||||
|
```
|
||||||
|
|
||||||
|
**Enhanced Input Binding:**
|
||||||
|
```cpp
|
||||||
|
if (UEnhancedInputComponent* EnhancedInput = Cast<UEnhancedInputComponent>(InputComponent))
|
||||||
|
{
|
||||||
|
EnhancedInput->BindAction(HammerAction, ETriggerEvent::Triggered, this, &ThisClass::SwingHammer);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Plugin Structure
|
||||||
|
|
||||||
|
**Enabled Plugins (BladeSmith.uproject):**
|
||||||
|
- GeometryScripting (UE5 built-in)
|
||||||
|
- ModelingToolsEditorMode (UE5 built-in)
|
||||||
|
- HDRIBackdrop (UE5 built-in)
|
||||||
|
- RiderLink (in repository, for JetBrains Rider integration)
|
||||||
|
- MiceShaders (custom plugin)
|
||||||
|
|
||||||
|
## Content Organization
|
||||||
|
|
||||||
|
- `Content/BP/` - Blueprint actors and components
|
||||||
|
- `Content/Res/` - Game resources (meshes, materials, textures)
|
||||||
|
- `Content/Maps/` - Level maps
|
||||||
|
- `Content/Input/` - Enhanced Input assets
|
||||||
|
- `Content/FirstPersonArms/`, `Content/FPWeapon/` - First-person character assets
|
||||||
|
|
||||||
|
## Notes for AI Agents
|
||||||
|
|
||||||
|
- The AGENTS.md file contains comprehensive coding style guidelines; refer to it for detailed conventions
|
||||||
|
- When modifying deformation logic, always consider the temperature-plasticity coupling
|
||||||
|
- GPU buffer operations must be thread-safe; use atomic flags and render thread commands
|
||||||
|
- Test deformation with varying temperatures to ensure plasticity affects results correctly
|
||||||
|
- Voxel resolution trades off between accuracy and performance; adjust `TemperatureVoxelSize` accordingly
|
||||||
@@ -0,0 +1,684 @@
|
|||||||
|
# 刀刃形成技术细节
|
||||||
|
|
||||||
|
**所属系统**: 高真实度锻造系统
|
||||||
|
**文档类型**: 技术实现细节
|
||||||
|
**最后更新**: 2026-01-22
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 问题分析
|
||||||
|
|
||||||
|
### 1.1 核心难点
|
||||||
|
|
||||||
|
锻造刀刃时需要:
|
||||||
|
1. **材料沿边缘方向流动**(而非均匀扩散)
|
||||||
|
2. **边缘保持平整锐利**(而非波浪状凸起)
|
||||||
|
3. **厚度逐渐减小**(形成薄片状结构)
|
||||||
|
|
||||||
|
当前系统的球形凸起(BulgeRatio)只能处理块状形变,无法满足刀刃形成的方向性需求。
|
||||||
|
|
||||||
|
### 1.2 真实锻造物理过程
|
||||||
|
|
||||||
|
```
|
||||||
|
初始状态:圆柱形铁块
|
||||||
|
███████████
|
||||||
|
███████████
|
||||||
|
|
||||||
|
第一次锻打:
|
||||||
|
▼锤击
|
||||||
|
███████████ ← 受击处变扁
|
||||||
|
█████████████ ← 材料向侧面流动
|
||||||
|
|
||||||
|
多次锻打:
|
||||||
|
██ ← 继续变薄
|
||||||
|
███████
|
||||||
|
███████████ ← 逐渐形成锥形
|
||||||
|
|
||||||
|
最终成刃:
|
||||||
|
▲ ← 边缘平整锐利
|
||||||
|
█████
|
||||||
|
████████
|
||||||
|
```
|
||||||
|
|
||||||
|
关键:材料要**优先沿着边缘方向流动**(垂直于锤击方向 + 朝向边缘)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 技术方案:各向异性材料流动
|
||||||
|
|
||||||
|
### 2.1 核心思想
|
||||||
|
|
||||||
|
将凸起从**球形**改为**椭球形**,长轴沿着"最小阻力方向"(边缘方向)。
|
||||||
|
|
||||||
|
### 2.2 方向检测算法
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
/**
|
||||||
|
* 查找最近的自由边界方向
|
||||||
|
* 材料会优先朝向自由边界流动(阻力最小)
|
||||||
|
*/
|
||||||
|
FVector FindNearestFreeEdgeDirection(
|
||||||
|
const FDynamicMesh3& Mesh,
|
||||||
|
const FVector& LocalPos,
|
||||||
|
const FVector& BoxHalfSize)
|
||||||
|
{
|
||||||
|
// 计算到6个面的距离
|
||||||
|
float DistToEdges[6] = {
|
||||||
|
BoxHalfSize.X + LocalPos.X, // -X面
|
||||||
|
BoxHalfSize.X - LocalPos.X, // +X面
|
||||||
|
BoxHalfSize.Y + LocalPos.Y, // -Y面
|
||||||
|
BoxHalfSize.Y - LocalPos.Y, // +Y面
|
||||||
|
BoxHalfSize.Z + LocalPos.Z, // -Z面
|
||||||
|
BoxHalfSize.Z - LocalPos.Z // +Z面
|
||||||
|
};
|
||||||
|
|
||||||
|
// 找最近的边缘
|
||||||
|
int32 NearestEdge = 0;
|
||||||
|
float MinDist = DistToEdges[0];
|
||||||
|
for (int32 i = 1; i < 6; ++i)
|
||||||
|
{
|
||||||
|
if (DistToEdges[i] < MinDist)
|
||||||
|
{
|
||||||
|
MinDist = DistToEdges[i];
|
||||||
|
NearestEdge = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回朝向最近边缘的方向
|
||||||
|
static const FVector EdgeDirections[6] = {
|
||||||
|
FVector(-1, 0, 0), FVector(1, 0, 0),
|
||||||
|
FVector(0, -1, 0), FVector(0, 1, 0),
|
||||||
|
FVector(0, 0, -1), FVector(0, 0, 1)
|
||||||
|
};
|
||||||
|
|
||||||
|
return EdgeDirections[NearestEdge];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算材料流动的主方向
|
||||||
|
* 主方向 = 朝向边缘 + 垂直于锤击方向
|
||||||
|
*/
|
||||||
|
FVector3f CalculatePrimaryFlowDirection(
|
||||||
|
const FVector& HammerDir, // 锤击方向(通常是-Z)
|
||||||
|
const FVector& ToNearestEdge) // 朝向最近边缘的方向
|
||||||
|
{
|
||||||
|
// 移除锤击方向的分量(材料不能向锤头方向流动)
|
||||||
|
FVector3f PrimaryFlow = FVector3f(ToNearestEdge) -
|
||||||
|
FVector3f(HammerDir) * FVector::DotProduct(ToNearestEdge, HammerDir);
|
||||||
|
|
||||||
|
return PrimaryFlow.GetSafeNormal();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算次要流动方向(垂直于主方向和锤击方向)
|
||||||
|
*/
|
||||||
|
FVector3f CalculateSecondaryFlowDirection(
|
||||||
|
const FVector& HammerDir,
|
||||||
|
const FVector& PrimaryFlow)
|
||||||
|
{
|
||||||
|
return FVector3f::CrossProduct(FVector3f(HammerDir), PrimaryFlow).GetSafeNormal();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 各向异性凸起计算
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
/**
|
||||||
|
* 应用各向异性材料流动
|
||||||
|
* @param LocalHitPos 锤击位置(局部空间)
|
||||||
|
* @param ImpactRadius 锤击影响半径
|
||||||
|
* @param HammerDir 锤击方向
|
||||||
|
* @param AnisotropyRatio 各向异性比(主方向/次方向的流动强度比)
|
||||||
|
*/
|
||||||
|
void ApplyAnisotropicDeformation(
|
||||||
|
FDynamicMesh3& Mesh,
|
||||||
|
const FVector& LocalHitPos,
|
||||||
|
float ImpactRadius,
|
||||||
|
const FVector& HammerDir,
|
||||||
|
float AnisotropyRatio, // 默认3.0,主方向流动强度是次方向的3倍
|
||||||
|
float BulgeStrength)
|
||||||
|
{
|
||||||
|
// 计算流动方向
|
||||||
|
FVector ToNearestEdge = FindNearestFreeEdgeDirection(Mesh, LocalHitPos, BoxHalfSize);
|
||||||
|
FVector3f PrimaryFlow = CalculatePrimaryFlowDirection(HammerDir, ToNearestEdge);
|
||||||
|
FVector3f SecondaryFlow = CalculateSecondaryFlowDirection(HammerDir, PrimaryFlow);
|
||||||
|
|
||||||
|
// 对每个顶点应用形变
|
||||||
|
for (int32 VID : Mesh.VertexIndicesItr())
|
||||||
|
{
|
||||||
|
FVector VertexPos = Mesh.GetVertex(VID);
|
||||||
|
float Distance = FVector::Dist(VertexPos, LocalHitPos);
|
||||||
|
|
||||||
|
if (Distance < ImpactRadius)
|
||||||
|
{
|
||||||
|
// 将位置投影到流动方向
|
||||||
|
float DistAlongPrimary = FVector::DotProduct(
|
||||||
|
FVector(VertexPos - LocalHitPos), PrimaryFlow);
|
||||||
|
float DistAlongSecondary = FVector::DotProduct(
|
||||||
|
FVector(VertexPos - LocalHitPos), SecondaryFlow);
|
||||||
|
|
||||||
|
// 各向异性距离计算
|
||||||
|
// 主方向的"有效距离"更小(更容易到达)→ 凸起更大
|
||||||
|
float EffectiveDistance = FMath::Sqrt(
|
||||||
|
FMath::Square(DistAlongPrimary / AnisotropyRatio) +
|
||||||
|
FMath::Square(DistAlongSecondary)
|
||||||
|
);
|
||||||
|
|
||||||
|
float DistanceRatio = EffectiveDistance / ImpactRadius;
|
||||||
|
|
||||||
|
if (DistanceRatio < 1.0f)
|
||||||
|
{
|
||||||
|
// 使用FalloffExponent控制边缘过渡
|
||||||
|
float FalloffExponent = 2.0f;
|
||||||
|
float BulgeFactor = BulgeStrength *
|
||||||
|
FMath::Pow(1.0f - DistanceRatio, FalloffExponent);
|
||||||
|
|
||||||
|
// 凸起方向 = 锤击方向(材料被挤压向外)
|
||||||
|
FVector Displacement = HammerDir * (-BulgeFactor);
|
||||||
|
|
||||||
|
// 应用形变
|
||||||
|
Mesh.SetVertex(VID, VertexPos + Displacement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.4 参数说明
|
||||||
|
|
||||||
|
| 参数 | 默认值 | 范围 | 效果 |
|
||||||
|
|------|--------|------|------|
|
||||||
|
| **AnisotropyRatio** | 3.0 | 1.0-10.0 | 主方向流动强度比。越大,边缘拉伸越明显 |
|
||||||
|
| **BulgeStrength** | 0.5 | 0.1-2.0 | 凸起高度。需要配合ImpactRadius调整 |
|
||||||
|
| **ImpactRadius** | 5.0 | 1.0-20.0 | 锤击影响半径(cm) |
|
||||||
|
| **FalloffExponent** | 2.0 | 1.0-4.0 | 边缘过渡平滑度。越大,边界越锐利 |
|
||||||
|
|
||||||
|
**调参建议**:
|
||||||
|
- **锻造平面**: AnisotropyRatio = 1.0(均匀流动)
|
||||||
|
- **锻造边缘**: AnisotropyRatio = 3.0-5.0(明显方向性)
|
||||||
|
- **精细整形**: AnisotropyRatio = 8.0-10.0(极端定向)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 边缘平整约束(可选增强)
|
||||||
|
|
||||||
|
### 3.1 问题
|
||||||
|
|
||||||
|
即使有各向异性流动,边缘仍可能有轻微波纹:
|
||||||
|
|
||||||
|
```
|
||||||
|
理想边缘: ▲
|
||||||
|
████
|
||||||
|
██████
|
||||||
|
|
||||||
|
实际边缘: ▲▲▲
|
||||||
|
████
|
||||||
|
██████ ← 波纹状
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 解决方案:边缘顶点几何约束
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
/**
|
||||||
|
* 检测并标记边缘顶点
|
||||||
|
* 距离任何边界< EdgeThreshold 的顶点
|
||||||
|
*/
|
||||||
|
void MarkEdgeVertices(
|
||||||
|
FDynamicMesh3& Mesh,
|
||||||
|
TSet<int32>& OutEdgeVertices,
|
||||||
|
float EdgeThreshold = 0.5f) // 5mm
|
||||||
|
{
|
||||||
|
FVector BoxHalfSize = GetBoxHalfSize();
|
||||||
|
|
||||||
|
for (int32 VID : Mesh.VertexIndicesItr())
|
||||||
|
{
|
||||||
|
FVector Pos = Mesh.GetVertex(VID);
|
||||||
|
|
||||||
|
// 检查是否接近任何边界
|
||||||
|
bool bNearEdge = false;
|
||||||
|
bNearEdge |= FMath::Abs(Pos.X - BoxHalfSize.X) < EdgeThreshold;
|
||||||
|
bNearEdge |= FMath::Abs(Pos.X + BoxHalfSize.X) < EdgeThreshold;
|
||||||
|
bNearEdge |= FMath::Abs(Pos.Y - BoxHalfSize.Y) < EdgeThreshold;
|
||||||
|
bNearEdge |= FMath::Abs(Pos.Y + BoxHalfSize.Y) < EdgeThreshold;
|
||||||
|
bNearEdge |= FMath::Abs(Pos.Z - BoxHalfSize.Z) < EdgeThreshold;
|
||||||
|
bNearEdge |= FMath::Abs(Pos.Z + BoxHalfSize.Z) < EdgeThreshold;
|
||||||
|
|
||||||
|
if (bNearEdge)
|
||||||
|
{
|
||||||
|
OutEdgeVertices.Add(VID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对边缘顶点应用线性约束
|
||||||
|
* 形变后将边缘顶点投影到拟合直线上,保持平整
|
||||||
|
*/
|
||||||
|
void ApplyEdgeLinearConstraint(
|
||||||
|
FDynamicMesh3& Mesh,
|
||||||
|
const TSet<int32>& EdgeVertices)
|
||||||
|
{
|
||||||
|
for (int32 VID : EdgeVertices)
|
||||||
|
{
|
||||||
|
if (!Mesh.IsVertex(VID))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// 找到此顶点的边缘邻居
|
||||||
|
TArray<int32> EdgeNeighbors;
|
||||||
|
for (int32 NbrVID : Mesh.VtxVerticesItr(VID))
|
||||||
|
{
|
||||||
|
if (EdgeVertices.Contains(NbrVID))
|
||||||
|
{
|
||||||
|
EdgeNeighbors.Add(NbrVID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (EdgeNeighbors.Num() >= 2)
|
||||||
|
{
|
||||||
|
// 使用主成分分析(PCA)拟合直线
|
||||||
|
FVector CurrentPos = Mesh.GetVertex(VID);
|
||||||
|
FVector LineOrigin, LineDirection;
|
||||||
|
|
||||||
|
FitLineToPoints(Mesh, EdgeNeighbors, LineOrigin, LineDirection);
|
||||||
|
|
||||||
|
// 投影到拟合线上
|
||||||
|
FVector ProjectedPos = ProjectPointToLine(CurrentPos, LineOrigin, LineDirection);
|
||||||
|
|
||||||
|
// 稍微平滑更新(避免突变)
|
||||||
|
FVector SmoothPos = FMath::Lerp(CurrentPos, ProjectedPos, 0.5f);
|
||||||
|
|
||||||
|
Mesh.SetVertex(VID, SmoothPos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用PCA拟合直线到点集
|
||||||
|
*/
|
||||||
|
void FitLineToPoints(
|
||||||
|
const FDynamicMesh3& Mesh,
|
||||||
|
const TArray<int32>& VertexIDs,
|
||||||
|
FVector& OutOrigin,
|
||||||
|
FVector& OutDirection)
|
||||||
|
{
|
||||||
|
// 计算质心
|
||||||
|
FVector Centroid = FVector::ZeroVector;
|
||||||
|
for (int32 VID : VertexIDs)
|
||||||
|
{
|
||||||
|
Centroid += Mesh.GetVertex(VID);
|
||||||
|
}
|
||||||
|
Centroid /= VertexIDs.Num();
|
||||||
|
|
||||||
|
// 计算协方差矩阵
|
||||||
|
float CovXX = 0, CovYY = 0, CovZZ = 0;
|
||||||
|
float CovXY = 0, CovXZ = 0, CovYZ = 0;
|
||||||
|
|
||||||
|
for (int32 VID : VertexIDs)
|
||||||
|
{
|
||||||
|
FVector P = Mesh.GetVertex(VID) - Centroid;
|
||||||
|
CovXX += P.X * P.X;
|
||||||
|
CovYY += P.Y * P.Y;
|
||||||
|
CovZZ += P.Z * P.Z;
|
||||||
|
CovXY += P.X * P.Y;
|
||||||
|
CovXZ += P.X * P.Z;
|
||||||
|
CovYZ += P.Y * P.Z;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建协方差矩阵
|
||||||
|
FMatrix33 CovMatrix(
|
||||||
|
FVector3f(CovXX, CovXY, CovXZ),
|
||||||
|
FVector3f(CovXY, CovYY, CovYZ),
|
||||||
|
FVector3f(CovXZ, CovYZ, CovZZ)
|
||||||
|
);
|
||||||
|
|
||||||
|
// 计算特征向量(主方向 = 最大特征值对应的向量)
|
||||||
|
FVector3f EigenValues;
|
||||||
|
FMatrix33 EigenVectors;
|
||||||
|
FMatrix33::EigenDecomposition(CovMatrix, EigenValues, EigenVectors);
|
||||||
|
|
||||||
|
// 最大特征值对应的列向量是主方向
|
||||||
|
OutOrigin = Centroid;
|
||||||
|
OutDirection = FVector(EigenVectors.GetColumn(0)); // 第一主成分
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将点投影到直线上
|
||||||
|
*/
|
||||||
|
FVector ProjectPointToLine(
|
||||||
|
const FVector& Point,
|
||||||
|
const FVector& LineOrigin,
|
||||||
|
const FVector& LineDirection)
|
||||||
|
{
|
||||||
|
FVector ToPoint = Point - LineOrigin;
|
||||||
|
float ProjectionLength = FVector::DotProduct(ToPoint, LineDirection);
|
||||||
|
return LineOrigin + LineDirection * ProjectionLength;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 完整锤击流程
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void ADynamicIronBlock::OnHammerImpact(
|
||||||
|
FVector HitLocation,
|
||||||
|
FVector HitNormal,
|
||||||
|
float ImpactRadius,
|
||||||
|
float ImpactDepth,
|
||||||
|
float ImpactForce)
|
||||||
|
{
|
||||||
|
FVector LocalHitPos = GetTransform().InverseTransformPosition(HitLocation);
|
||||||
|
FVector HammerDir = GetTransform().InverseTransformPosition(HitNormal);
|
||||||
|
|
||||||
|
DynamicMesh->EditMesh([&](FDynamicMesh3& Mesh)
|
||||||
|
{
|
||||||
|
// 1. 应用各向异性形变
|
||||||
|
ApplyAnisotropicDeformation(
|
||||||
|
Mesh,
|
||||||
|
LocalHitPos,
|
||||||
|
ImpactRadius,
|
||||||
|
HammerDir,
|
||||||
|
AnisotropyRatio,
|
||||||
|
BulgeStrength
|
||||||
|
);
|
||||||
|
|
||||||
|
// 2. (可选)边缘平整约束
|
||||||
|
if (bEnableEdgeConstraint)
|
||||||
|
{
|
||||||
|
TSet<int32> EdgeVertices;
|
||||||
|
MarkEdgeVertices(Mesh, EdgeVertices, EdgeThreshold);
|
||||||
|
|
||||||
|
// 只处理受影响区域的边缘顶点
|
||||||
|
TSet<int32> AffectedEdges;
|
||||||
|
for (int32 VID : EdgeVertices)
|
||||||
|
{
|
||||||
|
FVector Pos = Mesh.GetVertex(VID);
|
||||||
|
if (FVector::Dist(Pos, LocalHitPos) < ImpactRadius * 1.5f)
|
||||||
|
{
|
||||||
|
AffectedEdges.Add(VID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplyEdgeLinearConstraint(Mesh, AffectedEdges);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 自适应网格细分(可选高级功能)
|
||||||
|
|
||||||
|
### 4.1 目的
|
||||||
|
|
||||||
|
刀刃区域需要更高密度才能表现锐利边缘:
|
||||||
|
|
||||||
|
```
|
||||||
|
低密度(5万顶点):
|
||||||
|
▲
|
||||||
|
████ ← 锯齿状,不平整
|
||||||
|
|
||||||
|
高密度(200万顶点):
|
||||||
|
▲
|
||||||
|
████ ← 平滑锐利
|
||||||
|
██████
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 曲率检测与细分
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
/**
|
||||||
|
* 检测高曲率边缘并细分
|
||||||
|
*/
|
||||||
|
void SubdivideHighCurvatureEdges(
|
||||||
|
FDynamicMesh3& Mesh,
|
||||||
|
float CurvatureThreshold, // 曲率阈值
|
||||||
|
float MaxEdgeLength, // 最大边长
|
||||||
|
int32 MaxSubdivisions) // 最大细分次数
|
||||||
|
{
|
||||||
|
int32 SubdivisionCount = 0;
|
||||||
|
|
||||||
|
for (int32 EID : Mesh.EdgeIndicesItr())
|
||||||
|
{
|
||||||
|
if (SubdivisionCount >= MaxSubdivisions)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// 跳过边界边
|
||||||
|
if (Mesh.IsBoundaryEdge(EID))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
FIndex2i EdgeVerts = Mesh.GetEdgeV(EID);
|
||||||
|
FVector V0 = Mesh.GetVertex(EdgeVerts.A);
|
||||||
|
FVector V1 = Mesh.GetVertex(EdgeVerts.B);
|
||||||
|
float EdgeLength = FVector::Dist(V0, V1);
|
||||||
|
|
||||||
|
// 边太长或曲率太高 → 细分
|
||||||
|
float EdgeCurvature = CalculateEdgeCurvature(Mesh, EID);
|
||||||
|
|
||||||
|
if (EdgeLength > MaxEdgeLength && EdgeCurvature > CurvatureThreshold)
|
||||||
|
{
|
||||||
|
// 细分此边
|
||||||
|
FDynamicMesh3::FEdgeSplitInfo SplitInfo;
|
||||||
|
EMeshResult Result = Mesh.SplitEdge(EID, SplitInfo);
|
||||||
|
|
||||||
|
if (Result == EMeshResult::Ok)
|
||||||
|
{
|
||||||
|
// 新顶点位置 = 中点
|
||||||
|
FVector MidPoint = (V0 + V1) * 0.5f;
|
||||||
|
Mesh.SetVertex(SplitInfo.NewVertex, MidPoint);
|
||||||
|
|
||||||
|
// 插值顶点属性
|
||||||
|
InterpolateVertexAttributes(Mesh, EdgeVerts.A, EdgeVerts.B, SplitInfo.NewVertex);
|
||||||
|
|
||||||
|
SubdivisionCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算边的曲率(基于邻面法线夹角)
|
||||||
|
*/
|
||||||
|
float CalculateEdgeCurvature(const FDynamicMesh3& Mesh, int32 EdgeID)
|
||||||
|
{
|
||||||
|
FIndex2i EdgeTris = Mesh.GetEdgeT(EdgeID);
|
||||||
|
|
||||||
|
// 边界边曲率为0
|
||||||
|
if (EdgeTris.B == IndexConstants::InvalidID)
|
||||||
|
return 0.0f;
|
||||||
|
|
||||||
|
// 获取两个三角形的法线
|
||||||
|
FVector Normal0 = Mesh.GetTriNormal(EdgeTris.A);
|
||||||
|
FVector Normal1 = Mesh.GetTriNormal(EdgeTris.B);
|
||||||
|
|
||||||
|
// 法线夹角 = 曲率度量
|
||||||
|
float Dot = FVector::DotProduct(Normal0, Normal1);
|
||||||
|
return FMath::Acos(Dot); // 返回弧度
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插值顶点属性
|
||||||
|
* 确保新顶点继承正确的材料状态
|
||||||
|
*/
|
||||||
|
void InterpolateVertexAttributes(
|
||||||
|
FDynamicMesh3& Mesh,
|
||||||
|
int32 VID0,
|
||||||
|
int32 VID1,
|
||||||
|
int32 NewVID)
|
||||||
|
{
|
||||||
|
// 位置(已由SplitEdge处理)
|
||||||
|
|
||||||
|
// 初始位置
|
||||||
|
FVector InitPos0 = InitialPositions->GetValue(VID0);
|
||||||
|
FVector InitPos1 = InitialPositions->GetValue(VID1);
|
||||||
|
InitialPositions->SetValue(NewVID, (InitPos0 + InitPos1) * 0.5f);
|
||||||
|
|
||||||
|
// 打磨度
|
||||||
|
float Polish0 = PolishAmount->GetValue(VID0);
|
||||||
|
float Polish1 = PolishAmount->GetValue(VID1);
|
||||||
|
PolishAmount->SetValue(NewVID, (Polish0 + Polish1) * 0.5f);
|
||||||
|
|
||||||
|
// 累积应变
|
||||||
|
float Strain0 = AccumulatedStrain->GetValue(VID0);
|
||||||
|
float Strain1 = AccumulatedStrain->GetValue(VID1);
|
||||||
|
AccumulatedStrain->SetValue(NewVID, (Strain0 + Strain1) * 0.5f);
|
||||||
|
|
||||||
|
// 硬度
|
||||||
|
float Hardness0 = HardnessCoefficient->GetValue(VID0);
|
||||||
|
float Hardness1 = HardnessCoefficient->GetValue(VID1);
|
||||||
|
HardnessCoefficient->SetValue(NewVID, (Hardness0 + Hardness1) * 0.5f);
|
||||||
|
|
||||||
|
// 晶粒尺寸
|
||||||
|
float Grain0 = GrainSize->GetValue(VID0);
|
||||||
|
float Grain1 = GrainSize->GetValue(VID1);
|
||||||
|
GrainSize->SetValue(NewVID, (Grain0 + Grain1) * 0.5f);
|
||||||
|
|
||||||
|
// 顶点颜色
|
||||||
|
FColor Color0 = Mesh.GetVertexColor(VID0);
|
||||||
|
FColor Color1 = Mesh.GetVertexColor(VID1);
|
||||||
|
FLinearColor LinearColor0(Color0);
|
||||||
|
FLinearColor LinearColor1(Color1);
|
||||||
|
FLinearColor AvgColor = (LinearColor0 + LinearColor1) * 0.5f;
|
||||||
|
Mesh.SetVertexColor(NewVID, AvgColor.ToFColor());
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 集成到锤击流程
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void ADynamicIronBlock::OnHammerImpact(...)
|
||||||
|
{
|
||||||
|
// ... 形变计算 ...
|
||||||
|
|
||||||
|
// 自适应细分(每隔几次锤击执行,避免过度细分)
|
||||||
|
if (bEnableAdaptiveRefinement && HammerHitCount % 5 == 0)
|
||||||
|
{
|
||||||
|
SubdivideHighCurvatureEdges(
|
||||||
|
*DynamicMesh,
|
||||||
|
CurvatureThreshold = 0.5f, // ~30度角
|
||||||
|
MaxEdgeLength = 2.0f, // 2cm
|
||||||
|
MaxSubdivisions = 100
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 性能考虑
|
||||||
|
|
||||||
|
### 5.1 计算复杂度
|
||||||
|
|
||||||
|
| 操作 | 复杂度 | 实际耗时(20万顶点) |
|
||||||
|
|------|--------|---------------------|
|
||||||
|
| 各向异性凸起 | O(N) | 2-3ms |
|
||||||
|
| 边缘约束(PCA) | O(N×K) | +3-5ms |
|
||||||
|
| 自适应细分 | O(E) | +2-4ms |
|
||||||
|
| 属性插值 | O(1) per顶点 | 可忽略 |
|
||||||
|
|
||||||
|
### 5.2 优化建议
|
||||||
|
|
||||||
|
1. **限制约束区域**:只对受影响的边缘顶点应用约束
|
||||||
|
2. **降低细分频率**:每5-10次锤击才细分一次
|
||||||
|
3. **LOD策略**:远距离铁块使用简化约束
|
||||||
|
4. **异步计算**:细分可以延后到帧尾执行
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 调试工具
|
||||||
|
|
||||||
|
### 6.1 可视化流动方向
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void ADynamicIronBlock::DebugDrawFlowDirections()
|
||||||
|
{
|
||||||
|
FVector BoxHalfSize = GetBoxHalfSize();
|
||||||
|
|
||||||
|
// 在网格表面采样点
|
||||||
|
for (int32 VID : DynamicMesh->VertexIndicesItr())
|
||||||
|
{
|
||||||
|
if (VID % 100 != 0) // 稀疏采样
|
||||||
|
continue;
|
||||||
|
|
||||||
|
FVector Pos = GetActorTransform().TransformPosition(DynamicMesh->GetVertex(VID));
|
||||||
|
|
||||||
|
// 计算流动方向
|
||||||
|
FVector ToNearestEdge = FindNearestFreeEdgeDirection(*DynamicMesh,
|
||||||
|
DynamicMesh->GetVertex(VID), BoxHalfSize);
|
||||||
|
FVector PrimaryFlow = CalculatePrimaryFlowDirection(FVector::DownVector, ToNearestEdge);
|
||||||
|
|
||||||
|
// 绘制方向箭头
|
||||||
|
DrawDebugDirectionalArrow(
|
||||||
|
GetWorld(),
|
||||||
|
Pos,
|
||||||
|
Pos + GetActorTransform().TransformVector(PrimaryFlow) * 5.0f,
|
||||||
|
2.0f,
|
||||||
|
FColor::Cyan,
|
||||||
|
false,
|
||||||
|
5.0f,
|
||||||
|
0,
|
||||||
|
0.5f
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.2 可视化边缘顶点
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void ADynamicIronBlock::DebugDrawEdgeVertices()
|
||||||
|
{
|
||||||
|
TSet<int32> EdgeVertices;
|
||||||
|
MarkEdgeVertices(*DynamicMesh, EdgeVertices, EdgeThreshold);
|
||||||
|
|
||||||
|
for (int32 VID : EdgeVertices)
|
||||||
|
{
|
||||||
|
FVector Pos = GetActorTransform().TransformPosition(DynamicMesh->GetVertex(VID));
|
||||||
|
DrawDebugPoint(GetWorld(), Pos, 8.0f, FColor::Red, false, 5.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 参数配置示例
|
||||||
|
|
||||||
|
### 7.1 锻造平面(打铁块顶面)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
AnisotropyRatio = 1.0f; // 均匀流动
|
||||||
|
ImpactRadius = 5.0f;
|
||||||
|
BulgeStrength = 0.5f;
|
||||||
|
bEnableEdgeConstraint = false;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.2 锻造边缘(形成刀刃)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
AnisotropyRatio = 4.0f; // 强方向性
|
||||||
|
ImpactRadius = 3.0f; // 小范围
|
||||||
|
BulgeStrength = 0.3f; // 较小凸起
|
||||||
|
bEnableEdgeConstraint = true;
|
||||||
|
EdgeThreshold = 1.0f; // 1cm
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.3 精细整形(最后修整)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
AnisotropyRatio = 8.0f; // 极端定向
|
||||||
|
ImpactRadius = 1.5f; // 很小范围
|
||||||
|
BulgeStrength = 0.1f; // 微调
|
||||||
|
bEnableEdgeConstraint = true;
|
||||||
|
EdgeThreshold = 0.5f; // 5mm
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 已知限制
|
||||||
|
|
||||||
|
1. **极端形变**: 当边缘厚度<1mm时,各向异性假设可能失效
|
||||||
|
2. **拓扑变化**: 当前方法无法处理网格撕裂或融合(如锻打折叠)
|
||||||
|
3. **多重边缘**: 复杂形状(如叉状)的方向检测可能不准确
|
||||||
|
|
||||||
|
**未来改进方向**:
|
||||||
|
- 基于曲率张量的精确流动方向
|
||||||
|
- 支持拓扑变化(网格切割与连接)
|
||||||
|
- 多工具系统(不同锤头形状)
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,980 @@
|
|||||||
|
# 网格质量优化细节
|
||||||
|
|
||||||
|
**所属系统**: 高真实度锻造系统
|
||||||
|
**文档类型**: 技术实现细节
|
||||||
|
**最后更新**: 2026-01-22
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 问题分析
|
||||||
|
|
||||||
|
### 1.1 网格退化现象
|
||||||
|
|
||||||
|
在连续锻打过程中,动态网格会逐渐退化:
|
||||||
|
|
||||||
|
```
|
||||||
|
初始均匀网格(良好):
|
||||||
|
/\ /\ /\
|
||||||
|
/__\/__\/__\
|
||||||
|
/\ /\ /\
|
||||||
|
/__\/__\/__\
|
||||||
|
|
||||||
|
锤击后(退化):
|
||||||
|
/\ /|\ /\ ← 细长三角形(Sliver)
|
||||||
|
/__\ /_|_\ /__\
|
||||||
|
|
||||||
|
^^ 过小 ^^ ← 重叠三角形
|
||||||
|
|
||||||
|
问题:
|
||||||
|
1. 细长三角形:长宽比>10:1
|
||||||
|
2. 过小三角形:面积<0.01mm²
|
||||||
|
3. 分布不均:密度相差100倍
|
||||||
|
4. 法线扭曲:相邻三角形法线夹角>90°
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.2 影响评估
|
||||||
|
|
||||||
|
#### 视觉影响
|
||||||
|
- **着色异常**: 细长三角形法线插值错误 → 表面出现条纹、色块
|
||||||
|
- **闪烁问题**: 极小三角形像素覆盖不足 → 出现"空洞"或闪烁
|
||||||
|
- **边缘锯齿**: 边缘三角形拉伸 → 刀刃呈锯齿状
|
||||||
|
|
||||||
|
#### 性能影响
|
||||||
|
- **BVH查询效率下降**: 大量小三角形导致遍历深度增加,碰撞检测耗时+50-80%
|
||||||
|
- **渲染开销**: 顶点处理效率降低,Overdraw增加
|
||||||
|
- **内存浪费**: 大量无效三角形占用内存
|
||||||
|
|
||||||
|
#### 数值稳定性
|
||||||
|
- **浮点误差累积**: 极小边长导致叉积精度丢失
|
||||||
|
- **法线计算错误**: 接近共线的三点 → 法线方向不确定
|
||||||
|
- **碰撞检测失效**: 射线-三角形交点测试不稳定
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 解决方案:增量式局部优化
|
||||||
|
|
||||||
|
### 2.1 核心思想
|
||||||
|
|
||||||
|
```
|
||||||
|
传统方法(全局重建):
|
||||||
|
锤击 → 全网格检测 → 全网格优化 → 重建BVH
|
||||||
|
↓
|
||||||
|
耗时50-100ms,顶点属性可能丢失
|
||||||
|
|
||||||
|
增量优化(局部):
|
||||||
|
锤击 → 仅检测受影响区域 → 局部优化 → 局部BVH更新
|
||||||
|
↓
|
||||||
|
耗时5-15ms,保留所有顶点属性
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 优化三步法
|
||||||
|
|
||||||
|
```
|
||||||
|
Step 1: 边翻转(Edge Flip)
|
||||||
|
┌───────┐ ┌───────┐
|
||||||
|
│╱ │ → │ ╲│ 改善长宽比
|
||||||
|
│╱ │ │ ╲│
|
||||||
|
└───────┘ └───────┘
|
||||||
|
|
||||||
|
Step 2: 顶点平滑(Laplacian Smoothing)
|
||||||
|
●────●────● ●────●────●
|
||||||
|
╰─╯ │ 均匀化分布
|
||||||
|
|
||||||
|
Step 3: 自适应重网格(Remeshing)
|
||||||
|
/\ /\ /\ /\ 合并过大三角形
|
||||||
|
/__\ /__\ → /__\ /\__/ 细分过小三角形
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 网格质量度量
|
||||||
|
|
||||||
|
### 3.1 质量指标
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
/**
|
||||||
|
* 三角形质量指标
|
||||||
|
*/
|
||||||
|
struct FTriangleQualityMetrics
|
||||||
|
{
|
||||||
|
float Area; // 面积(mm²)
|
||||||
|
float AspectRatio; // 长宽比(0-1,1=正三角形)
|
||||||
|
float MinAngle; // 最小内角(度)
|
||||||
|
float MaxAngle; // 最大内角(度)
|
||||||
|
float EdgeLengthVariance;// 边长方差
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算三角形质量
|
||||||
|
*/
|
||||||
|
FTriangleQualityMetrics CalculateTriangleQuality(
|
||||||
|
const FDynamicMesh3& Mesh,
|
||||||
|
int32 TriangleID)
|
||||||
|
{
|
||||||
|
FTriangleQualityMetrics Metrics;
|
||||||
|
|
||||||
|
// 获取三角形顶点
|
||||||
|
FIndex3i Tri = Mesh.GetTriangle(TriangleID);
|
||||||
|
FVector A = Mesh.GetVertex(Tri.A);
|
||||||
|
FVector B = Mesh.GetVertex(Tri.B);
|
||||||
|
FVector C = Mesh.GetVertex(Tri.C);
|
||||||
|
|
||||||
|
// 计算边长
|
||||||
|
float EdgeAB = FVector::Dist(A, B);
|
||||||
|
float EdgeBC = FVector::Dist(B, C);
|
||||||
|
float EdgeCA = FVector::Dist(C, A);
|
||||||
|
|
||||||
|
// 计算面积
|
||||||
|
FVector AB = B - A;
|
||||||
|
FVector AC = C - A;
|
||||||
|
Metrics.Area = FVector::CrossProduct(AB, AC).Size() * 0.5f;
|
||||||
|
|
||||||
|
// 计算长宽比
|
||||||
|
float MaxEdge = FMath::Max3(EdgeAB, EdgeBC, EdgeCA);
|
||||||
|
float MinEdge = FMath::Min3(EdgeAB, EdgeBC, EdgeCA);
|
||||||
|
Metrics.AspectRatio = MinEdge / (MaxEdge + 1e-6f);
|
||||||
|
|
||||||
|
// 计算内角(余弦定理)
|
||||||
|
Metrics.MaxAngle = FMath::RadiansToDegrees(FMath::Acos(
|
||||||
|
FMath::Clamp((EdgeBC*EdgeBC + EdgeCA*EdgeCA - EdgeAB*EdgeAB) / (2.0f * EdgeBC * EdgeCA),
|
||||||
|
-1.0f, 1.0f)));
|
||||||
|
|
||||||
|
// 边长方差
|
||||||
|
float MeanEdge = (EdgeAB + EdgeBC + EdgeCA) / 3.0f;
|
||||||
|
Metrics.EdgeLengthVariance = (
|
||||||
|
FMath::Square(EdgeAB - MeanEdge) +
|
||||||
|
FMath::Square(EdgeBC - MeanEdge) +
|
||||||
|
FMath::Square(EdgeCA - MeanEdge)
|
||||||
|
) / 3.0f;
|
||||||
|
|
||||||
|
return Metrics;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断三角形是否退化
|
||||||
|
*/
|
||||||
|
bool IsDegenerateTriangle(const FTriangleQualityMetrics& Metrics)
|
||||||
|
{
|
||||||
|
return
|
||||||
|
Metrics.Area < 0.01f || // 面积太小
|
||||||
|
Metrics.AspectRatio < 0.1f || // 长宽比>10:1
|
||||||
|
Metrics.MaxAngle > 150.0f; // 最大角>150度
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 质量阈值配置
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
class ADynamicIronBlock
|
||||||
|
{
|
||||||
|
// 网格质量参数
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Mesh Quality")
|
||||||
|
float MinTriangleArea = 0.01f; // 最小面积(mm²)
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Mesh Quality")
|
||||||
|
float MaxTriangleArea = 100.0f; // 最大面积(mm²)
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Mesh Quality")
|
||||||
|
float MinAspectRatio = 0.1f; // 最小长宽比
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Mesh Quality")
|
||||||
|
bool bAutoOptimizeMesh = true; // 自动优化开关
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Mesh Quality")
|
||||||
|
float OptimizationRadius = 1.5f; // 优化半径倍数(相对于ImpactRadius)
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Step 1: 边翻转优化
|
||||||
|
|
||||||
|
### 4.1 算法原理
|
||||||
|
|
||||||
|
```
|
||||||
|
翻转前(质量差):
|
||||||
|
C
|
||||||
|
│╲
|
||||||
|
│ ╲
|
||||||
|
A─┼──B ← 边AB
|
||||||
|
│ ╱
|
||||||
|
│╱
|
||||||
|
D
|
||||||
|
|
||||||
|
质量指标:
|
||||||
|
- 长宽比 = 0.08(很差)
|
||||||
|
- 最大角 = 160°
|
||||||
|
|
||||||
|
翻转后(改善):
|
||||||
|
C
|
||||||
|
│╲
|
||||||
|
│ ╲
|
||||||
|
╲ ╲ ← 新边CD
|
||||||
|
╲ ╲
|
||||||
|
╲ ╲
|
||||||
|
D──A──B ← 边AB分成两条
|
||||||
|
|
||||||
|
质量指标:
|
||||||
|
- 长宽比 = 0.6(良好)
|
||||||
|
- 最大角 = 80°
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 实现代码
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
/**
|
||||||
|
* 边翻转优化
|
||||||
|
* 通过翻转边来改善三角形长宽比
|
||||||
|
*/
|
||||||
|
void FlipEdgesToImproveQuality(
|
||||||
|
FDynamicMesh3& Mesh,
|
||||||
|
const TSet<int32>& AffectedTriangles,
|
||||||
|
float MinAspectRatio)
|
||||||
|
{
|
||||||
|
// 收集相关边
|
||||||
|
TSet<int32> EdgesToCheck;
|
||||||
|
for (int32 TID : AffectedTriangles)
|
||||||
|
{
|
||||||
|
FIndex3i TriEdges = Mesh.GetTriEdges(TID);
|
||||||
|
EdgesToCheck.Add(TriEdges.A);
|
||||||
|
EdgesToCheck.Add(TriEdges.B);
|
||||||
|
EdgesToCheck.Add(TriEdges.C);
|
||||||
|
}
|
||||||
|
|
||||||
|
int32 FlipCount = 0;
|
||||||
|
|
||||||
|
for (int32 EID : EdgesToCheck)
|
||||||
|
{
|
||||||
|
// 跳过边界边和内部边
|
||||||
|
if (!Mesh.IsEdge(EID) || Mesh.IsBoundaryEdge(EID))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// 获取边的两个三角形
|
||||||
|
FIndex2i EdgeTris = Mesh.GetEdgeT(EID);
|
||||||
|
if (EdgeTris.B == IndexConstants::InvalidID)
|
||||||
|
continue; // 边界边,无法翻转
|
||||||
|
|
||||||
|
// 计算翻转前的质量
|
||||||
|
float QualityBefore = CalculateAspectRatio(Mesh, EdgeTris.A) +
|
||||||
|
CalculateAspectRatio(Mesh, EdgeTris.B);
|
||||||
|
|
||||||
|
// 尝试翻转
|
||||||
|
FDynamicMesh3::FEdgeFlipInfo FlipInfo;
|
||||||
|
EMeshResult Result = Mesh.FlipEdge(EID, FlipInfo);
|
||||||
|
|
||||||
|
if (Result == EMeshResult::Ok)
|
||||||
|
{
|
||||||
|
// 计算翻转后的质量
|
||||||
|
float QualityAfter = CalculateAspectRatio(Mesh, EdgeTris.A) +
|
||||||
|
CalculateAspectRatio(Mesh, EdgeTris.B);
|
||||||
|
|
||||||
|
// 如果质量没有改善,翻转回去
|
||||||
|
if (QualityAfter <= QualityBefore)
|
||||||
|
{
|
||||||
|
Mesh.FlipEdge(EID, FlipInfo); // 撤销
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
FlipCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//UE_LOG(LogBladeSmith, Log, TEXT("Flipped %d edges"), FlipCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算单个三角形的长宽比
|
||||||
|
*/
|
||||||
|
float CalculateAspectRatio(const FDynamicMesh3& Mesh, int32 TriID)
|
||||||
|
{
|
||||||
|
FIndex3i Tri = Mesh.GetTriangle(TriID);
|
||||||
|
FVector A = Mesh.GetVertex(Tri.A);
|
||||||
|
FVector B = Mesh.GetVertex(Tri.B);
|
||||||
|
FVector C = Mesh.GetVertex(Tri.C);
|
||||||
|
|
||||||
|
float EdgeAB = FVector::Dist(A, B);
|
||||||
|
float EdgeBC = FVector::Dist(B, C);
|
||||||
|
float EdgeCA = FVector::Dist(C, A);
|
||||||
|
|
||||||
|
float MaxEdge = FMath::Max3(EdgeAB, EdgeBC, EdgeCA);
|
||||||
|
float MinEdge = FMath::Min3(EdgeAB, EdgeBC, EdgeCA);
|
||||||
|
|
||||||
|
return MinEdge / (MaxEdge + 1e-6f);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Step 2: 顶点平滑
|
||||||
|
|
||||||
|
### 5.1 Laplacian平滑原理
|
||||||
|
|
||||||
|
```
|
||||||
|
平滑前:
|
||||||
|
●
|
||||||
|
╱ ╲ 不规则分布
|
||||||
|
● ●
|
||||||
|
|
||||||
|
平滑后(每个顶点移向邻居的平均位置):
|
||||||
|
●
|
||||||
|
│ ╲ 均匀分布
|
||||||
|
│ ╲
|
||||||
|
●───●
|
||||||
|
|
||||||
|
公式:
|
||||||
|
V_new = V_old + λ × (Σ(V_neighbor) / N - V_old)
|
||||||
|
|
||||||
|
其中:
|
||||||
|
λ = 平滑系数(0-1)
|
||||||
|
N = 邻居顶点数量
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 实现代码
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
/**
|
||||||
|
* Laplacian顶点平滑
|
||||||
|
* 均匀化顶点分布,改善网格质量
|
||||||
|
*/
|
||||||
|
void SmoothVertices(
|
||||||
|
FDynamicMesh3& Mesh,
|
||||||
|
const TSet<int32>& AffectedTriangles,
|
||||||
|
int32 Iterations,
|
||||||
|
float SmoothFactor)
|
||||||
|
{
|
||||||
|
if (Iterations <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// 收集需要平滑的顶点
|
||||||
|
TSet<int32> VerticesToSmooth;
|
||||||
|
for (int32 TID : AffectedTriangles)
|
||||||
|
{
|
||||||
|
FIndex3i Tri = Mesh.GetTriangle(TID);
|
||||||
|
VerticesToSmooth.Add(Tri.A);
|
||||||
|
VerticesToSmooth.Add(Tri.B);
|
||||||
|
VerticesToSmooth.Add(Tri.C);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 排除边界顶点(保持形状)
|
||||||
|
TSet<int32> InteriorVertices;
|
||||||
|
for (int32 VID : VerticesToSmooth)
|
||||||
|
{
|
||||||
|
if (!Mesh.IsBoundaryVertex(VID))
|
||||||
|
{
|
||||||
|
InteriorVertices.Add(VID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 迭代平滑
|
||||||
|
for (int32 Iter = 0; Iter < Iterations; ++Iter)
|
||||||
|
{
|
||||||
|
TMap<int32, FVector> NewPositions;
|
||||||
|
|
||||||
|
for (int32 VID : InteriorVertices)
|
||||||
|
{
|
||||||
|
// 获取邻接顶点
|
||||||
|
TArray<int32> Neighbors;
|
||||||
|
for (int32 NbrVID : Mesh.VtxVerticesItr(VID))
|
||||||
|
{
|
||||||
|
Neighbors.Add(NbrVID);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Neighbors.Num() == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// 计算平均位置
|
||||||
|
FVector Sum = FVector::ZeroVector;
|
||||||
|
for (int32 NbrVID : Neighbors)
|
||||||
|
{
|
||||||
|
Sum += Mesh.GetVertex(NbrVID);
|
||||||
|
}
|
||||||
|
FVector AvgPos = Sum / Neighbors.Num();
|
||||||
|
|
||||||
|
// Laplacian平滑
|
||||||
|
FVector CurrentPos = Mesh.GetVertex(VID);
|
||||||
|
FVector NewPos = FMath::Lerp(CurrentPos, AvgPos, SmoothFactor);
|
||||||
|
|
||||||
|
NewPositions.Add(VID, NewPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量更新位置
|
||||||
|
for (const auto& Pair : NewPositions)
|
||||||
|
{
|
||||||
|
Mesh.SetVertex(Pair.Key, Pair.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.3 加权平滑(保留特征)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
/**
|
||||||
|
* 特征保留的加权平滑
|
||||||
|
* 根据曲率调整平滑强度:高曲率区域少平滑
|
||||||
|
*/
|
||||||
|
void WeightedSmoothVertices(
|
||||||
|
FDynamicMesh3& Mesh,
|
||||||
|
const TSet<int32>& AffectedTriangles,
|
||||||
|
float BaseSmoothFactor)
|
||||||
|
{
|
||||||
|
TMap<int32, FVector> NewPositions;
|
||||||
|
|
||||||
|
for (int32 VID : AffectedTriangles)
|
||||||
|
{
|
||||||
|
if (Mesh.IsBoundaryVertex(VID))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// 计算局部曲率
|
||||||
|
float Curvature = CalculateVertexCurvature(Mesh, VID);
|
||||||
|
|
||||||
|
// 高曲率区域平滑系数小(保留锐利特征)
|
||||||
|
float AdaptiveSmoothFactor = BaseSmoothFactor / (1.0f + Curvature * 5.0f);
|
||||||
|
|
||||||
|
// ... 其余逻辑同Laplacian平滑
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算顶点曲率(简化:基于邻面法线方差)
|
||||||
|
*/
|
||||||
|
float CalculateVertexCurvature(const FDynamicMesh3& Mesh, int32 VID)
|
||||||
|
{
|
||||||
|
TArray<int32> Triangles;
|
||||||
|
for (int32 TID : Mesh.VtxTrianglesItr(VID))
|
||||||
|
{
|
||||||
|
Triangles.Add(TID);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Triangles.Num() < 2)
|
||||||
|
return 0.0f;
|
||||||
|
|
||||||
|
// 计算平均法线
|
||||||
|
FVector AverageNormal = FVector::ZeroVector;
|
||||||
|
for (int32 TID : Triangles)
|
||||||
|
{
|
||||||
|
AverageNormal += Mesh.GetTriNormal(TID);
|
||||||
|
}
|
||||||
|
AverageNormal /= Triangles.Num();
|
||||||
|
|
||||||
|
// 计算法线方差
|
||||||
|
float Variance = 0.0f;
|
||||||
|
for (int32 TID : Triangles)
|
||||||
|
{
|
||||||
|
FVector TriNormal = Mesh.GetTriNormal(TID);
|
||||||
|
Variance += FVector::DistSquared(TriNormal, AverageNormal);
|
||||||
|
}
|
||||||
|
Variance /= Triangles.Num();
|
||||||
|
|
||||||
|
return FMath::Sqrt(Variance);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Step 3: 自适应重网格
|
||||||
|
|
||||||
|
### 6.1 使用UE5的FRemesher
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include "MeshSimplification.h"
|
||||||
|
#include "Remesher.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 局部自适应重网格
|
||||||
|
* 使用FRemesher自动细分和合并三角形
|
||||||
|
*/
|
||||||
|
void RemeshRegion(
|
||||||
|
FDynamicMesh3& Mesh,
|
||||||
|
const TSet<int32>& AffectedTriangles,
|
||||||
|
float TargetEdgeLength)
|
||||||
|
{
|
||||||
|
// 创建Remesher
|
||||||
|
FRemesher Remesher(&Mesh);
|
||||||
|
|
||||||
|
// 配置Remesher
|
||||||
|
Remesher.bEnableSplits = true; // 允许边细分
|
||||||
|
Remesher.bEnableCollapses = true; // 允许边合并
|
||||||
|
Remesher.bEnableFlips = true; // 允许边翻转
|
||||||
|
Remesher.bEnableSmoothing = true; // 允许平滑
|
||||||
|
Remesher.bPreventNormalFlips = true; // 防止法线反转
|
||||||
|
|
||||||
|
// 设置目标边长
|
||||||
|
Remesher.SetTargetEdgeLength(TargetEdgeLength);
|
||||||
|
Remesher.SetMinEdgeLength(TargetEdgeLength * 0.5f);
|
||||||
|
Remesher.SetMaxEdgeLength(TargetEdgeLength * 2.0f);
|
||||||
|
|
||||||
|
// 限制重网格化到受影响区域
|
||||||
|
// 方法1: 使用投影约束
|
||||||
|
for (int32 TID : AffectedTriangles)
|
||||||
|
{
|
||||||
|
Remesher.ProjectionTarget.AddTriangle(TID);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行重网格化(限制迭代次数)
|
||||||
|
int32 MaxIterations = 5;
|
||||||
|
for (int32 i = 0; i < MaxIterations; ++i)
|
||||||
|
{
|
||||||
|
Remesher.BasicRemeshPass();
|
||||||
|
|
||||||
|
// 检查是否收敛
|
||||||
|
float AvgEdgeLength = CalculateAverageEdgeLength(Mesh, AffectedTriangles);
|
||||||
|
if (FMath::Abs(AvgEdgeLength - TargetEdgeLength) < TargetEdgeLength * 0.1f)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算平均边长
|
||||||
|
*/
|
||||||
|
float CalculateAverageEdgeLength(
|
||||||
|
const FDynamicMesh3& Mesh,
|
||||||
|
const TSet<int32>& Triangles)
|
||||||
|
{
|
||||||
|
float TotalLength = 0.0f;
|
||||||
|
int32 Count = 0;
|
||||||
|
|
||||||
|
TSet<int32> ProcessedEdges;
|
||||||
|
|
||||||
|
for (int32 TID : Triangles)
|
||||||
|
{
|
||||||
|
FIndex3i Edges = Mesh.GetTriEdges(TID);
|
||||||
|
|
||||||
|
for (int32 i = 0; i < 3; ++i)
|
||||||
|
{
|
||||||
|
int32 EID = Edges[i];
|
||||||
|
if (!ProcessedEdges.Contains(EID))
|
||||||
|
{
|
||||||
|
ProcessedEdges.Add(EID);
|
||||||
|
|
||||||
|
FIndex2i EdgeVerts = Mesh.GetEdgeV(EID);
|
||||||
|
FVector V0 = Mesh.GetVertex(EdgeVerts.A);
|
||||||
|
FVector V1 = Mesh.GetVertex(EdgeVerts.B);
|
||||||
|
|
||||||
|
TotalLength += FVector::Dist(V0, V1);
|
||||||
|
Count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Count > 0 ? (TotalLength / Count) : 1.0f;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.2 保留顶点属性
|
||||||
|
|
||||||
|
重网格化会改变顶点ID,需要重新插值属性:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
/**
|
||||||
|
* 保存当前顶点属性
|
||||||
|
*/
|
||||||
|
TMap<int32, FVertexAttributes> SaveVertexAttributes(const FDynamicMesh3& Mesh)
|
||||||
|
{
|
||||||
|
TMap<int32, FVertexAttributes> AttributeMap;
|
||||||
|
|
||||||
|
for (int32 VID : Mesh.VertexIndicesItr())
|
||||||
|
{
|
||||||
|
FVertexAttributes Attr;
|
||||||
|
Attr.Position = Mesh.GetVertex(VID);
|
||||||
|
Attr.Color = Mesh.GetVertexColor(VID);
|
||||||
|
// ... 其他属性
|
||||||
|
|
||||||
|
AttributeMap.Add(VID, Attr);
|
||||||
|
}
|
||||||
|
|
||||||
|
return AttributeMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重网格化后插值属性
|
||||||
|
*/
|
||||||
|
void RestoreVertexAttributes(
|
||||||
|
FDynamicMesh3& Mesh,
|
||||||
|
const TMap<int32, FVertexAttributes>& OldAttributes)
|
||||||
|
{
|
||||||
|
// 简化:最近邻插值
|
||||||
|
for (int32 NewVID : Mesh.VertexIndicesItr())
|
||||||
|
{
|
||||||
|
FVector NewPos = Mesh.GetVertex(NewVID);
|
||||||
|
|
||||||
|
// 找最近的旧顶点
|
||||||
|
int32 BestVID = IndexConstants::InvalidID;
|
||||||
|
float MinDist = TNumericLimits<float>::Max();
|
||||||
|
|
||||||
|
for (const auto& Pair : OldAttributes)
|
||||||
|
{
|
||||||
|
float Dist = FVector::Dist(NewPos, Pair.Value.Position);
|
||||||
|
if (Dist < MinDist)
|
||||||
|
{
|
||||||
|
MinDist = Dist;
|
||||||
|
BestVID = Pair.Key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BestVID != IndexConstants::InvalidID)
|
||||||
|
{
|
||||||
|
const FVertexAttributes& OldAttr = OldAttributes[BestVID];
|
||||||
|
Mesh.SetVertexColor(NewVID, OldAttr.Color);
|
||||||
|
// ... 其他属性
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**更优方案**:使用重网格化回调
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// FRemesher支持回调,可以在顶点创建/删除时插值属性
|
||||||
|
Remesher.OnVertexSplit.AddLambda(
|
||||||
|
[&](int32 OriginalVID, int32 NewVID, float SplitAlpha)
|
||||||
|
{
|
||||||
|
// 插值属性
|
||||||
|
float Polish0 = PolishAmount->GetValue(OriginalVID);
|
||||||
|
float Polish1 = /* 邻接顶点的Polish */;
|
||||||
|
PolishAmount->SetValue(NewVID, FMath::Lerp(Polish0, Polish1, SplitAlpha));
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 完整优化流程
|
||||||
|
|
||||||
|
### 7.1 集成到锤击事件
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void ADynamicIronBlock::OnHammerImpact(
|
||||||
|
FVector HitLocation,
|
||||||
|
FVector HitNormal,
|
||||||
|
float ImpactRadius,
|
||||||
|
float ImpactDepth,
|
||||||
|
float ImpactForce)
|
||||||
|
{
|
||||||
|
FVector LocalHitPos = GetTransform().InverseTransformPosition(HitLocation);
|
||||||
|
|
||||||
|
double StartTime = FPlatformTime::Seconds();
|
||||||
|
|
||||||
|
DynamicMesh->EditMesh([&](FDynamicMesh3& Mesh)
|
||||||
|
{
|
||||||
|
// ===== 1. 执行形变 =====
|
||||||
|
ApplyAnisotropicDeformation(Mesh, LocalHitPos, ImpactRadius, ...);
|
||||||
|
|
||||||
|
// ===== 2. 网格质量优化 =====
|
||||||
|
if (bAutoOptimizeMesh)
|
||||||
|
{
|
||||||
|
// 找到受影响的三角形
|
||||||
|
TSet<int32> AffectedTriangles = FindAffectedTriangles(Mesh, LocalHitPos, ImpactRadius);
|
||||||
|
|
||||||
|
if (AffectedTriangles.Num() > 0)
|
||||||
|
{
|
||||||
|
// Step 1: 边翻转
|
||||||
|
FlipEdgesToImproveQuality(Mesh, AffectedTriangles, MinAspectRatio);
|
||||||
|
|
||||||
|
// Step 2: 顶点平滑
|
||||||
|
SmoothVertices(Mesh, AffectedTriangles, 2, 0.5f);
|
||||||
|
|
||||||
|
// Step 3: 自适应重网格(每隔几次执行)
|
||||||
|
static int32 RemeshCounter = 0;
|
||||||
|
if (++RemeshCounter % 5 == 0)
|
||||||
|
{
|
||||||
|
float TargetEdgeLength = CalculateAverageEdgeLength(Mesh, AffectedTriangles);
|
||||||
|
RemeshRegion(Mesh, AffectedTriangles, TargetEdgeLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== 3. 更新顶点颜色(温度、打磨度)=====
|
||||||
|
UpdateVertexColors(Mesh);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ===== 4. 重建BVH =====
|
||||||
|
GenerateBVH();
|
||||||
|
|
||||||
|
double ElapsedTime = (FPlatformTime::Seconds() - StartTime) * 1000.0;
|
||||||
|
//UE_LOG(LogBladeSmith, Log, TEXT("Hammer impact + optimization: %.2f ms"), ElapsedTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 找到受锤击影响的三角形
|
||||||
|
*/
|
||||||
|
TSet<int32> ADynamicIronBlock::FindAffectedTriangles(
|
||||||
|
const FDynamicMesh3& Mesh,
|
||||||
|
FVector Center,
|
||||||
|
float Radius) const
|
||||||
|
{
|
||||||
|
TSet<int32> AffectedTriangles;
|
||||||
|
|
||||||
|
for (int32 TID : Mesh.TriangleIndicesItr())
|
||||||
|
{
|
||||||
|
FVector Centroid = Mesh.GetTriCentroid(TID);
|
||||||
|
if (FVector::Dist(Centroid, Center) < Radius * OptimizationRadius)
|
||||||
|
{
|
||||||
|
AffectedTriangles.Add(TID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return AffectedTriangles;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 性能分析
|
||||||
|
|
||||||
|
### 8.1 计算复杂度
|
||||||
|
|
||||||
|
| 操作 | 时间复杂度 | 空间复杂度 | 实际耗时(20万顶点) |
|
||||||
|
|------|-----------|-----------|---------------------|
|
||||||
|
| 边翻转 | O(E×K) | O(E) | 3-5ms |
|
||||||
|
| 顶点平滑 | O(N×K×V) | O(N) | 2-3ms |
|
||||||
|
| 重网格化 | O(E×log E) | O(E) | 5-10ms |
|
||||||
|
| **总计** | - | - | **10-18ms** |
|
||||||
|
|
||||||
|
其中:
|
||||||
|
- N = 顶点数(20万)
|
||||||
|
- E = 边数(约3万)
|
||||||
|
- K = 迭代次数(2-5)
|
||||||
|
- V = 平均邻接顶点数(6-8)
|
||||||
|
|
||||||
|
### 8.2 优化策略
|
||||||
|
|
||||||
|
#### 策略1: 限制优化区域
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// 仅优化距离锤击中心 < OptimizationRadius × ImpactRadius 的区域
|
||||||
|
float OptimizationRadius = 1.5f; // 仅优化150%影响半径内
|
||||||
|
|
||||||
|
// 效果:优化区域减少60%,耗时减少50%
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 策略2: 降低重网格频率
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// 每5次锤击才重网格一次
|
||||||
|
if (++RemeshCounter % 5 == 0)
|
||||||
|
{
|
||||||
|
RemeshRegion(...);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 效果:平均耗时减少40%
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 策略3: 质量阈值触发
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// 仅当网格质量低于阈值时才优化
|
||||||
|
float AverageQuality = CalculateAverageMeshQuality(Mesh, AffectedTriangles);
|
||||||
|
if (AverageQuality < QualityThreshold)
|
||||||
|
{
|
||||||
|
OptimizeMeshQuality(...);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 效果:仅必要时优化,平均耗时减少70%
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 策略4: 分帧处理
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// 将重网格化分散到多帧
|
||||||
|
if (bPendingRemesh)
|
||||||
|
{
|
||||||
|
RemeshRegion(...); // 执行一小部分
|
||||||
|
if (!IsRemeshComplete())
|
||||||
|
{
|
||||||
|
// 下一帧继续
|
||||||
|
GetWorldTimerManager().SetTimerForNextTick(this, &ThisClass::ContinueRemesh);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 调试与可视化
|
||||||
|
|
||||||
|
### 9.1 可视化退化三角形
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void ADynamicIronBlock::DebugDrawDegenerateTriangles()
|
||||||
|
{
|
||||||
|
if (!DynamicMesh->HasMesh())
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (int32 TID : DynamicMesh->TriangleIndicesItr())
|
||||||
|
{
|
||||||
|
FTriangleQualityMetrics Quality = CalculateTriangleQuality(*DynamicMesh, TID);
|
||||||
|
|
||||||
|
if (IsDegenerateTriangle(Quality))
|
||||||
|
{
|
||||||
|
// 绘制退化三角形(红色)
|
||||||
|
FIndex3i Tri = DynamicMesh->GetTriangle(TID);
|
||||||
|
FVector A = GetActorTransform().TransformPosition(DynamicMesh->GetVertex(Tri.A));
|
||||||
|
FVector B = GetActorTransform().TransformPosition(DynamicMesh->GetVertex(Tri.B));
|
||||||
|
FVector C = GetActorTransform().TransformPosition(DynamicMesh->GetVertex(Tri.C));
|
||||||
|
|
||||||
|
DrawDebugLine(GetWorld(), A, B, FColor::Red, false, 5.0f, 0, 2.0f);
|
||||||
|
DrawDebugLine(GetWorld(), B, C, FColor::Red, false, 5.0f, 0, 2.0f);
|
||||||
|
DrawDebugLine(GetWorld(), C, A, FColor::Red, false, 5.0f, 0, 2.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.2 可视化网格质量热图
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void ADynamicIronBlock::DebugDrawQualityHeatmap()
|
||||||
|
{
|
||||||
|
if (!DynamicMesh->HasMesh())
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (int32 TID : DynamicMesh->TriangleIndicesItr())
|
||||||
|
{
|
||||||
|
if (TID % 10 != 0) // 稀疏采样
|
||||||
|
continue;
|
||||||
|
|
||||||
|
float Quality = CalculateAspectRatio(*DynamicMesh, TID);
|
||||||
|
|
||||||
|
// 颜色映射:蓝色(好)→ 绿色(中)→ 红色(差)
|
||||||
|
FColor HeatColor;
|
||||||
|
if (Quality > 0.5f)
|
||||||
|
{
|
||||||
|
HeatColor = FLinearColor::LerpUsingHSV(FColor::Green, FColor::Blue,
|
||||||
|
(Quality - 0.5f) / 0.5f).ToFColor();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
HeatColor = FLinearColor::LerpUsingHSV(FColor::Red, FColor::Green,
|
||||||
|
Quality / 0.5f).ToFColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
FIndex3i Tri = DynamicMesh->GetTriangle(TID);
|
||||||
|
FVector Center = GetActorTransform().TransformPosition(DynamicMesh->GetTriCentroid(TID));
|
||||||
|
|
||||||
|
DrawDebugPoint(GetWorld(), Center, 5.0f, HeatColor, false, 5.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.3 性能统计
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void ADynamicIronBlock::LogMeshQualityStats()
|
||||||
|
{
|
||||||
|
if (!DynamicMesh->HasMesh())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// 统计质量指标
|
||||||
|
int32 TotalTriangles = DynamicMesh->TriangleCount();
|
||||||
|
int32 DegenerateCount = 0;
|
||||||
|
float TotalAspectRatio = 0.0f;
|
||||||
|
float TotalArea = 0.0f;
|
||||||
|
|
||||||
|
for (int32 TID : DynamicMesh->TriangleIndicesItr())
|
||||||
|
{
|
||||||
|
FTriangleQualityMetrics Quality = CalculateTriangleQuality(*DynamicMesh, TID);
|
||||||
|
|
||||||
|
TotalAspectRatio += Quality.AspectRatio;
|
||||||
|
TotalArea += Quality.Area;
|
||||||
|
|
||||||
|
if (IsDegenerateTriangle(Quality))
|
||||||
|
{
|
||||||
|
DegenerateCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float AvgAspectRatio = TotalAspectRatio / TotalTriangles;
|
||||||
|
float DegenerateRatio = (float)DegenerateCount / TotalTriangles;
|
||||||
|
|
||||||
|
UE_LOG(LogBladeSmith, Warning,
|
||||||
|
TEXT("Mesh Quality: AvgAspectRatio=%.3f, Degenerate=%.1f%%"),
|
||||||
|
AvgAspectRatio,
|
||||||
|
DegenerateRatio * 100.0f
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. 参数调优指南
|
||||||
|
|
||||||
|
### 10.1 不同场景的推荐参数
|
||||||
|
|
||||||
|
#### 锻造平面(大范围形变)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
MinTriangleArea = 0.5f; // 允许较大三角形
|
||||||
|
MaxTriangleArea = 200.0f;
|
||||||
|
MinAspectRatio = 0.15f; // 放宽长宽比要求
|
||||||
|
OptimizationRadius = 2.0f; // 大范围优化
|
||||||
|
SmoothIterations = 2;
|
||||||
|
RemeshFrequency = 10; // 低频重网格
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 锻造边缘(精细形变)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
MinTriangleArea = 0.01f; // 高质量要求
|
||||||
|
MaxTriangleArea = 50.0f;
|
||||||
|
MinAspectRatio = 0.2f; // 严格长宽比
|
||||||
|
OptimizationRadius = 1.2f; // 局部优化
|
||||||
|
SmoothIterations = 3;
|
||||||
|
RemeshFrequency = 5; // 高频重网格
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 性能优先模式
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
MinTriangleArea = 1.0f; // 放宽质量要求
|
||||||
|
MaxTriangleArea = 500.0f;
|
||||||
|
MinAspectRatio = 0.05f; // 允许严重退化
|
||||||
|
OptimizationRadius = 1.0f;
|
||||||
|
SmoothIterations = 1;
|
||||||
|
RemeshFrequency = 20; // 极低频重网格
|
||||||
|
```
|
||||||
|
|
||||||
|
### 10.2 自动调参
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
/**
|
||||||
|
* 根据当前帧率自动调整优化强度
|
||||||
|
*/
|
||||||
|
void AutoAdjustOptimizationSettings()
|
||||||
|
{
|
||||||
|
float CurrentFPS = GetWorld()->GetDeltaSeconds();
|
||||||
|
|
||||||
|
if (CurrentFPS < 30.0f)
|
||||||
|
{
|
||||||
|
// 帧率低:降低优化频率
|
||||||
|
RemeshFrequency = FMath::Min(RemeshFrequency + 1, 20);
|
||||||
|
OptimizationRadius = FMath::Max(OptimizationRadius - 0.1f, 1.0f);
|
||||||
|
}
|
||||||
|
else if (CurrentFPS > 55.0f)
|
||||||
|
{
|
||||||
|
// 帧率高:提高质量
|
||||||
|
RemeshFrequency = FMath::Max(RemeshFrequency - 1, 3);
|
||||||
|
OptimizationRadius = FMath::Min(OptimizationRadius + 0.1f, 2.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. 已知限制与未来改进
|
||||||
|
|
||||||
|
### 11.1 当前限制
|
||||||
|
|
||||||
|
1. **属性插值精度**: 重网格化后顶点属性需要插值,可能有精度损失
|
||||||
|
2. **BVH重建开销**: 每次形变后重建BVH仍需5-10ms
|
||||||
|
3. **极端形变**: 当形变超过50%时,优化效果显著下降
|
||||||
|
4. **拓扑变化**: 无法处理网格撕裂、融合等拓扑变化
|
||||||
|
|
||||||
|
### 11.2 未来改进方向
|
||||||
|
|
||||||
|
1. **增量式BVH更新**: 仅更新受影响区域的BVH节点
|
||||||
|
2. **GPU加速优化**: 将边翻转、平滑放到Compute Shader
|
||||||
|
3. **自适应优化频率**: 基于退化程度动态调整
|
||||||
|
4. **多线程并行**: 独立区域可以并行优化
|
||||||
|
5. **预测性优化**: 在锤击前预测退化区域并提前优化
|
||||||
@@ -0,0 +1,677 @@
|
|||||||
|
# 表面磨损系统细节
|
||||||
|
|
||||||
|
**所属系统**: 高真实度锻造系统
|
||||||
|
**文档类型**: 技术实现细节
|
||||||
|
**最后更新**: 2026-01-22
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 需求概述
|
||||||
|
|
||||||
|
### 1.1 目标效果
|
||||||
|
|
||||||
|
- 锤击后表面从"生锈"变"光亮",露出金属本体
|
||||||
|
- 金属度(Metallic)增加:0.0 → 1.0
|
||||||
|
- 粗糙度(Roughness)降低:0.9 → 0.2
|
||||||
|
- 支持多种表面状态:铁锈、氧化层、酸蚀痕迹、划痕
|
||||||
|
- **关键要求**:表面特征必须随形变一起移动,不能"漂移"
|
||||||
|
|
||||||
|
### 1.2 铁锈的物理本质
|
||||||
|
|
||||||
|
真实铁锈层:
|
||||||
|
- **厚度**: 0.1-2mm(可视严重程度)
|
||||||
|
- **成分**: Fe₂O₃(红锈)、Fe₃O₄(黑锈)
|
||||||
|
- **附着强度**: 低,易被机械摩擦去除
|
||||||
|
- **颜色**: 橙红到深褐
|
||||||
|
- **粗糙度**: 高(微观凹凸不平)
|
||||||
|
|
||||||
|
锻造去锈原理:
|
||||||
|
- 锤击压力 → 摩擦力 → 铁锈层破碎脱落
|
||||||
|
- 塑性形变 → 新鲜金属表面暴露
|
||||||
|
- 高温 → 部分氧化层还原
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 技术方案:基于顶点属性 + 程序化细节
|
||||||
|
|
||||||
|
### 2.1 方案对比回顾
|
||||||
|
|
||||||
|
| 方案 | 分辨率 | 内存 | 性能 | 漂移问题 | 推荐度 |
|
||||||
|
|------|--------|------|------|---------|--------|
|
||||||
|
| **基于顶点** | ★★★☆☆ | ★★★★★ | ★★★★★ | ✅无 | ★★★★★ |
|
||||||
|
| **基于纹理** | ★★★★★ | ★★☆☆☆ | ★★★☆☆ | ⚠️有扭曲 | ★★★☆☆ |
|
||||||
|
| **基于体素** | ★★☆☆☆ | ★★★☆☆ | ★★★★☆ | ✅无 | ★★★☆☆ |
|
||||||
|
|
||||||
|
**选择**: 基于顶点属性(方案A)+ 程序化细节补充
|
||||||
|
|
||||||
|
### 2.2 数据通道分配
|
||||||
|
|
||||||
|
顶点颜色(RGBA)+ UV1通道的完整编码方案:
|
||||||
|
|
||||||
|
| 通道 | 用途 | 范围 | 更新频率 | 说明 |
|
||||||
|
|------|------|------|---------|------|
|
||||||
|
| **R** | 温度可视化 | 0-1 | 每帧 | 从体素采样,黑体辐射颜色 |
|
||||||
|
| **G** | 打磨度(Polish) | 0-1 | 锤击时 | 0=完全生锈,1=完全光亮 |
|
||||||
|
| **B** | 噪声种子(Seed) | 0-1 | 初始化 | 用于程序化噪声,固定不变 |
|
||||||
|
| **A** | 硬度/应变 | 0-1 | 锤击时 | 可视化累积应变 |
|
||||||
|
| **UV1.x** | 初始位置X | 0-1 | 初始化 | 编码3D位置的一部分 |
|
||||||
|
| **UV1.y** | 初始位置Y | 0-1 | 初始化 | 编码3D位置的一部分 |
|
||||||
|
|
||||||
|
额外数据通过自定义顶点属性存储。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 数据结构定义
|
||||||
|
|
||||||
|
### 3.1 扩展顶点属性
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
class ADynamicIronBlock
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
// ===== 顶点自定义属性层 =====
|
||||||
|
TDynamicMeshVertexAttribute<FVector>* InitialPositions; // 初始局部位置
|
||||||
|
TDynamicMeshVertexAttribute<float>* PolishAmount; // 打磨度(0-1)
|
||||||
|
TDynamicMeshVertexAttribute<float>* RustSeed; // 铁锈噪声种子
|
||||||
|
TDynamicMeshVertexAttribute<float>* OxideThickness; // 氧化层厚度
|
||||||
|
|
||||||
|
// ===== 材质参数 =====
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Surface")
|
||||||
|
float RustRoughness = 0.9f; // 铁锈粗糙度
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Surface")
|
||||||
|
float MetalRoughness = 0.2f; // 金属粗糙度
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Surface")
|
||||||
|
FLinearColor RustBaseColor = FLinearColor(0.6f, 0.3f, 0.1f); // 铁锈基色
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Surface")
|
||||||
|
FLinearColor MetalBaseColor = FLinearColor(0.8f, 0.8f, 0.85f); // 金属基色
|
||||||
|
|
||||||
|
public:
|
||||||
|
// 初始化顶点属性
|
||||||
|
void InitializeVertexAttributes();
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 初始化逻辑
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void ADynamicIronBlock::InitializeVertexAttributes()
|
||||||
|
{
|
||||||
|
if (!DynamicMesh->HasMesh())
|
||||||
|
return;
|
||||||
|
|
||||||
|
DynamicMesh->EditMesh([&](FDynamicMesh3& Mesh)
|
||||||
|
{
|
||||||
|
// 确保属性系统已启用
|
||||||
|
if (!Mesh.HasAttributes())
|
||||||
|
{
|
||||||
|
Mesh.EnableAttributes();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建自定义属性层
|
||||||
|
InitialPositions = new TDynamicMeshVertexAttribute<FVector>(&Mesh);
|
||||||
|
InitialPositions->Initialize(FVector::ZeroVector);
|
||||||
|
|
||||||
|
PolishAmount = new TDynamicMeshVertexAttribute<float>(&Mesh);
|
||||||
|
PolishAmount->Initialize(0.0f);
|
||||||
|
|
||||||
|
RustSeed = new TDynamicMeshVertexAttribute<float>(&Mesh);
|
||||||
|
RustSeed->Initialize(0.0f);
|
||||||
|
|
||||||
|
OxideThickness = new TDynamicMeshVertexAttribute<float>(&Mesh);
|
||||||
|
OxideThickness->Initialize(1.0f); // 初始完全氧化
|
||||||
|
|
||||||
|
// 获取或创建UV1层
|
||||||
|
FDynamicMeshUVOverlay* UV1 = Mesh.Attributes()->GetUVLayer(1);
|
||||||
|
if (!UV1)
|
||||||
|
{
|
||||||
|
Mesh.Attributes()->SetNumUVLayers(2);
|
||||||
|
UV1 = Mesh.Attributes()->GetUVLayer(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算包围盒(用于位置编码)
|
||||||
|
FAABB3 Box = Mesh.GetBounds();
|
||||||
|
FVector BoxMin = Box.Min;
|
||||||
|
FVector BoxMax = Box.Max;
|
||||||
|
FVector BoxSize = Box.Max - Box.Min;
|
||||||
|
|
||||||
|
// 初始化每个顶点
|
||||||
|
for (int32 VID : Mesh.VertexIndicesItr())
|
||||||
|
{
|
||||||
|
FVector LocalPos = Mesh.GetVertex(VID);
|
||||||
|
|
||||||
|
// 1. 保存初始位置(不变)
|
||||||
|
InitialPositions->SetValue(VID, LocalPos);
|
||||||
|
|
||||||
|
// 2. 初始化打磨度为0(完全生锈)
|
||||||
|
PolishAmount->SetValue(VID, 0.0f);
|
||||||
|
|
||||||
|
// 3. 分配随机铁锈种子
|
||||||
|
RustSeed->SetValue(VID, FMath::FRand());
|
||||||
|
|
||||||
|
// 4. 编码初始位置到UV1(仅X和Y)
|
||||||
|
FVector2f EncodedPos;
|
||||||
|
EncodedPos.X = (LocalPos.X - BoxMin.X) / (BoxSize.X + 1e-6f);
|
||||||
|
EncodedPos.Y = (LocalPos.Y - BoxMin.Y) / (BoxSize.Y + 1e-6f);
|
||||||
|
UV1->SetElement(VID, EncodedPos);
|
||||||
|
|
||||||
|
// 5. 编码初始位置Z到顶点颜色.B通道(替换随机种子方案)
|
||||||
|
FColor VertexColor = Mesh.GetVertexColor(VID);
|
||||||
|
FLinearColor LinearColor(VertexColor);
|
||||||
|
LinearColor.B = (LocalPos.Z - BoxMin.Z) / (BoxSize.Z + 1e-6f); // Z坐标归一化
|
||||||
|
LinearColor.G = 0.0f; // 初始打磨度
|
||||||
|
Mesh.SetVertexColor(VID, LinearColor.ToFColor());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 更新材质参数
|
||||||
|
if (DynamicMaterialInstance)
|
||||||
|
{
|
||||||
|
DynamicMaterialInstance->SetScalarParameterValue(TEXT("RustRoughness"), RustRoughness);
|
||||||
|
DynamicMaterialInstance->SetScalarParameterValue(TEXT("MetalRoughness"), MetalRoughness);
|
||||||
|
DynamicMaterialInstance->SetVectorParameterValue(TEXT("RustColor"), RustBaseColor);
|
||||||
|
DynamicMaterialInstance->SetVectorParameterValue(TEXT("MetalColor"), MetalBaseColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 锤击更新逻辑
|
||||||
|
|
||||||
|
### 4.1 打磨度更新
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void ADynamicIronBlock::OnHammerImpact(
|
||||||
|
FVector HitLocation,
|
||||||
|
FVector HitNormal,
|
||||||
|
float ImpactRadius,
|
||||||
|
float ImpactForce)
|
||||||
|
{
|
||||||
|
FVector LocalHitPos = GetTransform().InverseTransformPosition(HitLocation);
|
||||||
|
|
||||||
|
DynamicMesh->EditMesh([&](FDynamicMesh3& Mesh)
|
||||||
|
{
|
||||||
|
for (int32 VID : Mesh.VertexIndicesItr())
|
||||||
|
{
|
||||||
|
FVector VertexPos = Mesh.GetVertex(VID);
|
||||||
|
float Distance = FVector::Dist(VertexPos, LocalHitPos);
|
||||||
|
|
||||||
|
// 只更新受击区域
|
||||||
|
if (Distance < ImpactRadius)
|
||||||
|
{
|
||||||
|
// 计算打磨强度
|
||||||
|
float DistanceRatio = Distance / ImpactRadius;
|
||||||
|
float FalloffExponent = 2.0f;
|
||||||
|
float Intensity = FMath::Pow(1.0f - DistanceRatio, FalloffExponent);
|
||||||
|
|
||||||
|
// 打磨强度与力度成正比
|
||||||
|
float PolishIncrease = Intensity * ImpactForce * 0.001f; // 可调系数
|
||||||
|
|
||||||
|
// 累积打磨度(不可逆:只能增加,不能减少)
|
||||||
|
float CurrentPolish = PolishAmount->GetValue(VID);
|
||||||
|
float NewPolish = FMath::Min(1.0f, CurrentPolish + PolishIncrease);
|
||||||
|
|
||||||
|
PolishAmount->SetValue(VID, NewPolish);
|
||||||
|
|
||||||
|
// 同步到顶点颜色.G通道
|
||||||
|
FColor VertexColor = Mesh.GetVertexColor(VID);
|
||||||
|
FLinearColor LinearColor(VertexColor);
|
||||||
|
LinearColor.G = NewPolish;
|
||||||
|
Mesh.SetVertexColor(VID, LinearColor.ToFColor());
|
||||||
|
|
||||||
|
// 铁锈种子(顶点颜色.B)保持不变
|
||||||
|
// 初始位置Z(已在.B中)保持不变
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 氧化层更新(可选)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void ADynamicIronBlock::UpdateOxideLayer(float DeltaTime)
|
||||||
|
{
|
||||||
|
if (!DynamicMesh->HasMesh())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// 简化的氧化模型:
|
||||||
|
// - 高温(>300°C)时氧化加快
|
||||||
|
// - 打磨区域氧化较慢(新鲜表面)
|
||||||
|
|
||||||
|
DynamicMesh->EditMesh([&](FDynamicMesh3& Mesh)
|
||||||
|
{
|
||||||
|
for (int32 VID : Mesh.VertexIndicesItr())
|
||||||
|
{
|
||||||
|
float Temperature = SampleTemperatureAtVertex(Mesh, VID);
|
||||||
|
float Polish = PolishAmount->GetValue(VID);
|
||||||
|
|
||||||
|
// 氧化速率
|
||||||
|
float BaseOxidationRate = 0.01f; // 基础速率
|
||||||
|
float TemperatureFactor = FMath::Clamp((Temperature - 300.0f) / 1000.0f, 0.0f, 1.0f);
|
||||||
|
float PolishProtection = 1.0f - Polish * 0.8f; // 打磨后氧化慢
|
||||||
|
|
||||||
|
float OxidationRate = BaseOxidationRate * (1.0f + TemperatureFactor * 10.0f) * PolishProtection;
|
||||||
|
|
||||||
|
// 更新氧化层厚度
|
||||||
|
float CurrentOxide = OxideThickness->GetValue(VID);
|
||||||
|
float NewOxide = FMath::Min(1.0f, CurrentOxide + OxidationRate * DeltaTime);
|
||||||
|
OxideThickness->SetValue(VID, NewOxide);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 材质实现(UE5材质节点)
|
||||||
|
|
||||||
|
### 5.1 材质图表结构
|
||||||
|
|
||||||
|
```
|
||||||
|
[VertexColor] ────────┐
|
||||||
|
├─> [Polish = VertexColor.g]
|
||||||
|
[TexCoord1] ─────────┘
|
||||||
|
|
||||||
|
[Polish] ──> [Lerp]
|
||||||
|
├─> [RustColor] (Polish=0)
|
||||||
|
└─> [MetalColor] (Polish=1)
|
||||||
|
|
||||||
|
[Polish] ──> [Lerp]
|
||||||
|
├─> [RustRoughness] (0.9)
|
||||||
|
└─> [MetalRoughness] (0.2)
|
||||||
|
|
||||||
|
[Polish] ──> [Lerp]
|
||||||
|
├─> [RustMetallic] (0.0)
|
||||||
|
└─> [MetalMetallic] (1.0)
|
||||||
|
|
||||||
|
[TextureCoordinate1] ──> [重建初始位置]
|
||||||
|
──> [噪声采样]
|
||||||
|
──> [铁锈图案]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 HLSL实现
|
||||||
|
|
||||||
|
创建自定义材质函数 `MF_SurfaceWear`:
|
||||||
|
|
||||||
|
```hlsl
|
||||||
|
// 输入参数
|
||||||
|
float3 WorldPosition; // GetWorldPosition()
|
||||||
|
float3 WorldNormal; // GetWorldNormal()
|
||||||
|
float4 VertexColor; // GetVertexColor()
|
||||||
|
float2 TexCoord1; // GetTexCoord(1)
|
||||||
|
|
||||||
|
// 材质参数(从材质实例传入)
|
||||||
|
float3 RustColor; // 铁锈颜色
|
||||||
|
float3 MetalColor; // 金属颜色
|
||||||
|
float RustRoughness; // 铁锈粗糙度
|
||||||
|
float MetalRoughness; // 金属粗糙度
|
||||||
|
float RustMetallic; // 铁锈金属度(通常0)
|
||||||
|
float MetalMetallic; // 金属金属度(通常1)
|
||||||
|
|
||||||
|
// ===== 主函数 =====
|
||||||
|
|
||||||
|
// 1. 读取打磨度
|
||||||
|
float Polish = VertexColor.g; // 来自G通道
|
||||||
|
|
||||||
|
// 2. 重建初始位置(用于无漂移噪声采样)
|
||||||
|
// 假设包围盒是 [-50, 50] cm
|
||||||
|
float3 BoxMin = float3(-50.0, -50.0, -50.0);
|
||||||
|
float3 BoxMax = float3(50.0, 50.0, 50.0);
|
||||||
|
float3 BoxSize = BoxMax - BoxMin;
|
||||||
|
|
||||||
|
float InitialPosX = TexCoord1.x * BoxSize.x + BoxMin.x;
|
||||||
|
float InitialPosY = TexCoord1.y * BoxSize.y + BoxMin.y;
|
||||||
|
float InitialPosZ = VertexColor.b * BoxSize.z + BoxMin.z;
|
||||||
|
|
||||||
|
float3 InitialLocalPos = float3(InitialPosX, InitialPosY, InitialPosZ);
|
||||||
|
|
||||||
|
// 3. 基于初始位置采样噪声(空间连续,无漂移)
|
||||||
|
float RustSeedNoise = frac(InitialLocalPos.x * 0.1 + InitialLocalPos.y * 0.1 + InitialLocalPos.z * 0.1);
|
||||||
|
float RustMacro = PseudoRandom(RustSeedNoise * 100.0); // 宏观铁锈斑块
|
||||||
|
float RustDetail = Noise(InitialLocalPos * 20.0); // 细节纹理
|
||||||
|
float RustMicro = Noise(InitialLocalPos * 100.0); // 微观细节
|
||||||
|
|
||||||
|
// 组合多层噪声
|
||||||
|
float RustPattern = RustMacro * 0.6 + RustDetail * 0.3 + RustMicro * 0.1;
|
||||||
|
|
||||||
|
// 4. 计算最终颜色、金属度、粗糙度
|
||||||
|
float3 FinalRustColor = RustColor * (0.8 + RustPattern * 0.4); // 铁锈颜色变化
|
||||||
|
float3 FinalBaseColor = lerp(FinalRustColor, MetalColor, Polish);
|
||||||
|
|
||||||
|
float FinalMetallic = lerp(RustMetallic, MetalMetallic, Polish);
|
||||||
|
float FinalRoughness = lerp(RustRoughness, MetalRoughness, Polish);
|
||||||
|
|
||||||
|
// 5. 可选:添加划痕细节(基于初始位置的高频噪声)
|
||||||
|
float ScratchNoise = Noise(InitialLocalPos * 200.0) * 0.1;
|
||||||
|
FinalRoughness += ScratchNoise * (1.0 - Polish); // 划痕在未打磨区域更明显
|
||||||
|
|
||||||
|
// 6. 输出
|
||||||
|
return float4(FinalBaseColor, FinalRoughness, FinalMetallic, 1.0);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.3 材质节点连接
|
||||||
|
|
||||||
|
```
|
||||||
|
材质输出:
|
||||||
|
Base Color ──> FinalBaseColor
|
||||||
|
Roughness ──> FinalRoughness
|
||||||
|
Metallic ──> FinalMetallic
|
||||||
|
Normal ───> [默认法线]
|
||||||
|
Ambient Occlusion ──> [可选]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 无漂移保证机制
|
||||||
|
|
||||||
|
### 6.1 问题示例
|
||||||
|
|
||||||
|
**错误做法(会漂移)**:
|
||||||
|
```hlsl
|
||||||
|
// 基于世界坐标采样噪声
|
||||||
|
float3 WorldPos = GetWorldPosition();
|
||||||
|
float Rust = Noise(WorldPos * 10.0);
|
||||||
|
|
||||||
|
// 问题:
|
||||||
|
// 锻打前: 顶点A在(100, 50, 0),Noise → 0.3(铁锈)
|
||||||
|
// 锻打后: 顶点A移动到(105, 52, 2),Noise → 0.8(变光亮?)
|
||||||
|
// 铁锈"滑走"了!
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.2 正确做法(无漂移)
|
||||||
|
|
||||||
|
**方案1:初始位置采样**
|
||||||
|
```hlsl
|
||||||
|
// 用初始的、不变的位置采样噪声
|
||||||
|
float3 InitialLocalPos = ReconstructInitialPosition(TexCoord1, VertexColor.b);
|
||||||
|
float Rust = Noise(InitialLocalPos * 10.0);
|
||||||
|
|
||||||
|
// 优点:
|
||||||
|
// - 顶点移动后,InitialLocalPos不变
|
||||||
|
// - 铁锈图案"锁定"在材料上
|
||||||
|
// - 空间连续:相邻顶点采样相似的噪声值
|
||||||
|
```
|
||||||
|
|
||||||
|
**方案2:种子采样**
|
||||||
|
```hlsl
|
||||||
|
// 用每个顶点的唯一种子采样
|
||||||
|
float Seed = VertexColor.b; // 0-1范围
|
||||||
|
float Rust = PseudoRandom(Seed * 1000.0);
|
||||||
|
|
||||||
|
// 优点:
|
||||||
|
// - 完全无漂移
|
||||||
|
// - 实现简单
|
||||||
|
|
||||||
|
// 缺点:
|
||||||
|
// - 空间不连续:相邻顶点可能有跳变
|
||||||
|
// - 需要高网格密度掩盖
|
||||||
|
```
|
||||||
|
|
||||||
|
**推荐**:方案1(初始位置采样)
|
||||||
|
|
||||||
|
### 6.3 完整的无漂移采样函数
|
||||||
|
|
||||||
|
```hlsl
|
||||||
|
/**
|
||||||
|
* 从UV1和顶点颜色重建初始位置
|
||||||
|
*/
|
||||||
|
float3 ReconstructInitialPosition(float2 TexCoord1, float EncodedZ)
|
||||||
|
{
|
||||||
|
// 假设包围盒 [-50, 50] cm
|
||||||
|
float3 BoxMin = float3(-50.0, -50.0, -50.0);
|
||||||
|
float3 BoxSize = float3(100.0, 100.0, 100.0);
|
||||||
|
|
||||||
|
float InitialX = TexCoord1.x * BoxSize.x + BoxMin.x;
|
||||||
|
float InitialY = TexCoord1.y * BoxSize.y + BoxMin.y;
|
||||||
|
float InitialZ = EncodedZ * BoxSize.z + BoxMin.z;
|
||||||
|
|
||||||
|
return float3(InitialX, InitialY, InitialZ);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 采样铁锈图案(无漂移版本)
|
||||||
|
*/
|
||||||
|
float3 SampleRustPattern(float2 TexCoord1, float EncodedZ, float3 WorldNormal)
|
||||||
|
{
|
||||||
|
// 重建初始位置
|
||||||
|
float3 InitialLocalPos = ReconstructInitialPosition(TexCoord1, EncodedZ);
|
||||||
|
|
||||||
|
// 多层噪声
|
||||||
|
float Rust1 = Noise(InitialLocalPos * 10.0 + float3(1.0, 2.0, 3.0)); // 低频
|
||||||
|
float Rust2 = Noise(InitialLocalPos * 30.0 + float3(4.0, 5.0, 6.0)); // 中频
|
||||||
|
float Rust3 = Noise(InitialLocalPos * 100.0 + float3(7.0, 8.0, 9.0)); // 高频
|
||||||
|
|
||||||
|
// 组合
|
||||||
|
float RustPattern = Rust1 * 0.5 + Rust2 * 0.3 + Rust3 * 0.2;
|
||||||
|
|
||||||
|
// 基于法线的变化(侧面铁锈更多)
|
||||||
|
float VerticalFactor = abs(WorldNormal.z); // z=0表示侧面
|
||||||
|
RustPattern *= (0.7 + VerticalFactor * 0.3);
|
||||||
|
|
||||||
|
return RustPattern;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 高级特性
|
||||||
|
|
||||||
|
### 7.1 酸蚀痕迹(大马士革钢)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
/**
|
||||||
|
* 标记大马士革层
|
||||||
|
* 在初始化时分配"材料ID"
|
||||||
|
*/
|
||||||
|
void InitializeDamascusLayers(FDynamicMesh3& Mesh)
|
||||||
|
{
|
||||||
|
TDynamicMeshVertexAttribute<int32>* MaterialLayerID =
|
||||||
|
new TDynamicMeshVertexAttribute<int32>(&Mesh);
|
||||||
|
|
||||||
|
for (int32 VID : Mesh.VertexIndicesItr())
|
||||||
|
{
|
||||||
|
FVector Pos = Mesh.GetVertex(VID);
|
||||||
|
|
||||||
|
// 简化的层状分布:基于Z坐标
|
||||||
|
int32 LayerID = (int32)FMath::Floor(Pos.Z / 2.0f) % 2; // 每2cm一层
|
||||||
|
|
||||||
|
MaterialLayerID->SetValue(VID, LayerID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 材质中基于LayerID选择颜色
|
||||||
|
float3 GetDamascusColor(float3 InitialLocalPos, float LayerID)
|
||||||
|
{
|
||||||
|
// 高碳钢层(深色)
|
||||||
|
float3 HighCarbonColor = float3(0.15, 0.1, 0.08);
|
||||||
|
|
||||||
|
// 低碳钢层(浅色)
|
||||||
|
float3 LowCarbonColor = float3(0.75, 0.7, 0.65);
|
||||||
|
|
||||||
|
// 混合过渡
|
||||||
|
float Mix = SmoothLayerPattern(InitialLocalPos);
|
||||||
|
return lerp(HighCarbonColor, LowCarbonColor, Mix);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.2 划痕系统
|
||||||
|
|
||||||
|
```hlsl
|
||||||
|
// 基于初始位置的各向异性噪声(模拟划痕)
|
||||||
|
float3 InitialPos = ReconstructInitialPosition(TexCoord1, VertexColor.b);
|
||||||
|
|
||||||
|
// 方向性划痕(沿着锤击方向)
|
||||||
|
float ScratchDirection = InitialPos.x * 50.0; // X方向划痕
|
||||||
|
float ScratchPattern = Noise(float3(ScratchDirection, InitialPos.y * 20.0, 0.0));
|
||||||
|
|
||||||
|
float3 ScratchColor = lerp(float3(1.0, 1.0, 1.0), BaseColor, ScratchPattern);
|
||||||
|
float ScratchRoughness = Roughness * (1.0 + ScratchPattern * 0.5);
|
||||||
|
|
||||||
|
// 仅在部分区域应用
|
||||||
|
float ScratchMask = SmoothNoise(InitialPos * 15.0);
|
||||||
|
FinalBaseColor = lerp(FinalBaseColor, ScratchColor, ScratchMask * 0.3);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.3 水渍和氧化
|
||||||
|
|
||||||
|
```hlsl
|
||||||
|
// 淬火时的水渍痕迹
|
||||||
|
float WaterStainMask = SampleWaterStainTexture(WorldPos);
|
||||||
|
float3 PatinaColor = float3(0.3, 0.4, 0.5); // 蓝绿色氧化
|
||||||
|
|
||||||
|
// 仅在潮湿区域显示
|
||||||
|
float HumidityFactor = 0.3; // 可调
|
||||||
|
FinalBaseColor = lerp(FinalBaseColor, FinalBaseColor * PatinaColor,
|
||||||
|
WaterStainMask * HumidityFactor);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 性能优化
|
||||||
|
|
||||||
|
### 8.1 材质优化
|
||||||
|
|
||||||
|
```hlsl
|
||||||
|
// 早期退出优化
|
||||||
|
float Polish = VertexColor.g;
|
||||||
|
if (Polish > 0.95)
|
||||||
|
{
|
||||||
|
// 完全打磨:直接返回金属色,跳过噪声计算
|
||||||
|
return MetalColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
// LOD:基于距离减少噪声层数
|
||||||
|
float DistanceToCamera = length(View.WorldPosition);
|
||||||
|
int32 NoiseOctaves = DistanceToCamera < 100.0 ? 3 : 1;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.2 顶点属性压缩
|
||||||
|
|
||||||
|
如果内存紧张,可以压缩属性:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// 打磨度:8位精度足够
|
||||||
|
uint8 CompressedPolish = FMath::RoundToInt(PolishAmount * 255.0f);
|
||||||
|
PolishAmount->SetValue(VID, CompressedPolish / 255.0f);
|
||||||
|
|
||||||
|
// 初始位置:16位量化
|
||||||
|
FVector QuantizedPos(
|
||||||
|
FMath::RoundToInt(LocalPos.X * 100.0f) / 100.0f,
|
||||||
|
FMath::RoundToInt(LocalPos.Y * 100.0f) / 100.0f,
|
||||||
|
FMath::RoundToInt(LocalPos.Z * 100.0f) / 100.0f
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 调试与可视化
|
||||||
|
|
||||||
|
### 9.1 可视化打磨度
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void ADynamicIronBlock::DebugDrawPolishHeatmap()
|
||||||
|
{
|
||||||
|
if (!DynamicMesh->HasMesh())
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (int32 VID : DynamicMesh->VertexIndicesItr())
|
||||||
|
{
|
||||||
|
if (VID % 50 != 0) // 稀疏采样
|
||||||
|
continue;
|
||||||
|
|
||||||
|
float Polish = PolishAmount->GetValue(VID);
|
||||||
|
FVector WorldPos = GetActorTransform().TransformPosition(DynamicMesh->GetVertex(VID));
|
||||||
|
|
||||||
|
// 颜色映射:蓝色(0)→ 绿色(0.5)→ 红色(1.0)
|
||||||
|
FColor DebugColor = FMath::Lerp(
|
||||||
|
FColor::Blue,
|
||||||
|
FColor::Red,
|
||||||
|
Polish
|
||||||
|
);
|
||||||
|
|
||||||
|
DrawDebugPoint(GetWorld(), WorldPos, 5.0f, DebugColor, false, 5.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.2 材质调试模式
|
||||||
|
|
||||||
|
在材质中添加调试开关:
|
||||||
|
|
||||||
|
```hlsl
|
||||||
|
// 材质参数
|
||||||
|
int32 DebugMode; // 0=正常, 1=显示打磨度, 2=显示铁锈图案, 3=显示初始位置
|
||||||
|
|
||||||
|
float3 DebugOutput;
|
||||||
|
switch (DebugMode)
|
||||||
|
{
|
||||||
|
case 1: // 打磨度热图
|
||||||
|
DebugOutput = float3(Polish, 0.0, 1.0 - Polish);
|
||||||
|
break;
|
||||||
|
case 2: // 铁锈图案
|
||||||
|
DebugOutput = RustPattern;
|
||||||
|
break;
|
||||||
|
case 3: // 初始位置可视化
|
||||||
|
DebugOutput = frac(InitialLocalPos * 0.1);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
DebugOutput = FinalBaseColor;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return DebugOutput;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. 参数配置示例
|
||||||
|
|
||||||
|
### 10.1 新铁块(完全生锈)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
RoughRoughness = 0.95f;
|
||||||
|
MetalRoughness = 0.15f;
|
||||||
|
RustColor = FLinearColor(0.7f, 0.35f, 0.15f); // 橙红色
|
||||||
|
MetalColor = FLinearColor(0.75f, 0.75f, 0.8f); // 钢铁色
|
||||||
|
```
|
||||||
|
|
||||||
|
### 10.2 半旧铁块(部分打磨)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
RoughRoughness = 0.85f;
|
||||||
|
MetalRoughness = 0.25f;
|
||||||
|
RustColor = FLinearColor(0.5f, 0.25f, 0.1f); // 深褐色
|
||||||
|
MetalColor = FLinearColor(0.8f, 0.8f, 0.85f);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 10.3 高碳钢(大马士革)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
RoughRoughness = 0.9f;
|
||||||
|
MetalRoughness = 0.2f;
|
||||||
|
RustColor = FLinearColor(0.3f, 0.15f, 0.1f); // 深色层
|
||||||
|
MetalColor = FLinearColor(0.6f, 0.55f, 0.5f); // 浅色层
|
||||||
|
bEnableDamascusLayers = true;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. 已知限制与未来改进
|
||||||
|
|
||||||
|
### 11.1 当前限制
|
||||||
|
|
||||||
|
1. **顶点密度瓶颈**: 20-50万顶点的细节仍不及2048²纹理
|
||||||
|
2. **通道紧张**: 4个顶点颜色通道不够用,需要UV1补充
|
||||||
|
3. **无物理反射**: 打磨效果仅通过粗糙度模拟,无真实光线追踪
|
||||||
|
|
||||||
|
### 11.2 未来改进方向
|
||||||
|
|
||||||
|
1. **混合方案**: 顶点属性(大区域)+ 纹理细节(微观)
|
||||||
|
2. **动态纹理**: 运行时更新高分辨率磨损纹理
|
||||||
|
3. **程序化细节**: 材质中用噪声补充细节
|
||||||
|
4. **多层材质**: 支持油污、涂层、水渍等多层效果
|
||||||
|
5. **法线贴图**: 从打磨度生成动态法线贴图
|
||||||
@@ -0,0 +1,462 @@
|
|||||||
|
# 高真实度锻造系统设计
|
||||||
|
|
||||||
|
**项目**: BladeSmith
|
||||||
|
**日期**: 2026-01-22
|
||||||
|
**目标**: 实现游戏娱乐级的高真实度金属锻造物理系统
|
||||||
|
**性能目标**: 60 FPS @ 锤击操作
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 设计目标
|
||||||
|
|
||||||
|
### 1.1 核心物理现象
|
||||||
|
|
||||||
|
实现以下四大冶金学物理过程:
|
||||||
|
|
||||||
|
1. **加工硬化/应变硬化**
|
||||||
|
- 金属在锻打过程中逐渐变硬
|
||||||
|
- 需要回火(加热)才能继续加工
|
||||||
|
- 影响后续形变的阻力
|
||||||
|
|
||||||
|
2. **应力/应变分布**
|
||||||
|
- 不均匀形变导致内部应力累积
|
||||||
|
- 可能引起开裂、扭曲、回弹
|
||||||
|
- 影响武器最终性能
|
||||||
|
|
||||||
|
3. **淬火硬化(马氏体相变)**
|
||||||
|
- 快速冷却改变晶体结构
|
||||||
|
- 极大提升硬度和脆性
|
||||||
|
- 临界温度控制(800-1200°C)
|
||||||
|
|
||||||
|
4. **微观组织演化(晶粒)**
|
||||||
|
- 晶粒尺寸、形状、取向影响材料特性
|
||||||
|
- 影响强度、韧性、视觉花纹
|
||||||
|
- 支持大马士革钢花纹效果
|
||||||
|
|
||||||
|
### 1.2 设计原则
|
||||||
|
|
||||||
|
- **游戏娱乐级精度**: 简化物理模型,保持真实感,60 FPS性能
|
||||||
|
- **渐进式实现**: 分阶段开发,每阶段可独立运行
|
||||||
|
- **架构扩展性**: 为未来功能预留接口
|
||||||
|
- **YAGNI原则**: 避免过度设计,专注核心功能
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 整体架构方案
|
||||||
|
|
||||||
|
### 2.1 技术方案:混合体素-顶点架构
|
||||||
|
|
||||||
|
**核心思想**: 体素存储"物理场",顶点存储"材料历史"
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────┐
|
||||||
|
│ 锤击输入 │
|
||||||
|
└─────────────────┬───────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
┌────────┴────────┐
|
||||||
|
│ │
|
||||||
|
┌────▼─────┐ ┌─────▼────┐
|
||||||
|
│ 体素层 │ │ 顶点层 │
|
||||||
|
│ (GPU) │ │ (GPU) │
|
||||||
|
└────┬─────┘ └─────┬────┘
|
||||||
|
│ │
|
||||||
|
│ 双向采样通信 │
|
||||||
|
└────────┬────────┘
|
||||||
|
│
|
||||||
|
┌────────▼────────┐
|
||||||
|
│ 渲染 & 显示 │
|
||||||
|
└─────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 体素层(全局物理场)
|
||||||
|
|
||||||
|
**存储内容**:
|
||||||
|
- 温度场(已有)
|
||||||
|
- 平均应变率场(新增)
|
||||||
|
- 冷却速率场(用于淬火判断)
|
||||||
|
- 表面打磨度场(可选)
|
||||||
|
|
||||||
|
**分辨率**: 50×50×50(可配置)
|
||||||
|
**更新方式**: GPU Compute Shader(Ping-Pong双缓冲)
|
||||||
|
|
||||||
|
#### 顶点层(局部材料历史)
|
||||||
|
|
||||||
|
**存储内容**:
|
||||||
|
- 累积塑性应变(记录形变历史)
|
||||||
|
- 当前硬度系数(影响形变阻力)
|
||||||
|
- 晶粒尺寸/取向(简化标量)
|
||||||
|
- 应力张量(简化为标量应力值)
|
||||||
|
- 初始位置(用于纹理固定)
|
||||||
|
- 打磨度(表面状态)
|
||||||
|
|
||||||
|
**数量**: 20-50万顶点(基础),刀刃区域局部加密至100万顶点/cm²
|
||||||
|
|
||||||
|
### 2.2 数据流
|
||||||
|
|
||||||
|
```
|
||||||
|
锤击事件
|
||||||
|
↓
|
||||||
|
1. 形变计算(顶点位移)
|
||||||
|
↓
|
||||||
|
2. 更新顶点应变累积
|
||||||
|
↓
|
||||||
|
3. 从温度体素采样塑性系数
|
||||||
|
↓
|
||||||
|
4. 硬化计算(基于应变+温度)
|
||||||
|
↓
|
||||||
|
5. 更新顶点硬度系数
|
||||||
|
↓
|
||||||
|
6. 更新体素应变率场
|
||||||
|
↓
|
||||||
|
7. 温度/淬火/退火判断
|
||||||
|
↓
|
||||||
|
8. 材质可视化更新
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 关键优势
|
||||||
|
|
||||||
|
| 特性 | 优势 |
|
||||||
|
|------|------|
|
||||||
|
| **性能可控** | 体素粗糙(50³),顶点精细(20-50万),可调节 |
|
||||||
|
| **物理直觉清晰** | 全局场(温度、应力扩散)+ 局部历史(材料记忆) |
|
||||||
|
| **利用现有基础** | 复用GPU Compute、Ping-Pong、RDG基础设施 |
|
||||||
|
| **易于调试** | 体素和顶点数据可独立可视化 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 核心子系统设计
|
||||||
|
|
||||||
|
### 3.1 刀刃形成系统
|
||||||
|
|
||||||
|
**目标**: 解决锻打边缘时材料流动方向性,形成平整锐利的刀刃
|
||||||
|
|
||||||
|
**技术方案**: 各向异性材料流动 + 局部边缘约束
|
||||||
|
|
||||||
|
**详细设计**: 见 [刀刃形成技术细节.md](./2026-01-22-刀刃形成技术细节.md)
|
||||||
|
|
||||||
|
**关键特性**:
|
||||||
|
- 材料优先沿边缘方向流动(非均匀扩散)
|
||||||
|
- 边缘顶点保持共线约束
|
||||||
|
- 自适应网格细分(刀刃区域高密度)
|
||||||
|
|
||||||
|
### 3.2 表面磨损系统
|
||||||
|
|
||||||
|
**目标**: 锤击在表面留下痕迹(铁锈脱落、金属光亮、划痕)
|
||||||
|
|
||||||
|
**技术方案**: 顶点属性存储 + 程序化细节 + 无漂移采样
|
||||||
|
|
||||||
|
**详细设计**: 见 [表面磨损系统细节.md](./2026-01-22-表面磨损系统细节.md)
|
||||||
|
|
||||||
|
**关键特性**:
|
||||||
|
- 顶点存储"打磨度"和"初始位置"
|
||||||
|
- 材质基于初始位置采样噪声(无漂移)
|
||||||
|
- 支持铁锈、氧化层、酸蚀痕迹等多种表面状态
|
||||||
|
- 金属度、粗糙度动态更新
|
||||||
|
|
||||||
|
### 3.3 网格质量维护系统
|
||||||
|
|
||||||
|
**目标**: 防止锻打导致的网格退化(细长三角形、分布不均)
|
||||||
|
|
||||||
|
**技术方案**: 增量式局部优化
|
||||||
|
|
||||||
|
**详细设计**: 见 [网格质量优化细节.md](./2026-01-22-网格质量优化细节.md)
|
||||||
|
|
||||||
|
**关键特性**:
|
||||||
|
- 每次锤击后局部优化(仅受影响区域)
|
||||||
|
- 边翻转 + Laplacian平滑 + 自适应重网格化
|
||||||
|
- 保留顶点属性(打磨度、应变等)
|
||||||
|
- 性能开销: +5-15ms/次
|
||||||
|
|
||||||
|
### 3.4 物理模拟系统
|
||||||
|
|
||||||
|
**目标**: 实现温度-塑性-硬化-相变的完整冶金学循环
|
||||||
|
|
||||||
|
**技术方案**: GPU Compute Shader多物理场耦合
|
||||||
|
|
||||||
|
**详细设计**: 见 [物理模拟架构细节.md](./2026-01-22-物理模拟架构细节.md)
|
||||||
|
|
||||||
|
**关键特性**:
|
||||||
|
- 温度传导与冷却(已有,需扩展)
|
||||||
|
- 加工硬化模型(基于累积应变)
|
||||||
|
- 淬火/退火相变判断
|
||||||
|
- 应力累积与松弛
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 数据结构设计
|
||||||
|
|
||||||
|
### 4.1 扩展体素结构
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
USTRUCT(BlueprintType)
|
||||||
|
struct FTemperatureVoxel
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
// ===== 已有字段 =====
|
||||||
|
UPROPERTY()
|
||||||
|
float Temperature; // 温度(°C)
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
float PlasticityCoefficient; // 塑性系数(0-1)
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
uint8 Flags; // 标志位(bit 0: 表面体素)
|
||||||
|
|
||||||
|
// ===== 新增字段 =====
|
||||||
|
UPROPERTY()
|
||||||
|
float AverageStrainRate; // 平均应变率(记录形变活跃度)
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
float CoolingRate; // 冷却速率(用于淬火判断)
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
uint8 PhaseState; // 相态(0=奥氏体, 1=马氏体, 2=珠光体)
|
||||||
|
|
||||||
|
FTemperatureVoxel()
|
||||||
|
: Temperature(20.0f)
|
||||||
|
, PlasticityCoefficient(0.0f)
|
||||||
|
, Flags(0)
|
||||||
|
, AverageStrainRate(0.0f)
|
||||||
|
, CoolingRate(0.0f)
|
||||||
|
, PhaseState(0)
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 顶点自定义属性
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// 在DynamicIronBlock.h中添加
|
||||||
|
class ADynamicIronBlock : public ADynamicMeshActor
|
||||||
|
{
|
||||||
|
// 顶点属性层(使用FDynamicMesh3的扩展属性系统)
|
||||||
|
TDynamicMeshVertexAttribute<FVector>* InitialPositions; // 初始位置(不变)
|
||||||
|
TDynamicMeshVertexAttribute<float>* AccumulatedStrain; // 累积应变
|
||||||
|
TDynamicMeshVertexAttribute<float>* HardnessCoefficient; // 硬度系数
|
||||||
|
TDynamicMeshVertexAttribute<float>* PolishAmount; // 打磨度
|
||||||
|
TDynamicMeshVertexAttribute<float>* GrainSize; // 晶粒尺寸
|
||||||
|
TDynamicMeshVertexAttribute<float>* StressValue; // 应力值(简化)
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 顶点颜色编码方案
|
||||||
|
|
||||||
|
由于顶点颜色只有4通道(RGBA),需要精心分配:
|
||||||
|
|
||||||
|
| 通道 | 用途 | 范围 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| **R** | 温度可视化 | 0-1 | 从体素采样,映射到颜色 |
|
||||||
|
| **G** | 打磨度 | 0-1 | 0=生锈,1=光亮金属 |
|
||||||
|
| **B** | 噪声种子 | 0-1 | 用于无漂移纹理采样 |
|
||||||
|
| **A** | 硬度/应变 | 0-1 | 可视化应变或硬化程度 |
|
||||||
|
|
||||||
|
额外数据通过UV1通道或自定义属性传递。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 性能预算
|
||||||
|
|
||||||
|
### 5.1 目标帧时间分解(60 FPS = 16.67ms)
|
||||||
|
|
||||||
|
| 系统 | 预算 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| 渲染(UE5基础) | 10ms | 场景渲染、后处理 |
|
||||||
|
| 锤击形变计算 | 2-3ms | 顶点位移、BVH更新 |
|
||||||
|
| 网格质量优化 | 1-2ms | 边翻转、平滑 |
|
||||||
|
| GPU温度计算 | 0.5ms | Compute Shader(异步) |
|
||||||
|
| 顶点属性更新 | 0.5ms | 硬化、应力计算 |
|
||||||
|
| 材质可视化 | 1ms | 顶点颜色更新 |
|
||||||
|
| **总锤击开销** | **≤7ms** | 保持在预算内 |
|
||||||
|
|
||||||
|
### 5.2 内存预算
|
||||||
|
|
||||||
|
| 数据 | 大小 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| 网格顶点(20万) | 13.6 MB | 68字节/顶点 |
|
||||||
|
| 网格三角形(40万) | 4.8 MB | 3×4字节索引 |
|
||||||
|
| BVH结构 | 5 MB | 加速结构 |
|
||||||
|
| 温度体素(50³) | 0.3 MB | 24字节/体素 |
|
||||||
|
| GPU缓冲区 | 2 MB | Ping-Pong温度缓冲 |
|
||||||
|
| **单铁块总计** | **~26 MB** | 可接受 |
|
||||||
|
|
||||||
|
### 5.3 可扩展性
|
||||||
|
|
||||||
|
- **网格密度**: 可配置20万-100万顶点
|
||||||
|
- **体素分辨率**: 可配置30³-80³
|
||||||
|
- **优化开关**: 各子系统可独立禁用
|
||||||
|
- **LOD支持**: 远距离降低网格密度
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 实施计划
|
||||||
|
|
||||||
|
### 阶段1:核心形变增强(2-3天)
|
||||||
|
|
||||||
|
**目标**: 实现刀刃形成和表面磨损的基础版本
|
||||||
|
|
||||||
|
- [ ] 各向异性材料流动(刀刃方向检测)
|
||||||
|
- [ ] 顶点打磨度属性系统
|
||||||
|
- [ ] 初始位置存储(无漂移采样)
|
||||||
|
- [ ] 材质扩展(铁锈/金属混合)
|
||||||
|
- [ ] 简单Laplacian平滑(网格质量维护基础)
|
||||||
|
|
||||||
|
**验收标准**:
|
||||||
|
- 锻打边缘时材料优先沿边缘流动
|
||||||
|
- 锤击后表面铁锈脱落,露出光亮金属
|
||||||
|
- 形变后铁锈图案不"滑动"
|
||||||
|
|
||||||
|
### 阶段2:网格质量系统(1-2天)
|
||||||
|
|
||||||
|
**目标**: 防止网格退化
|
||||||
|
|
||||||
|
- [ ] 三角形质量检测(长宽比、面积)
|
||||||
|
- [ ] 边翻转优化算法
|
||||||
|
- [ ] 增量式Laplacian平滑(带权重)
|
||||||
|
- [ ] 局部自适应重网格化(FRemesher集成)
|
||||||
|
- [ ] 性能优化(仅处理受影响区域)
|
||||||
|
|
||||||
|
**验收标准**:
|
||||||
|
- 连续锻打50次后网格仍保持均匀
|
||||||
|
- 无明显细长三角形(长宽比>0.1)
|
||||||
|
- 锤击响应时间<10ms
|
||||||
|
|
||||||
|
### 阶段3:物理模拟扩展(3-4天)
|
||||||
|
|
||||||
|
**目标**: 实现完整冶金学循环
|
||||||
|
|
||||||
|
- [ ] 扩展体素结构(应变率、冷却率、相态)
|
||||||
|
- [ ] 顶点应变累积系统
|
||||||
|
- [ ] 加工硬化模型(应变→硬度映射)
|
||||||
|
- [ ] 淬火判断逻辑(温度+冷却速率→马氏体转变)
|
||||||
|
- [ ] 退火/回火逻辑(温度+时间→硬度降低)
|
||||||
|
- [ ] GPU Compute Shader扩展(多物理场耦合)
|
||||||
|
|
||||||
|
**验收标准**:
|
||||||
|
- 连续锻打后铁块变硬(形变阻力增加)
|
||||||
|
- 加热后硬度降低,可继续加工
|
||||||
|
- 淬火后硬度显著提升(快速冷却)
|
||||||
|
|
||||||
|
### 阶段4:晶粒与视觉效果(2-3天)
|
||||||
|
|
||||||
|
**目标**: 视觉真实度提升
|
||||||
|
|
||||||
|
- [ ] 晶粒尺寸标量模拟(简化模型)
|
||||||
|
- [ ] 大马士革花纹支持(多层材料ID)
|
||||||
|
- [ ] 应力可视化(顶点颜色.A通道)
|
||||||
|
- [ ] 程序化细节噪声(材质层)
|
||||||
|
- [ ] 开裂判定与可视化(应力阈值)
|
||||||
|
|
||||||
|
**验收标准**:
|
||||||
|
- 高应变区域可视化(颜色变化)
|
||||||
|
- 支持简单的大马士革花纹(层状结构)
|
||||||
|
- 应力过大时出现视觉裂纹(不破坏网格)
|
||||||
|
|
||||||
|
### 阶段5:优化与打磨(1-2天)
|
||||||
|
|
||||||
|
**目标**: 性能优化和细节调整
|
||||||
|
|
||||||
|
- [ ] 性能Profiling(Unreal Insights)
|
||||||
|
- [ ] GPU Compute优化(共享内存、波前优化)
|
||||||
|
- [ ] 参数曝光到Blueprint(方便调试)
|
||||||
|
- [ ] 调试可视化工具(体素场可视化、应力热图)
|
||||||
|
- [ ] 文档和示例场景
|
||||||
|
|
||||||
|
**验收标准**:
|
||||||
|
- 60 FPS @ 连续锤击
|
||||||
|
- 所有参数可在编辑器调节
|
||||||
|
- 提供完整的使用文档
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 风险评估
|
||||||
|
|
||||||
|
### 7.1 技术风险
|
||||||
|
|
||||||
|
| 风险 | 概率 | 影响 | 缓解措施 |
|
||||||
|
|------|------|------|---------|
|
||||||
|
| 网格拓扑动态变化导致属性丢失 | 中 | 高 | 使用UUID或持久化ID追踪顶点 |
|
||||||
|
| GPU Compute性能不足 | 低 | 高 | 降低体素分辨率,异步计算 |
|
||||||
|
| 大马士革花纹实现困难 | 高 | 中 | 阶段4可选功能,简化模型 |
|
||||||
|
| 网格重建开销过大 | 中 | 中 | 限制重建频率,局部优化 |
|
||||||
|
|
||||||
|
### 7.2 设计风险
|
||||||
|
|
||||||
|
| 风险 | 概率 | 影响 | 缓解措施 |
|
||||||
|
|------|------|------|---------|
|
||||||
|
| 过度复杂化,开发周期过长 | 高 | 高 | 严格遵守YAGNI,分阶段验收 |
|
||||||
|
| 物理模型过于简化,真实感不足 | 中 | 中 | 阶段性用户测试,迭代调整 |
|
||||||
|
| 顶点通道不足,数据无法传递 | 低 | 中 | 使用UV1或自定义缓冲区 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 技术债务与未来扩展
|
||||||
|
|
||||||
|
### 8.1 当前简化假设
|
||||||
|
|
||||||
|
1. **应力张量简化为标量**: 真实应力是3×3张量,当前仅用单值
|
||||||
|
2. **晶粒模型极度简化**: 真实晶粒有复杂的3D形状和取向
|
||||||
|
3. **相变模型经验化**: 马氏体转变实际需要CCT曲线
|
||||||
|
4. **无热-力-电-磁耦合**: 忽略了磁性、电阻率等二次效应
|
||||||
|
|
||||||
|
### 8.2 未来可扩展方向
|
||||||
|
|
||||||
|
- **多材料支持**: 当前仅钢,未来可扩展铜、青铜、钛合金
|
||||||
|
- **焊接系统**: 多块金属的连接与材料混合
|
||||||
|
- **锻造工具多样性**: 不同形状的锤头(平锤、圆锤、凿子)
|
||||||
|
- **多人协作锻造**: 网络同步动态网格
|
||||||
|
- **VR支持**: 空间交互与力反馈
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 参考资料
|
||||||
|
|
||||||
|
### 9.1 物理基础
|
||||||
|
|
||||||
|
- 《金属塑性成形原理》(清华大学出版社)
|
||||||
|
- 《材料科学基础》(上海交通大学出版社)
|
||||||
|
- *Metals Handbook: Forging and Forming* (ASM International)
|
||||||
|
|
||||||
|
### 9.2 技术参考
|
||||||
|
|
||||||
|
- Unreal Engine 5 Geometry Scripting文档
|
||||||
|
- *Real-Time Collision Detection* by Christer Ericson (网格优化)
|
||||||
|
- *GPU Gems 3: Chapter 39* - Parallel Prefix Sum (GPU算法)
|
||||||
|
- *Siggraph 2020: Dynamic Mesh Simplification* (网格优化)
|
||||||
|
|
||||||
|
### 9.3 游戏案例
|
||||||
|
|
||||||
|
- *My Summer Car* - 机械装配真实度
|
||||||
|
- *BeamNG.drive* - 动态网格形变
|
||||||
|
- *Dwarf Fortress* - 材料属性系统
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. 附录
|
||||||
|
|
||||||
|
### 10.1 术语表
|
||||||
|
|
||||||
|
| 术语 | 英文 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| 塑性形变 | Plastic Deformation | 永久性的形状改变 |
|
||||||
|
| 加工硬化 | Work Hardening | 形变导致材料变硬的现象 |
|
||||||
|
| 马氏体 | Martensite | 淬火后的硬相组织 |
|
||||||
|
| 奥氏体 | Austenite | 高温下的软相组织 |
|
||||||
|
| 晶粒 | Grain | 金属微观结构的基本单元 |
|
||||||
|
| 应变 | Strain | 材料的相对变形量 |
|
||||||
|
| 应力 | Stress | 材料内部的力 |
|
||||||
|
| BVH | Bounding Volume Hierarchy | 碰撞检测加速结构 |
|
||||||
|
| RDG | Render Dependency Graph | UE5的GPU任务调度系统 |
|
||||||
|
|
||||||
|
### 10.2 文件索引
|
||||||
|
|
||||||
|
- [刀刃形成技术细节.md](./2026-01-22-刀刃形成技术细节.md)
|
||||||
|
- [表面磨损系统细节.md](./2026-01-22-表面磨损系统细节.md)
|
||||||
|
- [网格质量优化细节.md](./2026-01-22-网格质量优化细节.md)
|
||||||
|
- [物理模拟架构细节.md](./2026-01-22-物理模拟架构细节.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**文档版本**: 1.0
|
||||||
|
**最后更新**: 2026-01-22
|
||||||
|
**状态**: 设计完成,待评审
|
||||||
@@ -0,0 +1,530 @@
|
|||||||
|
# 应力累积系统设计
|
||||||
|
|
||||||
|
**项目**: BladeSmith
|
||||||
|
**日期**: 2026-01-23
|
||||||
|
**目标**: 模拟多来源应力累积与物理表现
|
||||||
|
**设计原则**: 真实物理表现优先,辅助功能可选
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 应力来源分类
|
||||||
|
|
||||||
|
### 1.1 塑性形变应力
|
||||||
|
|
||||||
|
**来源**: 锤击导致材料永久形变
|
||||||
|
|
||||||
|
**计算公式**:
|
||||||
|
```
|
||||||
|
Stress_plastic = k_plastic × StrainIncrement × Hardness
|
||||||
|
```
|
||||||
|
|
||||||
|
**参数说明**:
|
||||||
|
- `k_plastic = 10.0`: 塑性应力系数(可调)
|
||||||
|
- `StrainIncrement`: 本次锤击的应变增量
|
||||||
|
- `Hardness`: 当前硬度系数
|
||||||
|
|
||||||
|
**应用范围**: 锤击影响区域内的所有顶点
|
||||||
|
|
||||||
|
### 1.2 热应力
|
||||||
|
|
||||||
|
**来源**: 温度梯度导致内部膨胀不均匀
|
||||||
|
|
||||||
|
**计算公式**:
|
||||||
|
```
|
||||||
|
Stress_thermal = ThermalStressCoeff × CoolingRate × (T_current - 20°C)
|
||||||
|
```
|
||||||
|
|
||||||
|
**参数说明**:
|
||||||
|
- `ThermalStressCoeff = 0.005`: 热应力系数(可调)
|
||||||
|
- `CoolingRate`: 冷却速率(°C/s)
|
||||||
|
- `T_current`: 当前温度(°C)
|
||||||
|
- 20°C: 环境温度
|
||||||
|
|
||||||
|
**应用范围**: 所有体素和顶点
|
||||||
|
|
||||||
|
### 1.3 相变应力
|
||||||
|
|
||||||
|
**来源**: 马氏体转变导致体积膨胀
|
||||||
|
|
||||||
|
**计算公式**:
|
||||||
|
```
|
||||||
|
Stress_phase = PhaseStressCoeff × MartensiteFraction
|
||||||
|
```
|
||||||
|
|
||||||
|
**参数说明**:
|
||||||
|
- `PhaseStressCoeff = 50.0`: 相变应力系数(可调)
|
||||||
|
- `MartensiteFraction`: 马氏体转化比例
|
||||||
|
```
|
||||||
|
MartensiteFraction = clamp(CoolingRate / 80°C/s, 0, 1)
|
||||||
|
```
|
||||||
|
|
||||||
|
**应用范围**: 淬火区域的所有顶点
|
||||||
|
|
||||||
|
### 1.4 残余应力
|
||||||
|
|
||||||
|
**来源**: 锤击时周边材料限制形变
|
||||||
|
|
||||||
|
**计算公式**:
|
||||||
|
```
|
||||||
|
Stress_residual = k_residual × (TargetStrain - ActualStrain)
|
||||||
|
```
|
||||||
|
|
||||||
|
**参数说明**:
|
||||||
|
- `k_residual = 5.0`: 残余应力系数(可调)
|
||||||
|
- `TargetStrain`: 目标应变(基于锤击强度)
|
||||||
|
- `ActualStrain`: 实际应变(受限后)
|
||||||
|
|
||||||
|
**应用范围**: 锤击区域周边的顶点
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 应力累积规则
|
||||||
|
|
||||||
|
### 2.1 总应力计算
|
||||||
|
|
||||||
|
**综合应力公式**:
|
||||||
|
```
|
||||||
|
TotalStress = Stress_plastic + Stress_thermal + Stress_phase + Stress_residual
|
||||||
|
```
|
||||||
|
|
||||||
|
**顶点更新**:
|
||||||
|
```
|
||||||
|
InternalStress += TotalStress × DeltaTime
|
||||||
|
```
|
||||||
|
|
||||||
|
**应力限制**:
|
||||||
|
```
|
||||||
|
InternalStress = clamp(InternalStress, 0.0, MaxStress)
|
||||||
|
```
|
||||||
|
|
||||||
|
其中`MaxStress`为材料极限应力,可配置。
|
||||||
|
|
||||||
|
### 2.2 应力扩散机制
|
||||||
|
|
||||||
|
**目的**: 应力向周围顶点扩散,模拟真实物理行为
|
||||||
|
|
||||||
|
**拉普拉斯平滑**:
|
||||||
|
```
|
||||||
|
Stress_diffused = Stress_original + α × (AverageNeighborStress - Stress_original)
|
||||||
|
```
|
||||||
|
|
||||||
|
**参数说明**:
|
||||||
|
- `α`: 扩散系数(0.1-0.3,可调)
|
||||||
|
- `AverageNeighborStress`: 相邻顶点的平均应力
|
||||||
|
|
||||||
|
**实现方式**:
|
||||||
|
每帧对每个顶点执行扩散,迭代1-2次。
|
||||||
|
|
||||||
|
### 2.3 应力松弛机制
|
||||||
|
|
||||||
|
**温度驱动的松弛**:
|
||||||
|
|
||||||
|
**松弛因子**:
|
||||||
|
```
|
||||||
|
RelaxationFactor =
|
||||||
|
0.99 (T < 100°C, 常温慢松弛)
|
||||||
|
0.90 (200°C ≤ T < 400°C, 回火快松弛)
|
||||||
|
0.85 (T ≥ 400°C, 快速松弛)
|
||||||
|
```
|
||||||
|
|
||||||
|
**每帧更新**:
|
||||||
|
```
|
||||||
|
InternalStress = InternalStress × RelaxationFactor
|
||||||
|
```
|
||||||
|
|
||||||
|
**时间常数公式**:
|
||||||
|
```
|
||||||
|
InternalStress = InternalStress × exp(-t / τ)
|
||||||
|
```
|
||||||
|
|
||||||
|
其中:
|
||||||
|
- `t`: 时间(秒)
|
||||||
|
- `τ`: 松弛时间常数
|
||||||
|
- τ = 60秒(常温)
|
||||||
|
- τ = 10秒(回火温度)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 应力对形变阻力的影响
|
||||||
|
|
||||||
|
### 3.1 形变阻力公式
|
||||||
|
|
||||||
|
**DeformationResistance(形变阻力)**:
|
||||||
|
```
|
||||||
|
DeformationResistance = BaseResistance × HardnessFactor × StressFactor × TemperatureFactor
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 各因子详细公式
|
||||||
|
|
||||||
|
**基础阻力**:
|
||||||
|
```
|
||||||
|
BaseResistance = 1.0
|
||||||
|
```
|
||||||
|
|
||||||
|
**硬度因子**:
|
||||||
|
```
|
||||||
|
HardnessFactor = 1.0 + (Hardness / MaxHardness) × 2.0
|
||||||
|
```
|
||||||
|
|
||||||
|
硬度越高,形变阻力越大(最大3倍阻力)。
|
||||||
|
|
||||||
|
**应力因子**:
|
||||||
|
```
|
||||||
|
StressFactor = 1.0 + (InternalStress / StressLimit) × 1.5
|
||||||
|
```
|
||||||
|
|
||||||
|
应力越高,材料越难形变(最大2.5倍阻力)。
|
||||||
|
|
||||||
|
**温度因子**:
|
||||||
|
```
|
||||||
|
TemperatureFactor =
|
||||||
|
3.0 (T > 800°C, 高温软)
|
||||||
|
1.5 (500°C < T ≤ 800°C, 中温)
|
||||||
|
1.0 (T ≤ 500°C, 常温)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 顶点位移计算
|
||||||
|
|
||||||
|
**锤击位移**:
|
||||||
|
```
|
||||||
|
Displacement = BaseDisplacement / DeformationResistance
|
||||||
|
```
|
||||||
|
|
||||||
|
**应力高的区域**:
|
||||||
|
- 更难锻打
|
||||||
|
- 需要更用力
|
||||||
|
- 或重复锤击
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 应力物理表现
|
||||||
|
|
||||||
|
### 4.1 弯曲/扭曲机制
|
||||||
|
|
||||||
|
**弯曲角度公式**:
|
||||||
|
```
|
||||||
|
BendAngle = (StressLocal - StressAverage) × BendSensitivity
|
||||||
|
BendDirection = CrossProduct(HammerDirection, Normal)
|
||||||
|
```
|
||||||
|
|
||||||
|
**实现步骤**:
|
||||||
|
1. 计算应力梯度:
|
||||||
|
```
|
||||||
|
∇Stress = Stress_neighbor - Stress_center
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 应用顶点位移:
|
||||||
|
```
|
||||||
|
VertexPosition += BendDirection × BendAngle × DistanceFromCenter
|
||||||
|
```
|
||||||
|
|
||||||
|
**扭曲公式**:
|
||||||
|
当材料两侧应力不均匀时:
|
||||||
|
```
|
||||||
|
TwistAngle = (Stress_Left - Stress_Right) × TwistSensitivity
|
||||||
|
VertexPosition = RotateAroundAxis(VertexPosition, Axis, TwistAngle)
|
||||||
|
```
|
||||||
|
|
||||||
|
**视觉效果**:
|
||||||
|
- 刀身沿应力梯度方向自然弯曲
|
||||||
|
- 应力集中区域出现扭曲
|
||||||
|
- 大幅应力差导致明显形变
|
||||||
|
|
||||||
|
### 4.2 裂纹起始判定
|
||||||
|
|
||||||
|
**临界应力阈值**:
|
||||||
|
```
|
||||||
|
CriticalStressThreshold = MaterialStrength × SafetyFactor
|
||||||
|
```
|
||||||
|
|
||||||
|
其中:
|
||||||
|
- `MaterialStrength`: 材料强度(可配置,默认100.0)
|
||||||
|
- `SafetyFactor`: 安全系数(默认1.2)
|
||||||
|
|
||||||
|
**开裂条件**:
|
||||||
|
```
|
||||||
|
if (InternalStress > CriticalStressThreshold)
|
||||||
|
{
|
||||||
|
CreateCrack(Vertex);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**概率开裂**:
|
||||||
|
为避免一次性开裂过多,引入概率:
|
||||||
|
```
|
||||||
|
CrackProbability = (InternalStress / CriticalStressThreshold) - 1.0
|
||||||
|
if (Random() < CrackProbability)
|
||||||
|
{
|
||||||
|
CreateCrack(Vertex);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 裂纹扩展机制
|
||||||
|
|
||||||
|
**裂纹生长方向**:
|
||||||
|
优先沿应力梯度方向扩展(应力最大方向)
|
||||||
|
|
||||||
|
**每帧检查**:
|
||||||
|
```
|
||||||
|
if (Vertex.IsCracked)
|
||||||
|
{
|
||||||
|
// 裂纹扩展概率
|
||||||
|
ExtendProbability = (InternalStress / CriticalStressThreshold) - 0.8
|
||||||
|
|
||||||
|
if (Random() < ExtendProbability)
|
||||||
|
{
|
||||||
|
// 找到应力最高的相邻顶点
|
||||||
|
TargetNeighbor = FindMaxStressNeighbor();
|
||||||
|
CrackTo(TargetNeighbor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**裂纹合并**:
|
||||||
|
当两条裂纹相交时:
|
||||||
|
- 大块材料脱落
|
||||||
|
- 网格拓扑断裂
|
||||||
|
- 几何处理: 删除被包围的顶点和三角形
|
||||||
|
|
||||||
|
### 4.4 裂纹视觉表现
|
||||||
|
|
||||||
|
**几何表现**:
|
||||||
|
1. **裂缝开口**:
|
||||||
|
```
|
||||||
|
CrackVertex.Position += Normal × CrackOpeningWidth
|
||||||
|
```
|
||||||
|
其中`CrackOpeningWidth`为裂缝开口宽度(可配置)
|
||||||
|
|
||||||
|
2. **边缘抖动**:
|
||||||
|
裂纹边缘顶点位置随机抖动,模拟粗糙断裂
|
||||||
|
|
||||||
|
**材质表现**:
|
||||||
|
1. **锈迹**:
|
||||||
|
- 裂纹区域显示RustTexture
|
||||||
|
- 锈迹深度基于裂纹时间
|
||||||
|
|
||||||
|
2. **内部氧化**:
|
||||||
|
- 裂缝深处更暗
|
||||||
|
- 模拟内部氧化
|
||||||
|
|
||||||
|
3. **纹理扰动**:
|
||||||
|
使用顶点法线扰动模拟裂纹纹理
|
||||||
|
|
||||||
|
**物理影响**:
|
||||||
|
- 裂纹区域的硬度系数降为0.3
|
||||||
|
- 裂纹顶点不再参与后续锻打形变(断裂)
|
||||||
|
|
||||||
|
### 4.5 应力释放事件
|
||||||
|
|
||||||
|
**开裂后应力释放**:
|
||||||
|
```
|
||||||
|
if (CrackCreated)
|
||||||
|
{
|
||||||
|
InternalStress = InternalStress × 0.3 // 应力释放
|
||||||
|
// 释放应力用于下次计算
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**回火对应力的影响**:
|
||||||
|
加热至回火温度(200-400°C):
|
||||||
|
- 加速应力松弛(详见松弛机制)
|
||||||
|
- 但如果已经开裂,裂纹无法愈合
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 应力可视化(辅助功能)
|
||||||
|
|
||||||
|
### 5.1 应力扫描模式
|
||||||
|
|
||||||
|
**触发方式**:
|
||||||
|
- 玩家按住特定键(如Alt键)切换到"应力视图"
|
||||||
|
- 或在UI中选择"应力扫描"工具
|
||||||
|
|
||||||
|
**视觉效果**:
|
||||||
|
|
||||||
|
**顶点颜色着色**(彩虹色谱):
|
||||||
|
```
|
||||||
|
// 蓝色:低应力(< 30% StressLimit)
|
||||||
|
if (Stress < 0.3 × StressLimit)
|
||||||
|
{
|
||||||
|
Color = Lerp(Blue, Green, StressRatio);
|
||||||
|
}
|
||||||
|
// 绿色:中等应力(30-60% StressLimit)
|
||||||
|
else if (Stress < 0.6 × StressLimit)
|
||||||
|
{
|
||||||
|
Color = Lerp(Green, Yellow, StressRatio);
|
||||||
|
}
|
||||||
|
// 黄色:高应力(60-90% StressLimit)
|
||||||
|
else if (Stress < 0.9 × StressLimit)
|
||||||
|
{
|
||||||
|
Color = Lerp(Yellow, Red, StressRatio);
|
||||||
|
}
|
||||||
|
// 红色:接近开裂(≥ 90% StressLimit)
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Color = Red;
|
||||||
|
// 叠加闪烁效果
|
||||||
|
Emissive = Flicker();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**应力流线**(可选):
|
||||||
|
- 显示应力传导路径
|
||||||
|
- 从高应力区域流向低应力区域
|
||||||
|
- 帮助玩家理解应力分布
|
||||||
|
|
||||||
|
**UI面板**:
|
||||||
|
- 实时显示最大应力值
|
||||||
|
- 平均应力值
|
||||||
|
- 超阈值顶点数量
|
||||||
|
- 裂纹数量
|
||||||
|
|
||||||
|
### 5.2 退出可视化
|
||||||
|
|
||||||
|
**恢复正常视觉**:
|
||||||
|
释放辅助功能键后,完全恢复正常视觉表现:
|
||||||
|
- 顶点颜色恢复到正常状态(打磨度、回火色等)
|
||||||
|
- 材质参数恢复
|
||||||
|
- 无任何残留效果
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 实现架构
|
||||||
|
|
||||||
|
### 6.1 GPU-CPU分工
|
||||||
|
|
||||||
|
**GPU Compute Shader职责**:
|
||||||
|
- 热应力计算
|
||||||
|
- 应力扩散(简化版)
|
||||||
|
|
||||||
|
**CPU职责**:
|
||||||
|
- 塑性应力计算
|
||||||
|
- 相变应力应用
|
||||||
|
- 残余应力计算
|
||||||
|
- 应力累积
|
||||||
|
- 形变阻力计算
|
||||||
|
- 弯曲/扭曲应用
|
||||||
|
- 裂纹判定与扩展
|
||||||
|
- 应力扫描可视化
|
||||||
|
|
||||||
|
### 6.2 数据结构
|
||||||
|
|
||||||
|
**顶点属性扩展**:
|
||||||
|
```cpp
|
||||||
|
TDynamicMeshVertexAttribute<float>* InternalStress; // 内部应力值
|
||||||
|
TDynamicMeshVertexAttribute<uint8>* CrackState; // 裂纹状态(0=无, 1=有)
|
||||||
|
```
|
||||||
|
|
||||||
|
**裂纹数据结构**:
|
||||||
|
```cpp
|
||||||
|
USTRUCT(BlueprintType)
|
||||||
|
struct FCrackData
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
int32 StartVertex; // 裂纹起始顶点ID
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
int32 EndVertex; // 裂纹结束顶点ID
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
float CrackWidth; // 裂纹宽度
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
float CrackDepth; // 裂纹深度
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
float CreationTime; // 创建时间
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.3 性能优化
|
||||||
|
|
||||||
|
**应力扩散**:
|
||||||
|
- 只在锤击后更新,每帧不需要
|
||||||
|
- 局部扩散,仅受影响区域
|
||||||
|
|
||||||
|
**裂纹判定**:
|
||||||
|
- 仅在高应力区域检查
|
||||||
|
- 使用空间分区加速
|
||||||
|
|
||||||
|
**可视化**:
|
||||||
|
- 应力扫描模式按需启用
|
||||||
|
- 使用简化着色器
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 参数配置
|
||||||
|
|
||||||
|
### 7.1 可调参数(蓝图可配)
|
||||||
|
|
||||||
|
| 参数名 | 默认值 | 范围 | 说明 |
|
||||||
|
|-------|--------|------|------|
|
||||||
|
| `MaxStress` | 150.0 | 100-200 | 最大应力值 |
|
||||||
|
| `k_plastic` | 10.0 | 5-20 | 塑性应力系数 |
|
||||||
|
| `ThermalStressCoeff` | 0.005 | 0.001-0.01 | 热应力系数 |
|
||||||
|
| `PhaseStressCoeff` | 50.0 | 20-100 | 相变应力系数 |
|
||||||
|
| `k_residual` | 5.0 | 2-10 | 残余应力系数 |
|
||||||
|
| `DiffusionAlpha` | 0.2 | 0.1-0.3 | 扩散系数 |
|
||||||
|
| `BendSensitivity` | 0.001 | 0.0005-0.002 | 弯曲敏感度 |
|
||||||
|
| `TwistSensitivity` | 0.0005 | 0.0002-0.001 | 扭曲敏感度 |
|
||||||
|
| `CriticalStressThreshold` | 120.0 | 80-150 | 临界应力 |
|
||||||
|
| `CrackOpeningWidth` | 0.05 | 0.01-0.1 | 裂纹宽度 |
|
||||||
|
| `RelaxationTimeNormal` | 60.0 | 30-120 | 常温松弛时间 |
|
||||||
|
| `RelaxationTimeTemper` | 10.0 | 5-20 | 回火松弛时间 |
|
||||||
|
|
||||||
|
### 7.2 调试工具
|
||||||
|
|
||||||
|
- 应力热图:实时显示应力分布
|
||||||
|
- 裂纹追踪:记录裂纹生成和扩展
|
||||||
|
- 应力时间线:记录应力变化历史
|
||||||
|
- 统计面板:最大应力、平均应力、裂纹数量
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 实施计划
|
||||||
|
|
||||||
|
### 8.1 第一阶段:应力计算(1天)
|
||||||
|
|
||||||
|
- [ ] 四种应力来源实现
|
||||||
|
- [ ] 应力累积规则
|
||||||
|
- [ ] 应力扩散机制
|
||||||
|
- [ ] 应力松弛机制
|
||||||
|
- [ ] 单元测试
|
||||||
|
|
||||||
|
### 8.2 第二阶段:形变阻力(0.5天)
|
||||||
|
|
||||||
|
- [ ] 形变阻力公式
|
||||||
|
- [ ] 顶点位移计算
|
||||||
|
- [ ] 参数调优
|
||||||
|
- [ ] 集成测试
|
||||||
|
|
||||||
|
### 8.3 第三阶段:物理表现(1.5天)
|
||||||
|
|
||||||
|
- [ ] 弯曲/扭曲机制
|
||||||
|
- [ ] 裂纹起始判定
|
||||||
|
- [ ] 裂纹扩展逻辑
|
||||||
|
- [ ] 裂纹视觉表现
|
||||||
|
- [ ] 几何处理(裂纹开口、抖动)
|
||||||
|
- [ ] 材质表现(锈迹、氧化)
|
||||||
|
|
||||||
|
### 8.4 第四阶段:辅助功能(0.5天)
|
||||||
|
|
||||||
|
- [ ] 应力扫描模式
|
||||||
|
- [ ] 顶点颜色着色
|
||||||
|
- [ ] UI面板
|
||||||
|
- [ ] 应力流线(可选)
|
||||||
|
|
||||||
|
### 8.5 第五阶段:集成测试(0.5天)
|
||||||
|
|
||||||
|
- [ ] 完整流程测试
|
||||||
|
- [ ] 性能Profiling
|
||||||
|
- [ ] 参数调优
|
||||||
|
- [ ] 文档更新
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**文档版本**: 1.0
|
||||||
|
**最后更新**: 2026-01-23
|
||||||
|
**状态**: 设计完成,待评审
|
||||||
@@ -0,0 +1,424 @@
|
|||||||
|
# 淬火退火相变与应力疲劳系统总览
|
||||||
|
|
||||||
|
**项目**: BladeSmith
|
||||||
|
**日期**: 2026-01-23
|
||||||
|
**目标**: 实现游戏娱乐级的高真实度冶金学物理系统
|
||||||
|
**性能目标**: 60 FPS @ 锤击操作
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 设计目标
|
||||||
|
|
||||||
|
### 1.1 核心物理现象
|
||||||
|
|
||||||
|
实现以下冶金学物理过程:
|
||||||
|
|
||||||
|
1. **淬火硬化(马氏体相变)**
|
||||||
|
- 快速冷却改变晶体结构
|
||||||
|
- 极大提升硬度和脆性
|
||||||
|
- 温度区间与冷却速率双参数控制
|
||||||
|
|
||||||
|
2. **退火/回火机制**
|
||||||
|
- 重新加热软化材料
|
||||||
|
- 应力松弛,硬度降低
|
||||||
|
- 可逆性支持(可多次热处理)
|
||||||
|
|
||||||
|
3. **应力累积与松弛**
|
||||||
|
- 多来源应力:塑性形变、热应力、相变应力、残余应力
|
||||||
|
- 应力影响形变阻力和开裂风险
|
||||||
|
- 温度驱动的应力松弛机制
|
||||||
|
|
||||||
|
4. **疲劳累积与耐久度**
|
||||||
|
- 反复锻打导致的材料疲劳
|
||||||
|
- 裂纹起始与扩展机制
|
||||||
|
- 刀具使用中的耐久度系统
|
||||||
|
|
||||||
|
### 1.2 设计原则
|
||||||
|
|
||||||
|
- **游戏体验优先**: 简化物理模型,保留核心机制,60 FPS性能
|
||||||
|
- **三阶段可视化**: 淬火过程高辨识度→冷却过渡→真实物理表现
|
||||||
|
- **渐进式实现**: 分阶段开发,每阶段可独立运行
|
||||||
|
- **架构扩展性**: 为未来功能预留接口
|
||||||
|
- **YAGNI原则**: 避免过度设计,专注核心功能
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 整体架构方案
|
||||||
|
|
||||||
|
### 2.1 混合体素-顶点架构
|
||||||
|
|
||||||
|
**核心思想**: 体素存储"物理场",顶点存储"材料历史"
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────┐
|
||||||
|
│ 锤击输入 │
|
||||||
|
└─────────────────┬───────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
┌────────┴────────┐
|
||||||
|
│ │
|
||||||
|
┌────▼─────┐ ┌─────▼────┐
|
||||||
|
│ 体素层 │ │ 顶点层 │
|
||||||
|
│ (GPU) │ │ (GPU) │
|
||||||
|
└────┬─────┘ └─────┬────┘
|
||||||
|
│ │
|
||||||
|
│ 双向采样通信 │
|
||||||
|
└────────┬────────┘
|
||||||
|
│
|
||||||
|
┌────────▼────────┐
|
||||||
|
│ 渲染 & 显示 │
|
||||||
|
└─────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 体素层(全局物理场)
|
||||||
|
|
||||||
|
**存储内容**:
|
||||||
|
- 温度场(已有)
|
||||||
|
- 冷却速率场(用于淬火判定)
|
||||||
|
- 相态标记(0=奥氏体, 1=马氏体, 2=珠光体, 3=开裂)
|
||||||
|
|
||||||
|
**分辨率**: 50×50×50(可配置)
|
||||||
|
**更新方式**: GPU Compute Shader(Ping-Pong双缓冲)
|
||||||
|
|
||||||
|
### 2.3 顶点层(局部材料历史)
|
||||||
|
|
||||||
|
**存储内容**:
|
||||||
|
- 硬度系数(影响形变阻力)
|
||||||
|
- 脆性系数(影响开裂风险)
|
||||||
|
- 内部应力值(标量简化)
|
||||||
|
- 疲劳值(0-1,影响耐久度)
|
||||||
|
- 相态标记(从体素采样)
|
||||||
|
- 回火颜色(用于真实视觉表现)
|
||||||
|
- 氧化层度数(用于氧化层表现)
|
||||||
|
- 打磨度(表面状态)
|
||||||
|
|
||||||
|
**数量**: 20-50万顶点(基础)
|
||||||
|
|
||||||
|
### 2.4 数据流
|
||||||
|
|
||||||
|
```
|
||||||
|
锤击事件
|
||||||
|
↓
|
||||||
|
1. 形变计算(顶点位移)
|
||||||
|
↓
|
||||||
|
2. 应力累积(塑性应力+残余应力)
|
||||||
|
↓
|
||||||
|
3. 从温度体素采样相态和材料参数
|
||||||
|
↓
|
||||||
|
4. 硬度/脆性计算(基于相变+冷却速率)
|
||||||
|
↓
|
||||||
|
5. 更新顶点属性(硬度、应力、疲劳)
|
||||||
|
↓
|
||||||
|
6. 应力扩散与松弛
|
||||||
|
↓
|
||||||
|
7. 疲劳累积与裂纹判定
|
||||||
|
↓
|
||||||
|
8. 材质可视化更新
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 子系统概述
|
||||||
|
|
||||||
|
### 3.1 淬火退火相变系统
|
||||||
|
|
||||||
|
**目标**: 实现基于温度和冷却速率的真实热处理效果
|
||||||
|
|
||||||
|
**核心机制**:
|
||||||
|
- 温度区间判定(800-1200°C)
|
||||||
|
- 冷却速率计算(快速/中速/慢速)
|
||||||
|
- 硬度倍数映射(基于Sigmoid函数)
|
||||||
|
- 脆性系数增加(基于冷却速率)
|
||||||
|
- 相态标记永久保留
|
||||||
|
|
||||||
|
**可视化**:
|
||||||
|
- 三阶段可视化:过程高辨识度→冷却过渡→真实物理表现
|
||||||
|
- 真实回火色系统(基于最后回火温度)
|
||||||
|
- 氧化层表现(基于冷却介质)
|
||||||
|
|
||||||
|
**详细设计**: 见 [淬火退火相变系统设计.md](./2026-01-23-淬火退火相变系统设计.md)
|
||||||
|
|
||||||
|
### 3.2 应力累积系统
|
||||||
|
|
||||||
|
**目标**: 模拟多来源应力累积与物理表现
|
||||||
|
|
||||||
|
**应力来源**:
|
||||||
|
- 塑性形变应力(锤击)
|
||||||
|
- 热应力(温度梯度)
|
||||||
|
- 相变应力(马氏体膨胀)
|
||||||
|
- 残余应力(外部约束)
|
||||||
|
|
||||||
|
**物理表现**:
|
||||||
|
- 弯曲/扭曲(应力梯度导致)
|
||||||
|
- 裂纹起始与扩展(应力超阈值)
|
||||||
|
- 应力松弛(温度驱动)
|
||||||
|
|
||||||
|
**辅助功能**:
|
||||||
|
- 应力扫描模式(玩家主动开启)
|
||||||
|
- 彩虹色谱可视化应力分布
|
||||||
|
|
||||||
|
**详细设计**: 见 [应力累积系统设计.md](./2026-01-23-应力累积系统设计.md)
|
||||||
|
|
||||||
|
### 3.3 疲劳累积与耐久度系统
|
||||||
|
|
||||||
|
**目标**: 模拟材料疲劳和刀具耐久度
|
||||||
|
|
||||||
|
**疲劳机制**:
|
||||||
|
- 累积式疲劳值(每次锻打增加)
|
||||||
|
- 应力和温度影响疲劳率
|
||||||
|
- 疲劳裂纹起始与扩展
|
||||||
|
|
||||||
|
**耐久度系统**:
|
||||||
|
- 基于平均疲劳、裂纹数量、最大应力
|
||||||
|
- 使用中的损伤累积
|
||||||
|
- 回火部分恢复耐久度
|
||||||
|
|
||||||
|
**详细设计**: 见 [疲劳累积与耐久度系统设计.md](./2026-01-23-疲劳累积与耐久度系统设计.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 数据结构设计
|
||||||
|
|
||||||
|
### 4.1 扩展体素结构
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
USTRUCT(BlueprintType)
|
||||||
|
struct FTemperatureVoxel
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
// ===== 已有字段 =====
|
||||||
|
UPROPERTY()
|
||||||
|
float Temperature; // 温度(°C)
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
float PlasticityCoefficient; // 塑性系数(0-1)
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
uint8 Flags; // 标志位(bit 0: 表面体素)
|
||||||
|
|
||||||
|
// ===== 新增字段 =====
|
||||||
|
UPROPERTY()
|
||||||
|
float CoolingRate; // 冷却速率(°C/s)
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
uint8 PhaseState; // 相态(0=奥氏体, 1=马氏体, 2=珠光体, 3=开裂)
|
||||||
|
|
||||||
|
FTemperatureVoxel()
|
||||||
|
: Temperature(20.0f)
|
||||||
|
, PlasticityCoefficient(0.0f)
|
||||||
|
, Flags(0)
|
||||||
|
, CoolingRate(0.0f)
|
||||||
|
, PhaseState(0)
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 顶点自定义属性
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// 在DynamicIronBlock.h中添加
|
||||||
|
class ADynamicIronBlock : public ADynamicMeshActor
|
||||||
|
{
|
||||||
|
// 顶点属性层(使用FDynamicMesh3的扩展属性系统)
|
||||||
|
TDynamicMeshVertexAttribute<float>* HardnessCoefficient; // 硬度系数
|
||||||
|
TDynamicMeshVertexAttribute<float>* Brittleness; // 脆性系数(0-1)
|
||||||
|
TDynamicMeshVertexAttribute<float>* InternalStress; // 内部应力值
|
||||||
|
TDynamicMeshVertexAttribute<float>* FatigueValue; // 疲劳值(0-1)
|
||||||
|
TDynamicMeshVertexAttribute<uint8>* PhaseState; // 相态标记
|
||||||
|
TDynamicMeshVertexAttribute<FVector>* TemperColor; // 回火颜色
|
||||||
|
TDynamicMeshVertexAttribute<float>* OxidationLevel; // 氧化层度数
|
||||||
|
TDynamicMeshVertexAttribute<float>* PolishAmount; // 打磨度
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 顶点颜色编码方案
|
||||||
|
|
||||||
|
由于顶点颜色只有4通道(RGBA),需要精心分配:
|
||||||
|
|
||||||
|
| 通道 | 用途 | 范围 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| **R** | 温度可视化 | 0-1 | 淬火过程:温度映射,冷却后恢复 |
|
||||||
|
| **G** | 打磨度 | 0-1 | 0=生锈,1=光亮金属 |
|
||||||
|
| **B** | 噪声种子 | 0-1 | 用于无漂移纹理采样 |
|
||||||
|
| **A** | 辅助标记 | 0-3 | 0=正常, 1=裂纹区, 2=淬火区(临时) |
|
||||||
|
|
||||||
|
额外数据通过UV1通道或自定义属性传递。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. GPU-CPU分工
|
||||||
|
|
||||||
|
### 5.1 GPU Compute Shader职责
|
||||||
|
|
||||||
|
- 体素层面热力学计算
|
||||||
|
- 温度场更新
|
||||||
|
- 冷却速率计算
|
||||||
|
- 相态判定
|
||||||
|
- 应力扩散(简化版)
|
||||||
|
|
||||||
|
### 5.2 CPU职责
|
||||||
|
|
||||||
|
- 顶点属性更新
|
||||||
|
- 锤击碰撞检测
|
||||||
|
- 形变计算
|
||||||
|
- 硬度/脆性/应力/疲劳公式应用
|
||||||
|
- 开裂判定
|
||||||
|
- 材质可视化更新
|
||||||
|
- 游戏逻辑(辅助功能触发)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 性能预算
|
||||||
|
|
||||||
|
### 6.1 目标帧时间分解(60 FPS = 16.67ms)
|
||||||
|
|
||||||
|
| 系统 | 预算 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| 渲染(UE5基础) | 10ms | 场景渲染、后处理 |
|
||||||
|
| 锤击形变计算 | 2-3ms | 顶点位移、BVH更新 |
|
||||||
|
| 网格质量优化 | 1-2ms | 边翻转、平滑 |
|
||||||
|
| GPU物理计算 | 1ms | Compute Shader(异步) |
|
||||||
|
| 应力/疲劳更新 | 0.5ms | 公式计算 |
|
||||||
|
| 材质可视化 | 0.5ms | 顶点颜色更新 |
|
||||||
|
| **总锤击开销** | **≤7ms** | 保持在预算内 |
|
||||||
|
|
||||||
|
### 6.2 内存预算
|
||||||
|
|
||||||
|
| 数据 | 大小 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| 网格顶点(20万) | 16.8 MB | 84字节/顶点(新增属性) |
|
||||||
|
| 网格三角形(40万) | 4.8 MB | 3×4字节索引 |
|
||||||
|
| BVH结构 | 5 MB | 加速结构 |
|
||||||
|
| 物理场体素(50³) | 0.4 MB | 扩展后的体素结构 |
|
||||||
|
| GPU缓冲区 | 3 MB | Ping-Pong缓冲 |
|
||||||
|
| **单铁块总计** | **~30 MB** | 可接受 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 实施计划
|
||||||
|
|
||||||
|
### 阶段1:淬火退火相变系统(3-4天)
|
||||||
|
|
||||||
|
**目标**: 实现完整的热处理机制
|
||||||
|
|
||||||
|
- [ ] 扩展体素结构(冷却速率、相态)
|
||||||
|
- [ ] 顶点硬度/脆性属性系统
|
||||||
|
- [ ] 温度区间判定逻辑
|
||||||
|
- [ ] 冷却速率计算(滑动平均)
|
||||||
|
- [ ] 硬度/脆性公式实现
|
||||||
|
- [ ] 三阶段可视化系统
|
||||||
|
- [ ] 回火色系统
|
||||||
|
- [ ] 氧化层表现
|
||||||
|
|
||||||
|
**验收标准**:
|
||||||
|
- 淬火后硬度显著提升
|
||||||
|
- 冷却速率影响硬度和脆性
|
||||||
|
- 回火后硬度降低,应力松弛
|
||||||
|
- 视觉表现符合真实物理规律
|
||||||
|
|
||||||
|
### 阶段2:应力累积系统(2-3天)
|
||||||
|
|
||||||
|
**目标**: 实现应力累积与物理表现
|
||||||
|
|
||||||
|
- [ ] 应力来源分类与计算
|
||||||
|
- [ ] 应力累积规则
|
||||||
|
- [ ] 应力扩散机制
|
||||||
|
- [ ] 形变阻力公式
|
||||||
|
- [ ] 弯曲/扭曲机制
|
||||||
|
- [ ] 裂纹起始判定
|
||||||
|
- [ ] 裂纹扩展逻辑
|
||||||
|
- [ ] 应力扫描辅助功能
|
||||||
|
|
||||||
|
**验收标准**:
|
||||||
|
- 多来源应力正确累积
|
||||||
|
- 高应力区域形变困难
|
||||||
|
- 应力超阈值产生裂纹
|
||||||
|
- 裂纹沿应力梯度扩展
|
||||||
|
|
||||||
|
### 阶段3:疲劳累积与耐久度系统(2-3天)
|
||||||
|
|
||||||
|
**目标**: 实现材料疲劳和耐久度机制
|
||||||
|
|
||||||
|
- [ ] 疲劳值累积规则
|
||||||
|
- [ ] 应力和温度影响疲劳率
|
||||||
|
- [ ] 疲劳裂纹起始
|
||||||
|
- [ ] 裂纹扩展概率
|
||||||
|
- [ ] 耐久度计算公式
|
||||||
|
- [ ] 使用中的损伤累积
|
||||||
|
- [ ] 回火恢复机制
|
||||||
|
- [ ] 刀具失效判定
|
||||||
|
|
||||||
|
**验收标准**:
|
||||||
|
- 反复锻打疲劳累积
|
||||||
|
- 疲劳裂纹产生并扩展
|
||||||
|
- 耐久度正确计算
|
||||||
|
- 使用导致耐久度下降
|
||||||
|
- 回火部分恢复耐久度
|
||||||
|
|
||||||
|
### 阶段4:集成与优化(1-2天)
|
||||||
|
|
||||||
|
**目标**: 系统集成与性能优化
|
||||||
|
|
||||||
|
- [ ] 各子系统联动测试
|
||||||
|
- [ ] 参数调优
|
||||||
|
- [ ] 性能Profiling
|
||||||
|
- [ ] 调试工具完善
|
||||||
|
- [ ] 文档更新
|
||||||
|
|
||||||
|
**验收标准**:
|
||||||
|
- 60 FPS @ 连续锤击
|
||||||
|
- 所有功能正常运行
|
||||||
|
- 调试工具完善
|
||||||
|
- 文档齐全
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 风险评估
|
||||||
|
|
||||||
|
### 8.1 技术风险
|
||||||
|
|
||||||
|
| 风险 | 概率 | 影响 | 缓解措施 |
|
||||||
|
|------|------|------|---------|
|
||||||
|
| 公式过于复杂,性能不足 | 中 | 高 | 简化计算,使用GPU并行 |
|
||||||
|
| 参数调优困难 | 中 | 中 | 提供调试工具,分阶段测试 |
|
||||||
|
| 可视化效果不真实 | 中 | 中 | 参考真实照片,迭代优化 |
|
||||||
|
| 网格拓扑变化导致属性丢失 | 中 | 高 | 使用持久化ID追踪顶点 |
|
||||||
|
|
||||||
|
### 8.2 设计风险
|
||||||
|
|
||||||
|
| 风险 | 概率 | 影响 | 缓解措施 |
|
||||||
|
|------|------|------|---------|
|
||||||
|
| 过度复杂化,开发周期过长 | 高 | 高 | 严格遵守YAGNI,分阶段验收 |
|
||||||
|
| 物理模型过于简化,真实感不足 | 中 | 中 | 阶段性用户测试,迭代调整 |
|
||||||
|
| 辅助功能与真实表现混淆 | 低 | 中 | 明确区分,UI提示清晰 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 参考资料
|
||||||
|
|
||||||
|
### 9.1 物理基础
|
||||||
|
|
||||||
|
- 《金属塑性成形原理》(清华大学出版社)
|
||||||
|
- 《材料科学基础》(上海交通大学出版社)
|
||||||
|
- 《金属热处理原理与工艺》(机械工业出版社)
|
||||||
|
- *Metals Handbook: Heat Treating* (ASM International)
|
||||||
|
|
||||||
|
### 9.2 游戏案例
|
||||||
|
|
||||||
|
- *My Summer Car* - 机械装配真实度
|
||||||
|
- *BeamNG.drive* - 动态网格形变
|
||||||
|
- *Dwarf Fortress* - 材料属性系统
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. 文件索引
|
||||||
|
|
||||||
|
- [淬火退火相变系统设计.md](./2026-01-23-淬火退火相变系统设计.md)
|
||||||
|
- [应力累积系统设计.md](./2026-01-23-应力累积系统设计.md)
|
||||||
|
- [疲劳累积与耐久度系统设计.md](./2026-01-23-疲劳累积与耐久度系统设计.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**文档版本**: 1.0
|
||||||
|
**最后更新**: 2026-01-23
|
||||||
|
**状态**: 设计完成,待评审
|
||||||
@@ -0,0 +1,436 @@
|
|||||||
|
# 淬火退火相变系统设计
|
||||||
|
|
||||||
|
**项目**: BladeSmith
|
||||||
|
**日期**: 2026-01-23
|
||||||
|
**目标**: 实现基于温度和冷却速率的真实热处理效果
|
||||||
|
**设计原则**: 游戏体验优先,三阶段可视化
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 核心机制
|
||||||
|
|
||||||
|
### 1.1 温度区间判定
|
||||||
|
|
||||||
|
**三个关键温度阈值**:
|
||||||
|
|
||||||
|
| 阈值名称 | 温度范围 | 效果 |
|
||||||
|
|---------|---------|------|
|
||||||
|
| 临界下限 | < 800°C | 淬火无效,硬度无提升 |
|
||||||
|
| 黄金区间 | 900-1100°C | 最佳淬火,硬度最大提升 |
|
||||||
|
| 临界上限 | > 1200°C | 材料开裂风险,硬度提升低 |
|
||||||
|
|
||||||
|
**温度区间修正系数(TemperatureFactor)**:
|
||||||
|
```
|
||||||
|
TemperatureFactor =
|
||||||
|
3.0 (900°C ≤ T ≤ 1100°C, 黄金区间)
|
||||||
|
1.8 (800°C ≤ T < 900°C 或 1100°C < T ≤ 1200°C, 次优区间)
|
||||||
|
0.5 (T < 800°C, 温度过低)
|
||||||
|
0.2 (T > 1200°C, 温度过高且可能开裂)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.2 冷却速率计算
|
||||||
|
|
||||||
|
**基础公式**:
|
||||||
|
```
|
||||||
|
CoolingRate = |T_current - T_previous| / Δt
|
||||||
|
```
|
||||||
|
|
||||||
|
- `T_current`: 当前帧体素温度
|
||||||
|
- `T_previous`: Δt时间前的体素温度
|
||||||
|
- `Δt`: 采样时间窗口(推荐0.5-1.0秒)
|
||||||
|
|
||||||
|
**滑动平均平滑**:
|
||||||
|
```
|
||||||
|
CoolingRate_smoothed = Σ(CoolingRate_i) / N (i=1 to N, N=10)
|
||||||
|
```
|
||||||
|
|
||||||
|
使用过去10帧的平均值,消除瞬时波动。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 硬度映射系统
|
||||||
|
|
||||||
|
### 2.1 硬度倍数公式
|
||||||
|
|
||||||
|
**基于冷却速率的Sigmoid函数**:
|
||||||
|
```
|
||||||
|
HardnessMultiplier = BaseMultiplier × Sigmoid(CoolingRate)
|
||||||
|
```
|
||||||
|
|
||||||
|
其中:
|
||||||
|
```
|
||||||
|
Sigmoid(CoolingRate) = 1 + 2 / (1 + exp(-k × (CoolingRate - Threshold)))
|
||||||
|
```
|
||||||
|
|
||||||
|
**参数说明**:
|
||||||
|
- `BaseMultiplier = 1.0`: 基准倍数
|
||||||
|
- `Threshold = 35°C/s`: 中点冷却速率
|
||||||
|
- `k = 0.1`: 曲线陡峭度(可调节)
|
||||||
|
|
||||||
|
**冷却速率分类**:
|
||||||
|
- 快速冷却(>50°C/s): 水淬,硬度最高
|
||||||
|
- 中速冷却(20-50°C/s): 油淬,硬度适中
|
||||||
|
- 慢速冷却(<20°C/s): 空冷,硬度低(退火)
|
||||||
|
|
||||||
|
### 2.2 最终硬度计算
|
||||||
|
|
||||||
|
**综合硬度公式**:
|
||||||
|
```
|
||||||
|
FinalHardness = InitialHardness × HardnessMultiplier × TemperatureFactor
|
||||||
|
```
|
||||||
|
|
||||||
|
**硬度系数对形变的影响**:
|
||||||
|
```
|
||||||
|
HardnessFactor = 1.0 + (Hardness / MaxHardness) × 2.0
|
||||||
|
```
|
||||||
|
|
||||||
|
硬度越高,形变阻力越大(最大3倍阻力)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 脆性计算系统
|
||||||
|
|
||||||
|
### 3.1 脆性系数公式
|
||||||
|
|
||||||
|
**基础脆性公式**:
|
||||||
|
```
|
||||||
|
Brittleness = BaseBrittleness + BrittlenessIncrease × CoolingRate_normalized
|
||||||
|
```
|
||||||
|
|
||||||
|
**参数说明**:
|
||||||
|
- `BaseBrittleness = 0.1`: 基准脆性(未经淬火的钢)
|
||||||
|
- `BrittlenessIncrease = 0.6`: 最大脆性增加量
|
||||||
|
- `CoolingRate_normalized`: 归一化冷却速率
|
||||||
|
```
|
||||||
|
CoolingRate_normalized = clamp(CoolingRate / 80°C/s, 0, 1)
|
||||||
|
```
|
||||||
|
|
||||||
|
**脆性对开裂的影响**:
|
||||||
|
脆性系数与开裂概率直接相关,详见应力系统文档。
|
||||||
|
|
||||||
|
### 3.2 脆性可视化阈值
|
||||||
|
|
||||||
|
| 脆性范围 | 颜色 | 含义 |
|
||||||
|
|---------|------|------|
|
||||||
|
| < 0.3 | 绿色 | 韧性良好 |
|
||||||
|
| 0.3 - 0.6 | 黄色 | 中度脆性 |
|
||||||
|
| ≥ 0.6 | 红色 | 高度脆性 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 内部应力公式
|
||||||
|
|
||||||
|
### 4.1 应力分量
|
||||||
|
|
||||||
|
**热应力分量(温度梯度)**:
|
||||||
|
```
|
||||||
|
StressThermal = ThermalStressCoeff × CoolingRate × (T_current - 20°C)
|
||||||
|
```
|
||||||
|
|
||||||
|
- `ThermalStressCoeff = 0.005`: 热应力系数(可调)
|
||||||
|
- `T_current`: 当前温度(°C)
|
||||||
|
- 20°C: 环境温度
|
||||||
|
|
||||||
|
**相变应力分量(马氏体体积膨胀)**:
|
||||||
|
```
|
||||||
|
StressPhase = PhaseStressCoeff × MartensiteFraction
|
||||||
|
```
|
||||||
|
|
||||||
|
- `PhaseStressCoeff = 50.0`: 相变应力系数
|
||||||
|
- `MartensiteFraction`: 马氏体转化比例
|
||||||
|
```
|
||||||
|
MartensiteFraction = clamp(CoolingRate / 80°C/s, 0, 1)
|
||||||
|
```
|
||||||
|
|
||||||
|
**总应力**:
|
||||||
|
```
|
||||||
|
InternalStress = StressThermal + StressPhase
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 应力松弛机制
|
||||||
|
|
||||||
|
**温度驱动的松弛公式**:
|
||||||
|
```
|
||||||
|
RelaxationFactor =
|
||||||
|
0.99 (T < 100°C, 常温慢松弛)
|
||||||
|
0.90 (200°C ≤ T < 400°C, 回火快松弛)
|
||||||
|
0.85 (T ≥ 400°C, 快速松弛)
|
||||||
|
```
|
||||||
|
|
||||||
|
**每帧更新**:
|
||||||
|
```
|
||||||
|
InternalStress = InternalStress × RelaxationFactor
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 相态系统
|
||||||
|
|
||||||
|
### 5.1 相态枚举
|
||||||
|
|
||||||
|
每个体素维护相态标记:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
enum class EPhaseState : uint8
|
||||||
|
{
|
||||||
|
Austenite = 0, // 奥氏体(软)
|
||||||
|
Martensite = 1, // 马氏体(硬)
|
||||||
|
Pearlite = 2, // 珠光体(中)
|
||||||
|
Cracked = 3 // 开裂(永久缺陷)
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 相变判定规则
|
||||||
|
|
||||||
|
**奥氏体→马氏体(淬火)**:
|
||||||
|
```
|
||||||
|
if (Temperature > 800°C && CoolingRate > 20°C/s)
|
||||||
|
{
|
||||||
|
PhaseState = EPhaseState::Martensite;
|
||||||
|
// 硬度和脆性提升
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**奥氏体→珠光体(退火)**:
|
||||||
|
```
|
||||||
|
if (Temperature > 800°C && CoolingRate < 20°C/s)
|
||||||
|
{
|
||||||
|
PhaseState = EPhaseState::Pearlite;
|
||||||
|
// 硬度适中,脆性低
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**重置相态**:
|
||||||
|
```
|
||||||
|
if (Temperature > 800°C && TimeAbove800C > 5.0s)
|
||||||
|
{
|
||||||
|
PhaseState = EPhaseState::Austenite;
|
||||||
|
// 材料回到软态,可重新淬火
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 三阶段可视化设计
|
||||||
|
|
||||||
|
### 6.1 阶段1:淬火过程(临时高辨识度)
|
||||||
|
|
||||||
|
**目的**: 给玩家实时反馈,知道淬火是否成功、哪些区域被覆盖
|
||||||
|
|
||||||
|
**顶点颜色强制覆盖**:
|
||||||
|
```
|
||||||
|
// R通道:温度的互补色
|
||||||
|
R = 1.0 - Temperature / MaxTemperature
|
||||||
|
|
||||||
|
// G通道:冷却速率(快=绿色,慢=红色)
|
||||||
|
G = clamp(1.0 - CoolingRate / 80°C/s, 0, 1)
|
||||||
|
|
||||||
|
// B通道:硬度倍数(暗色=软,亮色=硬)
|
||||||
|
B = (Hardness - MinHardness) / (MaxHardness - MinHardness)
|
||||||
|
|
||||||
|
// A通道:脆性(透明=韧性,不透明=脆)
|
||||||
|
A = Brittleness
|
||||||
|
```
|
||||||
|
|
||||||
|
**材质临时参数**:
|
||||||
|
- 自发光:启用,随温度变化
|
||||||
|
- 金属度:暂时设为1.0(全金属感)
|
||||||
|
- 粗糙度:暂时设为0.0(镜面反光)
|
||||||
|
|
||||||
|
**UI辅助**:
|
||||||
|
- 屏幕边缘显示"淬火进行中"
|
||||||
|
- 顶点统计:"淬火覆盖率:XX%"
|
||||||
|
- 品质评分实时显示
|
||||||
|
|
||||||
|
### 6.2 阶段2:冷却过渡(逐步恢复正常)
|
||||||
|
|
||||||
|
**目的**: 从游戏化反馈平滑过渡到真实物理表现
|
||||||
|
|
||||||
|
**颜色过渡**(0.5-1.0秒):
|
||||||
|
```
|
||||||
|
// 插值因子
|
||||||
|
Alpha = (CurrentTime - QuenchStartTime) / TransitionDuration
|
||||||
|
|
||||||
|
// R通道:温度回归到常温
|
||||||
|
R = Lerp(TemporaryR, 0.0, Alpha)
|
||||||
|
|
||||||
|
// G通道:恢复到打磨度
|
||||||
|
G = Lerp(TemporaryG, PolishAmount, Alpha)
|
||||||
|
|
||||||
|
// B通道:恢复到噪声种子
|
||||||
|
B = Lerp(TemporaryB, NoiseSeed, Alpha)
|
||||||
|
|
||||||
|
// A通道:保留相态标记
|
||||||
|
A = PhaseState // 0=奥氏体, 1=马氏体, 2=珠光体, 3=开裂
|
||||||
|
```
|
||||||
|
|
||||||
|
**材质过渡**:
|
||||||
|
```
|
||||||
|
// 自发光:指数衰减至0
|
||||||
|
Emissive = TemporaryEmissive × exp(-Alpha × 5.0)
|
||||||
|
|
||||||
|
// 金属度/粗糙度:恢复到打磨度驱动
|
||||||
|
Metallic = 0.5 + PolishAmount × 0.5
|
||||||
|
Roughness = 0.6 - PolishAmount × 0.4
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.3 阶段3:最终真实表现(永久保留)
|
||||||
|
|
||||||
|
**目的**: 展示真实的物理热处理效果
|
||||||
|
|
||||||
|
**回火色系统**:
|
||||||
|
|
||||||
|
回火色映射(新增TemperColor顶点属性):
|
||||||
|
|
||||||
|
| 回火温度 | 颜色 | RGB值 |
|
||||||
|
|---------|------|-------|
|
||||||
|
| 20-150°C(无回火) | 银灰色 | (0.8, 0.8, 0.8) |
|
||||||
|
| 150-200°C | 淡黄色 | (0.95, 0.9, 0.7) |
|
||||||
|
| 200-250°C | 金黄/麦秆色 | (0.95, 0.8, 0.4) |
|
||||||
|
| 250-300°C | 棕色 | (0.7, 0.5, 0.3) |
|
||||||
|
| 300-350°C | 紫色 | (0.6, 0.4, 0.7) |
|
||||||
|
| 350-400°C | 蓝色 | (0.4, 0.5, 0.8) |
|
||||||
|
| >400°C | 灰色 | (0.6, 0.6, 0.6) |
|
||||||
|
|
||||||
|
实现方式: 根据最后回火温度设置顶点TemperColor属性。
|
||||||
|
|
||||||
|
**氧化层表现**:
|
||||||
|
|
||||||
|
新增OxidationLevel顶点属性(0-1):
|
||||||
|
|
||||||
|
| 冷却介质 | OxidationLevel | 视觉效果 |
|
||||||
|
|---------|---------------|---------|
|
||||||
|
| 水淬 | 0.8-1.0 | 深色氧化层 |
|
||||||
|
| 油淬 | 0.4-0.6 | 浅棕色氧化层 |
|
||||||
|
| 空冷 | 0.1-0.3 | 轻微氧化 |
|
||||||
|
|
||||||
|
**最终材质组合**:
|
||||||
|
```
|
||||||
|
BaseColor = OxidationColor × (1 - PolishAmount) + TemperColor × PolishAmount
|
||||||
|
```
|
||||||
|
|
||||||
|
其中:
|
||||||
|
- `OxidationColor`: 基于OxidationLevel的深灰色
|
||||||
|
- `TemperColor`: 基于TemperColor的回火色
|
||||||
|
- `PolishAmount`: 打磨度(打磨后露出金属底色)
|
||||||
|
|
||||||
|
**相态的微妙影响**:
|
||||||
|
虽然马氏体组织肉眼不可见,但通过细微色调表现:
|
||||||
|
- 马氏体区域: 略微更冷色调(蓝灰倾向)
|
||||||
|
- 珠光体区域: 略微更暖色调(棕灰倾向)
|
||||||
|
- 奥氏体区域: 中性灰调
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 实现架构
|
||||||
|
|
||||||
|
### 7.1 GPU-CPU分工
|
||||||
|
|
||||||
|
**GPU Compute Shader职责**:
|
||||||
|
- 体素层面热力学计算
|
||||||
|
- 温度场更新
|
||||||
|
- 冷却速率计算(滑动平均)
|
||||||
|
- 相态判定
|
||||||
|
- 热应力计算
|
||||||
|
|
||||||
|
**CPU职责**:
|
||||||
|
- 顶点属性更新
|
||||||
|
- 硬度/脆性公式应用
|
||||||
|
- 相态采样到顶点
|
||||||
|
- 三阶段可视化逻辑
|
||||||
|
- 材质参数更新
|
||||||
|
|
||||||
|
### 7.2 数据流
|
||||||
|
|
||||||
|
```
|
||||||
|
温度更新(GPU)
|
||||||
|
↓
|
||||||
|
冷却速率计算(GPU)
|
||||||
|
↓
|
||||||
|
相态判定(GPU)
|
||||||
|
↓
|
||||||
|
硬度/脆性公式(CPU)
|
||||||
|
↓
|
||||||
|
顶点属性更新(CPU)
|
||||||
|
↓
|
||||||
|
材质可视化(CPU)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.3 性能优化
|
||||||
|
|
||||||
|
**冷却速率计算**:
|
||||||
|
- 使用Ping-Pong双缓冲避免冲突
|
||||||
|
- 滑动平均使用历史缓冲,减少计算量
|
||||||
|
|
||||||
|
**相态判定**:
|
||||||
|
- 只在温度变化时重新计算
|
||||||
|
- 使用标志位避免重复判定
|
||||||
|
|
||||||
|
**可视化**:
|
||||||
|
- 临时效果使用简单着色器
|
||||||
|
- 最终效果复用现有材质系统
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 参数配置
|
||||||
|
|
||||||
|
### 8.1 可调参数(蓝图可配)
|
||||||
|
|
||||||
|
| 参数名 | 默认值 | 范围 | 说明 |
|
||||||
|
|-------|--------|------|------|
|
||||||
|
| `CriticalTempLow` | 800.0°C | 600-900 | 临界下限 |
|
||||||
|
| `CriticalTempHigh` | 1200.0°C | 1100-1400 | 临界上限 |
|
||||||
|
| `GoldTempMin` | 900.0°C | 800-1000 | 黄金区间下限 |
|
||||||
|
| `GoldTempMax` | 1100.0°C | 1000-1200 | 黄金区间上限 |
|
||||||
|
| `CoolingRateThreshold` | 35.0°C/s | 20-50 | Sigmoid中点 |
|
||||||
|
| `SigmoidK` | 0.1 | 0.05-0.2 | 曲线陡峭度 |
|
||||||
|
| `ThermalStressCoeff` | 0.005 | 0.001-0.01 | 热应力系数 |
|
||||||
|
| `PhaseStressCoeff` | 50.0 | 20-100 | 相变应力系数 |
|
||||||
|
| `RelaxationTimeNormal` | 60.0s | 30-120 | 常温松弛时间 |
|
||||||
|
| `RelaxationTimeTemper` | 10.0s | 5-20 | 回火松弛时间 |
|
||||||
|
|
||||||
|
### 8.2 调试工具
|
||||||
|
|
||||||
|
- 体素可视化:显示温度、冷却速率、相态
|
||||||
|
- 顶点属性检查:硬度、脆性、应力值
|
||||||
|
- 时间线:记录淬火-回火过程
|
||||||
|
- 统计面板:覆盖率、平均硬度、最大脆性
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 实施计划
|
||||||
|
|
||||||
|
### 9.1 第一阶段:核心公式(1天)
|
||||||
|
|
||||||
|
- [ ] 冷却速率计算
|
||||||
|
- [ ] 硬度/脆性公式
|
||||||
|
- [ ] 相态判定逻辑
|
||||||
|
- [ ] 单元测试
|
||||||
|
|
||||||
|
### 9.2 第二阶段:顶点属性(1天)
|
||||||
|
|
||||||
|
- [ ] 顶点属性扩展
|
||||||
|
- [ ] 公式应用到顶点
|
||||||
|
- [ ] 材质参数更新
|
||||||
|
- [ ] 基础可视化
|
||||||
|
|
||||||
|
### 9.3 第三阶段:三阶段可视化(1-2天)
|
||||||
|
|
||||||
|
- [ ] 淬火过程临时效果
|
||||||
|
- [ ] 冷却过渡动画
|
||||||
|
- [ ] 回火色系统
|
||||||
|
- [ ] 氧化层表现
|
||||||
|
- [ ] UI辅助工具
|
||||||
|
|
||||||
|
### 9.4 第四阶段:集成测试(0.5天)
|
||||||
|
|
||||||
|
- [ ] 完整流程测试
|
||||||
|
- [ ] 性能Profiling
|
||||||
|
- [ ] 参数调优
|
||||||
|
- [ ] 文档更新
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**文档版本**: 1.0
|
||||||
|
**最后更新**: 2026-01-23
|
||||||
|
**状态**: 设计完成,待评审
|
||||||
@@ -0,0 +1,629 @@
|
|||||||
|
# 疲劳累积与耐久度系统设计
|
||||||
|
|
||||||
|
**项目**: BladeSmith
|
||||||
|
**日期**: 2026-01-23
|
||||||
|
**目标**: 模拟材料疲劳和刀具耐久度
|
||||||
|
**设计原则**: 累积式疲劳,可恢复耐久度
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 疲劳累积机制
|
||||||
|
|
||||||
|
### 1.1 疲劳值定义
|
||||||
|
|
||||||
|
**疲劳值(Fatigue)**:
|
||||||
|
- 范围:0(完好)到 1(完全疲劳/失效)
|
||||||
|
- 每个顶点独立维护
|
||||||
|
- 累积式,不可逆(除非回火)
|
||||||
|
|
||||||
|
### 1.2 疲劳累积规则
|
||||||
|
|
||||||
|
**基础公式**:
|
||||||
|
```
|
||||||
|
Fatigue += BaseFatigueRate × StressFactor × TemperatureFactor × DeltaTime
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.3 各因子详细说明
|
||||||
|
|
||||||
|
**基础疲劳率(BaseFatigueRate)**:
|
||||||
|
|
||||||
|
| 锻打类型 | BaseFatigueRate | 说明 |
|
||||||
|
|---------|---------------|------|
|
||||||
|
| 正常锻打 | 0.001 | 标准锤击 |
|
||||||
|
| 过度锻打 | 0.005 | 形变过大(超过阈值) |
|
||||||
|
| 应力集中 | 0.010 | 高应力区域 |
|
||||||
|
|
||||||
|
判定过度锻打:
|
||||||
|
```
|
||||||
|
if (StrainIncrement > CriticalStrainThreshold)
|
||||||
|
{
|
||||||
|
BaseFatigueRate = 0.005; // 过度锻打
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**应力因子**:
|
||||||
|
```
|
||||||
|
StressFactor = clamp(InternalStress / StressLimit, 1.0, 3.0)
|
||||||
|
```
|
||||||
|
|
||||||
|
应力越高,疲劳累积越快:
|
||||||
|
- 应力 < 1.0 × StressLimit: StressFactor = 1.0
|
||||||
|
- 应力 = 1.5 × StressLimit: StressFactor = 1.5
|
||||||
|
- 应力 ≥ 3.0 × StressLimit: StressFactor = 3.0
|
||||||
|
|
||||||
|
**温度因子**:
|
||||||
|
```
|
||||||
|
TemperatureFactor =
|
||||||
|
0.1 (T > 800°C, 高温疲劳慢)
|
||||||
|
1.0 (常温)
|
||||||
|
2.0 (T < 200°C, 低温疲劳快)
|
||||||
|
```
|
||||||
|
|
||||||
|
高温时材料更"柔软",疲劳较慢;低温时更"脆",疲劳快。
|
||||||
|
|
||||||
|
### 1.4 疲劳限制
|
||||||
|
|
||||||
|
```
|
||||||
|
Fatigue = clamp(Fatigue, 0.0, 1.0)
|
||||||
|
```
|
||||||
|
|
||||||
|
疲劳值达到1.0后不再增加,但可能触发失效。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 疲劳可视化(辅助功能)
|
||||||
|
|
||||||
|
### 2.1 疲劳扫描模式
|
||||||
|
|
||||||
|
**触发方式**:
|
||||||
|
- 玩家按住特定键(如Shift键)切换到"疲劳视图"
|
||||||
|
- 或在UI中选择"疲劳扫描"工具
|
||||||
|
|
||||||
|
### 2.2 视觉效果
|
||||||
|
|
||||||
|
**顶点颜色着色**:
|
||||||
|
```
|
||||||
|
// 蓝色:疲劳值 < 0.3
|
||||||
|
if (Fatigue < 0.3)
|
||||||
|
{
|
||||||
|
Color = Lerp(Blue, Green, Fatigue / 0.3);
|
||||||
|
}
|
||||||
|
// 绿色:疲劳值 0.3-0.7
|
||||||
|
else if (Fatigue < 0.7)
|
||||||
|
{
|
||||||
|
Color = Lerp(Green, Yellow, (Fatigue - 0.3) / 0.4);
|
||||||
|
}
|
||||||
|
// 黄色:疲劳值 0.7-0.9
|
||||||
|
else if (Fatigue < 0.9)
|
||||||
|
{
|
||||||
|
Color = Lerp(Yellow, Orange, (Fatigue - 0.7) / 0.2);
|
||||||
|
}
|
||||||
|
// 红色:疲劳值 > 0.9(危险区)
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Color = Red;
|
||||||
|
// 叠加脉冲效果
|
||||||
|
Pulse = sin(Time × 5.0) × 0.5 + 0.5;
|
||||||
|
Emissive = Pulse × Red;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 UI面板
|
||||||
|
|
||||||
|
- 平均疲劳值
|
||||||
|
- 最大疲劳值
|
||||||
|
- 危险区域数量(Fatigue > 0.9)
|
||||||
|
- 预估寿命
|
||||||
|
|
||||||
|
### 2.4 退出可视化
|
||||||
|
|
||||||
|
释放辅助功能键后,完全恢复正常视觉表现。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 疲劳裂纹系统
|
||||||
|
|
||||||
|
### 3.1 疲劳裂纹起始
|
||||||
|
|
||||||
|
**触发条件**:
|
||||||
|
```
|
||||||
|
if (Fatigue > 0.9 && InternalStress > 0.5 × StressLimit)
|
||||||
|
{
|
||||||
|
// 触发疲劳开裂
|
||||||
|
if (Random() < CrackProbability)
|
||||||
|
{
|
||||||
|
CreateFatigueCrack(Vertex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**疲劳开裂概率**:
|
||||||
|
```
|
||||||
|
CrackProbability = (Fatigue - 0.9) × 10.0 // 最大1.0
|
||||||
|
```
|
||||||
|
|
||||||
|
疲劳值越高,开裂概率越大:
|
||||||
|
- Fatigue = 0.9: CrackProbability = 0.0
|
||||||
|
- Fatigue = 0.95: CrackProbability = 0.5
|
||||||
|
- Fatigue = 1.0: CrackProbability = 1.0
|
||||||
|
|
||||||
|
### 3.2 疲劳裂纹扩展
|
||||||
|
|
||||||
|
**每次锻打检查**:
|
||||||
|
```
|
||||||
|
if (Vertex.IsCracked && CrackType == Fatigue)
|
||||||
|
{
|
||||||
|
// 裂纹扩展概率
|
||||||
|
ExtendProbability = (Fatigue - 0.7) × 2.0
|
||||||
|
|
||||||
|
if (Random() < ExtendProbability)
|
||||||
|
{
|
||||||
|
// 找到疲劳值最高的相邻顶点
|
||||||
|
TargetNeighbor = FindMaxFatigueNeighbor();
|
||||||
|
CrackTo(TargetNeighbor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**疲劳裂纹特点**:
|
||||||
|
- 沿高疲劳值方向扩展
|
||||||
|
- 扩展速度与疲劳值成正比
|
||||||
|
- 可能产生多条细小裂纹(应力裂纹通常是单条)
|
||||||
|
|
||||||
|
### 3.3 疲劳裂纹视觉表现
|
||||||
|
|
||||||
|
**几何表现**:
|
||||||
|
1. **细小裂缝**:
|
||||||
|
- 开口宽度较小(0.01-0.02)
|
||||||
|
- 多条裂纹可能交织
|
||||||
|
|
||||||
|
2. **边缘锯齿**:
|
||||||
|
- 裂纹边缘不光滑
|
||||||
|
- 顶点位置随机抖动
|
||||||
|
|
||||||
|
**材质表现**:
|
||||||
|
1. **表面氧化**:
|
||||||
|
- 显示轻微锈迹
|
||||||
|
- 氧化程度与时间相关
|
||||||
|
|
||||||
|
2. **内部损伤**:
|
||||||
|
- 深处 darker
|
||||||
|
- 模拟材料损伤
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 耐久度系统
|
||||||
|
|
||||||
|
### 4.1 耐久度定义
|
||||||
|
|
||||||
|
**耐久度(Durability)**:
|
||||||
|
- 范围:0(损坏)到 1.0(完好)
|
||||||
|
- 基于整个刀具计算
|
||||||
|
- 影响刀具使用效果
|
||||||
|
|
||||||
|
### 4.2 耐久度计算公式
|
||||||
|
|
||||||
|
**综合耐久度**:
|
||||||
|
```
|
||||||
|
Durability = 1.0 - (AverageFatigue + CrackPenalty + StressPenalty)
|
||||||
|
```
|
||||||
|
|
||||||
|
**各分量说明**:
|
||||||
|
|
||||||
|
**平均疲劳**:
|
||||||
|
```
|
||||||
|
AverageFatigue = Σ(Fatigue_i) / VertexCount
|
||||||
|
```
|
||||||
|
|
||||||
|
**裂纹惩罚**:
|
||||||
|
```
|
||||||
|
CrackPenalty = CrackCount × 0.1 // 每条裂纹扣10%耐久
|
||||||
|
```
|
||||||
|
|
||||||
|
裂纹包括:
|
||||||
|
- 应力裂纹(由应力超阈值产生)
|
||||||
|
- 疲劳裂纹(由疲劳累积产生)
|
||||||
|
|
||||||
|
**应力惩罚**:
|
||||||
|
```
|
||||||
|
StressPenalty = (MaxStress / StressLimit) × 0.2
|
||||||
|
```
|
||||||
|
|
||||||
|
高应力区域最高扣除20%耐久度。
|
||||||
|
|
||||||
|
### 4.3 耐久度限制
|
||||||
|
|
||||||
|
```
|
||||||
|
Durability = clamp(Durability, 0.0, 1.0)
|
||||||
|
```
|
||||||
|
|
||||||
|
耐久度降到0后刀具可能失效。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 使用中的损伤累积
|
||||||
|
|
||||||
|
### 5.1 刀具使用场景
|
||||||
|
|
||||||
|
**切割/砍击**:
|
||||||
|
- 每次使用造成冲击
|
||||||
|
- 冲击点附近疲劳增加
|
||||||
|
|
||||||
|
**抛光/打磨**:
|
||||||
|
- 减少疲劳(回火效果)
|
||||||
|
- 恢复部分耐久度
|
||||||
|
|
||||||
|
### 5.2 冲击疲劳计算
|
||||||
|
|
||||||
|
**冲击应力**:
|
||||||
|
```
|
||||||
|
ImpactStress = ImpactForce × MaterialStiffness × (1 - ImpactAngle)
|
||||||
|
```
|
||||||
|
|
||||||
|
其中:
|
||||||
|
- `ImpactForce`: 冲击力(来自物理引擎)
|
||||||
|
- `MaterialStiffness`: 材料刚度
|
||||||
|
- `ImpactAngle`: 冲击角度(0-1,垂直=1.0)
|
||||||
|
|
||||||
|
**疲劳增量**:
|
||||||
|
```
|
||||||
|
Fatigue += ImpactStress / Durability × FatigueRate
|
||||||
|
```
|
||||||
|
|
||||||
|
其中:
|
||||||
|
- `FatigueRate`: 疲劳率(可配置,默认0.0001)
|
||||||
|
|
||||||
|
**局部更新**:
|
||||||
|
冲击影响半径内的顶点更新疲劳值。
|
||||||
|
|
||||||
|
### 5.3 耐久度实时更新
|
||||||
|
|
||||||
|
**每次使用后**:
|
||||||
|
```
|
||||||
|
// 重新计算耐久度
|
||||||
|
Durability = CalculateDurability();
|
||||||
|
|
||||||
|
// 更新UI
|
||||||
|
UpdateDurabilityUI();
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 耐久度恢复机制
|
||||||
|
|
||||||
|
### 6.1 回火恢复
|
||||||
|
|
||||||
|
**加热至回火温度**(200-400°C):
|
||||||
|
|
||||||
|
**疲劳恢复**:
|
||||||
|
```
|
||||||
|
if (Temperature > 200°C && Temperature < 400°C)
|
||||||
|
{
|
||||||
|
Fatigue -= 0.1 × DeltaTime; // 慢慢消除疲劳
|
||||||
|
Fatigue = clamp(Fatigue, 0.0, 1.0);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**耐久度恢复**:
|
||||||
|
```
|
||||||
|
if (Temperature > 200°C && Temperature < 400°C)
|
||||||
|
{
|
||||||
|
Durability += 0.05 × DeltaTime;
|
||||||
|
Durability = clamp(Durability, 0.0, 1.0);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**注意事项**:
|
||||||
|
- 疲劳可部分恢复
|
||||||
|
- 但裂纹无法愈合
|
||||||
|
- 裂纹惩罚永久存在
|
||||||
|
|
||||||
|
### 6.2 打磨恢复
|
||||||
|
|
||||||
|
**打磨操作**:
|
||||||
|
|
||||||
|
**表面损伤修复**:
|
||||||
|
- 打磨可去除表面疲劳痕迹
|
||||||
|
- 减少视觉上的"老伤"
|
||||||
|
|
||||||
|
**疲劳影响**:
|
||||||
|
```
|
||||||
|
if (PolishAmount > OldPolishAmount)
|
||||||
|
{
|
||||||
|
Fatigue -= (PolishAmount - OldPolishAmount) × 0.5;
|
||||||
|
Fatigue = clamp(Fatigue, 0.0, 1.0);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**耐久度影响**:
|
||||||
|
- 打磨不恢复耐久度
|
||||||
|
- 但可以改善外观
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 刀具失效判定
|
||||||
|
|
||||||
|
### 7.1 失效条件
|
||||||
|
|
||||||
|
**条件1: 耐久度耗尽**:
|
||||||
|
```
|
||||||
|
if (Durability < 0.0)
|
||||||
|
{
|
||||||
|
BreakWeapon();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**条件2: 主裂纹存在**:
|
||||||
|
```
|
||||||
|
if (MajorCrackExists)
|
||||||
|
{
|
||||||
|
// 主裂纹判定:长度超过阈值或穿过关键区域
|
||||||
|
if (CrackLength > MajorCrackThreshold || CrackThroughCriticalArea)
|
||||||
|
{
|
||||||
|
BreakWeapon();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**条件3: 瞬时过载**:
|
||||||
|
```
|
||||||
|
if (ImpactForce > UltimateStrength)
|
||||||
|
{
|
||||||
|
// 瞬间断裂
|
||||||
|
InstantBreakWeapon();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.2 失效表现
|
||||||
|
|
||||||
|
**几何表现**:
|
||||||
|
1. **断裂**:
|
||||||
|
- 刀身分离成两段
|
||||||
|
- 网格拓扑断裂
|
||||||
|
- 删除断裂部分的顶点和三角形
|
||||||
|
|
||||||
|
2. **破碎**:
|
||||||
|
- 多个小碎片
|
||||||
|
- 适合薄刃刀具
|
||||||
|
|
||||||
|
**材质表现**:
|
||||||
|
1. **断口**:
|
||||||
|
- 显示粗糙断裂面
|
||||||
|
- 颜色暗淡
|
||||||
|
- 无光泽
|
||||||
|
|
||||||
|
2. **音效**:
|
||||||
|
- 断裂音效
|
||||||
|
- 碎片掉落声
|
||||||
|
|
||||||
|
**物理表现**:
|
||||||
|
- 断裂部分掉落
|
||||||
|
- 受重力影响
|
||||||
|
- 碰撞地面产生音效
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 实现架构
|
||||||
|
|
||||||
|
### 8.1 数据结构
|
||||||
|
|
||||||
|
**顶点属性扩展**:
|
||||||
|
```cpp
|
||||||
|
TDynamicMeshVertexAttribute<float>* FatigueValue; // 疲劳值(0-1)
|
||||||
|
```
|
||||||
|
|
||||||
|
**裂纹数据结构**:
|
||||||
|
```cpp
|
||||||
|
UENUM(BlueprintType)
|
||||||
|
enum class ECrackType : uint8
|
||||||
|
{
|
||||||
|
Stress = 0, // 应力裂纹
|
||||||
|
Fatigue = 1, // 疲劳裂纹
|
||||||
|
Impact = 2 // 冲击裂纹
|
||||||
|
};
|
||||||
|
|
||||||
|
USTRUCT(BlueprintType)
|
||||||
|
struct FCrackData
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
int32 StartVertex; // 裂纹起始顶点ID
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
int32 EndVertex; // 裂纹结束顶点ID
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
TEnumAsByte<ECrackType> CrackType; // 裂纹类型
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
float CrackWidth; // 裂纹宽度
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
float CrackDepth; // 裂纹深度
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
float CreationTime; // 创建时间
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
bool bIsMajor; // 是否主裂纹
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
**刀具耐久度数据**:
|
||||||
|
```cpp
|
||||||
|
USTRUCT(BlueprintType)
|
||||||
|
struct FWeaponDurability
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
float Durability; // 耐久度(0-1)
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
float AverageFatigue; // 平均疲劳
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
int32 CrackCount; // 裂纹数量
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
float MaxStress; // 最大应力
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
int32 MajorCrackCount; // 主裂纹数量
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.2 GPU-CPU分工
|
||||||
|
|
||||||
|
**GPU Compute Shader职责**:
|
||||||
|
- 无(疲劳计算纯CPU)
|
||||||
|
|
||||||
|
**CPU职责**:
|
||||||
|
- 疲劳累积计算
|
||||||
|
- 疲劳裂纹判定
|
||||||
|
- 裂纹扩展逻辑
|
||||||
|
- 耐久度计算
|
||||||
|
- 使用中的损伤累积
|
||||||
|
- 回火/打磨恢复
|
||||||
|
- 刀具失效判定
|
||||||
|
- 疲劳扫描可视化
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 性能优化
|
||||||
|
|
||||||
|
### 9.1 疲劳计算优化
|
||||||
|
|
||||||
|
**局部更新**:
|
||||||
|
- 锤击时仅更新受影响区域
|
||||||
|
- 使用空间分区加速查询
|
||||||
|
|
||||||
|
**批量更新**:
|
||||||
|
- 每帧最多更新N个顶点
|
||||||
|
- 使用优先级队列(高疲劳优先)
|
||||||
|
|
||||||
|
### 9.2 裂纹判定优化
|
||||||
|
|
||||||
|
**阈值检查**:
|
||||||
|
```
|
||||||
|
// 仅检查接近阈值的顶点
|
||||||
|
if (Fatigue > 0.8 && InternalStress > 0.5 × StressLimit)
|
||||||
|
{
|
||||||
|
// 检查裂纹
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**空间分区**:
|
||||||
|
- 使用八叉树或BVH
|
||||||
|
- 快速查询相邻顶点
|
||||||
|
|
||||||
|
### 9.3 可视化优化
|
||||||
|
|
||||||
|
**疲劳扫描**:
|
||||||
|
- 按需启用
|
||||||
|
- 使用简化着色器
|
||||||
|
- 降低更新频率
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. 参数配置
|
||||||
|
|
||||||
|
### 10.1 可调参数(蓝图可配)
|
||||||
|
|
||||||
|
| 参数名 | 默认值 | 范围 | 说明 |
|
||||||
|
|-------|--------|------|------|
|
||||||
|
| `BaseFatigueRateNormal` | 0.001 | 0.0005-0.002 | 正常锻打疲劳率 |
|
||||||
|
| `BaseFatigueRateOverwork` | 0.005 | 0.002-0.01 | 过度锻打疲劳率 |
|
||||||
|
| `BaseFatigueRateStress` | 0.010 | 0.005-0.02 | 应力集中疲劳率 |
|
||||||
|
| `CriticalStrainThreshold` | 0.1 | 0.05-0.2 | 临界应变阈值 |
|
||||||
|
| `TemperatureFactorHigh` | 0.1 | 0.05-0.2 | 高温疲劳因子 |
|
||||||
|
| `TemperatureFactorNormal` | 1.0 | 0.8-1.2 | 常温疲劳因子 |
|
||||||
|
| `TemperatureFactorLow` | 2.0 | 1.5-2.5 | 低温疲劳因子 |
|
||||||
|
| `CrackPenalty` | 0.1 | 0.05-0.2 | 单条裂纹惩罚 |
|
||||||
|
| `StressPenalty` | 0.2 | 0.1-0.3 | 应力惩罚系数 |
|
||||||
|
| `FatigueRecoveryRate` | 0.1 | 0.05-0.2 | 回火疲劳恢复率 |
|
||||||
|
| `DurabilityRecoveryRate` | 0.05 | 0.02-0.1 | 回火耐久恢复率 |
|
||||||
|
| `ImpactFatigueRate` | 0.0001 | 0.00005-0.0002 | 冲击疲劳率 |
|
||||||
|
| `MajorCrackThreshold` | 10.0 | 5.0-20.0 | 主裂纹长度阈值 |
|
||||||
|
|
||||||
|
### 10.2 调试工具
|
||||||
|
|
||||||
|
- 疲劳热图:实时显示疲劳分布
|
||||||
|
- 疲劳时间线:记录疲劳变化历史
|
||||||
|
- 裂纹追踪:记录裂纹生成和扩展
|
||||||
|
- 耐久度面板:实时显示耐久度
|
||||||
|
- 预估寿命:基于当前疲劳率预估
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. 实施计划
|
||||||
|
|
||||||
|
### 11.1 第一阶段:疲劳累积(1天)
|
||||||
|
|
||||||
|
- [ ] 疲劳值属性系统
|
||||||
|
- [ ] 疲劳累积规则
|
||||||
|
- [ ] 应力和温度因子
|
||||||
|
- [ ] 疲劳限制
|
||||||
|
- [ ] 单元测试
|
||||||
|
|
||||||
|
### 11.2 第二阶段:疲劳裂纹(1天)
|
||||||
|
|
||||||
|
- [ ] 疲劳裂纹起始判定
|
||||||
|
- [ ] 疲劳裂纹扩展逻辑
|
||||||
|
- [ ] 疲劳裂纹视觉表现
|
||||||
|
- [ ] 集成测试
|
||||||
|
|
||||||
|
### 11.3 第三阶段:耐久度系统(1天)
|
||||||
|
|
||||||
|
- [ ] 耐久度计算公式
|
||||||
|
- [ ] 使用中的损伤累积
|
||||||
|
- [ ] 回火/打磨恢复
|
||||||
|
- [ ] 耐久度UI
|
||||||
|
- [ ] 集成测试
|
||||||
|
|
||||||
|
### 11.4 第四阶段:刀具失效(0.5天)
|
||||||
|
|
||||||
|
- [ ] 失效条件判定
|
||||||
|
- [ ] 失效表现(几何/材质/音效)
|
||||||
|
- [ ] 集成测试
|
||||||
|
|
||||||
|
### 11.5 第五阶段:辅助功能(0.5天)
|
||||||
|
|
||||||
|
- [ ] 疲劳扫描模式
|
||||||
|
- [ ] 疲劳热图
|
||||||
|
- [ ] UI面板
|
||||||
|
- [ ] 调试工具
|
||||||
|
|
||||||
|
### 11.6 第六阶段:集成测试(0.5天)
|
||||||
|
|
||||||
|
- [ ] 完整流程测试
|
||||||
|
- [ ] 性能Profiling
|
||||||
|
- [ ] 参数调优
|
||||||
|
- [ ] 文档更新
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. 风险评估
|
||||||
|
|
||||||
|
### 12.1 技术风险
|
||||||
|
|
||||||
|
| 风险 | 概率 | 影响 | 缓解措施 |
|
||||||
|
|------|------|------|---------|
|
||||||
|
| 疲劳计算性能不足 | 中 | 中 | 局部更新,批量处理 |
|
||||||
|
| 裂纹判定过于频繁 | 低 | 高 | 提高阈值,减少检查 |
|
||||||
|
| 耐久度平衡困难 | 中 | 中 | 参数可调,快速迭代 |
|
||||||
|
|
||||||
|
### 12.2 设计风险
|
||||||
|
|
||||||
|
| 风险 | 概率 | 影响 | 缓解措施 |
|
||||||
|
|------|------|------|---------|
|
||||||
|
| 疲劳积累过快,玩家受挫 | 中 | 高 | 降低基础疲劳率,提供恢复机制 |
|
||||||
|
| 刀具失效过于突然 | 低 | 中 | 增加预警,渐进式失效 |
|
||||||
|
| 辅助功能滥用 | 低 | 低 | 限制使用时长,增加冷却 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**文档版本**: 1.0
|
||||||
|
**最后更新**: 2026-01-23
|
||||||
|
**状态**: 设计完成,待评审
|
||||||
Reference in New Issue
Block a user