【转载】(HDRP to URP) Base PBR

403 阅读5分钟

原文链接

(HDRP to URP)Base PBR | 水滴丿

原文写的不错,但是格式和代码主题我不太喜欢,所以我转载过来并将一些重点进行标注,并调整了一下格式

正文

前言

接触 Shader 这些东西也有个一两年了,跟着知乎大佬们学到了很多,也是时候写一些自己的东西了,以作为自己的笔记以及参考资料。

新人上路,大家多多关照,本系列着重编码(主要是怕水平不够理论部分讲不清楚),如有写错的地方感谢指出。


本系列在 Unity2020.3,URP10.5 下编写,兼容URP大部分功能,着色与贴图规则参考的是 Unity HDRP,金属流程,即 M_AO_D_SMap

  • R:Metallic,
  • G:AO
  • B:DetialMask,
  • A:Smoothness

整体结构

float4 HDRPLitFragment(InputCoord inputData, half3 albedo, half metallic, half smoothness, half3 normalTS, half3 emission, half occlusion, half alpha)
{
    Light mainLight = GetMainLight(inputData.shadowCoord, inputData.positionWS, inputData.shadowMask);
//  ------------------------------------------------------------- BRDF --------------------------------------------------
    BRDF brdfData;
    InitializationBRDFData(brdfData, albedo, metallic, smoothness);

    DirectLighting directLighting;
    PhysicallyBaseDirectLighting(brdfData, mainLight, directLighting, inputData.viewDirectionWS, normalTS);

    IndirectLighting indirectLighting;
    ImageBaseInDirectLighting(inputData, brdfData, mainLight, indirectLighting);

    half3 diffuseLighting = CombineDiffuseLighting(directLighting, indirectLighting);
    half3 specularLighting = CombineSpecularLighting(directLighting, indirectLighting);
//  ----------------------------------------------------------- End BRDF -------------------------------------------------
//  ------------------------------------------------------- Additional Lights --------------------------------------------
    PhysicallyBaseDirectAdditionalLighting(inputData, brdfData, diffuseLighting, specularLighting);
//  ----------------------------------------------------- End Additional Lights ------------------------------------------
//  -------------------------------------------------------------- AO ----------------------------------------------------
    ApplyAmbientOcclusion(inputData, occlusion, diffuseLighting, specularLighting);
//  ------------------------------------------------------------ End AO --------------------------------------------------
    half3 color = diffuseLighting + specularLighting + emission;
    return float4(color, alpha);
}

InputCoord 在函数外面计算好,其中的坐标基本都与兼容 Unity 的功能有关,比如烘培,shadowmask,ssao 等。

struct InputCoord
{
    float3 positionWS;
    float3 viewDirectionWS;
    float3 lightDirectionWS;
    float3 normalWS;
    float3 tangentWS;
    float3 bitangentWS;
    float4 shadowCoord;
    float4 shadowMask;
    float2 normalizedScreenSpaceUV;
    float2 lightmapUV;
};

BRDF 是自定义的结构体,与 URP 本身的 BRDFData 的差别是增加了 iorfresnel0,这些数值会在后面起到作用。

struct BRDF
{
    half3 diffuse;
    half3 specular;
    half3 fresnel0;
    half3 diffuseReflectivity;
    half3 reflectivity;
    half perceptualRoughness;
    half roughness;
    half roughness2;
    half3 grazingTerm;
    half3 ior;
};

inline void InitializationBRDFData(out BRDF brdfData, half3 albedo, half metallic, half smoothness)
{
    brdfData.ior = 1.5;
    brdfData.fresnel0 = FRESNEL0;
    brdfData.diffuseReflectivity = (1 - brdfData.fresnel0) * (1 - metallic);
    brdfData.diffuse = albedo * brdfData.diffuseReflectivity;
    brdfData.specular = lerp(brdfData.fresnel0, albedo, metallic);
    brdfData.reflectivity = 1 - brdfData.diffuseReflectivity;
    brdfData.perceptualRoughness = 1 - smoothness;
    brdfData.roughness = brdfData.perceptualRoughness * brdfData.perceptualRoughness;
    brdfData.roughness2 = brdfData.roughness * brdfData.roughness;
    brdfData.grazingTerm = saturate(smoothness + brdfData.reflectivity);
}

进入 BRDF 的计算

  • BRDF 方程镇楼 image.png

直接光的 BRDF

我们先计算灯光的阴影及距离衰减等,然后计算 BRDF,这里用的是 BSDF.hlsl 中的结构体,但在此处并 没有考虑 材质的透射,所以 diffTspecT 没有用到,计算的结果的 直接光漫反射 部分储存在 directLighting.diffuseLighting 中,直接光高光 储存在 directLighting.specularLighting

struct DirectLighting
{
    float3 diffuseLighting;
    float3 specularLighting;
};

