TECHARTNOMAD | TECHARTFLOWIO.COM

TECH.ART.FLOW.IO

[번역] UE5 머티리얼에서 렌더링 파이프라인 텍스처 접근하기

jplee 2025. 10. 20. 15:46

저자 : 张氏男子

들어가며

언리얼 엔진의 렌더링 시스템은 상당히 폐쇄적으로 구성되어 있다. 대부분의 경우 머티리얼 에디터에서 노드를 연결하는 방식으로 렌더링 결과를 제어하게 되는데, 셰이더 코드 레벨에서 직접 무언가를 추가하거나 수정하려면 꽤 번거로운 작업이 필요하다. 게다가 머티리얼에서 접근할 수 있는 리소스도 매우 제한적이다.

이번 글에서는 머티리얼에 텍스처를 주입하는 두 가지 방법을 소개한다. 여기서는 평면 반사(Planar Reflection) 텍스처를 예시로 사용했지만, 같은 방법으로 다른 렌더 타겟도 머티리얼에서 접근할 수 있다. 각 방법마다 장단점이 있으므로 상황에 맞게 선택하면 된다.

첫 번째 방법: BasePassUniformParameters 활용

BasePass에 텍스처를 직접 주입하는 방식이다.

먼저 MobileBasePassUniformParameters에서 텍스처를 선언한다.

BEGIN_GLOBAL_SHADER_PARAMETER_STRUCT(FMobileBasePassUniformParameters, )
    ..........
    //----YHRP---- PreviousSceneColor  Begin
    SHADER_PARAMETER_RDG_TEXTURE(Texture2D , PreviousHistoryTexture)
    SHADER_PARAMETER_SAMPLER(SamplerState, PreviousSceneColorSampler)
    //----YHRP---- PreviousSceneColor  End
    ..........
END_GLOBAL_SHADER_PARAMETER_STRUCT()

이제 텍스처를 등록해야 한다. PrevViewInfo에는 AO, SSPR, SceneColor 등 이전 프레임의 다양한 PooledRenderTarget이 포함되어 있다. 대부분 필요한 데이터는 여기서 얻을 수 있다.

