[UE 5.7] Substrate 도입 이후의 Diffuse 모델 변경

2025. 12. 8. 17:15·언리얼엔진/그 외

Substrate가 정식 버전이 되면서 5.7 버전으로 시작하는 프로젝트에는 Substrate가 기본적으로 활성화 된다고 한다.

기존 셰이딩 모델은 Diffuse에 Lambert를 사용했지만, Substrate는 다른 모델을 사용한다는 글을 봐서 찾아봤다.

 

// Shaders/Private/Substrate/SubstrateEvaluation.ush

#if MATERIAL_ROUGHDIFFUSE
    if (Settings.bRoughDiffuseEnabled && any(DiffuseColor > 0))
    {
        Sample.DiffusePathValue = Diffuse_GGX_Rough(DiffuseColor, SafeRoughness, NoV, AreaLight.NoL, VoH, NoH, GetAreaLightDiffuseMicroReflWeight(AreaLight));
    }
    else
#endif
    {
        Sample.DiffusePathValue = Diffuse_Lambert(DiffuseColor);
    }

Material 설정의 Rough Diffuse는 기본적으로 비활성화 되어있지만, Substrate가 활성화 되면 이 옵션 또한 강제로 활성화 된다.

따라서 DiffuseColor가 float3(0.0, 0.0, 0.0)만 아니라면 Diffuse_GGX_Rough 함수로 디퓨즈를 계산한다.

 

// Shaders/Private/BRDF.ush

#define ROUGH_DIFFUSE_BRDF_VERSION 2
// This models a rough surface that has a GGX NDF where each microfacet has a lambertian response. Various models have been proposed
// to try and approximate this behavior.
float3 Diffuse_GGX_Rough( float3 DiffuseColor, float Roughness, float NoV, float NoL, float VoH, float NoH, float RetroReflectivityWeight )
{
    // We saturate each input to avoid out of range negative values which would result in weird darkening at the edge of meshes (resulting from tangent space interpolation).
    NoV = saturate(NoV);
    NoL = saturate(NoL);
    VoH = saturate(VoH);
    NoH = saturate(NoH);
#if ROUGH_DIFFUSE_BRDF_VERSION == 3
    // It turns out the EON model in the range [0, 0.4] is nearly a perfect match to a ground truth
    // simulation of diffuse microfacets oriented with a GGX NDF.
    float VoL = 2 * VoH * VoH - 1;		// double angle identity to keep signature above consistent with other models
    return Diffuse_EON(DiffuseColor, RetroReflectivityWeight * Roughness * 0.4, NoV, NoL, VoL);
#elif ROUGH_DIFFUSE_BRDF_VERSION == 2
    // [ Chan 2024, "Multiscattering Diffuse and Specular BRDFs", Unpublished manuscript ]
    Roughness *= RetroReflectivityWeight;
    const float Alpha = Roughness * Roughness;
    // The original writeup uses an FSmooth term inspired by Burley diffuse to balance energy between spec/diffuse.
    // However in our implementation the energy balance between diffuse and spec is handled externally, so we stick
    // to a plain lambertian for the Roughness=0 limit.
    const float FSmooth = 1;
    const float Scale = max(0.55 - 0.2 * Roughness, 1.25 - 1.6 * Roughness);
    const float Bias = saturate(4 * Alpha);
    const float FRough = Scale * (NoH + Bias) * rcp(NoH + 0.025) * VoH * VoH;
    const float DiffuseSS = lerp(FSmooth, FRough, Roughness);
    const float DiffuseMS = Alpha * 0.38;
    return (1 / PI) * DiffuseColor * (DiffuseSS + DiffuseMS);
#else
    // [ Chan 2018, "Material Advances in Call of Duty: WWII" ]
    // It has been extended here to fade out retro reflectivity contribution from area light in order to avoid visual artefacts.
    float a2 = Pow4(Roughness);
    // a2 = 2 / ( 1 + exp2( 18 * g )
    float g = saturate( (1.0 / 18.0) * log2( 2 * rcpFast(a2) - 1 ) );

    float F0 = VoH + Pow5( 1 - VoH );
    float FdV = 1 - 0.75 * Pow5( 1 - NoV );
    float FdL = 1 - 0.75 * Pow5( 1 - NoL );

    // Rough (F0) to smooth (FdV * FdL) response interpolation
    float Fd = lerp( F0, FdV * FdL, saturate( 2.2 * g - 0.5 ) );

    // Retro reflectivity contribution.
    float Fb = ( (34.5 * g - 59 ) * g + 24.5 ) * VoH * exp2( -max( 73.2 * g - 21.2, 8.9 ) * sqrtFast( NoH ) );
    // It fades out when lights become area lights in order to avoid visual artefacts.
    Fb *= RetroReflectivityWeight;

    float Lobe = (1 / PI) * (Fd + Fb);

    // We clamp the BRDF lobe value to an arbitrary value of 1 to get some practical benefits at high roughness:
    // - This is to avoid too bright edges when using normal map on a mesh and the local bases, L, N and V ends up in an top emisphere setup.
    // - This maintains the full proper rough look of a sphere when not using normal maps.
    // - This also fixes the furnace test returning too much energy at the edge of a mesh.
    Lobe = min(1.0, Lobe);

    return DiffuseColor * Lobe;
#endif
}

