Files
beyond/Assets/ThirdParty/Lux URP Essentials/Shaders/Includes/Lux URP Hair Lighting.hlsl
2024-11-20 15:21:28 +01:00

281 lines
9.9 KiB
HLSL

// NOTE: Based on URP Lighting.hlsl which replaced some half3 with floats to avoid lighting artifacts on mobile
// Hair lighting functions renamed to solves problems with LWRP 6.x
#ifndef LIGHTWEIGHT_HAIRLIGHTING_INCLUDED
#define LIGHTWEIGHT_HAIRLIGHTING_INCLUDED
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl"
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/EntityLighting.hlsl"
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/ImageBasedLighting.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Shadows.hlsl"
// From HDRP -----------------------------------------
float RoughnessToBlinnPhongSpecularExponent_Lux(float roughness)
{
return clamp(2 * rcp(roughness * roughness) - 2, FLT_EPS, rcp(FLT_EPS));
}
//http://web.engr.oregonstate.edu/~mjb/cs519/Projects/Papers/HairRendering.pdf
float3 ShiftTangent_Lux(float3 T, float3 N, float shift)
{
return normalize(T + N * shift);
}
// Note: this is Blinn-Phong, the original paper uses Phong.
float3 D_KajiyaKay_Lux(float3 T, float3 H, float specularExponent)
{
float TdotH = dot(T, H);
float sinTHSq = saturate(1.0 - TdotH * TdotH);
float dirAttn = saturate(TdotH + 1.0); // Evgenii: this seems like a hack? Do we really need this?
// Note: Kajiya-Kay is not energy conserving.
// We attempt at least some energy conservation by approximately normalizing Blinn-Phong NDF.
// We use the formulation with the NdotL.
// See http://www.thetenthplanet.de/archives/255.
float n = specularExponent;
float norm = (n + 2) * rcp(2 * PI);
return dirAttn * norm * PositivePow(sinTHSq, 0.5 * n);
}
/*
#if (_USE_LIGHT_FACING_NORMAL)
// The Kajiya-Kay model has a "built-in" transmission, and the 'NdotL' is always positive.
float cosTL = dot(bsdfData.hairStrandDirectionWS, L);
float sinTL = sqrt(saturate(1 - cosTL * cosTL));
float NdotL = sinTL; // Corresponds to the cosine w.r.t. the light-facing normal
#else
// Double-sided Lambert.
float NdotL = dot(bsdfData.normalWS, L);
#endif
*/
// From HDRP END -----------------------------------------
/*
This is input data:
struct InputData
{
float3 positionWS;
half3 normalWS;
half3 viewDirectionWS;
float4 shadowCoord;
half fogCoord;
half3 vertexLighting;
half3 bakedGI;
};
*/
// Ref: Donald Revie - Implementing Fur Using Deferred Shading (GPU Pro 2)
// The grain direction (e.g. hair or brush direction) is assumed to be orthogonal to the normal.
// The returned normal is NOT normalized.
half3 ComputeGrainNormal_Lux(half3 grainDir, half3 V)
{
half3 B = cross(-V, grainDir);
return cross(B, grainDir);
}
// Fake anisotropic by distorting the normal.
// The grain direction (e.g. hair or brush direction) is assumed to be orthogonal to N.
// Anisotropic ratio (0->no isotropic; 1->full anisotropy in tangent direction)
half3 GetAnisotropicModifiedNormal_Lux(half3 grainDir, half3 N, half3 V, half anisotropy)
{
half3 grainNormal = ComputeGrainNormal_Lux(grainDir, V);
return lerp(N, grainNormal, anisotropy);
}
half3 GlobalIlluminationHair_Lux(
//BRDFData brdfData,
half3 albedo,
half3 specular,
half roughness,
half perceptualRoughness,
half occlusion,
half3 bakedGI,
half3 normalWS,
half3 viewDirectionWS,
half3 bitangentWS,
half ambientReflection
)
{
// We do not handle backfaces properly yet.
half NdotV = dot(normalWS, viewDirectionWS);
half s = sign(NdotV);
// Lets fix this for reflections?
//NdotV = s * NdotV;
// Strengthen occlusion on backfaces
//occlusion = lerp(occlusion * 0.5, occlusion, saturate(1 + s));
// We do not "fix" the reflection vector. This gives us some scattering like reflections
//half3 reflectNormalWS = GetAnisotropicModifiedNormal_Lux(s * bitangentWS, s * normalWS, viewDirectionWS, 0.6h);
half3 reflectNormalWS = GetAnisotropicModifiedNormal_Lux(bitangentWS, normalWS, viewDirectionWS, 0.6h);
half3 reflectVector = reflect(-viewDirectionWS, reflectNormalWS);
half fresnelTerm = Pow4(1.0 - saturate(NdotV) );
// ??? perceptualRoughness *= saturate(1.2 - 0.8); //abs(bsdfData.anisotropy));
half3 indirectDiffuse = bakedGI * occlusion;
half3 indirectSpecular = GlossyEnvironmentReflection(reflectVector, perceptualRoughness, occlusion) * ambientReflection;
// EnvironmentBRDFHair
half3 c = indirectDiffuse * albedo;
float surfaceReduction = 1.0 / (roughness * roughness + 1.0);
half reflectivity = ReflectivitySpecular(specular);
half grazingTerm = saturate( (1.0h - roughness) + reflectivity);
c += surfaceReduction * indirectSpecular * lerp(specular, grazingTerm, fresnelTerm);
return c;
}
half3 LightingHair_Lux(
half3 albedo,
half3 specular,
Light light,
half3 normalWS,
half geomNdotV,
half3 viewDirectionWS,
half roughness1,
half roughness2,
half3 t1,
half3 t2,
half3 specularTint,
half3 secondarySpecularTint,
half rimTransmissionIntensity
)
{
half NdotL = dot(normalWS, light.direction);
half LdotV = dot(light.direction, viewDirectionWS);
float invLenLV = rsqrt(max(2.0 * LdotV + 2.0, FLT_EPS));
float3 halfDir = (light.direction + viewDirectionWS) * invLenLV;
half3 hairSpec1 = specularTint * D_KajiyaKay_Lux(t1, halfDir, roughness1);
#if defined(_SECONDARYLOBE)
half3 hairSpec2 = secondarySpecularTint * D_KajiyaKay_Lux(t2, halfDir, roughness2);
#endif
float NdotH = saturate(dot(normalWS, halfDir));
half LdotH = saturate(dot(light.direction, halfDir));
half3 F = F_Schlick(specular, LdotH);
// Reflection
half3 specR = 0.25h * F * (hairSpec1
#if defined(_SECONDARYLOBE)
+ hairSpec2
#endif
) * saturate(NdotL) * saturate(geomNdotV * FLT_MAX);
// Transmission // Yibing's and Morten's hybrid scatter model hack.
half scatterFresnel1 = pow(saturate(-LdotV), 9.0h) * pow(saturate(1.0h - geomNdotV * geomNdotV), 12.0h);
// This looks shitty (using 20)
//half scatterFresnel2 = saturate(PositivePow((1.0h - geomNdotV), 20.0h));
half scatterFresnel2 = saturate(Pow4(1.0h - geomNdotV));
half transmission = scatterFresnel1 + rimTransmissionIntensity * scatterFresnel2;
half3 specT = albedo * transmission;
half3 diffuse = albedo * saturate(NdotL);
// combine
half3 result = (diffuse + specR + specT) * light.color * light.distanceAttenuation * light.shadowAttenuation;
return result;
}
half4 LuxURPHairFragment(
InputData inputData,
half3 tangentWS,
half3 albedo,
half3 specular,
half occlusion,
half3 emission,
half3 noise,
half specularShift,
half3 specularTint,
half perceptualRoughness,
half secondarySpecularShift,
half3 secondarySpecularTint,
half secondaryPerceptualRoughness,
half rimTransmissionIntensity,
half ambientReflection
)
{
// TODO: Simplify this...
perceptualRoughness = PerceptualSmoothnessToPerceptualRoughness(perceptualRoughness); // * saturate(noise.r * 2) );
half roughness1 = PerceptualRoughnessToRoughness(perceptualRoughness);
half pbRoughness1 = RoughnessToBlinnPhongSpecularExponent_Lux(roughness1);
#if defined(_SECONDARYLOBE)
secondaryPerceptualRoughness = PerceptualSmoothnessToPerceptualRoughness(secondaryPerceptualRoughness); // * saturate(noise.r * 2) );
half roughness2 = PerceptualRoughnessToRoughness(secondaryPerceptualRoughness);
half pbRoughness2 = RoughnessToBlinnPhongSpecularExponent_Lux(roughness2);
#else
secondaryPerceptualRoughness = 0;
half roughness2 = 0;
half pbRoughness2 = 0;
#endif
half geomNdotV = dot(inputData.normalWS, inputData.viewDirectionWS);
// Adjust tangentWS in case normal mapping is enabled
#if defined(_NORMALMAP)
tangentWS = Orthonormalize(tangentWS, inputData.normalWS);
#endif
// Always calculate bitangent WS
half3 bitangentWS = cross(inputData.normalWS, tangentWS);
#if defined(_STRANDDIR_BITANGENT)
half3 strandDirWS = bitangentWS;
#else
half3 strandDirWS = tangentWS;
#endif
half3 t1 = ShiftTangent_Lux(strandDirWS, inputData.normalWS, specularShift);
#if defined(_SECONDARYLOBE)
half3 t2 = ShiftTangent_Lux(strandDirWS, inputData.normalWS, secondarySpecularShift);
#else
half3 t2 = 0;
#endif
// Start Lighting
// (From HDRP) Note: For Kajiya hair we currently rely on a single cubemap sample instead of two, as in practice smoothness of both lobe aren't too far from each other.
// and we take smoothness of the secondary lobe as it is often more rough (it is the colored one).
// NOPE: We use primary!!!!!
half3 color = GlobalIlluminationHair_Lux(albedo, specular, roughness1, perceptualRoughness, occlusion, inputData.bakedGI, inputData.normalWS, inputData.viewDirectionWS, bitangentWS, ambientReflection);
// Main Light
Light light = GetMainLight(inputData.shadowCoord);
MixRealtimeAndBakedGI(light, inputData.normalWS, inputData.bakedGI, half4(0, 0, 0, 0));
color += LightingHair_Lux(albedo, specular, light, inputData.normalWS, geomNdotV, inputData.viewDirectionWS, pbRoughness1, pbRoughness2, t1, t2, specularTint, secondarySpecularTint, rimTransmissionIntensity);
// Additional Lights
#ifdef _ADDITIONAL_LIGHTS
uint pixelLightCount = GetAdditionalLightsCount();
for (uint i = 0u; i < pixelLightCount; ++i) {
light = GetAdditionalLight(i, inputData.positionWS);
color += LightingHair_Lux(albedo, specular, light, inputData.normalWS, geomNdotV, inputData.viewDirectionWS, pbRoughness1, pbRoughness2, t1, t2, specularTint, secondarySpecularTint, rimTransmissionIntensity);
}
#endif
#ifdef _ADDITIONAL_LIGHTS_VERTEX
color += inputData.vertexLighting * albedo;
#endif
color += emission;
return half4(color, 1);
}
#endif