본문 바로가기
Shader

유니티 Lit 셰이더 PBR 뒤져보기2

by 대마왕J 2024. 10. 10.

앞에서는 

lightingData.mainLightColor = LightingPhysicallyBased(brdfData, brdfDataClearCoat,
                                                              mainLight,
                                                              inputData.normalWS, inputData.viewDirectionWS,
                                                              surfaceData.clearCoatMask, specularHighlightsOff);

이 부분이 PBR 에서의 Direct Light의 본체라는걸 알았습니다. 

inDirectLight는 

lightingData.giColor = GlobalIllumination(brdfData, brdfDataClearCoat, surfaceData.clearCoatMask,
                                              inputData.bakedGI, aoFactor.indirectAmbientOcclusion, inputData.positionWS,
                                              inputData.normalWS, inputData.viewDirectionWS, inputData.normalizedScreenSpaceUV);

이 부분인거구요. 

이 둘만 분석하면 다 끝나겠구만?
LightingPhysicallyBased 부터 봅시다.

 DirectLight 구문 분석 

LightingPhysicallyBased 를 봅니다. 

half3 LightingPhysicallyBased(BRDFData brdfData, BRDFData brdfDataClearCoat,
    half3 lightColor, half3 lightDirectionWS, float lightAttenuation,
    half3 normalWS, half3 viewDirectionWS,
    half clearCoatMask, bool specularHighlightsOff)
{
    half NdotL = saturate(dot(normalWS, lightDirectionWS));
    half3 radiance = lightColor * (lightAttenuation * NdotL);

    half3 brdf = brdfData.diffuse;
#ifndef _SPECULARHIGHLIGHTS_OFF
    [branch] if (!specularHighlightsOff)
    {
        brdf += brdfData.specular * DirectBRDFSpecular(brdfData, normalWS, lightDirectionWS, viewDirectionWS);

#if defined(_CLEARCOAT) || defined(_CLEARCOATMAP)
        // Clear coat evaluates the specular a second timw and has some common terms with the base specular.
        // We rely on the compiler to merge these and compute them only once.
        half brdfCoat = kDielectricSpec.r * DirectBRDFSpecular(brdfDataClearCoat, normalWS, lightDirectionWS, viewDirectionWS);

            // Mix clear coat and base layer using khronos glTF recommended formula
            // Use NoV for direct too instead of LoH as an optimization (NoV is light invariant).
            half NoV = saturate(dot(normalWS, viewDirectionWS));
            // Use slightly simpler fresnelTerm (Pow4 vs Pow5) as a small optimization.
            // It is matching fresnel used in the GI/Env, so should produce a consistent clear coat blend (env vs. direct)
            half coatFresnel = kDielectricSpec.x + kDielectricSpec.a * Pow4(1.0 - NoV);

        brdf = brdf * (1.0 - clearCoatMask * coatFresnel) + brdfCoat * clearCoatMask;
#endif // _CLEARCOAT
    }
#endif // _SPECULARHIGHLIGHTS_OFF

    return brdf * radiance;
}

요 있네. 역시 디파인에서 클리어코트랑 스페큘러 하이라이트가 있네요. 그것만 지우면 간단하겠는데? 지워보죠 

half3 LightingPhysicallyBased(BRDFData brdfData, BRDFData brdfDataClearCoat,
    half3 lightColor, half3 lightDirectionWS, float lightAttenuation,
    half3 normalWS, half3 viewDirectionWS,
    half clearCoatMask, bool specularHighlightsOff)
{
    half NdotL = saturate(dot(normalWS, lightDirectionWS));
    half3 radiance = lightColor * (lightAttenuation * NdotL);

    half3 brdf = brdfData.diffuse;
    brdf += brdfData.specular * DirectBRDFSpecular(brdfData, normalWS, lightDirectionWS, viewDirectionWS);

    return brdf * radiance;
}

오호호호 간단해졌다. 

그럼 이제 이게 DirectLight 구문이니까.. Diffuse와 Specular로 구분되어 있겠지요 
일단 radiance가 아래와 같이 구현되어 있습니다. 

half NdotL = saturate(dot(normalWS, lightDirectionWS));
    half3 radiance = lightColor * (lightAttenuation * NdotL);

그리고 diffuse과 specular를 곱해주고 