Diffuse_GGX_Rough 함수에서는 Chan 모델을 사용하고 있는걸 확인할 수 있다.

 

ROUGH_DIFFUSE_BRDF_VERSION 값은 기본적으로 2로 지정되어있고 Chan 2024 모델을,

3이라면 EON(Energy-Preserving Oren–Nayar) 모델을,

그 외의 값이면 Chan 2018 모델을 사용한다.

 

// Shaders/Private/BRDF.ush

// [Portsmouth et al. 2025, "EON: A Practical Energy-Preserving Rough Diffuse BRDF"]
float3 Diffuse_EON( float3 DiffuseColor, float Roughness, float NoV, float NoL, float VoL )
{
    // Albedo inversion for EON model to maintain a consistent color with lambert
    float3 Rho = DiffuseColor * (1.0 + (0.189468 - 0.189468 * DiffuseColor) * Roughness);

    // This is the main shaping term from the Oren-Nayar model (with tweaks by Fujii)
    float S = VoL - NoV * NoL;
    float SOverT = max(S * rcp(max(1e-6, max(NoV, NoL))), S);
    const float constant1_FON = 0.5f - 2.0f / (3.0f * PI);
    // AF = rcp(1 + Roughness * constant1_FON) is nearly a straight line, so approximate it as such
    float AF = 1 - Roughness * (1 - 1 / (1 + constant1_FON));
    float f_ss = AF * (1 + Roughness * SOverT);

    // 4th Order approximation from the paper is a bit too heavy, first order seems to work just as well
    const float g1 = 0.262048f;
    float GoverPi_V = g1 - g1 * NoV;
    // Use (1 - Eo) only as a non-reciprocal approach to energy conservation
    float f_ms = 1.0f - AF * (1 + Roughness * GoverPi_V);
    // The Rho_ms term from the paper can be approximated as just Rho^2
    return Rho * (f_ss + Rho * f_ms) * (1.0 / PI);
}

 

 

다음 커밋 기록을 보면, 이후 EON 모델로 바꿀 가능성이 있어 보인다.

https://github.com/EpicGames/UnrealEngine/commit/72c33452e9e6db428b72281571dcac7b16fe312b

 

 

'언리얼엔진 > 그 외' 카테고리의 다른 글

플러그인 호환 작업을 하며 언리얼 엔진에 기여한 경험  (0) 2026.01.31
[UE5] GameState의 PlayerArray가 동기화되는 방법  (1) 2024.09.09
[UE5] C++로 애니메이션 시퀀스 변경, 커브 추가  (0) 2024.07.03
[UE5] 캐릭터 무브먼트 컴포넌트 작동 순서 정리  (0) 2024.06.13
[UE5] USTRUCT의 NetSerialize  (1) 2024.03.12
'언리얼엔진/그 외' 카테고리의 다른 글
  • 플러그인 호환 작업을 하며 언리얼 엔진에 기여한 경험
  • [UE5] GameState의 PlayerArray가 동기화되는 방법
  • [UE5] C++로 애니메이션 시퀀스 변경, 커브 추가
  • [UE5] 캐릭터 무브먼트 컴포넌트 작동 순서 정리
mstone
mstone
프로젝트의 진행 상황과 공부하면서 기억해둬야 하는 내용을 정리해서 올립니다.
  • mstone
    // 주석
    mstone
  • 전체
    오늘
    어제
    • 분류 전체보기 (53)
      • 언리얼엔진 (35)
        • 노노그램 (16)
        • FPS 프로젝트 (10)
        • 그 외 (9)
      • 그래픽스 (2)
      • 게임테크랩 1기 (8)
        • Direct3D 11 엔진 (1)
        • Audio Tracing (7)
      • DirectX11 (7)
      • 취미 (1)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
    • 카테고리
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    에디터 유틸리티
    위젯
    NetSerialize
    DirectX11
    Nonogram Solver
    언리얼엔진
    캐릭터 무브먼트 컴포넌트
    Audio Tracing
    Unreal Engine 5
    게임플레이 태그
    루트 모션
    애니메이션 블루프린트 링크
    노노그램
    SCompundWidget
    슬레이트
    Direct3D 11
    FPS
    그래픽스
    애니메이션
    언리얼 엔진
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
mstone
[UE 5.7] Substrate 도입 이후의 Diffuse 모델 변경
상단으로

티스토리툴바