Mecanim 发表于 2022-9-1 17:32

Unity URP(PBR源码分析)

PBR工作流

URP管线的PBR有两个工作流,分别是:

[*]Metallic(金属工作流)
[*]Specular(高光工作流)
Metallic(金属工作流)


[*]BaseMap:表示物体颜色,最终用来计算物体表面的漫反射颜色和镜面反射颜色。
[*]MetallicMap:表示金属度,范围0-1,最终用来计算物体表面的漫反射率和镜面反射率。
[*]Smoothness:表示光滑度,范围0-1,最终用来计算物体表面的粗糙度。
//universal/ShaderLibrary/BRDF.hlsl

#define kDielectricSpec half4(0.04, 0.04, 0.04, 1.0 - 0.04)

//计算漫反射率和镜面反射率
half oneMinusReflectivity = OneMinusReflectivityMetallic(metallic); //计算漫反射率,范围0.96-0
half reflectivity = half(1.0) - oneMinusReflectivity; //计算反射率,范围0.04-1
half3 brdfDiffuse = albedo * oneMinusReflectivity; //计算漫反射颜色,oneMinusReflectivity为漫反射率
half3 brdfSpecular = lerp(kDieletricSpec.rgb, albedo, metallic); //计算镜面反射颜色,范围0.04-albedo
InitializeBRDFDataDirect(albedo, brdfDiffuse, brdfSpecular, reflectivity, oneMinusReflectivity, smoothness, alpha, outBRDFData);

//计算漫反射率(metallic范围0-1,计算结果为0.96-0)
half OneMinusReflectivityMetallic(half metallic)
{
    half oneMinusDielectricSpec = kDielectricSpec.a;
    return oneMinusDielectricSpec - metallic * oneMinusDielectricSpec;
}漫反射率 = 0.96 - metallic * 0.96
高光反射率 = 1 - 漫反射率
漫反射颜色 = BaseMap * 漫反射率
高光反射颜色 = lerp(half4(0.04, 0.04, 0.04, 0.96), BaseMap, metallic)
注意1:当Metallic为0时,高光反射率为0.04(所以依然会有微弱的高光)。
注意2:当Metallic为1时,高光反射率为1,漫反射颜色为黑色,高光的颜色就是BaseMap,这也符合金属的性质(金属的高光颜色就是金属自身的颜色,和灯光颜色无关)。
Specular(高光工作流)


[*]BaseMap:表示物体颜色,最终用来计算物体表面的漫反射颜色和镜面反射颜色。
[*]SpecularMap:表示高光,最终用来计算物体表面的漫反射率和镜面反射率。
[*]Smoothness:表示光滑度,范围0-1,最终用来计算物体表面的粗糙度。
//universal/ShaderLibrary/BRDF.hlsl

#define kDielectricSpec half4(0.04, 0.04, 0.04, 1.0 - 0.04)

//计算漫反射率和镜面反射率
half reflectivity = ReflectivitySpecular(specular); //计算镜面反射率,范围0-1
half oneMinusReflectivity = half(1.0) - reflectivity; //计算漫反射率,范围0-1
half3 brdfDiffuse = albedo * (half3(1.0, 1.0, 1.0) - specular); //计算漫反射颜色
half3 brdfSpecular = specular; //计算镜面反射颜色
InitializeBRDFDataDirect(albedo, brdfDiffuse, brdfSpecular, reflectivity, oneMinusReflectivity, smoothness, alpha, outBRDFData);

//计算反射率(GLES就用简单的算法,否则使用较复杂的算法)
half ReflectivitySpecular(half3 specular)
{
#if defined(SHADER_API_GLES)
    return specular.r; // Red channel - because most metals are either monocrhome or with redish/yellowish tint
#else
    return Max3(specular.r, specular.g, specular.b);
#endif
}高光反射率 = Max3(specular.r, specular.g, specular.b)
漫反射率 = 1 - 高光反射率
漫反射颜色 = BaseMap * (half3(1.0, 1.0, 1.0) - specular)
高光反射颜色 = specular
注意:当specular为黑色时,高光颜色为黑色,漫反射颜色为BaseMap的颜色,当specular为1时,高光颜色为specular颜色,漫反射颜色为黑色。
计算BRDFData

经过如上计算,我们获得了漫反射率、高光反射率、漫反射颜色、高光反射颜色,接下来Unity会根据这些数据构建一个数据结构BRDFData,用来进行接下来的BRDF计算。
//universal/ShaderLibrary/BRDF.hlsl

// 根据之前结算的漫反射率、高光反射率、漫反射颜色、高光反射颜色,光滑度,计算一个数据结构,用来计算BRDF。
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);//1-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);

#ifdef _ALPHAPREMULTIPLY_ON
    outBRDFData.diffuse *= alpha;
    alpha = alpha * oneMinusReflectivity + reflectivity; // NOTE: alpha modified and propagated up.
#endif
}

