|
|
|
|
|
|
return saturate(mipmapLevel / UNITY_SPECCUBE_LOD_STEPS); |
|
|
|
} |
|
|
|
|
|
|
|
// Ref: See "Moving Frostbite to PBR" Listing 22 |
|
|
|
// This formulation is for GGX only (with smith joint visibility or regular) |
|
|
|
float3 GetSpecularDominantDir(float3 N, float3 R, float roughness) |
|
|
|
{ |
|
|
|
float a = 1.0 - roughness; |
|
|
|
float lerpFactor = a * (sqrt(a) + roughness); |
|
|
|
// The result is not normalized as we fetch in a cubemap |
|
|
|
return lerp(N, R, lerpFactor); |
|
|
|
} |
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
// Anisotropic image based lighting |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
// To simulate the streching of highlight at grazing angle for IBL we shrink the roughness |
|
|
|
// which allow to fake an anisotropic specular lobe. |
|
|
|
// Ref: http://www.frostbite.com/2015/08/stochastic-screen-space-reflections/ - slide 84 |
|
|
|
float AnisotropicStrechAtGrazingAngle(float roughness, float perceptualRoughness, float NdotV) |
|
|
|
{ |
|
|
|
return roughness * lerp(saturate(NdotV * 2.0), 1.0, perceptualRoughness); |
|
|
|
} |
|
|
|
|
|
|
|
float3 SphericalToCartesian(float phi, float sinTheta, float cosTheta) |
|
|
|
float3 SphericalToCartesian(float phi, float cosTheta) |
|
|
|
float sinTheta = sqrt(saturate(1.0 - cosTheta * cosTheta)); |
|
|
|
|
|
|
|
return float3(sinTheta * cosPhi, sinTheta * sinPhi, cosTheta); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
float3 TransformGLtoDX(float x, float y, float z) |
|
|
|
{ |
|
|
|
return float3(x, z, y); |
|
|
|
} |
|
|
|
|
|
|
|
float3 TransformGLtoDX(float3 v) |
|
|
|
{ |
|
|
|
return v.xzy; |
|
|
|
|
|
|
float3 ConvertEquiarealToCubemap(float u, float v) |
|
|
|
{ |
|
|
|
// The equiareal mapping is defined as follows: |
|
|
|
// phi = TWO_PI * (1.0 - u) |
|
|
|
// cos(theta) = 1.0 - 2.0 * v |
|
|
|
// sin(theta) = sqrt(1.0 - cos^2(theta)) = 2.0 * sqrt(v - v * v) |
|
|
|
|
|
|
|
float sinTheta = 2.0 * sqrt(v - v * v); |
|
|
|
return TransformGLtoDX(SphericalToCartesian(phi, sinTheta, cosTheta)); |
|
|
|
} |
|
|
|
|
|
|
|
// Ref: See "Moving Frostbite to PBR" Listing 22 |
|
|
|
// This formulation is for GGX only (with smith joint visibility or regular) |
|
|
|
float3 GetSpecularDominantDir(float3 N, float3 R, float roughness) |
|
|
|
{ |
|
|
|
float a = 1.0 - roughness; |
|
|
|
float lerpFactor = a * (sqrt(a) + roughness); |
|
|
|
// The result is not normalized as we fetch in a cubemap |
|
|
|
return lerp(N, R, lerpFactor); |
|
|
|
} |
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
// Anisotropic image based lighting |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
// To simulate the streching of highlight at grazing angle for IBL we shrink the roughness |
|
|
|
// which allow to fake an anisotropic specular lobe. |
|
|
|
// Ref: http://www.frostbite.com/2015/08/stochastic-screen-space-reflections/ - slide 84 |
|
|
|
float AnisotropicStrechAtGrazingAngle(float roughness, float perceptualRoughness, float NdotV) |
|
|
|
{ |
|
|
|
return roughness * lerp(saturate(NdotV * 2.0), 1.0, perceptualRoughness); |
|
|
|
return TransformGLtoDX(SphericalToCartesian(phi, cosTheta)); |
|
|
|
} |
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------- |
|
|
|
|
|
|
void ImportanceSampleCosDir(float2 u, |
|
|
|
// Performs uniform sampling of the unit disk. |
|
|
|
// Ref: PBRT v3, p. 777. |
|
|
|
float2 SampleDiskUniform(float2 u) |
|
|
|
{ |
|
|
|
float r = sqrt(u.x); |
|
|
|
float phi = TWO_PI * u.y; |
|
|
|
|
|
|
|
float sinPhi, cosPhi; |
|
|
|
sincos(phi, sinPhi, cosPhi); |
|
|
|
|
|
|
|
return r * float2(cosPhi, sinPhi); |
|
|
|
} |
|
|
|
|
|
|
|
// Performs cosine-weighted sampling of the hemisphere. |
|
|
|
// Ref: PBRT v3, p. 780. |
|
|
|
void SampleHemisphereCosine(float2 u, |
|
|
|
// Cosine sampling - ref: http://www.rorydriscoll.com/2009/01/07/better-sampling/ |
|
|
|
float cosTheta = sqrt(1.0 - u.x); |
|
|
|
float sinTheta = sqrt(u.x); |
|
|
|
float phi = TWO_PI * u.y; |
|
|
|
float3 localL; |
|
|
|
|
|
|
|
// Since we don't really care about the area distortion, |
|
|
|
// we substitute uniform disk sampling for the concentric one. |
|
|
|
localL.xy = SampleDiskUniform(u); |
|
|
|
float3 localL = SphericalToCartesian(phi, sinTheta, cosTheta); |
|
|
|
// Project the point from the disk onto the hemisphere. |
|
|
|
localL.z = sqrt(1.0 - u.x); |
|
|
|
|
|
|
|
NdotL = localL.z; |
|
|
|
|
|
|
|
|
|
|
void ImportanceSampleGGXDir(float2 u, |
|
|
|
float3 V, |
|
|
|
float3x3 localToWorld, |
|
|
|
float roughness, |
|
|
|
out float3 L, |
|
|
|
out float NdotL, |
|
|
|
out float NdotH, |
|
|
|
out float VdotH, |
|
|
|
bool VeqN = false) |
|
|
|
void SampleGGXDir(float2 u, |
|
|
|
float3 V, |
|
|
|
float3x3 localToWorld, |
|
|
|
float roughness, |
|
|
|
out float3 L, |
|
|
|
out float NdotL, |
|
|
|
out float NdotH, |
|
|
|
out float VdotH, |
|
|
|
bool VeqN = false) |
|
|
|
float sinTheta = sqrt(1.0 - cosTheta * cosTheta); |
|
|
|
float3 localH = SphericalToCartesian(phi, sinTheta, cosTheta); |
|
|
|
float3 localH = SphericalToCartesian(phi, cosTheta); |
|
|
|
|
|
|
|
NdotH = cosTheta; |
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// ref: http://blog.selfshadow.com/publications/s2012-shading-course/burley/s2012_pbs_disney_brdf_notes_v3.pdf p26 |
|
|
|
void ImportanceSampleAnisoGGXDir( float2 u, |
|
|
|
float3 V, |
|
|
|
float3 N, |
|
|
|
float3 tangentX, |
|
|
|
float3 tangentY, |
|
|
|
float roughnessT, |
|
|
|
float roughnessB, |
|
|
|
out float3 H, |
|
|
|
out float3 L) |
|
|
|
void SampleAnisoGGXDir(float2 u, |
|
|
|
float3 V, |
|
|
|
float3 N, |
|
|
|
float3 tangentX, |
|
|
|
float3 tangentY, |
|
|
|
float roughnessT, |
|
|
|
float roughnessB, |
|
|
|
out float3 H, |
|
|
|
out float3 L) |
|
|
|
// Local to world |
|
|
|
// H = tangentX * H.x + tangentY * H.y + N * H.z; |
|
|
|
|
|
|
|
// Convert sample from half angle to incident angle |
|
|
|
L = 2.0 * saturate(dot(V, H)) * H - V; |
|
|
|
} |
|
|
|
|
|
|
out float NdotL, |
|
|
|
out float weightOverPdf) |
|
|
|
{ |
|
|
|
ImportanceSampleCosDir(u, localToWorld, L, NdotL); |
|
|
|
SampleHemisphereCosine(u, localToWorld, L, NdotL); |
|
|
|
|
|
|
|
// Importance sampling weight for each sample |
|
|
|
// pdf = N.L / PI |
|
|
|
|
|
|
out float weightOverPdf) |
|
|
|
{ |
|
|
|
float NdotH; |
|
|
|
ImportanceSampleGGXDir(u, V, localToWorld, roughness, L, NdotL, NdotH, VdotH); |
|
|
|
SampleGGXDir(u, V, localToWorld, roughness, L, NdotL, NdotH, VdotH); |
|
|
|
|
|
|
|
// Importance sampling weight for each sample |
|
|
|
// pdf = D(H) * (N.H) / (4 * (L.H)) |
|
|
|
|
|
|
out float weightOverPdf) |
|
|
|
{ |
|
|
|
float3 H; |
|
|
|
ImportanceSampleAnisoGGXDir(u, V, N, tangentX, tangentY, roughnessT, roughnessB, H, L); |
|
|
|
SampleAnisoGGXDir(u, V, N, tangentX, tangentY, roughnessT, roughnessB, H, L); |
|
|
|
|
|
|
|
float NdotH = saturate(dot(N, H)); |
|
|
|
// Note: since L and V are symmetric around H, LdotH == VdotH |
|
|
|
|
|
|
|
|
|
|
float3 L; |
|
|
|
float NdotL, NdotH, VdotH; |
|
|
|
ImportanceSampleGGXDir(u, V, localToWorld, roughness, L, NdotL, NdotH, VdotH, true); |
|
|
|
SampleGGXDir(u, V, localToWorld, roughness, L, NdotL, NdotH, VdotH, true); |
|
|
|
|
|
|
|
float mipLevel; |
|
|
|
|
|
|
|