본문 바로가기

ShaderStudy발표자료

[번역] UE5의 카툰 렌더링 - Custom Stroke Pass (OutLine Pass)

728x90
반응형
  • UE5 렌더링 파이프라인에 커스텀 DrawPass를 추가하여 아웃라인를 그리는 방법에 대해 이야기하겠습니다.

 

그림에 표시된 아웃라인 효과에는 다음과 같은 특징이 있습니다.

  • 셰이더 세부 정보 패널에서 아웃라인를 사용여부를 선택할 수 있습니다.
  • 색상은 셰이더 세부 정보 패널에서 수동으로 제어할 수 있습니다
    • (모델에 정점 색상 제어 스트로크가 있는 경우 정점 색상 사용).
  • 두께는 셰이더 세부 정보 패널에서 수동으로 조정할 수 있습니다
    • (모델에 정점 색상 제어 두께가 있는 경우 영향도 추가됩니다).
  • 보는 거리에 따라 두께가 자동으로 조정됩니다.
    • (거리가 멀수록 두꺼워지지만 상한선이 있고, 가까울수록 얇아집니다.)

아웃라인을 그리는 방법

  • 아웃라인을 그리는 방법에는 여러 가지가 있지만 일반적인 아웃라인 그리기에는 크게 두 가지 아이디어가 있다.
    • 하나는 노말을 확장하여 아웃라인을 그리는 것
    • 포스트 프로세스 처리에서 모서리 인식을 수행하여 그리는 것
  • 둘의 차이점과 장단점
    • 정점 노말 확장 아웃라인 효과에 영향을 미치는 요소는 주로 정점 노말의 확장 각도에서 나온다.
    • 포스트 프로세스 아웃라인 효과는 영향을 미치는 요소는 더욱 다양하다
    • RT에서 Detph 또는 사용자 정의 Stencil Bit 를 작성할지 여부를 선택하면 나중에 가장자리 인식 효과에 영향을 미치며 동시에 가장자리 인식 알고리즘의 선택은 최종 효과에 영향을 미치며 포스트프로세스 처리 아웃라인의 특정 개선 체계에서 자신의 필요에 따라 그리기에 가장 적합한 솔루션을 선택해야합니다.
  • 이 글의 목적은 일반적인 확장 아웃라인 기능을 구현하여 UE5에서 Draw Pass와 관련된 원리와 프로세스를 설명하는 것이므로, 포스트 프로세스 아웃라인처리에 대한 설명은 당분간 생략하겠습니다. 

Mesh Draw Pass의 간단한 프로세스

  • Mesh Draw Pass를 추가하려면 주로 세 가지를 구현해야 합니다.
    • Mesh Draw Pass의 프로세서 클래스
    • VS 및 PS 클래스와 셰이더 구현 정의
    • RDG의 렌더링 기능 호출
  • 위의 세 가지 클래스 또는 함수를 구현하고 기타 여러 가지 수정 및 구현을 완료하면 사용자 정의 패스 생성이 완료될 수 있습니다. 

Mesh Draw Pass 개발

  • 첫 번째는 여전히 동일합니다. Mesh Draw Pass가 정의된 위치를 찾고 정의된 열거에 자체 패스 이름을 추가합니다.
//MeshPassProcessor.h
namespace EMeshPass
{
    enum Type : uint8
    {
        DepthPass,
        BasePass,
        AnisotropyPass,
        SkyPass,
        SingleLayerWaterPass,
        SingleLayerWaterDepthPrepass,
        CSMShadowDepth,
        VSMShadowDepth,
        Distortion,
        Velocity,
        TranslucentVelocity,
        TranslucencyStandard,
        TranslucencyStandardModulate,
        TranslucencyAfterDOF,
        TranslucencyAfterDOFModulate,
        TranslucencyAfterMotionBlur,
        TranslucencyAll, /** Drawing all translucency, regardless of separate or standard.  Used when drawing translucency outside of the main renderer, eg FRendererModule::DrawTile. */
        LightmapDensity,
        DebugViewMode, /** Any of EDebugViewShaderMode */
        CustomDepth,
        MobileBasePassCSM,  /** Mobile base pass with CSM shading enabled */
        VirtualTexture,
        LumenCardCapture,
        LumenCardNanite,
        LumenTranslucencyRadianceCacheMark,
        LumenFrontLayerTranslucencyGBuffer,
        DitheredLODFadingOutMaskPass, /** A mini depth pass used to mark pixels with dithered LOD fading out. Currently only used by ray tracing shadows. */
        NaniteMeshPass,
        MeshDecal,

#if WITH_EDITOR
        HitProxy,
        HitProxyOpaqueOnly,
        EditorLevelInstance,
        EditorSelection,
#endif
        OutlinePass, //추가

