BlueRose
文章92
标签23
分类7
UE4渲染用贴图资源实时更换

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界面中有SubsufaceProfile(SubsufaceProfile类,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;