void SetupMobileBasePassUniformParameters(
    FRDGBuilder& GraphBuilder,
    const FViewInfo& View, 
    EMobileBasePass BasePass,
    EMobileSceneTextureSetupMode SetupMode,
    const FMobileBasePassTextures& MobileBasePassTextures,
    FMobileBasePassUniformParameters& BasePassParameters)
{
    ..........

    //----YHRP---- Set Shader Parameters Begin
    FRDGTextureRef PreviousHistoryTexture = [SystemTextures.Black](http://SystemTextures.Black);
    if(View.PrevViewInfo.MobilePixelProjectedReflection.IsValid())        
    {
        PreviousHistoryTexture = GraphBuilder.RegisterExternalTexture(View.PrevViewInfo.MobilePixelProjectedReflection);
    }
    BasePassParameters.PreviousHistoryTexture = PreviousHistoryTexture;
    //----YHRP---- Set Shader Parameters End

    ..........
}

셰이더 코드도 수정이 필요하다. MobileBasePassPixelShader.usf 파일을 다음과 같이 변경한다.

......
//----YHRP----Previous 
#define PreviousHistoryTexture            MobileBasePass.PreviousHistoryTexture
#define PreviousSceneColorSampler        MobileBasePass.PreviousSceneColorSampler
//----YHRP----Previous
......

......
void Main(
......
......
FPixelMaterialInputs PixelMaterialInputs;
    {
        float4 ScreenPosition = SvPositionToResolvedScreenPosition(SvPosition);
        float3 WorldPosition = [BasePassInterpolants.PixelPosition.xyz](http://BasePassInterpolants.PixelPosition.xyz);
        float3 WorldPositionExcludingWPO = [BasePassInterpolants.PixelPosition.xyz](http://BasePassInterpolants.PixelPosition.xyz);
        #if USE_WORLD_POSITION_EXCLUDING_SHADER_OFFSETS
        WorldPositionExcludingWPO = BasePassInterpolants.PixelPositionExcludingWPO;
        #endif

        //----YHRP---- Inject History Ref to materialParameters
        MaterialParameters.HistoryReflectionData = Texture2DSample(PreviousHistoryTexture,PreviousSceneColorSampler , SvPositionToBufferUV(SvPosition) );

        CalcMaterialParametersEx(MaterialParameters, PixelMaterialInputs, SvPosition, ScreenPosition, bIsFrontFace, WorldPosition, WorldPositionExcludingWPO);
......
    }

MaterialTemplate.ush에도 구조체 정의를 추가해준다.

struct FMaterialPixelParameters
{
    ......
    float4 HistoryReflectionData;
    ......

이렇게 설정하면 머티리얼에서 Parameters.HistoryReflectionData를 통해 SSPR 정보를 샘플링할 수 있다.

두 번째 방법: ViewUniformBuffer 활용 (권장)

개인적으로 더 선호하는 방식이다.

ViewUniformBuffer에 텍스처를 추가하는 방법인데, ViewUniformBuffer는 전역 UniformBuffer에 가까워서 거의 모든 곳에서 접근할 수 있다. 머티리얼에서 사용 가능한 Atmosphere 관련 텍스처나 사전 계산된 BRDF 텍스처 같은 것들도 모두 여기서 정의된다. ViewUniformBuffer는 정말 다양한 데이터를 저장하고 있다.

/** The uniform shader parameters associated with a view. */
BEGIN_GLOBAL_SHADER_PARAMETER_STRUCT_WITH_CONSTRUCTOR(FViewUniformShaderParameters, ENGINE_API)

    VIEW_UNIFORM_BUFFER_MEMBER_TABLE
    ..........
    // ----YHRP----History PPR
    SHADER_PARAMETER_TEXTURE(Texture2D , HistoryPPRTex)
    SHADER_PARAMETER_SAMPLER(SamplerState , HistoryPPRTexSampler)
    ..........
END_GLOBAL_SHADER_PARAMETER_STRUCT()

초기화 코드도 작성한다.

FViewUniformShaderParameters::FViewUniformShaderParameters()
{
    ......
    //----YHRP---- HistoryPPRTex
    HistoryPPRTex = GBlackTexture->TextureRHI;
    ......
}

이제 실제로 텍스처를 주입하는 부분이다.

/** Creates the view's uniform buffers given a set of view transforms. */
void FViewInfo::SetupUniformBufferParameters(
    const FViewMatrices& InViewMatrices,
    const FViewMatrices& InPrevViewMatrices,
    FBox* OutTranslucentCascadeBoundsArray,
    int32 NumTranslucentCascades,
    FViewUniformShaderParameters& ViewUniformShaderParameters) const
{
    ......
    //----YHRP---- HistoryPPR Texture
    FRHITexture* HistoryPPRTextureFound = nullptr;
    ......
    //----YHRP---- HistoryPPR
    const TRefCountPtr<IPooledRenderTarget>& PooledHistoryPPRTextureFound = PrevViewInfo.MobilePixelProjectedReflection;
    if(PooledHistoryPPRTextureFound.IsValid())
    {
        HistoryPPRTextureFound = PooledHistoryPPRTextureFound->GetRHI();
    }
    ......
    //----YHRP---- History PPR Texture
    ViewUniformShaderParameters.HistoryPPRTex = OrBlack2DIfNull(HistoryPPRTextureFound);
    ViewUniformShaderParameters.HistoryPPRTexSampler = TStaticSamplerState<SF_Bilinear>::GetRHI();
    ......
}

이제 머티리얼에서 다음과 같이 텍스처 객체와 샘플러에 접근할 수 있다.

View.HistoryPPRTex 
View.HistoryPPRTexSampler

커스텀 머티리얼 노드로 편리하게 사용하기

위 코드를 머티리얼 노드로 만들면 훨씬 편하게 사용할 수 있다. 노드 생성 자체는 그리 복잡하지 않으므로 코드를 직접 보여주겠다.

헤더 파일은 다음과 같다.

UCLASS(collapsecategories, hidecategories = Object, MinimalAPI)
class UMaterialExpressionExponential : public UMaterialExpression
{
    GENERATED_UCLASS_BODY()

    UPROPERTY()
    FExpressionInput Input;

    //~ Begin UMaterialExpression Interface
#if WITH_EDITOR
    virtual int32 Compile(class FMaterialCompiler* Compiler, int32 OutputIndex) override;
    virtual void GetCaption(TArray<FString>& OutCaptions) const override;
#endif
    //~ End UMaterialExpression Interface
};

CPP 구현 코드는 다음과 같다.

#define LOCTEXT_NAMESPACE "MaterialExpressionHistoryPR"

UMaterialExpressionHistoryPR::UMaterialExpressionHistoryPR(const FObjectInitializer& ObjectInitializer)
    :Super(ObjectInitializer)
{
    // Structure to hold one-time initialization
    struct FConstructorStatics
    {
        FText NAME_Utility;
        FConstructorStatics()
            : NAME_Utility(LOCTEXT("Utility", "Utility"))
        {}
    };
    static FConstructorStatics ConstructorStatics;

#if WITH_EDITORONLY_DATA
    MenuCategories.Add([ConstructorStatics.NAME](http://ConstructorStatics.NAME)_Utility);
#endif
}

#if WITH_EDITOR
int32 UMaterialExpressionHistoryPR::Compile(FMaterialCompiler* Compiler, int32 OutputIndex)
{
    if(!Input.GetTracedInput().Expression)
    {
        return Compiler->Errorf(TEXT("Missing UV input"));
    }

    return Compiler->HistoryPR(Input.Compile(Compiler));//TODO:Fix
}

void UMaterialExpressionHistoryPR::GetCaption(TArray<FString>& OutCaptions) const
{
    OutCaptions.Add(TEXT("HistoryPR"));
}

FName UMaterialExpressionHistoryPR::GetInputName(int32 InputIndex) const
{
    if(InputIndex == 0)
    {
        return TEXT("ViewportUV");
    }
    return FName();
}
#endif
#undef LOCTEXT_NAMESPACE
//----YHRP---- HistoryPR
int32 FHLSLMaterialTranslator::HistoryPR(int32 InputUV)
{

    FString InputUVCode = *GetParameterCode(InputUV);
    return AddCodeChunk(MCT_Float4, TEXT("Texture2DSampleLevel(View.HistoryPPRTex, View.HistoryPPRTexSampler, %s, 0.0f)") , *InputUVCode);
}

정리하며

두 가지 방법 모두 렌더링 파이프라인의 텍스처를 머티리얼에서 접근할 수 있게 해준다. BasePassUniformParameters 방식은 BasePass에서만 동작하지만 구현이 비교적 직관적이다. 반면 ViewUniformBuffer 방식은 더 광범위한 곳에서 접근 가능하고 다른 머티리얼 접근 가능 텍스처들과 같은 방식으로 관리되므로 일관성이 있다. 프로젝트의 요구사항에 맞춰 적절한 방법을 선택하면 된다.


원문

https://zhuanlan.zhihu.com/p/663995419?share_code=1aUw7yS9RAmux&utm_psn=1963577349099487371