一、完整实现流程
1. 创建后处理材质
材质设置:
在材质编辑器中,将材质域(Material Domain)设为后处理(Post Process)
设置混合位置(Blendable Location)(如After Tonemapping)
创建标量/向量参数(如
Intensity
,ColorTint
)
材质蓝图示例:
[SceneTexture:PostProcessInput0] → [参数控制效果] → [输出节点]
2. 添加后处理材质到相机
方法1:通过相机组件(推荐)
// 创建动态材质实例
UMaterialInstanceDynamic* DynamicMaterial = UMaterialInstanceDynamic::Create(PostProcessMaterial, this);
// 添加到相机组件
if (UCameraComponent* Camera = GetPlayerCamera())
{
Camera->AddOrUpdateBlendable(DynamicMaterial, 1.0f); // 1.0为权重
}
方法2:通过相机管理器
APlayerCameraManager* CameraManager = UGameplayStatics::GetPlayerCameraManager(GetWorld(), 0);
if (CameraManager)
{
FWeightedBlendable Blendable;
Blendable.Object = DynamicMaterial;
Blendable.Weight = 1.0f;
CameraManager->GetCameraCacheView().PostProcessSettings.WeightedBlendables.Array.Add(Blendable);
CameraManager->MarkCameraSettingsDirty(); // 确保更新
}
3. 动态更新材质参数
// 高效参数更新函数
void UpdateMaterialParameter(FName ParamName, float Value)
{
if (!DynamicMaterial) return;
// 值变化检测避免无效更新
static float LastValue = MAX_FLT;
if (FMath::IsNearlyEqual(Value, LastValue, 0.001f)) return;
DynamicMaterial->SetScalarParameterValue(ParamName, Value);
LastValue = Value;
// 可选:强制刷新(某些版本需要)
if (UCameraComponent* Camera = GetPlayerCamera())
{
Camera->MarkRenderStateDirty();
}
}
// 使用示例
UpdateMaterialParameter("HitIntensity", FMath::Sin(GetWorld()->GetTimeSeconds()));
4. 移除后处理材质
// 通过相机组件移除
if (UCameraComponent* Camera = GetPlayerCamera())
{
Camera->RemoveBlendable(DynamicMaterial);
}
// 通过相机管理器移除
if (CameraManager)
{
FPostProcessSettings& PPSettings = CameraManager->GetCameraCacheView().PostProcessSettings;
for (int32 i = PPSettings.WeightedBlendables.Array.Num() - 1; i >= 0; i--)
{
if (PPSettings.WeightedBlendables.Array[i].Object == DynamicMaterial)
{
PPSettings.WeightedBlendables.Array.RemoveAt(i);
break;
}
}
}
二、组件化实现(最佳实践)
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "PostProcessComponent.generated.h"
UCLASS(ClassGroup=(Rendering), meta=(BlueprintSpawnableComponent))
class YOURPROJECT_API UPostProcessComponent : public UActorComponent
{
GENERATED_BODY()
public:
UPostProcessComponent();
protected:
virtual void BeginPlay() override;
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
public:
UFUNCTION(BlueprintCallable, Category = "Post Process")
void ActivateEffect(float Duration = 2.0f, float Intensity = 1.0f);
UFUNCTION(BlueprintCallable, Category = "Post Process")
void SetParameter(FName ParamName, float Value);
private:
void InitializeMaterial();
UCameraComponent* GetCamera() const;
void UpdateCamera();
UPROPERTY(EditAnywhere, Category = "Post Process")
UMaterialInterface* PostProcessMaterial;
UPROPERTY(Transient)
UMaterialInstanceDynamic* DynamicMaterial = nullptr;
UCameraComponent* TargetCamera = nullptr;
};
#include "PostProcessComponent.h"
#include "Camera/CameraComponent.h"
#include "Kismet/GameplayStatics.h"
UPostProcessComponent::UPostProcessComponent()
{
PrimaryComponentTick.bCanEverTick = false;
}
void UPostProcessComponent::BeginPlay()
{
Super::BeginPlay();
InitializeMaterial();
}
void UPostProcessComponent::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
if (DynamicMaterial && TargetCamera)
{
TargetCamera->RemoveBlendable(DynamicMaterial);
}
Super::EndPlay(EndPlayReason);
}
void UPostProcessComponent::InitializeMaterial()
{
if (!PostProcessMaterial) return;
DynamicMaterial = UMaterialInstanceDynamic::Create(PostProcessMaterial, this);
if (!DynamicMaterial) return;
TargetCamera = GetCamera();
if (TargetCamera)
{
TargetCamera->AddOrUpdateBlendable(DynamicMaterial, 1.0f);
}
}
void UPostProcessComponent::ActivateEffect(float Duration, float Intensity)
{
if (!DynamicMaterial) return;
// 重置效果权重
if (TargetCamera)
{
TargetCamera->AddOrUpdateBlendable(DynamicMaterial, 1.0f);
}
// 设置初始参数
DynamicMaterial->SetScalarParameterValue("Intensity", Intensity);
// 启动定时器自动结束
GetWorld()->GetTimerManager().SetTimer(EffectTimer, [this]()
{
if (TargetCamera)
{
TargetCamera->RemoveBlendable(DynamicMaterial);
}
}, Duration, false);
}
void UPostProcessComponent::SetParameter(FName ParamName, float Value)
{
if (DynamicMaterial)
{
// 带变化检测的更新
float CurrentValue;
if (DynamicMaterial->GetScalarParameterValue(ParamName, CurrentValue) &&
!FMath::IsNearlyEqual(CurrentValue, Value, 0.001f))
{
DynamicMaterial->SetScalarParameterValue(ParamName, Value);
}
}
}
UCameraComponent* UPostProcessComponent::GetCamera() const
{
AActor* Owner = GetOwner();
if (!Owner) return nullptr;
// 查找相机组件
UCameraComponent* Camera = Owner->FindComponentByClass<UCameraComponent>();
if (Camera) return Camera;
// 回退到玩家相机
if (APlayerController* PC = UGameplayStatics::GetPlayerController(GetWorld(), 0))
{
if (APawn* Pawn = PC->GetPawn())
{
return Pawn->FindComponentByClass<UCameraComponent>();
}
}
return nullptr;
}
三、常见问题与解决方案
1. 材质不显示
原因:
- 材质域未设置为Post Process
- 相机未启用后处理(bEnablePostProcessing=false)
- 混合权重为0
解决:
// 确保相机启用后处理
if (UCameraComponent* Camera = GetCamera())
{
Camera->bEnablePostProcessing = true;
}
// 验证材质域
if (DynamicMaterial->GetMaterial()->MaterialDomain != MD_PostProcess)
{
UE_LOG(LogTemp, Error, TEXT("Material domain must be Post Process!"));
}
2. 参数更新无效
原因:
- 参数名称拼写错误(区分大小写)
- 未使用动态材质实例
- 更新频率过高
解决:
// 验证参数存在
float TempValue;
if (!DynamicMaterial->GetScalarParameterValue("HitIntensity", TempValue))
{
UE_LOG(LogTemp, Warning, TEXT("Parameter not found!"));
}
// 添加变化检测
void SafeSetParameter(FName Name, float Value)
{
static TMap<FName, float> LastValues;
if (!LastValues.Contains(Name)) LastValues.Add(Name, MAX_FLT);
if (!FMath::IsNearlyEqual(Value, LastValues[Name], 0.001f))
{
DynamicMaterial->SetScalarParameterValue(Name, Value);
LastValues[Name] = Value;
}
}
3. 性能问题
优化策略:
- 参数批量更新:
void SetParameters(TMap<FName, float> Params)
{
for (auto& Param : Params)
{
DynamicMaterial->SetScalarParameterValue(Param.Key, Param.Value);
}
// 单次提交
DynamicMaterial->UpdateParameterValues();
}
使用材质参数集合:
// 全局参数控制
UMaterialParameterCollection* GlobalParams = ...;
GetWorld()->GetParameterCollectionInstance(GlobalParams)
->SetScalarParameterValue("GlobalIntensity", Value);
四、高级技巧
1. 效果混合与叠加
// 管理多个效果
TMap<FName, UMaterialInstanceDynamic*> ActiveEffects;
void AddEffect(FName EffectID, UMaterialInterface* Material)
{
UMaterialInstanceDynamic* NewMID = UMaterialInstanceDynamic::Create(Material, this);
ActiveEffects.Add(EffectID, NewMID);
if (UCameraComponent* Camera = GetCamera())
{
Camera->AddOrUpdateBlendable(NewMID, 1.0f);
}
}
void RemoveEffect(FName EffectID)
{
if (UMaterialInstanceDynamic** MIDPtr = ActiveEffects.Find(EffectID))
{
if (UCameraComponent* Camera = GetCamera())
{
Camera->RemoveBlendable(*MIDPtr);
}
ActiveEffects.Remove(EffectID);
}
}
2. 基于距离的LOD控制
void UpdateEffectLOD()
{
FVector PlayerLocation = ...;
float Distance = FVector::Distance(GetOwner()->GetActorLocation(), PlayerLocation);
float Intensity = FMath::Clamp(1.0f - (Distance / MaxDistance), 0.0f, 1.0f);
if (DynamicMaterial)
{
DynamicMaterial->SetScalarParameterValue("EffectIntensity", BaseIntensity * Intensity);
// 完全禁用远距离效果
if (Intensity < 0.01f)
{
if (UCameraComponent* Camera = GetCamera())
{
Camera->RemoveBlendable(DynamicMaterial);
}
}
}
}
3. 后处理材质动画
// 在tick中创建动画效果
void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
if (bIsAnimating)
{
AnimationTime += DeltaTime;
float Pulse = FMath::Sin(AnimationTime * 5.0f) * 0.5f + 0.5f;
DynamicMaterial->SetScalarParameterValue("PulseIntensity", Pulse);
if (AnimationTime > AnimationDuration)
{
bIsAnimating = false;
}
}
}
五、总结与最佳实践
1. 材质设置:
务必设置材质域为Post Process
使用暴露参数而非硬编码值
2. 生命周期管理:
virtual void BeginPlay() override; // 初始化
virtual void EndPlay() override; // 清理资源
3. 性能优化:
- 使用变化检测减少更新次数
- 批量更新材质参数
- 远距离禁用效果
4. 错误预防:
// 安全访问模式
if (DynamicMaterial && GetCamera())
{
// 操作
}
5. 调试工具:
// 控制台命令
r.PostProcessing.Debug 1 // 显示活动后处理
r.PostProcess 0 // 禁用所有后处理