앞에서는
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와 곱해주는 방식이죠.
그럼 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 에 여러 값을 넣어줘요. 러프니스쪽 값이 계산되어 있고 일반 알베도나 디퓨즈들도 여기 있네요.
댓글