half3 brdf = brdfData.diffuse;
    brdf += brdfData.specular * DirectBRDFSpecular(brdfData, normalWS, lightDirectionWS, viewDirectionWS);

마지막에 이걸 radiance와 곱해주는 방식이죠.

 return brdf * radiance;

그럼 brdfData.diffuse 와 brdfData.specular 에 중요한게 있겠군요? 

BRDFData를 파봐야겠습니다. 이것은 또 

InitializeBRDFData(surfaceData, brdfData);

여기서 초기화되는 데이터이죠 
자 또 고구마 캐러 가자.. 

inline void InitializeBRDFData(half3 albedo, half metallic, half3 specular, half smoothness, inout half alpha, out BRDFData outBRDFData)
{
#ifdef _SPECULAR_SETUP
    half reflectivity = ReflectivitySpecular(specular);
    half oneMinusReflectivity = half(1.0) - reflectivity;
    half3 brdfDiffuse = albedo * oneMinusReflectivity;
    half3 brdfSpecular = specular;
#else
    half oneMinusReflectivity = OneMinusReflectivityMetallic(metallic);
    half reflectivity = half(1.0) - oneMinusReflectivity;
    half3 brdfDiffuse = albedo * oneMinusReflectivity;
    half3 brdfSpecular = lerp(kDielectricSpec.rgb, albedo, metallic);
#endif

    InitializeBRDFDataDirect(albedo, brdfDiffuse, brdfSpecular, reflectivity, oneMinusReflectivity, smoothness, alpha, outBRDFData);
}

여기서 _SPECULAR_SETUP 은 물리기반에서 벗어나는 방식이니까 필요가 없죠. 
그러면 이렇게 봐야지.. 

inline void InitializeBRDFData(half3 albedo, half metallic, half3 specular, half smoothness, inout half alpha, out BRDFData outBRDFData)
{
 
    half oneMinusReflectivity = OneMinusReflectivityMetallic(metallic);
    half reflectivity = half(1.0) - oneMinusReflectivity;
    half3 brdfDiffuse = albedo * oneMinusReflectivity;
    half3 brdfSpecular = lerp(kDielectricSpec.rgb, albedo, metallic);

    InitializeBRDFDataDirect(albedo, brdfDiffuse, brdfSpecular, reflectivity, oneMinusReflectivity, smoothness, alpha, outBRDFData);
}

이제 고구마 줄기에서 벗어나서 하나하나 볼 게 생겼네요 
oneMinusReflectivity 는 OneMinusReflectivityMetallic(metallic) 입니다. 

half OneMinusReflectivityMetallic(half metallic)
{
    // We'll need oneMinusReflectivity, so
    //   1-reflectivity = 1-lerp(dielectricSpec, 1, metallic) = lerp(1-dielectricSpec, 0, metallic)
    // store (1-dielectricSpec) in kDielectricSpec.a, then
    //   1-reflectivity = lerp(alpha, 0, metallic) = alpha + metallic*(0 - alpha) =
    //                  = alpha - metallic * alpha
    half oneMinusDielectricSpec = kDielectricSpec.a;
    return oneMinusDielectricSpec - metallic * oneMinusDielectricSpec;
}

이니까 oneMinusReflectivity  은 kDielectricSpec.a 
여기서 kDielectricSpec은 상수로 정의되어 있습니다. 

#define kDielectricSpec half4(0.04, 0.04, 0.04, 1.0 - 0.04) // standard dielectric reflectivity coef at incident angle (= 4%)

후후후후 여깄었구나? 우리 작은 아기새 

0.04 가 RGB에 들어가 있고 1-0.04가 a 에 들어가 있어요. 
0.04란 4% 의 의미죠. 
// standard dielectric reflectivity coef at incident angle (= 4%) 라고 되어 있는 부분의 뜻은
표준적인 유전체의 반사율 계수는 입사각에서 약 4%이라는 말이예요 

그러니까 비메탈릭(유전체) 물체에서 4% 정도는 스페큘러 수치라는 말이예요. 그러니 96%는 디퓨즈 반사 수치라는 말이죠. 1-0.04가 a에 들어가 있으니 이 a 채널에 diffuse 반사양이 들어가 있다는 말예요. 수치로는 9.96 
그리고 oneMinusDielectricSpec - metallic * oneMinusDielectricSpec; 이니까 metallic이 1이 되면 이 수치가 0이 되어요. 
즉 메탈릭에서는 diffuse가 0%라는 뜻이죠. 
비메탈에서 diffuse는 96% 로 출력한다는 뜻이고요. (Diffuse가 공평하게 사방으로 반사한다 쳐도 4% 는 반드시 정반사가 된다는 말이니까) 

