whoimi

A geek blog

View on GitHub

HairShader光照模型

创建ShaderGraph:

定义的内容

#define SHADERPASS SHADERPASS_FORWARD
#pragma multi_compile _ DEBUG_DISPLAY
#pragma multi_compile _ LIGHTMAP_ON
#pragma multi_compile _ DIRLIGHTMAP_COMBINED
#pragma multi_compile _ DYNAMICLIGHTMAP_ON
#pragma multi_compile _ SHADOWS_SHADOWMASK
#pragma multi_compile DECALS_OFF DECALS_3RT DECALS_4RT
#pragma multi_compile USE_FPTL_LIGHTLIST USE_CLUSTERED_LIGHTLIST
#pragma multi_compile SHADOW_LOW SHADOW_MEDIUM SHADOW_HIGH SHADOW_VERY_HIGH

include文件

#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl"
        
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/NormalSurfaceGradient.hlsl"
        
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/ShaderPass/FragInputs.hlsl"

#include "Packages/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/ShaderPass/ShaderPass.cs.hlsl"

#include "Packages/com.unity.render-pipelines.high-definition/Runtime/ShaderLibrary/ShaderVariables.hlsl"


#include "Packages/com.unity.render-pipelines.high-definition/Runtime/Material/Material.hlsl"

// 这个在前面定义了
#if (SHADERPASS == SHADERPASS_FORWARD)
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/Lighting/Lighting.hlsl"

    #define HAS_LIGHTLOOP

    #include "Packages/com.unity.render-pipelines.high-definition/Runtime/Lighting/LightLoop/LightLoopDef.hlsl"
    #include "Packages/com.unity.render-pipelines.high-definition/Runtime/Material/Hair/Hair.hlsl"
    #include "Packages/com.unity.render-pipelines.high-definition/Runtime/Lighting/LightLoop/LightLoop.hlsl"
#else
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/Material/Hair/Hair.hlsl"
#endif

#include "Packages/com.unity.render-pipelines.high-definition/Runtime/Material/BuiltinUtilities.hlsl"
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/Material/MaterialUtilities.hlsl"
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/Material/Decal/DecalUtilities.hlsl"
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/ShaderLibrary/ShaderGraphFunctions.hlsl"

和头发表面相关的参数,这个函数主要是ShaderGraph的输入

SurfaceDescription SurfaceDescriptionFunction(SurfaceDescriptionInputs IN)
{
    SurfaceDescription surface = (SurfaceDescription)0;
    surface.Albedo = float3(0.7353569, 0.7353569, 0.7353569);
    surface.Normal = IN.TangentSpaceNormal;
    surface.BentNormal = IN.TangentSpaceNormal;
    surface.HairStrandDirection = float3 (0,-1,0); //头发走向
    surface.Occlusion = 1;
    surface.Alpha = 1;
    
    // 用于就算高光的内容:高光的颜色,粗糙的,高光的Shift
    surface.SpecularTint = float3(1, 1, 1);
    surface.Smoothness = 0.5;
    surface.SpecularShift = 0.1;
    
    surface.SecondarySpecularTint =  float3(0.5, 0.5, 0.5) ;
    surface.SecondarySmoothness = 0.5;
    surface.SecondarySpecularShift = -0.1;
    return surface;
}

上面的的函数在GetSurfaceAndBuiltinData函数当中调用。

