BlueRoseNote/03-UnrealEngine/Rendering/RenderingPipeline/UE4渲染用贴图资源实时更换.md
2023-06-29 11:55:02 +08:00

6.2 KiB
Raw Permalink Blame History

UE4渲染用贴图资源实时更换

最近做卡通渲染美术同学反应每次更换渲染贴图都需要手动替换资源并重启引擎相当麻烦。所以花时间了解了一下UE4的渲染资源逻辑。并且找到了解决方法。

可参考的资源

在FViewUniformShaderParameter中有 SHADER_PARAMETER_TEXTURE(Texture2D, PreIntegratedBRDF) SHADER_PARAMETER_TEXTURE(Texture2D, PerlinNoiseGradientTexture) 在渲染管线中调用FSystemTextures::InitializeCommonTextures生成之后通过GSystemTextures加载 SHADER_PARAMETER_TEXTURE(Texture3D, PerlinNoise3DTexture) 在渲染管线中调用FSystemTextures::InitializeCommonTextures生成之后通过GSystemTextures加载 SHADER_PARAMETER_TEXTURE(Texture2D, AtmosphereTransmittanceTexture)

在Material界面中有SubsufaceProfileSubsufaceProfile类SubsurfaceProfile.h

USubsurfaceProfile

资源的类型为USubsurfaceProfile实现了2个接口函数

void USubsurfaceProfile::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent)
{
	const FSubsurfaceProfileStruct SettingsLocal = this->Settings;
	USubsurfaceProfile* Profile = this;
	ENQUEUE_RENDER_COMMAND(UpdateSubsurfaceProfile)(
		[SettingsLocal, Profile](FRHICommandListImmediate& RHICmdList)
		{
			// any changes to the setting require an update of the texture
			GSubsurfaceProfileTextureObject.UpdateProfile(SettingsLocal, Profile);
		});
}
void USubsurfaceProfile::BeginDestroy()
{
	USubsurfaceProfile* Ref = this;
	ENQUEUE_RENDER_COMMAND(RemoveSubsurfaceProfile)(
		[Ref](FRHICommandList& RHICmdList)
		{
			GSubsurfaceProfileTextureObject.RemoveProfile(Ref);
		});

	Super::BeginDestroy();
}

在UMaterialInterface::UpdateMaterialRenderProxy()中开始触发下列渲染资源操作:

  • 升级渲染资源GSubsurfaceProfileTextureObject.UpdateProfile(SettingsLocal, Profile);
  • 移除渲染资源GSubsurfaceProfileTextureObject.RemoveProfile(Ref);

本质上更新SubsurfaceProfileEntries数组后将渲染进程里的GSSProfiles释放掉。在渲染管线中主要通过**GetSubsufaceProfileTexture_RT(RHICmdList);**来获取这个资源,其顺序为

GetSubsufaceProfileTexture_RT()=>GSubsurfaceProfileTextureObject.GetTexture(RHICmdList);=>return GSSProfiles;

如果GSSProfiles无效则调用CreateTexture()对SubsurfaceProfile进行编码并生成新的GSSProfiles。

PreIntegratedBRDF

  1. 在FViewUniformShaderParameters中添加PreIntegratedBRDF贴图变量

  2. 在FViewUniformShaderParameters::FViewUniformShaderParameters(),进行初始化PreIntegratedBRDF = GWhiteTexture->TextureRHI;

  3. 在FViewInfo::SetupUniformBufferParameters()中进行资源指定:ViewUniformShaderParameters.PreIntegratedBRDF = GEngine->PreIntegratedSkinBRDFTexture->Resource->TextureRHI;

  4. 在UEngine中定义资源指针class UTexture2D PreIntegratedSkinBRDFTexture;FSoftObjectPath PreIntegratedSkinBRDFTextureName;*

  5. 在UEngine::InitializeObjectReferences()中调用**LoadEngineTexture(PreIntegratedSkinBRDFTexture, *PreIntegratedSkinBRDFTextureName.ToString());**载入贴图。