/////////////////////////////////////////////////////////////////////////////
core/ShaderLibrary/CommonMaterial.hlsl

// 计算粗糙度
real PerceptualSmoothnessToPerceptualRoughness(real perceptualSmoothness)
{
    return (1.0 - perceptualSmoothness);
}

// 计算粗糙度的平方
real PerceptualRoughnessToRoughness(real perceptualRoughness)
{
    return perceptualRoughness * perceptualRoughness;
}

/////////////////////////////////////////////////////////////////////////////
core/ShaderLibrary\Macros.hlsl

#define HALF_MIN_SQRT 0.0078125注意:Unity用粗糙度的平方作为粗糙度roughness,用粗糙度的4次方作为roughness2。
计算高光BRDF

Unity的高光BRDF使用了CookTorrance模型,其由表面法线分布D、集合遮蔽V、菲涅耳反射F组成,下面三个公式为Unity简化版。

[*]BRDF = \frac{DVF}{4}
[*]D = \frac{roughness^2}{( NH^2 \cdot (roughness^2 - 1) + 1 )^2}
[*]V \cdot F = \frac{1.0}{ LH^2 \cdot (roughness + 0.5) }
// universal/ShaderLibrary/BRDF.hlsl

// 真正计算BRDF
// Computes the scalar specular term for Minimalist CookTorrance BRDF
// NOTE: needs to be multiplied with reflectance f0, i.e. specular color to complete
half DirectBRDFSpecular(BRDFData brdfData, half3 normalWS, half3 lightDirectionWS, half3 viewDirectionWS)
{
    float3 lightDirectionWSFloat3 = float3(lightDirectionWS);
    float3 halfDir = SafeNormalize(lightDirectionWSFloat3 + float3(viewDirectionWS));

    float NoH = saturate(dot(float3(normalWS), halfDir));
    half LoH = half(saturate(dot(lightDirectionWSFloat3, halfDir)));

    // GGX Distribution multiplied by combined approximation of Visibility and Fresnel
    // BRDFspec = (D * V * F) / 4.0
    // D = roughness^2 / ( NoH^2 * (roughness^2 - 1) + 1 )^2
    // V * F = 1.0 / ( LoH^2 * (roughness + 0.5) )
    // See "Optimizing PBR for Mobile" from Siggraph 2015 moving mobile graphics course
    // https://community.arm.com/events/1155

    // Final BRDFspec = roughness^2 / ( NoH^2 * (roughness^2 - 1) + 1 )^2 * (LoH^2 * (roughness + 0.5) * 4.0)
    // We further optimize a few light invariant terms
    // brdfData.normalizationTerm = (roughness + 0.5) * 4.0 rewritten as roughness * 4.0 + 2.0 to a fit a MAD.
    float d = NoH * NoH * brdfData.roughness2MinusOne + 1.00001f;
    half d2 = half(d * d);

    half LoH2 = LoH * LoH;
    half specularTerm = brdfData.roughness2 / (d2 * max(half(0.1), LoH2) * brdfData.normalizationTerm);

    // 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

return specularTerm;
}

// core/ShaderLibrary\Macros.hlsl

#define HALF_MIN 6.103515625e-5计算直接光

物体表面颜色(直接光)= 漫反射颜色 + 高光BRDF * 高光颜色
// Based on Minimalist CookTorrance BRDF
// Implementation is slightly different from original derivation: http://www.thetenthplanet.de/archives/255
//
// * NDF GGX
// * Modified Kelemen and Szirmay-Kalos for Visibility term
// * Fresnel approximated with 1/LdotH
half3 DirectBDRF(BRDFData brdfData, half3 normalWS, half3 lightDirectionWS, half3 viewDirectionWS, bool specularHighlightsOff)
{
    // Can still do compile-time optimisation.
    // If no compile-time optimized, extra overhead if branch taken is around +2.5% on some untethered platforms, -10% if not taken.
    if (!specularHighlightsOff)
    {
      half specularTerm = DirectBRDFSpecular(brdfData, normalWS, lightDirectionWS, viewDirectionWS);
      half3 color = brdfData.diffuse + specularTerm * brdfData.specular;
      return color;
    }
    else
      return brdfData.diffuse;
}

// Based on Minimalist CookTorrance BRDF
// Implementation is slightly different from original derivation: http://www.thetenthplanet.de/archives/255
//
// * NDF GGX
// * Modified Kelemen and Szirmay-Kalos for Visibility term
// * Fresnel approximated with 1/LdotH
half3 DirectBRDF(BRDFData brdfData, half3 normalWS, half3 lightDirectionWS, half3 viewDirectionWS)
{
#ifndef _SPECULARHIGHLIGHTS_OFF
    return brdfData.diffuse + DirectBRDFSpecular(brdfData, normalWS, lightDirectionWS, viewDirectionWS) * brdfData.specular;
#else
    return brdfData.diffuse;
#endif
}
页: [1]
查看完整版本: Unity URP(PBR源码分析)