void GetSurfaceAndBuiltinData(FragInputs fragInputs, inout SurfaceDescription surfaceDescription, float3 V, PositionInputs posInput, out SurfaceData surfaceData, out float3 bentNormalWS)
{
    ...
	
    // 双面处理
    float3 doubleSidedConstants = float3(1.0, 1.0, 1.0);
    ApplyDoubleSidedFlipOrMirror(fragInputs, doubleSidedConstants);
	
    // 基本的Normal 切线之类的
    SurfaceDescriptionInputs surfaceDescriptionInputs = FragInputsToSurfaceDescriptionInputs(fragInputs, V);
    
    // 表面信息 高光 粗糙度 从Graph当中读取
    SurfaceDescription surfaceDescription = SurfaceDescriptionFunction(surfaceDescriptionInputs);

    // 将信息写入到 需要计算的两个结构体当中
    float3 bentNormalWS;
    BuildSurfaceData(fragInputs, surfaceDescription, V, posInput, surfaceData, bentNormalWS);

    // Builtin Data
    // For back lighting we use the oposite vertex normal  背面的光使用相反的法线,主要用来采样GI?
    InitBuiltinData(surfaceDescription.Alpha, bentNormalWS, -fragInputs.worldToTangent[2]/*这部分内容是反向法线*/, fragInputs.positionRWS, fragInputs.texCoord1, fragInputs.texCoord2, builtinData);
        

    PostInitBuiltinData(V, posInput, surfaceData, builtinData);
}

BuilSurfaceData()

void BuildSurfaceData(FragInputs fragInputs, inout SurfaceDescription surfaceDescription, float3 V, PositionInputs posInput, out SurfaceData surfaceData, out float3 bentNormalWS)
{
   	// 初始化
    ZERO_INITIALIZE(SurfaceData, surfaceData);

    // copy across graph values, if defined 直接复制ShaderGraph的内容
    surfaceData.diffuseColor =                  surfaceDescription.Albedo;
    surfaceData.ambientOcclusion =              surfaceDescription.Occlusion; // AO

    // 两组高光相关的内容
    surfaceData.perceptualSmoothness =          surfaceDescription.Smoothness; // 粗糙度 
    surfaceData.specularTint =                  surfaceDescription.SpecularTint;
    surfaceData.specularShift =                 surfaceDescription.SpecularShift;

    surfaceData.secondaryPerceptualSmoothness = surfaceDescription.SecondarySmoothness;
    surfaceData.secondarySpecularTint =         surfaceDescription.SecondarySpecularTint;
    surfaceData.secondarySpecularShift =        surfaceDescription.SecondarySpecularShift;

    // These static material feature allow compile time optimization
    surfaceData.materialFeatures = 0;

    // 材质特性:头发的透光以及头发的 SSS效果
    #ifdef _MATERIAL_FEATURE_HAIR_KAJIYA_KAY
    surfaceData.materialFeatures = MATERIALFEATUREFLAGS_HAIR_KAJIYA_KAY;
    #endif

    #ifdef _MATERIAL_FEATURE_SUBSURFACE_SCATTERING
    surfaceData.materialFeatures |= MATERIALFEATUREFLAGS_HAIR_SUBSURFACE_SCATTERING;
    #endif

    #ifdef _MATERIAL_FEATURE_TRANSMISSION
    surfaceData.materialFeatures |= MATERIALFEATUREFLAGS_HAIR_TRANSMISSION;
    #endif
    

    // 法线 切线
    float3 doubleSidedConstants = float3(1.0, 1.0, 1.0);
    // tangent-space normal
    float3 normalTS = float3(0.0f, 0.0f, 1.0f);
    normalTS = surfaceDescription.Normal;
    // compute world space normal
    GetNormalWS(fragInputs, normalTS, surfaceData.normalWS, doubleSidedConstants);
    bentNormalWS = surfaceData.normalWS;
    surfaceData.geomNormalWS = fragInputs.worldToTangent[2];

    // For a typical Unity quad, you have tangent vectors pointing to the right (X axis),
    // and bitangent vectors pointing up (Y axis).
    // The current hair setup uses mesh cards (e.g. quads).
    // Hair is usually painted top-down, from the root to the tip.
    // Therefore, DefaultHairStrandTangent = -MeshCardBitangent. // <==这句话关键
    // Both the SurfaceData and the BSDFData store the hair tangent
    // (which represents the hair strand direction, root to tip). // 头发的方向
    surfaceData.hairStrandDirectionWS = -fragInputs.worldToTangent[1].xyz;
    // The hair strand direction texture contains tangent-space vectors.
    // We use the same convention for the texture, which means that
    // to get the default behavior (DefaultHairStrandTangent = -MeshCardBitangent),
    // the artist has to paint (0, -1, 0).
    // TODO: pending artist feedback...
    // The original Kajiya-Kay BRDF model expects an orthonormal TN frame.
    // Since we use the tangent shift hack 
    
    // 这个文档需要看一下
    // (http://web.engr.oregonstate.edu/~mjb/cs519/Projects/Papers/HairRendering.pdf),
    // we may as well not bother to orthonormalize anymore.
    // The tangent should still be a unit vector, though.
    surfaceData.hairStrandDirectionWS = normalize(surfaceData.hairStrandDirectionWS);

    // By default we use the ambient occlusion with Tri-ace trick (apply outside) for specular occlusion.
    // If user provide bent normal then we process a better term
    surfaceData.specularOcclusion = 1.0;
	
    // 下面有一部分是计算 specularOcclusion的
	..........
        
    // 下面是计算GAA的,后面需要看一下

    // Propagate the geometry normal
    surfaceData.geomNormalWS = fragInputs.worldToTangent[2];
}