template <typename TextureType>
static void LoadEngineTexture(TextureType*& InOutTexture, const TCHAR* InName)
{
	if (!InOutTexture)
	{
		InOutTexture = LoadObject<TextureType>(nullptr, InName, nullptr, LOAD_None, nullptr);
	}
	if (FPlatformProperties::RequiresCookedData() && InOutTexture)
	{
		InOutTexture->AddToRoot();
	}
}

AtmosphereTransmittanceTexture

  1. 在FViewUniformShaderParameters中添加AtmosphereTransmittanceTexture贴图变量
  2. 在FViewUniformShaderParameters::FViewUniformShaderParameters(),进行初始化AtmosphereTransmittanceTexture = GWhiteTexture->TextureRHI;
  3. 在FViewUniformShaderParameters::SetupUniformBufferParameters()中:ViewUniformShaderParameters.AtmosphereTransmittanceTexture = OrBlack2DIfNull(AtmosphereTransmittanceTexture);
  4. 在FogRendering阶段InitAtmosphereConstantsInView()中进行资源指定:View.AtmosphereTransmittanceTexture = (FogInfo.TransmittanceResource && FogInfo.TransmittanceResource->TextureRHI.GetReference()) ? (FTextureRHIRef)FogInfo.TransmittanceResource->TextureRHI : GBlackTexture->TextureRHI;
  5. 在UAtmosphericFogComponent::UpdatePrecomputedData()中对FogInfo进行预计算在Tick事件中调用每帧调用

在UpdatePrecomputedData()中的最后还会调用以下函数来对资源进行更新:

PrecomputeCounter = EValid;
FPlatformMisc::MemoryBarrier();
Scene->AtmosphericFog->bPrecomputationAcceptedByGameThread = true;

// Resolve to data...
ReleaseResource();
// Wait for release...
FlushRenderingCommands();

InitResource();
FComponentReregisterContext ReregisterContext(this);

生成贴图逻辑(部分)如下:

FScene* Scene = GetScene() ? GetScene()->GetRenderScene() : NULL;

{
	int32 SizeX = PrecomputeParams.TransmittanceTexWidth;
	int32 SizeY = PrecomputeParams.TransmittanceTexHeight;
	int32 TotalByte = sizeof(FColor) * SizeX * SizeY;
	check(TotalByte == Scene->AtmosphericFog->PrecomputeTransmittance.GetBulkDataSize());
	const FColor* PrecomputeData = (const FColor*)Scene->AtmosphericFog->PrecomputeTransmittance.Lock(LOCK_READ_ONLY);
	TransmittanceData.Lock(LOCK_READ_WRITE);
	FColor* TextureData = (FColor*)TransmittanceData.Realloc(TotalByte);
	FMemory::Memcpy(TextureData, PrecomputeData, TotalByte);
	TransmittanceData.Unlock();
	Scene->AtmosphericFog->PrecomputeTransmittance.Unlock();
}

本人尝试方法

个人觉得SubsurfaceProfile的方法是最好的但也因为相对麻烦所以放弃。我最后选择了修改UEngine(GEngine)中对应的的UTexture指针来实现渲染资源替换因为每帧都还会调用SetupUniformBufferParameters()来指定渲染用的资源。

为了保证载入的资源的生命周期我实现了一个UEngineSubSystem子类声明了若干UTexture指针并且移植了LoadEngineTexture()。这样就可以实现Runtime更换渲染资源了。大致代码如下

UToonEngineSubsystem* EngineSubsystem = GEngine->GetEngineSubsystem<UToonEngineSubsystem>();
EngineSubsystem->LoadEngineTexture(EngineSubsystem->ToonRampTexture, *ToonRampTexture->GetPathName());
GEngine->ToonRampTexture=EngineSubsystem->ToonRampTexture;