TECHARTNOMAD | TECHARTFLOWIO.COM

UNITY3D

DirectBRDF_DualLobeSpecular

jplee 2025. 6. 20. 02:11
half3 DirectBRDF_DualLobeSpecular(BRDFData brdfData, half3 normalWS, half3 lightDirectionWS, half3 viewDirectionWS,half mask, half lobeWeight)
{
    float3 halfDir = SafeNormalize(float3(lightDirectionWS) + float3(viewDirectionWS));
 
    float NoH = saturate(dot(normalWS, halfDir));
    half LoH = saturate(dot(lightDirectionWS, halfDir));
 
    float d = NoH * NoH * brdfData.roughness2MinusOne + 1.00001f;
    half nv = saturate(dot(normalWS,lightDirectionWS));
    half LoH2 = LoH * LoH;
    float sAO = saturate(-0.3f + nv * nv);
    sAO =  lerp(pow(0.75, 8.00f), 1.0f, sAO);
    half SpecularOcclusion = sAO;
    half specularTermGGX = brdfData.roughness2 / ((d * d) * max(0.1h, LoH2) * brdfData.normalizationTerm);
    half specularTermBeckMann =
        (2.0 * (brdfData.roughness2) /
        ((d * d) * max(0.1h, LoH2) * brdfData.normalizationTerm)) * lobeWeight * mask;
    half specularTerm = (specularTermGGX / 2 + specularTermBeckMann) * SpecularOcclusion ;
 
    // On platforms where half actually means something, the denominator has a risk of overflow
    // clamp below was added specifically to "fix" that, but dx compiler (we convert bytecode to metal/gles)
    // sees that specularTerm have only non-negative terms, so it skips max(0,..) in clamp (leaving only min(100,...))
    #if defined (SHADER_API_MOBILE) || defined (SHADER_API_SWITCH)
    specularTerm = specularTerm - HALF_MIN;
    specularTerm = clamp(specularTerm, 0.0, 100.0); // Prevent FP16 overflow on mobiles
    #endif
 
    half3 color = specularTerm * brdfData.specular;
    return color;
}

듀얼 로브 스페큘러 BRDF를 계산하는 셰이더 코드다. BRDF는 빛이 표면에서 어떻게 반사되는지를 수학적으로 모델링하는 함수로, 실시간 렌더링에서 사실적인 재질 표현을 위해 사용한다.

함수의 핵심 구조

입력 매개변수들:

  • brdfData: 거칠기, 스페큘러 색상 등 재질 정보
  • normalWS: 표면의 법선 벡터 (월드 스페이스)
  • lightDirectionWS: 빛의 방향
  • viewDirectionWS: 시점 방향
  • mask: 마스킹 값
  • lobeWeight: 로브 가중치

단계별 계산 과정

1. 하프 벡터 계산

float3 halfDir = SafeNormalize(float3(lightDirectionWS) + float3(viewDirectionWS));

빛의 방향과 시점 방향의 중간 벡터를 구한다. 이는 마이크로패싯 이론에서 반사가 일어나는 방향을 나타낸다.

2. 내적 계산

float NoH = saturate(dot(normalWS, halfDir));
half LoH = saturate(dot(lightDirectionWS, halfDir));
  • NoH: 법선과 하프벡터 사이의 각도
  • LoH: 빛 방향과 하프벡터 사이의 각도

3. 스페큘러 오클루전 계산

half nv = saturate(dot(normalWS,lightDirectionWS));
float sAO = saturate(-0.3f + nv * nv);
sAO = lerp(pow(0.75, 8.00f), 1.0f, sAO);

표면의 기하학적 특성에 따른 빛의 차폐 효과를 근사한다. 얕은 각도에서 들어오는 빛이 표면의 미세한 요철에 의해 가려지는 현상을 시뮬레이션한다.

4. 듀얼 로브 계산

GGX 분포:

half specularTermGGX = brdfData.roughness2 / ((d * d) * max(0.1h, LoH2) * brdfData.normalizationTerm);

Beckmann 분포:

half specularTermBeckMann = (2.0 * (brdfData.roughness2) / ((d * d) * max(0.1h, LoH2) * brdfData.normalizationTerm)) * lobeWeight * mask;

이 함수는 GGX와 Beckmann 두 가지 마이크로패싯 분포를 조합한다. GGX는 현실적인 반사를 제공하고, Beckmann은 추가적인 제어를 위해 사용한다.

5. 최종 조합

half specularTerm = (specularTermGGX / 2 + specularTermBeckMann) * SpecularOcclusion;

두 분포를 가중 평균하고 스페큘러 오클루전을 적용한다.

6. 모바일 최적화

#if defined (SHADER_API_MOBILE) || defined (SHADER_API_SWITCH)
specularTerm = specularTerm - HALF_MIN;
specularTerm = clamp(specularTerm, 0.0, 100.0);
#endif

모바일 GPU의 16비트 부동소수점 한계를 고려하여 오버플로우를 방지한다.

활용 목적

이 듀얼 로브 접근법은 단일 분포로는 표현하기 어려운 복잡한 재질의 반사 특성을 모델링한다. 예를 들어 금속 표면의 거친 반사와 부드러운 반사를 동시에 표현하거나, 클리어코트가 있는 페인트 같은 다층 재질을 시뮬레이션할 때 유용하다.

결과적으로 이 함수는 물리 기반 렌더링에서 더욱 사실적이고 세밀한 스페큘러 반사를 구현하기 위한 고급 BRDF 모델이라 할 수 있다.

원작자 정보

이 코드는 leegoonz(JP)가 개발한 것으로, Unity URP용 듀얼 로브 스킨 셰이더 기법의 일부다.

leegoonz의 블로그 링크

 

[Mobile shader] Dual Lobe GGX works

Unity3D URP Dual Lobe GGX works Dual Lobe Debug view. // Specular term half perceptualRoughness = SmoothnessToPerceptualRoughness(smoothness); half roughness = PerceptualRoughnessToRoughness(percep…

leegoonz.blog

 

 

Extra technique for Skin shader by URP

Dual Lobe BeckMann half3 DirectBDRFXD(BRDFData brdfData, BRDFDataXD brdfDataXD, half3 normalWS, half3 lightDirectionWS, half3 viewDirectionWS,half mask) { #ifndef _SPECULARHIGHLIGHTS_OFF float3 hal…

leegoonz.blog