You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
254 lines
9.3 KiB
254 lines
9.3 KiB
#pragma kernel ComputeAttenuationForward COMPUTE_HAIR_ATTENUATION=ComputeAttenuationForward FORWARD
|
|
#pragma kernel ComputeAttenuationBackward COMPUTE_HAIR_ATTENUATION=ComputeAttenuationBackward
|
|
#pragma kernel ComputeAzimuthalScattering
|
|
#pragma kernel ComputeLongitudinalScattering
|
|
|
|
#pragma only_renderers d3d11 playstation xboxone xboxseries vulkan metal switch
|
|
|
|
// This define is required for invoking BSDF.
|
|
#define HAS_LIGHTLOOP
|
|
|
|
// This define is required to map the reflectance to absorption for the preintegration.
|
|
#define _ABSORPTION_FROM_COLOR 1
|
|
|
|
#define DIM 64
|
|
#define SPHERE_SAMPLES 32
|
|
#define DPHI radians(10)
|
|
#define DH 0.1
|
|
|
|
// HDRP generic includes
|
|
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Common.hlsl"
|
|
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Color.hlsl"
|
|
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/ShaderLibrary/ShaderVariables.hlsl"
|
|
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/Lighting/Lighting.hlsl"
|
|
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/Material/Material.hlsl"
|
|
#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/RenderPipeline/HDStencilUsage.cs.hlsl"
|
|
|
|
SurfaceData ConfigureFiberSurface(float diffuseColor, float perceptualSmoothness, uint flags = 0)
|
|
{
|
|
SurfaceData surfaceData;
|
|
ZERO_INITIALIZE(SurfaceData, surfaceData);
|
|
|
|
// Our fiber scattering function is the Marschner-based BSDF.
|
|
surfaceData.materialFeatures |= MATERIALFEATUREFLAGS_HAIR_MARSCHNER;
|
|
|
|
// Setup any extra flags
|
|
surfaceData.materialFeatures |= flags;
|
|
|
|
// Here we factor by Diffuse Color, which will be converted to Absorption in ConvertSurfaceDataToBSDFData.
|
|
// Note, this LUT is parameterized by single color channel / wavelength, to reduce the dimensionality. This means to
|
|
// compute the average forward and backward scattering for a given absorption, the LUT must be sampled three times.
|
|
surfaceData.diffuseColor = diffuseColor.xxx;
|
|
|
|
// Smoothness (Longitudinal Roughness)
|
|
surfaceData.perceptualSmoothness = perceptualSmoothness;
|
|
|
|
// Radial Smoothness (Azimuthal Roughness).
|
|
// TODO: Support varying azimuthal roughness. We don't have enough dimensions in the LUT to parameterize this as well,
|
|
// so we fall back to a sensible default for now, this one is generally acceptable for human hair (less so for animal fur).
|
|
surfaceData.perceptualRadialSmoothness = 0.7;
|
|
|
|
// Cuticle Angle
|
|
surfaceData.cuticleAngle = -3;
|
|
|
|
// The theoretical fiber points points down the +x axis.
|
|
surfaceData.hairStrandDirectionWS = float3(0, 0, 1);
|
|
|
|
// Be sure to define the normal as well as it defines the hair shading basis
|
|
surfaceData.geomNormalWS = float3(0, 1, 0);
|
|
|
|
return surfaceData;
|
|
}
|
|
|
|
// Parameterization:
|
|
// X - Perceptual Smoothness
|
|
// Y - Theta
|
|
// Z - Diffuse Color (single channel)
|
|
RWTexture3D<float4> _HairAttenuationUAV;
|
|
|
|
// Pre-integrate the average attenuation on the front and back hemisphere on a hair fiber.
|
|
// Ref: Equation 6 & 12 in "Dual Scattering Approximation for Fast Multiple Scattering in Hair"
|
|
// -----------------------------------------------------------------
|
|
|
|
[numthreads(8, 8, 8)]
|
|
void COMPUTE_HAIR_ATTENUATION (uint3 dispatchThreadID : SV_DispatchThreadID)
|
|
{
|
|
// Convert the dispatch coordinates to the generation space [0,1].
|
|
float3 UVW = float3(((float3)dispatchThreadID + 0.5) / DIM);
|
|
|
|
// Configure a theoretical hair fiber to evaluate the average attenuation.
|
|
SurfaceData surfaceData = ConfigureFiberSurface(UVW.z, UVW.x);
|
|
|
|
// Use the conversion from the surface data to compute all of the per-lobe bsdf information.
|
|
BSDFData bsdfData = ConvertSurfaceDataToBSDFData(uint2(0, 0), surfaceData);
|
|
|
|
// The shading coordinate system.
|
|
const float3x3 localToWorld = GetLocalFrame(bsdfData.geomNormalWS, bsdfData.hairStrandDirectionWS);
|
|
const float3x3 worldToLocal = transpose(localToWorld);
|
|
|
|
// Unused in this case.
|
|
PreLightData preLightData;
|
|
ZERO_INITIALIZE(PreLightData, preLightData);
|
|
|
|
// Configure the initial incident theta direction.
|
|
float sinThetaI = UVW.y;
|
|
|
|
// Accumulation target for the integral.
|
|
float attenuation = 0;
|
|
|
|
// Integrate over the forward or backward scattering hemisphere.
|
|
for (uint w = 0; w < SPHERE_SAMPLES; w++)
|
|
{
|
|
float2 U = Hammersley2d(w, SPHERE_SAMPLES);
|
|
|
|
#if 0
|
|
float3 V = SampleHemisphereUniform(U.x, U.y);
|
|
float PDF = INV_TWO_PI;
|
|
#else
|
|
float3 V = SampleHemisphereCosine(U.x, U.y);
|
|
float PDF = dot(float3(0, 0, 1), V) * INV_PI;
|
|
#endif
|
|
|
|
// Re-orient the hemisphere for y-up.
|
|
V = V.xzy;
|
|
|
|
#ifdef FORWARD
|
|
// Flip the hemisphere to observe forward scattering.
|
|
V.y = -V.y;
|
|
#endif
|
|
|
|
// Transform into the shading coordinate system.
|
|
const float3 wi = mul(V, worldToLocal);
|
|
|
|
// Integrate phi for isotropic irradiance
|
|
for (float phi = 0; phi < PI; phi += DPHI)
|
|
{
|
|
float3 L = SphericalToCartesian(phi, sinThetaI);
|
|
|
|
// Transform into the shading coordinate system.
|
|
const float3 wo = mul(L, worldToLocal);
|
|
|
|
// Integrate over the fiber width.
|
|
for (float h = -1; h < +1; h += DH)
|
|
{
|
|
// This needs to vary if we are sampling the reference.
|
|
bsdfData.h = h;
|
|
|
|
// Invoke the fiber scattering function.
|
|
CBSDF cbsdf = EvaluateHairReference(wo, wi, bsdfData);
|
|
|
|
attenuation += (0.5 * Luminance(cbsdf.specR) * abs(wi.z) * DH * DPHI * rcp((float)SPHERE_SAMPLES)) / PDF;
|
|
}
|
|
}
|
|
}
|
|
|
|
attenuation *= INV_PI;
|
|
|
|
// Update the LUT
|
|
float4 A = _HairAttenuationUAV[dispatchThreadID];
|
|
{
|
|
// Manual read/write since it's not allowed to swizzle a UAV directly
|
|
#ifdef FORWARD
|
|
A.x = attenuation;
|
|
#else
|
|
A.y = attenuation;
|
|
#endif
|
|
}
|
|
_HairAttenuationUAV[dispatchThreadID] = float4(A.xy, 0, 0);
|
|
}
|
|
|
|
// TODO: Store the global term in the lower bits?
|
|
|
|
// Pre-integrate the azimuthal scattering distributions for the three primary lobes, parameterized by:
|
|
// X: Phi
|
|
// Y: Theta
|
|
// Z: Azimuthal Roughness
|
|
// -----------------------------------------------------------------
|
|
|
|
RWTexture3D<float4> _HairAzimuthalScatteringUAV;
|
|
|
|
[numthreads(8, 8, 8)]
|
|
void ComputeAzimuthalScattering (uint3 dispatchThreadID : SV_DispatchThreadID)
|
|
{
|
|
// Convert the dispatch coordinates to the generation space [0,1].
|
|
const float3 UV = ((float3)dispatchThreadID + 0.5) / DIM;
|
|
|
|
// Configure the initial phi, theta, and Bn.
|
|
const float phi = FOUR_PI * UV.x - TWO_PI; // Remap 0..1 -> -2PI..2PI
|
|
const float eta = ModifiedRefractionIndex(UV.y); // IOR currently fixed for human hair (1.55).
|
|
const float s = LogisticScaleFromBeta(UV.z); // Convert to azimuthal roughness logistic scale
|
|
|
|
float3 N = 0;
|
|
|
|
// Integrate over the fiber width.
|
|
for (float h = -1; h < 1; h += DH)
|
|
{
|
|
const float gammaO = FastASin(h);
|
|
const float gammaT = clamp(FastASin(h / eta), -1, 1);
|
|
|
|
// Re-used directly from the reference.
|
|
const float NR = AzimuthalScattering(phi, 0, s, gammaO, gammaT);
|
|
const float NTT = AzimuthalScattering(phi, 1, s, gammaO, gammaT);
|
|
const float NTRT = AzimuthalScattering(phi, 2, s, gammaO, gammaT);
|
|
|
|
N += float3( NR,
|
|
NTT,
|
|
NTRT ) * DH;
|
|
}
|
|
|
|
_HairAzimuthalScatteringUAV[dispatchThreadID] = float4(0.5 * N, 1);
|
|
}
|
|
|
|
// Pre-integrate the azimuthal scattering distributions for the three primary lobes, parameterized by:
|
|
// X: Cos Theta I
|
|
// Y: Cos Theta O
|
|
// Z: Longitudinal Roughness
|
|
// -----------------------------------------------------------------
|
|
|
|
RWTexture3D<float4> _HairLongitudinalScatteringUAV;
|
|
|
|
[numthreads(8, 8, 8)]
|
|
void ComputeLongitudinalScattering (uint3 dispatchThreadID : SV_DispatchThreadID)
|
|
{
|
|
// Convert the dispatch coordinates to the generation space [0,1].
|
|
const float3 UV = ((float3)dispatchThreadID + 0.5) / DIM;
|
|
|
|
ReferenceAngles angles;
|
|
ZERO_INITIALIZE(ReferenceAngles, angles);
|
|
{
|
|
// Small epsilon to suppress various compiler warnings + div-zero guard
|
|
const float epsilon = 1e-5;
|
|
|
|
angles.sinThetaI = -1 + 2 * UV.x;
|
|
angles.sinThetaO = -1 + 2 * UV.y;
|
|
angles.cosThetaI = SafeSqrt(1 - (Sq(angles.sinThetaI) + epsilon));
|
|
angles.cosThetaO = SafeSqrt(1 - (Sq(angles.sinThetaO) + epsilon));
|
|
}
|
|
|
|
ReferenceBSDFData data;
|
|
ZERO_INITIALIZE(ReferenceBSDFData, data);
|
|
{
|
|
LongitudinalVarianceFromBeta(UV.z, data.v);
|
|
|
|
// Pre-integrated long. scattering is hardcoded for 3º cuticle tilt.
|
|
data.alpha = radians(3);
|
|
|
|
// Fill the alpha terms for each lobe
|
|
GetAlphaScalesFromAlpha(data.alpha, data.sinAlpha, data.cosAlpha);
|
|
}
|
|
|
|
// Re-used directly from the reference.
|
|
float M[3];
|
|
|
|
for (uint p = 0; p < 3; ++p)
|
|
{
|
|
float sinThetaO, cosThetaO;
|
|
ApplyCuticleTilts(p, angles, data, sinThetaO, cosThetaO);
|
|
|
|
M[p] = LongitudinalScattering(angles.cosThetaI, cosThetaO, angles.sinThetaI, sinThetaO, data.v[p]);
|
|
}
|
|
|
|
_HairLongitudinalScatteringUAV[dispatchThreadID] = float4(M[0], M[1], M[2], 1);
|
|
}
|