CBSDF EvaluateBRDF(BRDF brdfData, Light light, float3 viewDirectionWS, float3 normalWS)
{
    CBSDF cbsdf;

    float3 H = normalize(viewDirectionWS + light.direction);
    float NdotL = dot(normalWS, light.direction);
    float NdotV = dot(normalWS, viewDirectionWS);
    float clampNdotL = max(saturate(NdotL), 0.00001);
    float clampNdotV = ClampNdotV(NdotV);

    float NdotH = saturate(dot(normalWS, H));
    float LdotH = dot(light.direction, H);
    float LdotV = dot(light.direction, viewDirectionWS);

    cbsdf.diffR = DirectionBRDFDiffTerm(brdfData, NdotL, clampNdotV, LdotV) * clampNdotL;
    cbsdf.specR = DirectionBRDFSpecTerm(brdfData, NdotL, clampNdotV, NdotH, LdotH) * clampNdotL;
    cbsdf.diffT = 0;
    cbsdf.specT = 0;
    return cbsdf;
}

void PhysicallyBaseDirectLighting(BRDF brdfData, Light light, out DirectLighting directLighting, float3 viewDirectionWS, float3 normalWS)
{
    half3 lighting = ComputeLight(light);
    CBSDF cbrdf = EvaluateBRDF(brdfData, light, viewDirectionWS, normalWS);
    directLighting.diffuseLighting = cbrdf.diffR * brdfData.diffuse * lighting;
    directLighting.specularLighting = cbrdf.specR * lighting;
}

直接光漫反射部分

采用了 迪士尼的漫反射项,当然也可以选择 Lambert

float3 DirectionBRDFDiffTerm(BRDF brdfData, float NdotL, float clampNdotV, float LdotV)
{
#ifdef USE_DIFFUSE_LAMBERT_BRDF
    float diffTerm = Lambert();
#else
    float diffTerm = DisneyDiffuse(clampNdotV, abs(NdotL), LdotV, brdfData.perceptualRoughness);
#endif
    return diffTerm.xxx;
}

直接光高光部分

SmithJointGGX,源码在 BSDF.hlsl 中,这里用到的 roughnessperceptualRoughness^2, 

DVF=FDG4NLNVDV*F = \frac{F*D*G}{4*N \cdot L* N \cdot V}
float3 DirectionBRDFSpecTerm(BRDF brdfData, float NdotL, float clampNdotV, float NdotH, float LdotH)
{
    float partLambdaV = GetSmithJointGGXPartLambdaV(clampNdotV, brdfData.roughness);
    float3 F = F_Schlick(brdfData.specular, LdotH);
    float DV = DV_SmithJointGGX(NdotH, abs(NdotL), clampNdotV, brdfData.roughness, partLambdaV);
    return DV * F;
}

间接光的 BRDF

间接光的漫反射部分

兼容 Unity 的 Lighting 面板,球谐函数与 Lightmap:

half3 GetIlluminationDiffuse(InputCoord inputData, Light light)
{
    half3 diffuseGI = half3(0, 0, 0);
#ifdef LIGHTMAP_ON
    diffuseGI = SampleLightmap(inputData.lightmapUV, inputData.normalWS);
#else
    diffuseGI = SampleSH(inputData.normalWS);
#endif
#if defined(LIGHTMAP_ON) && defined(_MIXED_LIGHTING_SUBTRACTIVE)
    diffuseGI = SubtractDirectMainLightFromLightmap(light, inputData.normalWS, diffuseGI);
#endif
    return diffuseGI;
}

间接光的镜面反射部分

基于图像的照明,采样一张 LOD 的 CubeMap:

float3 IBL_EnvironmentReflection(float3 viewDirWS, float3 normalWS, float roughness)
{
    float3 reflectDirectionWS = reflect(-viewDirWS, normalWS);
    float square_roughness = roughness * (1.7 - 0.7 * roughness);
    float Midlevel = square_roughness * 6;
    float4 specularColor = SAMPLE_TEXTURECUBE_LOD(unity_SpecCube0, samplerunity_SpecCube0, reflectDirectionWS, Midlevel);
    #if !defined(UNITY_USE_NATIVE_HDR)
        return DecodeHDREnvironment(specularColor, unity_SpecCube0_HDR).rgb;
    #else
        return specularColor.xyz;
    #endif
}

环境光照的 BRDF

除此之外,我们还需要环境光照的 BRDF,可以计算拟合曲线,也可以选择采样 BRDF LUT 图,这里我选择的是采样 BRDF LUT,只是这里用到的 LUT 图是这样的:

  • HDRP PreIntegratedFGDGGXAndDisneyDiffuse
    image.png

这张图是从 HDRP 的渲染管线中拿到的,z 通道中存放着 DisneyDiffuse 的影响因子。

TEXTURE2D(_PreIntegratedFGD_GGXDisneyDiffuse);
SAMPLER(s_linear_clamp_sampler);

