Files
Silly-Home/Assets/Filamented/SharedSHLib.hlsl

159 lines
6.5 KiB
HLSL

#ifndef COMMON_SH_INCLUDED
#define COMMON_SH_INCLUDED
/* http://www.geomerics.com/wp-content/uploads/2015/08/CEDEC_Geomerics_ReconstructingDiffuseLighting1.pdf */
half shEvaluateDiffuseL1Geomerics_local(half L0, half3 L1, float3 n)
{
// average energy
// Add max0 to fix an issue caused by probes having a negative ambient component (???)
// I'm not sure how normal that is but this can't handle it
half R0 = max(L0, 0);
// avg direction of incoming light
half3 R1 = 0.5f * L1;
// directional brightness
half lenR1 = length(R1);
// linear angle between normal and direction 0-1
half q = dot(normalize(R1), n) * 0.5 + 0.5;
q = saturate(q); // Thanks to ScruffyRuffles for the bug identity.
// power for q
// lerps from 1 (linear) to 3 (cubic) based on directionality
half p = 1.0f + 2.0f * lenR1 / R0;
// dynamic range constant
// should vary between 4 (highly directional) and 0 (ambient)
half a = (1.0f - lenR1 / R0) / (1.0f + lenR1 / R0);
return R0 * (a + (1.0f - a) * (p + 1.0f) * pow(q, p));
}
/*
// Paper: ZH3: Quadratic Zonal Harmonics, i3D 2024. https://torust.me/ZH3.pdf
// Code based on paper and demo https://www.shadertoy.com/view/Xfj3RK
// https://gist.github.com/pema99/f735ca33d1299abe0e143ee94fc61e73
*/
// L1 radiance = L1 irradiance * PI / Y_1 / AHat_1
// PI / (sqrt(3 / PI) / 2) / ((2 * PI) / 3) = sqrt(3 * PI)
const static float L0IrradianceToRadiance = 2 * sqrt(UNITY_PI);
// L0 radiance = L0 irradiance * PI / Y_0 / AHat_0
// PI / (sqrt(1 / PI) / 2) / PI = 2 * sqrt(PI)
const static float L1IrradianceToRadiance = sqrt(3 * UNITY_PI);
const static float4 L0L1IrradianceToRadiance = float4(L0IrradianceToRadiance, L1IrradianceToRadiance, L1IrradianceToRadiance, L1IrradianceToRadiance);
half SHEvalLinearL0L1_ZH3Hallucinate(half4 sh, float3 normal)
{
float4 radiance = sh * L0L1IrradianceToRadiance;
float3 zonalAxis = float3(radiance.w, radiance.y, radiance.z);
float l1Length = length(zonalAxis);
zonalAxis /= l1Length;
float ratio = l1Length / radiance.x;
float zonalL2Coeff = radiance.x * ratio * (0.08 + 0.6 * ratio); // Curve-fit.
float fZ = dot(zonalAxis, normal);
float zhNormal = sqrt(5.0f / (16.0f * UNITY_PI)) * (3.0f * fZ * fZ - 1.0f);
float result = dot(sh, float4(1, float3(normal.y, normal.z, normal.x)));
result += 0.25f * zhNormal * zonalL2Coeff;
return result;
}
// Evaluate irradiance in direction normal from the linear SH sh,
// hallucinating the ZH3 coefficient and then using that and linear SH
// for reconstruction.
half3 SHEvalLinearL0L1_ZH3Hallucinate(float3 normal)
{
float3 shL0 = float3(unity_SHAr.w, unity_SHAg.w, unity_SHAb.w) +
float3(unity_SHBr.z, unity_SHBg.z, unity_SHBb.z) / 3.0;
float3 shL1_1 = float3(unity_SHAr.y, unity_SHAg.y, unity_SHAb.y);
float3 shL1_2 = float3(unity_SHAr.z, unity_SHAg.z, unity_SHAb.z);
float3 shL1_3 = float3(unity_SHAr.x, unity_SHAg.x, unity_SHAb.x);
float3 result = 0.0;
float4 a = float4(shL0.r, shL1_1.r, shL1_2.r, shL1_3.r);
float4 b = float4(shL0.g, shL1_1.g, shL1_2.g, shL1_3.g);
float4 c = float4(shL0.b, shL1_1.b, shL1_2.b, shL1_3.b);
result.r = SHEvalLinearL0L1_ZH3Hallucinate(a, normal);
result.g = SHEvalLinearL0L1_ZH3Hallucinate(b, normal);
result.b = SHEvalLinearL0L1_ZH3Hallucinate(c, normal);
return result;
}
float3 SHEvalLinearL0L1_ZH3Hallucinate(float3 normal, float3 L0,
float3 L1r, float3 L1g, float3 L1b)
{
float3 shL0 = L0;
float3 shL1_1 = float3(L1r.y, L1g.y, L1b.y);
float3 shL1_2 = float3(L1r.z, L1g.z, L1b.z);
float3 shL1_3 = float3(L1r.x, L1g.x, L1b.x);
float3 result = 0.0;
float4 a = float4(shL0.r, shL1_1.r, shL1_2.r, shL1_3.r);
float4 b = float4(shL0.g, shL1_1.g, shL1_2.g, shL1_3.g);
float4 c = float4(shL0.b, shL1_1.b, shL1_2.b, shL1_3.b);
result.r = SHEvalLinearL0L1_ZH3Hallucinate(a, normal);
result.g = SHEvalLinearL0L1_ZH3Hallucinate(b, normal);
result.b = SHEvalLinearL0L1_ZH3Hallucinate(c, normal);
return result;
}
// Evaluate irradiance in direction normal from the linear SH sh,
// computing a shared luminance axis from the linear components,
// hallucinating the ZH3 coefficients along that axis,
// and then using ZH3 and linear SH for reconstruction in the direction normal.
float3 SHEvalLinearL0L1_ZH3Hallucinate_LumAxis(float3 direction)
{
// Get linear SH coefficients from Unity shader uniforms (without L2 folded into L0)
float3 shL0 = float3(unity_SHAr.w, unity_SHAg.w, unity_SHAb.w) +
float3(unity_SHBr.z, unity_SHBg.z, unity_SHBb.z) / 3.0;
float3 shL1_1 = float3(unity_SHAr.y, unity_SHAg.y, unity_SHAb.y);
float3 shL1_2 = float3(unity_SHAr.z, unity_SHAg.z, unity_SHAb.z);
float3 shL1_3 = float3(unity_SHAr.x, unity_SHAg.x, unity_SHAb.x);
float3 sh[4] = { shL0, shL1_1, shL1_2, shL1_3 };
// Deconvolve irradiance -> radiance
float3 radianceSH[4];
for (int i = 0; i < 3; i++)
{
radianceSH[0][i] = sh[0][i] * L0IrradianceToRadiance;
radianceSH[1][i] = sh[1][i] * L1IrradianceToRadiance;
radianceSH[2][i] = sh[2][i] * L1IrradianceToRadiance;
radianceSH[3][i] = sh[3][i] * L1IrradianceToRadiance;
}
// Use the zonal axis from the luminance SH.
const float3 lumCoeffs = float3(0.2126f, 0.7152f, 0.0722f); // sRGB luminance.
float3 zonalAxis = normalize(float3(dot(radianceSH[3], lumCoeffs), dot(radianceSH[1], lumCoeffs), dot(radianceSH[2], lumCoeffs)));
float3 ratio = 0.0;
ratio.r = abs(dot(float3(radianceSH[3].r, radianceSH[1].r, radianceSH[2].r), zonalAxis));
ratio.g = abs(dot(float3(radianceSH[3].g, radianceSH[1].g, radianceSH[2].g), zonalAxis));
ratio.b = abs(dot(float3(radianceSH[3].b, radianceSH[1].b, radianceSH[2].b), zonalAxis));
ratio /= radianceSH[0];
float3 zonalL2Coeff = radianceSH[0] * (0.08f * ratio + 0.6f * ratio * ratio); // Curve-fit; Section 3.4.3
float fZ = dot(zonalAxis, direction);
float zhDir = sqrt(5.0f / (16.0f * UNITY_PI)) * (3.0f * fZ * fZ - 1.0f);
// Evaluate irradiance from linear SH in the given direction.
float4 shDir = float4(1, direction.y, direction.z, direction.x);
float3 result = float3(0.0, 0.0, 0.0);
result += sh[0] * shDir[0];
result += sh[1] * shDir[1];
result += sh[2] * shDir[2];
result += sh[3] * shDir[3];
// Add irradiance from the ZH3 term. zonalL2Coeff is the ZH3 coefficient for a radiance signal, so we need to
// multiply by 1/4 (the L2 zonal scale for a normalized clamped cosine kernel) to evaluate irradiance.
result += 0.25f * zonalL2Coeff * zhDir;
return result;
}
#endif