其他部分的计算最终都会归结到BSDF函数

void BSDF(  float3 V, float3 L, float NdotL, float3 positionWS, PreLightData preLightData, BSDFData bsdfData,
            out float3 diffuseLighting,
            out float3 specularLighting)
{
	//preLightData	当中比较重要的就用到了NdotV
    float LdotV, NdotH, LdotH, NdotV, invLenLV;
    GetBSDFAngle(V, L, NdotL, preLightData.NdotV, LdotV, NdotH, LdotH, NdotV, invLenLV);

    if (HasFlag(bsdfData.materialFeatures, MATERIALFEATUREFLAGS_HAIR_KAJIYA_KAY))
    {
    	// 颜色以高光为主
        float3 t1 = ShiftTangent(bsdfData.hairStrandDirectionWS, bsdfData.normalWS, bsdfData.specularShift);
        float3 t2 = ShiftTangent(bsdfData.hairStrandDirectionWS, bsdfData.normalWS, bsdfData.secondarySpecularShift);

        float3 H = (L + V) * invLenLV;

        float3 hairSpec1 = bsdfData.specularTint * D_KajiyaKay(t1, H, bsdfData.specularExponent);
        float3 hairSpec2 = bsdfData.secondarySpecularTint * D_KajiyaKay(t2, H, bsdfData.secondarySpecularExponent);

        float3 F = F_Schlick(bsdfData.fresnel0, LdotH);
        specularLighting = F * (hairSpec1 + hairSpec2);

        // Diffuse lighting
        // #define INV_PI      0.31830988618379067154
        float diffuseTerm = Lambert();  // 漫反射项的强度
        diffuseLighting = diffuseTerm;
    }
    else
    {
        specularLighting = float3(0.0, 0.0, 0.0);
        diffuseLighting = float3(0.0, 0.0, 0.0);
    }
}

高光

float3 D_KajiyaKay(float3 T, float3 H, float specularExponent)
{
    float TdotH = dot(T, H);
    float sinTHSq = saturate(1.0 - (TdotH * TdotH));

    float dirAttn = saturate(TdotH + 1.0);

    return dirAttn * PositivePow(sinTHSq, specularExponent);
}

和BSDF相关的内容

// return usual BSDF angle
void GetBSDFAngle(float3 V, float3 L, float NdotL, float unclampNdotV, out float LdotV, out float NdotH, out float LdotH, out float clampNdotV, out float invLenLV)
{
    // Optimized math. Ref: PBR Diffuse Lighting for GGX + Smith Microsurfaces (slide 114).
    LdotV = dot(L, V);
    invLenLV = rsqrt(max(2.0 * LdotV + 2.0, FLT_EPS));    // invLenLV = rcp(length(L + V)), clamp to avoid rsqrt(0) = inf, inf * 0 = NaN
    NdotH = saturate((NdotL + unclampNdotV) * invLenLV);        // Do not clamp NdotV here
    LdotH = saturate(invLenLV * LdotV + invLenLV);
    clampNdotV = ClampNdotV(unclampNdotV);
}
// 平滑度到粗糙度
real PerceptualSmoothnessToPerceptualRoughness(real perceptualSmoothness)
{
    return (1.0 - perceptualSmoothness);
}
real PerceptualRoughnessToRoughness(real perceptualRoughness)
{
    return perceptualRoughness * perceptualRoughness;
}