즉 OneMinusReflectivityMetallic 함수는 diffuse 양을 구해주는 함수예요. 비메탈이면 0.96, 메탈이면 0이 나오죠 
half oneMinusReflectivity = OneMinusReflectivityMetallic(metallic);

즉 oneMinusReflectivity 에 이 값이 들어가죠. oneMinusReflectivity 라는 이름처럼 '역정반사율' 이예요. 
당연히 그 다음줄의  half reflectivity = half(1.0) - oneMinusReflectivity; 을 보면 
reflectivity 는 '정반사율' 이 나오겠죠. 이걸로 메탈이냐 아니냐 값이 float으로 나오게 될거예요 

다음에는 

    half3 brdfDiffuse = albedo * oneMinusReflectivity;
    half3 brdfSpecular = lerp(kDielectricSpec.rgb, albedo, metallic);

군요. 

brdfDiffuse 에는 albedo가 비메탈일 경우0.96 이 곱해지고, 메탈일 경우 0 이 곱해지겠네요 
brdfSpecular 에는 비메탈일 경우 kDielectricSpec.rgb 니까 float3(0.04, 0.04, 0.04) 즉 매우 어두운 스페큘러 색상 (1의 양의 빛이 들어와도 4% 만 반사했을테니 밝기도 4% 겠지요) 이겠고, 메탈일 경우 specular에 100% albedo가 출력되는 것을 알 수 있죠. 

이건.. DirectLight 뿐만 아니라 inDirectLight 에도 사용할 수 있겠군요 
그리고 그 다음줄에 

InitializeBRDFDataDirect(albedo, brdfDiffuse, brdfSpecular, reflectivity, oneMinusReflectivity, smoothness, alpha, outBRDFData);

가 되어 있군요. 여기서 다이렉트 라이트 부분을 한 번 더 초기화해주네요. 
위에는 인다이렉트에도 쓰려고 분리해놓은 모양. 
보면 이런 모양이예요. 

inline void InitializeBRDFDataDirect(half3 albedo, half3 diffuse, half3 specular, half reflectivity, half oneMinusReflectivity, half smoothness, inout half alpha, out BRDFData outBRDFData)
{
    outBRDFData = (BRDFData)0;
    outBRDFData.albedo = albedo;
    outBRDFData.diffuse = diffuse;
    outBRDFData.specular = specular;
    outBRDFData.reflectivity = reflectivity;

    outBRDFData.perceptualRoughness = PerceptualSmoothnessToPerceptualRoughness(smoothness);
    outBRDFData.roughness           = max(PerceptualRoughnessToRoughness(outBRDFData.perceptualRoughness), HALF_MIN_SQRT);
    outBRDFData.roughness2          = max(outBRDFData.roughness * outBRDFData.roughness, HALF_MIN);
    outBRDFData.grazingTerm         = saturate(smoothness + reflectivity);
    outBRDFData.normalizationTerm   = outBRDFData.roughness * half(4.0) + half(2.0);
    outBRDFData.roughness2MinusOne  = outBRDFData.roughness2 - half(1.0);

    // Input is expected to be non-alpha-premultiplied while ROP is set to pre-multiplied blend.
    // We use input color for specular, but (pre-)multiply the diffuse with alpha to complete the standard alpha blend equation.
    // In shader: Cs' = Cs * As, in ROP: Cs' + Cd(1-As);
    // i.e. we only alpha blend the diffuse part to background (transmittance).
    #if defined(_ALPHAPREMULTIPLY_ON)
        // TODO: would be clearer to multiply this once to accumulated diffuse lighting at end instead of the surface property.
        outBRDFData.diffuse *= alpha;
    #endif
}

오.. 다이렉트 라이트 연산을 할 때 사용할 값들이 들어있군요 outBRDFData 에 여러 값을 넣어줘요. 러프니스쪽 값이 계산되어 있고 일반 알베도나 디퓨즈들도 여기 있네요. 

 

 

 

반응형

댓글