        Num,
        NumBits = 6,
    };
}
  • 그리고 정적 검사에서 패스 수를 수정합니다.
inline const TCHAR* GetMeshPassName(EMeshPass::Type MeshPass)
{
    switch (MeshPass)
    {
    case EMeshPass::DepthPass: return TEXT("DepthPass");
    case EMeshPass::BasePass: return TEXT("BasePass");
    case EMeshPass::AnisotropyPass: return TEXT("AnisotropyPass");
    case EMeshPass::SkyPass: return TEXT("SkyPass");
    case EMeshPass::SingleLayerWaterPass: return TEXT("SingleLayerWaterPass");
    case EMeshPass::SingleLayerWaterDepthPrepass: return TEXT("SingleLayerWaterDepthPrepass");
    case EMeshPass::CSMShadowDepth: return TEXT("CSMShadowDepth");
    case EMeshPass::VSMShadowDepth: return TEXT("VSMShadowDepth");
    case EMeshPass::Distortion: return TEXT("Distortion");
    case EMeshPass::Velocity: return TEXT("Velocity");
    case EMeshPass::TranslucentVelocity: return TEXT("TranslucentVelocity");
    case EMeshPass::TranslucencyStandard: return TEXT("TranslucencyStandard");
    case EMeshPass::TranslucencyStandardModulate: return TEXT("TranslucencyStandardModulate");
    case EMeshPass::TranslucencyAfterDOF: return TEXT("TranslucencyAfterDOF");
    case EMeshPass::TranslucencyAfterDOFModulate: return TEXT("TranslucencyAfterDOFModulate");
    case EMeshPass::TranslucencyAfterMotionBlur: return TEXT("TranslucencyAfterMotionBlur");
    case EMeshPass::TranslucencyAll: return TEXT("TranslucencyAll");
    case EMeshPass::LightmapDensity: return TEXT("LightmapDensity");
    case EMeshPass::DebugViewMode: return TEXT("DebugViewMode");
    case EMeshPass::CustomDepth: return TEXT("CustomDepth");
    case EMeshPass::MobileBasePassCSM: return TEXT("MobileBasePassCSM");
    case EMeshPass::VirtualTexture: return TEXT("VirtualTexture");
    case EMeshPass::LumenCardCapture: return TEXT("LumenCardCapture");
    case EMeshPass::LumenCardNanite: return TEXT("LumenCardNanite");
    case EMeshPass::LumenTranslucencyRadianceCacheMark: return TEXT("LumenTranslucencyRadianceCacheMark");
    case EMeshPass::LumenFrontLayerTranslucencyGBuffer: return TEXT("LumenFrontLayerTranslucencyGBuffer");
    case EMeshPass::DitheredLODFadingOutMaskPass: return TEXT("DitheredLODFadingOutMaskPass");
    case EMeshPass::NaniteMeshPass: return TEXT("NaniteMeshPass");
    case EMeshPass::MeshDecal: return TEXT("MeshDecal");
#if WITH_EDITOR
    case EMeshPass::HitProxy: return TEXT("HitProxy");
    case EMeshPass::HitProxyOpaqueOnly: return TEXT("HitProxyOpaqueOnly");
    case EMeshPass::EditorLevelInstance: return TEXT("EditorLevelInstance");
    case EMeshPass::EditorSelection: return TEXT("EditorSelection");
#endif
    case EMeshPass::OutlinePass: return TEXT("OutlinePass"); //추가
    }
	
    //Enum값 추가로 아래 값을 +1 증가시켜 준다
#if WITH_EDITOR
    static_assert(EMeshPass::Num == 30 + 4, "Need to update switch(MeshPass) after changing EMeshPass");
#else
    static_assert(EMeshPass::Num == 30, "Need to update switch(MeshPass) after changing EMeshPass");
#endif

 

  • 개발 브랜치의 새 버전에는 PSO 수에 대한 정적 검사가 추가되었으므로 FPSOCollectorCreateManager에서 MaxPSOCollectorCount 값을 현재 패스 수로 수정해야 합니다.
//PSOPrecache.h
class ENGINE_API FPSOCollectorCreateManager
{
public:
	//추가 Enum값 증가로 인한 +1 
    constexpr static uint32 MaxPSOCollectorCount = 34;

    static PSOCollectorCreateFunction GetCreateFunction(EShadingPath ShadingPath, uint32 Index)
    {
        check(Index < MaxPSOCollectorCount);
        uint32 ShadingPathIdx = (uint32)ShadingPath;
        return JumpTable[ShadingPathIdx][Index];
    }

private:

    // Have to used fixed size array instead of TArray because of order of initialization of static member variables
    static PSOCollectorCreateFunction JumpTable[(uint32)EShadingPath::Num][MaxPSOCollectorCount];
    friend class FRegisterPSOCollectorCreateFunction;
};

 

  • 새 프로세서 클래스와 셰이더 클래스의 구현을 저장하기 위해 새 헤더 파일과 새 CPP 파일을 생성합니다.
    • 이 단계는 UE 자체 패스 구현을 모델링할 수도 있습니다.
    • UE 자체 패스에 대해서는 Custom Depth Pass를 참조하세요.
  • 명확한 프로세스와 적은 양의 코드로 좋은 선택입니다.
  • 먼저 헤더 파일에 Processor 클래스 선언을 구현합니다.
  • 이 클래스에는 주로 생성자와 두 개의 멤버 함수가 포함됩니다.

 

//OutlinePassRendering.h
class FOutlinePassProcessor : public FMeshPassProcessor
{
public:
    FOutlinePassProcessor(
        const FScene* Scene,
        const FSceneView* InViewIfDynamicMeshCommand,
        const FMeshPassProcessorRenderState& InPassDrawRenderState,
        FMeshPassDrawListContext* InDrawListContext
    );

    virtual void AddMeshBatch(
        const FMeshBatch& RESTRICT MeshBatch,
        uint64 BatchElementMask,
        const FPrimitiveSceneProxy* RESTRICT PrimitiveSceneProxy,
        int32 StaticMeshId = -1
    ) override final;

private:
    bool Process(
        const FMeshBatch& MeshBatch,
        uint64 BatchElementMask,
        int32 StaticMeshId,
        const FPrimitiveSceneProxy* RESTRICT PrimitiveSceneProxy,
        const FMaterialRenderProxy& RESTRICT MaterialRenderProxy,
        const FMaterial& RESTRICT MaterialResource,
        ERasterizerFillMode MeshFillMode,
        ERasterizerCullMode MeshCullMode
    );

    FMeshPassProcessorRenderState PassDrawRenderState;
};

 

  • 다음은 두 가지 Shader 클래스인 VS와 PS의 구현입니다.
  • 머티리얼에서 아웃라인 매개변수를 얻기 위한 인터페이스가 아직 구현되지 않았으므로 다음 코드에서 매개변수를 얻기 위한 코드를 일시적으로 주석 처리하고 대신 고정된 매개변수를 전달할 수 있습니다.
  • 기능이 정상적으로 사용될 수 있는지 테스트하기 위해 Shader에 값을 지정합니다.

 

//OutlinePassRendering.h
class FOutlineVS : public FMeshMaterialShader
{
    DECLARE_SHADER_TYPE(FOutlineVS, MeshMaterial);

public:
    FOutlineVS() = default;
    FOutlineVS(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
        : FMeshMaterialShader(Initializer)
    {
        OutLineScale.Bind(Initializer.ParameterMap, TEXT("OutLineScale"));
    }

    static void ModifyCompilationEnvironment(const FMaterialShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
    {}

    static bool ShouldCompilePermutation(const FMeshMaterialShaderPermutationParameters& Parameters)
    {
        return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5) &&
            Parameters.MaterialParameters.bHasOutline && 
            (Parameters.VertexFactoryType->GetFName() == FName(TEXT("FLocalVertexFactory")) || 
                Parameters.VertexFactoryType->GetFName() == FName(TEXT("TGPUSkinVertexFactoryDefault")));
    }

    void GetShaderBindings(
        const FScene* Scene,
        ERHIFeatureLevel::Type FeatureLevel,
        const FPrimitiveSceneProxy* PrimitiveSceneProxy,
        const FMaterialRenderProxy& MaterialRenderProxy,
        const FMaterial& Material,
        const FMeshPassProcessorRenderState& DrawRenderState,
        const FMeshMaterialShaderElementData& ShaderElementData,
        FMeshDrawSingleShaderBindings& ShaderBindings) const
    {
        FMeshMaterialShader::GetShaderBindings(Scene, FeatureLevel, PrimitiveSceneProxy, MaterialRenderProxy, Material, DrawRenderState, ShaderElementData, ShaderBindings);

        // const float OutlineScale = Material.GetOutlineScale();
        ShaderBindings.Add(OutLineScale, 1.0);
    }

    LAYOUT_FIELD(FShaderParameter, OutLineScale);
};


class FOutlinePS : public FMeshMaterialShader
{
    DECLARE_SHADER_TYPE(FOutlinePS, MeshMaterial);

public:

    FOutlinePS() = default;
    FOutlinePS(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
        : FMeshMaterialShader(Initializer)
    {
        OutLineColor.Bind(Initializer.ParameterMap, TEXT("OutLineColor"));
    }

    static void ModifyCompilationEnvironment(const FMaterialShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
    {}

    static bool ShouldCompilePermutation(const FMeshMaterialShaderPermutationParameters& Parameters)
    {
        return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM5) &&
            Parameters.MaterialParameters.bHasOutline && 
            (Parameters.VertexFactoryType->GetFName() == FName(TEXT("FLocalVertexFactory")) || 
                Parameters.VertexFactoryType->GetFName() == FName(TEXT("TGPUSkinVertexFactoryDefault")));
    }

    void GetShaderBindings(
        const FScene* Scene,
        ERHIFeatureLevel::Type FeatureLevel,
        const FPrimitiveSceneProxy* PrimitiveSceneProxy,
        const FMaterialRenderProxy& MaterialRenderProxy,
        const FMaterial& Material,
        const FMeshPassProcessorRenderState& DrawRenderState,
        const FMeshMaterialShaderElementData& ShaderElementData,
        FMeshDrawSingleShaderBindings& ShaderBindings) const
    {
        FMeshMaterialShader::GetShaderBindings(Scene, FeatureLevel, PrimitiveSceneProxy, MaterialRenderProxy, Material, DrawRenderState, ShaderElementData, ShaderBindings);

        // const FLinearColor OutlineColor = Material.GetOutlineColor();
        FVector3f Color(1.0, 0.0, 0.0);

        ShaderBindings.Add(OutLineColor, Color);
    }

    LAYOUT_FIELD(FShaderParameter, OutLineColor);
};

 

  • 전체 Shader 클래스의 구현은 매우 간단하며 다음 부분으로 나눌 수 있습니다.
    • DECLARE_SHADER_TYPEUE에서 셰이더를 선언하는 데 사용되는 유형
    • LAYOUT_FIELD셰이더에 전달할 수 있는 균일 매개변수를 선언하는 데 사용
    • ModifyCompilationEnvironment셰이더에서 특정 매크로를 정의하는 데 사용
    • GetShaderBindings선언된 균일 매개변수를 바인딩하는 데 사용
  • 이렇게 하면 기본적으로 Shader 클래스 부분이 완성되지만, 해당 Shader 파일을 CPP 파일의 이 Shader 클래스에 바인딩하여 UE가 컴파일 후에 해당 Shader 파일을 찾을 수 있도록 하는 것을 잊지 마세요.

 

//OutlinePassRendering.cpp
IMPLEMENT_MATERIAL_SHADER_TYPE(, FOutlineVS, TEXT("/Engine/Private/OutlinePassShader.usf"), TEXT("MainVS"), SF_Vertex);
IMPLEMENT_MATERIAL_SHADER_TYPE(, FOutlinePS, TEXT("/Engine/Private/OutlinePassShader.usf"), TEXT("MainPS"), SF_Pixel);

 

  • 동시에 아웃라인을 위한 셰이더를 구현하기 위해 새로운 usf 파일을 생성합니다.

 

//OutlinePassShader.usf
#include "Common.ush"
#include "/Engine/Generated/Material.ush"
#include "/Engine/Generated/VertexFactory.ush"

struct FSimpleMeshPassVSToPS
{
	FVertexFactoryInterpolantsVSToPS FactoryInterpolants;
	float4 Position : SV_POSITION;
};

float OutLineScale;
float3 OutLineColor;

#if VERTEXSHADER
void MainVS(
	FVertexFactoryInput Input,
	out FSimpleMeshPassVSToPS Output)
{
	ResolvedView = ResolveView();
	
	FVertexFactoryIntermediates VFIntermediates = GetVertexFactoryIntermediates(Input);
	
	float4 WorldPos = VertexFactoryGetWorldPosition(Input, VFIntermediates);
	float3 WorldNormal = VertexFactoryGetWorldNormal(Input, VFIntermediates);
	
	float3x3 TangentToLocal = VertexFactoryGetTangentToLocal(Input, VFIntermediates);

	FMaterialVertexParameters VertexParameters = GetMaterialVertexParameters(Input, VFIntermediates, WorldPos.xyz, TangentToLocal);
	WorldPos.xyz += GetMaterialWorldPositionOffset(VertexParameters);
	WorldPos.xyz += WorldNormal * OutLineScale;
    
	float4 RasterizedWorldPosition = VertexFactoryGetRasterizedWorldPosition(Input, VFIntermediates, WorldPos);

	Output.FactoryInterpolants = VertexFactoryGetInterpolantsVSToPS(Input, VFIntermediates, VertexParameters);
	Output.Position = mul(RasterizedWorldPosition, ResolvedView.TranslatedWorldToClip);

	float2 ExtentDir = normalize(mul(float4(WorldNormal, 1.0f), ResolvedView.TranslatedWorldToClip).xy);
	float Scale = clamp(0.0f, 0.5f, Output.Position.w * 1.0f * 0.1f);
	Output.Position.xy += ExtentDir * Scale;
}
#endif // VERTEXSHADER

void MainPS(
	FSimpleMeshPassVSToPS Input,
	out float4 OutColor : SV_Target0)
{
	OutColor = float4(OutLineColor, 1.0);
}

 

  • 다음은 프로세서에서 세 가지 기능을 구현하는 것입니다.
  • 마찬가지로 다음 구현은 머티리얼에서 아직 완료되지 않은 인터페이스 부분을 주석 처리합니다.

 

//OutlinePassRendering.cpp
FOutlinePassProcessor::FOutlinePassProcessor(
    const FScene* Scene,
    const FSceneView* InViewIfDynamicMeshCommand,
    const FMeshPassProcessorRenderState& InPassDrawRenderState,
    FMeshPassDrawListContext* InDrawListContext)
:FMeshPassProcessor(Scene, Scene->GetFeatureLevel(), InViewIfDynamicMeshCommand, InDrawListContext),
PassDrawRenderState(InPassDrawRenderState)
{
    // PassDrawRenderState.SetViewUniformBuffer(Scene->UniformBuffers.ViewUniform);
    if (PassDrawRenderState.GetDepthStencilState() == nullptr)
    {
        PassDrawRenderState.SetDepthStencilState(TStaticDepthStencilState<false, CF_NotEqual>().GetRHI());
    }
    if (PassDrawRenderState.GetBlendState() == nullptr)
    {
        PassDrawRenderState.SetBlendState(TStaticBlendState<>().GetRHI());
    }
}

void FOutlinePassProcessor::AddMeshBatch(
    const FMeshBatch& MeshBatch,
    uint64 BatchElementMask,
    const FPrimitiveSceneProxy* PrimitiveSceneProxy,
    int32 StaticMeshId)
{
    const FMaterialRenderProxy* MaterialRenderProxy = MeshBatch.MaterialRenderProxy;
    const FMaterialRenderProxy* FallBackMaterialRenderProxyPtr = nullptr;
    const FMaterial* Material = MaterialRenderProxy->GetMaterialNoFallback(FeatureLevel);

    // only set in Material will draw outline
    if (Material != nullptr && Material->GetRenderingThreadShaderMap() /*&& Material->HasOutline()*/)
    {
        // Determine the mesh's material and blend mode.
        const EBlendMode BlendMode = Material->GetBlendMode();

        bool bResult = true;
        if (BlendMode == BLEND_Opaque)
        {
            Process(
                MeshBatch,
                BatchElementMask,
                StaticMeshId,
                PrimitiveSceneProxy,
                *MaterialRenderProxy,
                *Material,
                FM_Solid,
                CM_CCW);
        }
    }
}

bool FOutlinePassProcessor::Process(
    const FMeshBatch& MeshBatch,
    uint64 BatchElementMask,
    int32 StaticMeshId,
    const FPrimitiveSceneProxy* PrimitiveSceneProxy,
    const FMaterialRenderProxy& MaterialRenderProxy,
    const FMaterial& RESTRICT MaterialResource,
    ERasterizerFillMode MeshFillMode,
    ERasterizerCullMode MeshCullMode)
{
    const FVertexFactory* VertexFactory = MeshBatch.VertexFactory;

    TMeshProcessorShaders<FOutlineVS, FOutlinePS> OutlineShaders;

    {
        FMaterialShaderTypes ShaderTypes;
        ShaderTypes.AddShaderType<FOutlineVS>();
        ShaderTypes.AddShaderType<FOutlinePS>();

        const FVertexFactoryType* VertexFactoryType = VertexFactory->GetType();

        FMaterialShaders Shaders;
        if (!MaterialResource.TryGetShaders(ShaderTypes, VertexFactoryType, Shaders))
        {
            UE_LOG(LogShaders, Warning, TEXT("Shader Not Found!"));
            return false;
        }

        Shaders.TryGetVertexShader(OutlineShaders.VertexShader);
        Shaders.TryGetPixelShader(OutlineShaders.PixelShader);
    }


    FMeshMaterialShaderElementData ShaderElementData;
    ShaderElementData.InitializeMeshMaterialData(ViewIfDynamicMeshCommand, PrimitiveSceneProxy, MeshBatch, StaticMeshId, false);

    const FMeshDrawCommandSortKey SortKey = CalculateMeshStaticSortKey(OutlineShaders.VertexShader, OutlineShaders.PixelShader);

    PassDrawRenderState.SetDepthStencilState(
    TStaticDepthStencilState<
    true, CF_GreaterEqual,// Enable DepthTest, It reverse about OpenGL(which is less)
    false, CF_Never, SO_Keep, SO_Keep, SO_Keep,
    false, CF_Never, SO_Keep, SO_Keep, SO_Keep,// enable stencil test when cull back
    0x00,// disable stencil read
    0x00>// disable stencil write
    ::GetRHI());
    PassDrawRenderState.SetStencilRef(0);

    BuildMeshDrawCommands(
        MeshBatch,
        BatchElementMask,
        PrimitiveSceneProxy,
        MaterialRenderProxy,
        MaterialResource,
        PassDrawRenderState,
        OutlineShaders,
        MeshFillMode,
        MeshCullMode,
        SortKey,
        EMeshPassFeatures::Default,
        ShaderElementData
    );

    return true;
}

 

  • 이 코드는 복잡해 보이지만 실제로는 주로 다음 기능을 완료합니다.
    • 생성자는 주로 렌더링 상태의 재설정 및 지우기를 완료합니다.
    • AddMeshBatchPass에서 그려야 할 Mesh를 수집하는 동시에 Process주요 기능을 구현하기 위한 함수를 호출하는데 주로 사용됩니다.
    • ProcessShader에서는 렌더링 상태 설정, BuildMeshDrawCommands구성 렌더링 명령 호출등 주로 다음 작업이 수행되며 이러한 렌더링 명령은 우리가 RDG에서 궁극적으로 실행하는 렌더링 명령입니다.
  • UE는 그래픽 렌더링 구현에 대해 많은 캡슐화를 수행했지만 동시에 자체 파이프라인의 복잡성으로 인해 자체 엔지니어링 구현이 매우 무겁습니다.
  • 그러나 위에서 언급한 프로세서를 구현한 후에는 렌더링에 필요한 것 기본적으로 준비는 끝났습니다.
  • 구현 과정에서 그래픽 API와 관련된 개념을 접하지 않고 대신 Mesh 처리와 렌더링 상태 처리에만 집중했다는 것을 알 수 있습니다.
  • 그래서 이 UE 세트 제 생각에는 파이프라인 캡슐화에는 장점과 단점이 모두 있는데, 양날의 검이라고 볼 수 있습니다.
  • 하지만 이것만으로는 이 패스를 실행하기에는 턱없이 부족하며, 다른 것들도 추가해야 합니다.
  • 첫 번째는 이 프로세서의 등록입니다.

 

//OutlinePassRendering.cpp
void SetupOutlinePassState(FMeshPassProcessorRenderState& DrawRenderState)
{
    DrawRenderState.SetDepthStencilState(TStaticDepthStencilState<true, CF_LessEqual>().GetRHI());
}

FMeshPassProcessor* CreateOutlinePassProcessor(const FScene* Scene, const FSceneView* InViewIfDynamicMeshCommand, FMeshPassDrawListContext* InDrawListContext)
{
    FMeshPassProcessorRenderState OutlinePassState;
    SetupOutlinePassState(OutlinePassState);
    return new FOutlinePassProcessor(Scene, InViewIfDynamicMeshCommand, OutlinePassState, InDrawListContext);
}

FRegisterPassProcessorCreateFunction RegisterOutlinePass(&CreateOutlinePassProcessor, EShadingPath::Deferred, EMeshPass::OutlinePass, EMeshPassFlags::CachedMeshCommands | EMeshPassFlags::MainView);

 

  • 등록이 완료되면 UE는 프로세서의 인스턴스 개체를 직접 구성할 필요 없이 프로세서를 생성합니다.
  • 다음으로 DeferredShadingRenderer에서 Render 함수를 선언합니다.

 

//DeferredShadingRenderer.h
void RenderPrePass(FRDGBuilder& GraphBuilder, FRDGTextureRef SceneDepthTexture, FInstanceCullingManager& InstanceCullingManager);
void RenderPrePassHMD(FRDGBuilder& GraphBuilder, FRDGTextureRef SceneDepthTexture);

void RenderOutlinePass(FRDGBuilder& GraphBuilder, FSceneTextures& SceneTextures);

void RenderFog(
    FRDGBuilder& GraphBuilder,
    const FMinimalSceneTextures& SceneTextures,
    FRDGTextureRef LightShaftOcclusionTexture);

 

  • Render 기능을 프로세서의 CPP 파일에 넣습니다.
  • RDG에서 그리는 데 필요한 View 및 SceneTexture를 전달해야 하므로 필요한 매개변수를 전달하려면 여기에서 균일 버퍼를 정의하기 위해 Shader 매크로를 사용해야 합니다.

 

//OutlinePassRendering.cpp
DECLARE_CYCLE_STAT(TEXT("OutlinePass"), STAT_CLP_OutlinePass, STATGROUP_ParallelCommandListMarkers);

BEGIN_SHADER_PARAMETER_STRUCT(FOutlineMeshPassParameters, )
    SHADER_PARAMETER_STRUCT_REF(FViewUniformShaderParameters, View)
    SHADER_PARAMETER_STRUCT_INCLUDE(FInstanceCullingDrawParams, InstanceCullingDrawParams)
    RENDER_TARGET_BINDING_SLOTS()
END_SHADER_PARAMETER_STRUCT()

FOutlineMeshPassParameters* GetOutlinePassParameters(FRDGBuilder& GraphBuilder, const FViewInfo& View, FSceneTextures& SceneTextures)
{
    FOutlineMeshPassParameters* PassParameters = GraphBuilder.AllocParameters<FOutlineMeshPassParameters>();
    PassParameters->View = View.ViewUniformBuffer;

    PassParameters->RenderTargets[0] = FRenderTargetBinding(SceneTextures.Color.Target, ERenderTargetLoadAction::ELoad);
    PassParameters->RenderTargets.DepthStencil = FDepthStencilBinding(SceneTextures.Depth.Target, ERenderTargetLoadAction::ELoad, ERenderTargetLoadAction::ELoad, FExclusiveDepthStencil::DepthWrite_StencilWrite);

    return PassParameters;
}


void FDeferredShadingSceneRenderer::RenderOutlinePass(FRDGBuilder& GraphBuilder, FSceneTextures& SceneTextures)
{
    RDG_EVENT_SCOPE(GraphBuilder, "OutlinePass");
    RDG_CSV_STAT_EXCLUSIVE_SCOPE(GraphBuilder, RenderOutlinePass);

    SCOPED_NAMED_EVENT(FDeferredShadingSceneRenderer_RenderOutlinePass, FColor::Emerald);

    for(int32 ViewIndex = 0; ViewIndex < Views.Num(); ++ViewIndex)
    {
        FViewInfo& View = Views[ViewIndex];
        RDG_GPU_MASK_SCOPE(GraphBuilder, View.GPUMask);
        RDG_EVENT_SCOPE_CONDITIONAL(GraphBuilder, Views.Num() > 1, "View%d", ViewIndex);

        const bool bShouldRenderView = View.ShouldRenderView();
        if(bShouldRenderView)
        {
            FOutlineMeshPassParameters* PassParameters = GetOutlinePassParameters(GraphBuilder, View, SceneTextures);

            View.ParallelMeshDrawCommandPasses[EMeshPass::OutlinePass].BuildRenderingCommands(GraphBuilder, Scene->GPUScene, PassParameters->InstanceCullingDrawParams);

            GraphBuilder.AddPass(
                RDG_EVENT_NAME("OutlinePass"),
                PassParameters,
                ERDGPassFlags::Raster | ERDGPassFlags::SkipRenderPass,
                [this, &View, PassParameters](const FRDGPass* InPass, FRHICommandListImmediate& RHICmdList)
            {
                FRDGParallelCommandListSet ParallelCommandListSet(InPass, RHICmdList, GET_STATID(STAT_CLP_OutlinePass), *this, View, FParallelCommandListBindings(PassParameters));
                ParallelCommandListSet.SetHighPriority();
                SetStereoViewport(RHICmdList, View, 1.0f);
                View.ParallelMeshDrawCommandPasses[EMeshPass::OutlinePass].DispatchDraw(&ParallelCommandListSet, RHICmdList, &PassParameters->InstanceCullingDrawParams);
            });
        }
    }
}

 

  • 다음으로 가시적인 상관 함수에 렌더링 명령의 구성 조건을 추가해야 하며, 여기서는 함수 테스트를 용이하게 하기 위해 구현되지 않은 인터페이스 부분도 주석 처리합니다.
//SceneVisibility.cpp
// if (StaticMeshRelevance.bHasOutline)
{
    DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, Scene, bCanCache, EMeshPass::OutlinePass);
}

if (StaticMeshRelevance.bUseAnisotropy)
{
    DrawCommandPacket.AddCommandsForMesh(PrimitiveIndex, PrimitiveSceneInfo, StaticMeshRelevance, StaticMesh, Scene, bCanCache, EMeshPass::AnisotropyPass);
}

 

  • 마지막으로 Render main 함수에 방금 구현한 Render 함수를 호출하면 추가된 Custom Pass가 실행되는 것을 확인할 수 있습니다.

 

//DeferredShadingRenderer.cpp
RenderOutlinePass(GraphBuilder, SceneTextures);

AddSubsurfacePass(GraphBuilder, SceneTextures, Views);

Strata::AddStrataOpaqueRoughRefractionPasses(GraphBuilder, SceneTextures, Views);

{
    RenderHairStrandsSceneColorScattering(GraphBuilder, SceneTextures.Color.Target, Scene, Views);
}

 

  • 현 시점에서 아웃라인 그리기 기능은 분명히 불완전하므로 다음 기사에서는 이 기능에 맞게 편집기를 사용자 정의하는 방법에 대해 계속 설명하겠습니다.

 

참고자료

아래 참고 자료를 바탕으로 구글 번역하였습니다.

 

https://eukky.github.io/GameDev/UnrealEngine/OutlineDrawPass.html

 

UE5中的卡通渲染——自定义描边Pass | META TECH

 

eukky.github.io

https://zhuanlan.zhihu.com/p/552283835

 

UE5 Add Custom MeshDrawPass

5.0.3版本,已经落后,有空更新5.1版操作 UE5 添加自定义Pass 解决“如何开始渲染”的问题 文章内容属于初版,但效果是对的,有任何问题后续更新。 图示所用模型是绝区零的妮寇,侵删。先放

zhuanlan.zhihu.com

https://zhuanlan.zhihu.com/p/565776677

 

UE5 Add Custom Variables in Material

5.3版本翻新 修改UE材质球的源码,用于为美术人员提供更方便的自定义接口一、原理解析在开始之前,先不急于操作,来看看UE材质代码实现,如下两张图: 结合上图,有两点要先理解: 其一,

zhuanlan.zhihu.com

 

728x90
반응형