Bsdf数据当中需要的内容“


BSDFData ConvertSurfaceDataToBSDFData(uint2 positionSS, SurfaceData surfaceData)
{
    BSDFData bsdfData;
    ZERO_INITIALIZE(BSDFData, bsdfData);

    // IMPORTANT: All enable flags are statically know at compile time, so the compiler can do compile time optimization
    bsdfData.materialFeatures = surfaceData.materialFeatures;

    bsdfData.ambientOcclusion = surfaceData.ambientOcclusion;
    bsdfData.specularOcclusion = surfaceData.specularOcclusion;

    bsdfData.diffuseColor = surfaceData.diffuseColor;

    bsdfData.normalWS = surfaceData.normalWS;
    bsdfData.geomNormalWS = surfaceData.geomNormalWS;
    bsdfData.perceptualRoughness = PerceptualSmoothnessToPerceptualRoughness(surfaceData.perceptualSmoothness);

    // This value will be override by the value in diffusion profile
    bsdfData.fresnel0 = DEFAULT_HAIR_SPECULAR_VALUE;

    // Note: we have ZERO_INITIALIZE the struct so bsdfData.anisotropy == 0.0
    // Note: DIFFUSION_PROFILE_NEUTRAL_ID is 0
    
    bsdfData.diffusionProfileIndex = FindDiffusionProfileIndex(surfaceData.diffusionProfileHash);

    if (HasFlag(surfaceData.materialFeatures, MATERIALFEATUREFLAGS_HAIR_SUBSURFACE_SCATTERING))
    {
        // Assign profile id and overwrite fresnel0
        FillMaterialSSS(bsdfData.diffusionProfileIndex, surfaceData.subsurfaceMask, bsdfData);
    }

    if (HasFlag(surfaceData.materialFeatures, MATERIALFEATUREFLAGS_HAIR_TRANSMISSION))
    {
        // Assign profile id and overwrite fresnel0
        FillMaterialTransmission(bsdfData.diffusionProfileIndex, surfaceData.thickness, bsdfData);
    }

    // This is the hair tangent (which represents the hair strand direction, root to tip).
    bsdfData.hairStrandDirectionWS = surfaceData.hairStrandDirectionWS;

    // Kajiya kay
    if (HasFlag(surfaceData.materialFeatures, MATERIALFEATUREFLAGS_HAIR_KAJIYA_KAY))
    {
        bsdfData.secondaryPerceptualRoughness = PerceptualSmoothnessToPerceptualRoughness(surfaceData.secondaryPerceptualSmoothness);        
        bsdfData.specularTint = surfaceData.specularTint;
        bsdfData.secondarySpecularTint = surfaceData.secondarySpecularTint;
        bsdfData.specularShift = surfaceData.specularShift;
        bsdfData.secondarySpecularShift = surfaceData.secondarySpecularShift;

        // We can rewrite specExp from exp2(10 * (1.0 - roughness)) in order
        // to remove the need to take the square root of sinTH
        // 这里是通过粗糙度计算曝光信息
        bsdfData.specularExponent = exp2(9.0 - 10.0 * PerceptualRoughnessToRoughness(bsdfData.perceptualRoughness));
        bsdfData.secondarySpecularExponent = exp2(9.0 - 10.0 * PerceptualRoughnessToRoughness(bsdfData.secondaryPerceptualRoughness));

        bsdfData.anisotropy = 0.8; // For hair we fix the anisotropy
    }

    ApplyDebugToBSDFData(bsdfData);

    return bsdfData;
}