void GetPreIntegratedFGDGGXAndDisneyDiffuse(float NdotV, float perceptualRoughness, float3 fresnel0, out float3 specularFGD, out float3 diffuseFGD, out float3 reflectivity)
{
    // We want the LUT to contain the entire [0, 1] range, without losing half a texel at each side.
    float2 coordLUT = Remap01ToHalfTexelCoord(float2(sqrt(NdotV), perceptualRoughness), FGDTEXTURE_RESOLUTION);

    float3 preFGD = SAMPLE_TEXTURE2D_LOD(_PreIntegratedFGD_GGXDisneyDiffuse, s_linear_clamp_sampler, coordLUT, 0).xyz;

    // Pre-integrate GGX FGD
    // Integral{BSDF * <N,L> dw} =
    // Integral{(F0 + (1 - F0) * (1 - <V,H>)^5) * (BSDF / F) * <N,L> dw} =
    // (1 - F0) * Integral{(1 - <V,H>)^5 * (BSDF / F) * <N,L> dw} + F0 * Integral{(BSDF / F) * <N,L> dw}=
    // (1 - F0) * x + F0 * y = lerp(x, y, F0)
    specularFGD = lerp(preFGD.xxx, preFGD.yyy, fresnel0);
    // Pre integrate DisneyDiffuse FGD:
    // z = DisneyDiffuse
    // Remap from the [0, 1] to the [0.5, 1.5] range.
    diffuseFGD = preFGD.z + 0.5;

    reflectivity = preFGD.yyy;
}

最终计算结果

struct IndirectIlluminationData
{
    float3 diffuseGI;
    float3 diffuseFGD;
    float3 specularGI;
    float3 specularFGD;
};
struct IndirectLighting
{
    float3 diffuseLighting;
    float3 specularLighting;
};

void ImageBaseInDirectLighting(BRDF brdfData, IndirectIlluminationData giData, out IndirectLighting indirectLighting)
{
    indirectLighting.diffuseLighting = giData.diffuseGI * brdfData.diffuse * giData.diffuseFGD;
    indirectLighting.specularLighting = giData.specularGI * giData.specularFGD;
}

void ImageBaseInDirectLighting(InputCoord inputData, BRDF brdfData, Light light, out IndirectLighting indirectLighting)
{
    float NdotV = max(saturate(dot(inputData.normalWS, inputData.viewDirectionWS)), 0.00001);

    IndirectIlluminationData giData;
    giData.diffuseGI = GetIlluminationDiffuse(inputData, light);
    giData.specularGI = IBL_EnvironmentReflection(inputData.viewDirectionWS, inputData.normalWS, brdfData.perceptualRoughness);
    GetPreIntegratedFGDGGXAndDisneyDiffuse(NdotV, brdfData.perceptualRoughness, brdfData.specular, giData.specularFGD, giData.diffuseFGD, brdfData.reflectivity);
    ImageBaseInDirectLighting(brdfData, giData, indirectLighting);
}

至此,BRDF 计算完毕了。

Addtional Lighting

接下来是 Addtional Lighting,这里只支持了 逐像素 的附加灯,灯光数量 在 URP 的 Render Asset 上可以设置。

这里将附加灯的 BRDF 也完整的计算了,也可以根据性能需求删减一些计算

void PhysicallyBaseDirectAdditionalLighting(InputCoord inputData, BRDF brdfData, inout half3 diffuse, inout half3 specular)
{
#ifdef _ADDITIONAL_LIGHTS
    uint pixelLightCount = GetAdditionalLightsCount();
    for (uint lightIndex = 0u; lightIndex < pixelLightCount; ++lightIndex)
    {
        Light light = GetAdditionalLight(lightIndex, inputData.positionWS, inputData.shadowMask);
        DirectLighting additionalLighting;
        PhysicallyBaseDirectLighting(brdfData, light, additionalLighting, inputData.viewDirectionWS, inputData.normalWS);
        diffuse += additionalLighting.diffuseLighting;
        specular += additionalLighting.specularLighting;
    }
#endif
}

AO

之后是 AO,将 AO 贴图与 SSAO 进行混合:

void ApplyAmbientOcclusion(InputCoord inputData, half occlusion, inout half3 diffuseLighting, inout half3 specularLighting)
{
#if defined(_SCREEN_SPACE_OCCLUSION)
    AmbientOcclusionFactor aoFactor = GetScreenSpaceAmbientOcclusion(inputData.normalizedScreenSpaceUV);
    diffuseLighting *= aoFactor.directAmbientOcclusion;
    specularLighting *= min(occlusion, aoFactor.indirectAmbientOcclusion);
#else
    diffuseLighting *= occlusion;
    specularLighting *= occlusion;
#endif
}

最终将所有结果相加,可以得到以下效果。


下一章预告

彩虹色(Iridescence)


参考