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.
 
 
 
 

644 lines
20 KiB

#ifndef UNITY_PATH_TRACING_BSDF_INCLUDED
#define UNITY_PATH_TRACING_BSDF_INCLUDED
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/PathTracing/Shaders/PathTracingSampling.hlsl"
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/Raytracing/Shaders/SubSurface.hlsl"
#define DELTA_PDF 1000000.0
#define MIN_GGX_ROUGHNESS 0.00001
#define MAX_GGX_ROUGHNESS 0.99999
float3x3 GetTangentFrame(float3 tangent, float3 bitangent, float3 normal, bool anisotropic)
{
// If we have anisotropy, we want our local frame to follow tangential directions, otherwise any orientation will do
return anisotropic ? float3x3(tangent, bitangent, normal) : GetLocalFrame(normal);
}
float Lambda_AnisoGGX(float roughnessX,
float roughnessY,
float3 V)
{
return 0.5 * (sqrt(1.0 + (Sq(roughnessX * V.x) + Sq(roughnessY * V.y)) / Sq(V.z)) - 1.0);
}
float G_AnisoGGX(float roughnessX,
float roughnessY,
float3 V)
{
return rcp(1.0 + Lambda_AnisoGGX(roughnessX, roughnessY, V));
}
float D_AnisoGGX(float roughnessX,
float roughnessY,
float3 H)
{
return rcp(PI * roughnessX * roughnessY * Sq(Sq(H.x / roughnessX) + Sq(H.y / roughnessY) + Sq(H.z)));
}
namespace BRDF
{
float GetGGXMultipleScatteringEnergy(float roughness, float sqrtNdotV)
{
float2 coordLUT = Remap01ToHalfTexelCoord(float2(sqrtNdotV, roughness), FGDTEXTURE_RESOLUTION);
float E = SAMPLE_TEXTURE2D_LOD(_PreIntegratedFGD_GGXDisneyDiffuse, s_linear_clamp_sampler, coordLUT, 0).y;
return (1.0 - E) / E;
}
bool SampleAnisoGGX(MaterialData mtlData,
float3 normal,
float roughnessX,
float roughnessY,
float3 fresnel0,
float3 inputSample,
out float3 outgoingDir,
out float3 value,
out float pdf,
out float3 fresnel)
{
roughnessX = clamp(roughnessX, MIN_GGX_ROUGHNESS, MAX_GGX_ROUGHNESS);
roughnessY = clamp(roughnessY, MIN_GGX_ROUGHNESS, MAX_GGX_ROUGHNESS);
float VdotH;
float3 localV, localH;
float3x3 localToWorld = GetTangentFrame(mtlData.bsdfData.tangentWS,
mtlData.bsdfData.bitangentWS,
normal, roughnessX != roughnessY);
SampleAnisoGGXVisibleNormal(inputSample.xy, mtlData.V, localToWorld, roughnessX, roughnessY, localV, localH, VdotH);
// Compute the reflection direction
float3 localL = 2.0 * VdotH * localH - localV;
outgoingDir = mul(localL, localToWorld);
if (localL.z < 0.001 || !IsAbove(mtlData, outgoingDir))
{
pdf = 0.0;
fresnel = 0.0;
return false;
}
float pdfNoGV = D_AnisoGGX(roughnessX, roughnessY, localH) / (4.0 * localV.z);
float lambdaVPlusOne = Lambda_AnisoGGX(roughnessX, roughnessY, localV) + 1.0;
pdf = pdfNoGV / lambdaVPlusOne;
if (pdf < 0.001)
return false;
float lambdaL = Lambda_AnisoGGX(roughnessX, roughnessY, localL);
fresnel = F_Schlick(fresnel0, VdotH);
value = fresnel * pdfNoGV / (lambdaVPlusOne + lambdaL);
return true;
}
bool SampleAnisoGGX(MaterialData mtlData,
float3 normal,
float roughnessX,
float roughnessY,
float3 fresnel0,
float3 inputSample,
out float3 outgoingDir,
out float3 value,
out float pdf)
{
float3 dummyFresnel;
return SampleAnisoGGX(mtlData, normal, roughnessX, roughnessY, fresnel0, inputSample, outgoingDir, value, pdf, dummyFresnel);
}
void EvaluateAnisoGGX(MaterialData mtlData,
float3 normal,
float roughnessX,
float roughnessY,
float3 fresnel0,
float3 outgoingDir,
out float3 value,
out float pdf,
out float3 fresnel)
{
float NdotV = dot(normal, mtlData.V);
if (NdotV < 0.001)
{
value = 0.0;
pdf = 0.0;
fresnel = 0.0;
return;
}
roughnessX = clamp(roughnessX, MIN_GGX_ROUGHNESS, MAX_GGX_ROUGHNESS);
roughnessY = clamp(roughnessY, MIN_GGX_ROUGHNESS, MAX_GGX_ROUGHNESS);
float3x3 worldToLocal = transpose(GetTangentFrame(mtlData.bsdfData.tangentWS,
mtlData.bsdfData.bitangentWS,
normal, roughnessX != roughnessY));
float3 localV = mul(mtlData.V, worldToLocal);
float3 localL = mul(outgoingDir, worldToLocal);
float3 localH = normalize(localV + localL);
float VdotH = dot(localV, localH);
float pdfNoGV = D_AnisoGGX(roughnessX, roughnessY, localH) / (4.0 * localV.z);
float lambdaVPlusOne = Lambda_AnisoGGX(roughnessX, roughnessY, localV) + 1.0;
float lambdaL = Lambda_AnisoGGX(roughnessX, roughnessY, localL);
fresnel = F_Schlick(fresnel0, VdotH);
value = fresnel * pdfNoGV / (lambdaVPlusOne + lambdaL);
pdf = pdfNoGV / lambdaVPlusOne;
}
void EvaluateAnisoGGX(MaterialData mtlData,
float3 normal,
float roughnessX,
float roughnessY,
float3 fresnel0,
float3 outgoingDir,
out float3 value,
out float pdf)
{
float3 dummyFresnel;
EvaluateAnisoGGX(mtlData, normal, roughnessX, roughnessY, fresnel0, outgoingDir, value, pdf, dummyFresnel);
}
bool SampleGGX(MaterialData mtlData,
float3 normal,
float roughness,
float3 fresnel0,
float3 inputSample,
out float3 outgoingDir,
out float3 value,
out float pdf,
out float3 fresnel)
{
return SampleAnisoGGX(mtlData, normal, roughness, roughness, fresnel0, inputSample, outgoingDir, value, pdf, fresnel);
}
bool SampleGGX(MaterialData mtlData,
float3 normal,
float roughness,
float3 fresnel0,
float3 inputSample,
out float3 outgoingDir,
out float3 value,
out float pdf)
{
float3 dummyFresnel;
return SampleGGX(mtlData, normal, roughness, fresnel0, inputSample, outgoingDir, value, pdf, dummyFresnel);
}
void EvaluateGGX(MaterialData mtlData,
float3 normal,
float roughness,
float3 fresnel0,
float3 outgoingDir,
out float3 value,
out float pdf,
out float3 fresnel)
{
return EvaluateAnisoGGX(mtlData, normal, roughness, roughness, fresnel0, outgoingDir, value, pdf, fresnel);
}
void EvaluateGGX(MaterialData mtlData,
float3 normal,
float roughness,
float3 fresnel0,
float3 outgoingDir,
out float3 value,
out float pdf)
{
float3 dummyFresnel;
EvaluateGGX(mtlData, normal, roughness, fresnel0, outgoingDir, value, pdf, dummyFresnel);
}
bool SampleDelta(MaterialData mtlData,
float3 normal,
float ior,
out float3 outgoingDir,
out float3 value,
out float pdf)
{
if (IsAbove(mtlData))
{
outgoingDir = reflect(-mtlData.V, normal);
float NdotV = dot(normal, mtlData.V);
value = F_Schlick(mtlData.bsdfData.fresnel0, NdotV);
}
else // Below
{
outgoingDir = -reflect(mtlData.V, normal);
float NdotV = -dot(normal, mtlData.V);
value = F_FresnelDielectric(1.0 / ior, NdotV);
}
value *= DELTA_PDF;
pdf = DELTA_PDF;
return any(outgoingDir);
}
bool SampleLambert(MaterialData mtlData,
float3 normal,
float3 inputSample,
out float3 outgoingDir,
out float3 value,
out float pdf)
{
outgoingDir = SampleHemisphereCosine(inputSample.x, inputSample.y, normal);
if (!IsAbove(mtlData, outgoingDir))
{
pdf = 0.0;
return false;
}
pdf = dot(normal, outgoingDir) * INV_PI;
if (pdf < 0.001)
return false;
value = mtlData.bsdfData.diffuseColor * pdf;
return true;
}
void EvaluateLambert(MaterialData mtlData,
float3 normal,
float3 outgoingDir,
out float3 value,
out float pdf)
{
pdf = saturate(dot(normal, outgoingDir)) * INV_PI;
value = mtlData.bsdfData.diffuseColor * pdf;
}
#ifndef USE_DIFFUSE_LAMBERT_BRDF
bool SampleBurley(MaterialData mtlData,
float3 normal,
float3 inputSample,
out float3 outgoingDir,
out float3 value,
out float pdf)
{
outgoingDir = SampleHemisphereCosine(inputSample.x, inputSample.y, normal);
if (!IsAbove(mtlData, outgoingDir))
{
pdf = 0.0;
return false;
}
float NdotL = dot(normal, outgoingDir);
pdf = NdotL * INV_PI;
if (pdf < 0.001)
return false;
float NdotV = saturate(dot(normal, mtlData.V));
float LdotV = saturate(dot(outgoingDir, mtlData.V));
value = mtlData.bsdfData.diffuseColor * DisneyDiffuseNoPI(NdotV, NdotL, LdotV, mtlData.bsdfData.perceptualRoughness) * pdf;
return true;
}
void EvaluateBurley(MaterialData mtlData,
float3 normal,
float3 outgoingDir,
out float3 value,
out float pdf)
{
float NdotL = saturate(dot(normal, outgoingDir));
float NdotV = saturate(dot(normal, mtlData.V));
float LdotV = saturate(dot(outgoingDir, mtlData.V));
pdf = NdotL * INV_PI;
value = mtlData.bsdfData.diffuseColor * DisneyDiffuseNoPI(NdotV, NdotL, LdotV, mtlData.bsdfData.perceptualRoughness) * pdf;
}
#endif // USE_DIFFUSE_LAMBERT_BRDF
bool SampleDiffuse(MaterialData mtlData,
float3 normal,
float3 inputSample,
out float3 outgoingDir,
out float3 value,
out float pdf)
{
#ifdef USE_DIFFUSE_LAMBERT_BRDF
return SampleLambert(mtlData, normal, inputSample, outgoingDir, value, pdf);
#else
return SampleBurley(mtlData, normal, inputSample, outgoingDir, value, pdf);
#endif
}
void EvaluateDiffuse(MaterialData mtlData,
float3 normal,
float3 outgoingDir,
out float3 value,
out float pdf)
{
#ifdef USE_DIFFUSE_LAMBERT_BRDF
EvaluateLambert(mtlData, normal, outgoingDir, value, pdf);
#else
EvaluateBurley(mtlData, normal, outgoingDir, value, pdf);
#endif
}
void EvaluateSheen(MaterialData mtlData,
float3 normal,
float roughness,
float3 outgoingDir,
out float3 value,
out float pdf)
{
// We use cosine-weighted sampling for this lobe
float NdotL = saturate(dot(normal, outgoingDir));
pdf = NdotL * INV_PI;
float NdotV = dot(normal, mtlData.V);
if (NdotV < 0.001)
{
value = 0.0;
return;
}
roughness = clamp(roughness, MIN_GGX_ROUGHNESS, MAX_GGX_ROUGHNESS);
float3 H = normalize(mtlData.V + outgoingDir);
float NdotH = dot(normal, H);
float D = D_Charlie(NdotH, roughness);
// We use this visibility term to match the raster implementation (Fabric.hlsl)
float Vg = V_Ashikhmin(NdotL, NdotV);
//float Vg = V_Charlie(NdotL, NdotV, roughness);
value = mtlData.bsdfData.fresnel0 * D * Vg * NdotL;
}
} // namespace BRDF
namespace BTDF
{
bool SampleGGX(MaterialData mtlData,
float3 normal,
float roughness,
float ior,
float3 inputSample,
out float3 outgoingDir,
out float3 value,
out float pdf)
{
roughness = clamp(roughness, MIN_GGX_ROUGHNESS, MAX_GGX_ROUGHNESS);
float NdotL, NdotH, VdotH;
float3x3 localToWorld = GetLocalFrame(normal);
SampleGGXDir(inputSample.xy, mtlData.V, localToWorld, roughness, outgoingDir, NdotL, NdotH, VdotH);
// FIXME: won't be necessary after new version of SampleGGXDir()
float3 H = normalize(mtlData.V + outgoingDir);
outgoingDir = refract(-mtlData.V, H, 1.0 / ior);
NdotL = dot(normal, outgoingDir);
if (NdotL > -0.001 || !IsBelow(mtlData, outgoingDir))
{
pdf = 0.0;
return false;
}
float NdotV = dot(normal, mtlData.V);
float LdotH = dot(outgoingDir, H);
float3 F = F_Schlick(mtlData.bsdfData.fresnel0, VdotH);
float D = D_GGX(NdotH, roughness);
float Vg = V_SmithJointGGX(-NdotL, NdotV, roughness);
// Compute the Jacobian
float jacobian = max(abs(VdotH + ior * LdotH), 0.001);
jacobian = Sq(ior) * abs(LdotH) / Sq(jacobian);
pdf = D * NdotH * jacobian;
value = abs(4.0 * (1.0 - F) * D * Vg * NdotL * VdotH * jacobian);
return pdf > 0.001;
}
bool SampleAnisoGGX(MaterialData mtlData,
float3 normal,
float roughnessX,
float roughnessY,
float ior,
float3 inputSample,
out float3 outgoingDir,
out float3 value,
out float pdf)
{
roughnessX = clamp(roughnessX, MIN_GGX_ROUGHNESS, MAX_GGX_ROUGHNESS);
roughnessY = clamp(roughnessY, MIN_GGX_ROUGHNESS, MAX_GGX_ROUGHNESS);
float VdotH;
float3 localV, localH;
float3x3 localToWorld = GetTangentFrame(mtlData.bsdfData.tangentWS,
mtlData.bsdfData.bitangentWS,
normal, roughnessX != roughnessY);
SampleAnisoGGXVisibleNormal(inputSample.xy, mtlData.V, localToWorld, roughnessX, roughnessY, localV, localH, VdotH);
// Compute refraction direction instead of reflection
float3 localL = refract(-localV, localH, 1.0 / ior);
outgoingDir = mul(localL, localToWorld);
if (localL.z > -0.001 || !IsBelow(mtlData, outgoingDir))
{
pdf = 0.0;
return false;
}
// Compute the Jacobian
float LdotH = dot(localL, localH);
float jacobian = max(abs(VdotH + ior * LdotH), 0.001);
jacobian = Sq(ior) * abs(LdotH) / Sq(jacobian);
float3 F = F_Schlick(mtlData.bsdfData.fresnel0, VdotH);
float D = D_AnisoGGX(roughnessX, roughnessY, localH);
float pdfNoGV = D * VdotH * jacobian / localV.z;
float lambdaVPlusOne = Lambda_AnisoGGX(roughnessX, roughnessY, localV) + 1.0;
float lambdaL = Lambda_AnisoGGX(roughnessX, roughnessY, localL);
pdf = pdfNoGV / lambdaVPlusOne;
value = abs((1.0 - F) * pdfNoGV / (lambdaVPlusOne + lambdaL));
return pdf > 0.001;
}
bool SampleDelta(MaterialData mtlData,
float3 normal,
float ior,
out float3 outgoingDir,
out float3 value,
out float pdf)
{
if (IsAbove(mtlData))
{
outgoingDir = refract(-mtlData.V, normal, 1.0 / ior);
float NdotV = dot(normal, mtlData.V);
value = 1.0 - F_Schlick(mtlData.bsdfData.fresnel0, NdotV);
}
else // Below
{
outgoingDir = -refract(mtlData.V, normal, ior);
float NdotV = -dot(normal, mtlData.V);
value = 1.0 - F_FresnelDielectric(1.0 / ior, NdotV);
}
value *= DELTA_PDF;
pdf = DELTA_PDF;
return any(outgoingDir);
}
bool SampleLambert(MaterialData mtlData,
float3 normal,
float3 inputSample,
out float3 outgoingDir,
out float3 value,
out float pdf)
{
bool retVal = BRDF::SampleLambert(mtlData, normal, inputSample, outgoingDir, value, pdf);
outgoingDir = -outgoingDir;
return retVal;
}
void EvaluateLambert(MaterialData mtlData,
float3 normal,
float3 outgoingDir,
out float3 value,
out float pdf)
{
BRDF::EvaluateLambert(mtlData, normal, -outgoingDir, value, pdf);
}
} // namespace BTDF
namespace SSS
{
#define MAX_WALK_STEPS 16
#define DIM_OFFSET 42
#define DIM_THIN_NORMAL_FLIP 108 // First fully available dimension
struct Result
{
float3 throughput;
float3 exitPosition;
float3 exitNormal;
};
bool RandomWalk(float3 position, float3 normal, float3 diffuseColor, float3 meanFreePath, uint2 pixelCoord, out Result result, bool isThin = false)
{
// Remap from our user-friendly parameters to and sigmaS and sigmaT
float3 sigmaS, sigmaT;
RemapSubSurfaceScatteringParameters(diffuseColor, meanFreePath, sigmaS, sigmaT);
// Initialize the payload
PathPayload payload;
payload.segmentID = SEGMENT_ID_RANDOM_WALK;
// Initialize the walk parameters
RayDesc ray;
ray.Origin = position - normal * _RayTracingRayBias;
ray.TMin = 0.0;
bool hit;
uint walkIdx = 0;
float4 walkSample;
result.throughput = 1.0;
do // Start our random walk
{
// Samples for direction, distance and channel selection
walkSample = GetSample4D(pixelCoord, _RaytracingSampleIndex, DIM_OFFSET + 4 * walkIdx);
// Compute the per-channel weight
float3 weights = result.throughput * SafeDivide(sigmaS, sigmaT);
// Normalize our weights
float wSum = weights.x + weights.y + weights.z;
float3 channelWeights = SafeDivide(weights, wSum);
// Evaluate what channel we should be using for this sample
uint channelIdx = GetChannel(walkSample[3], channelWeights);
// Evaluate the length of our steps
ray.TMax = -log(1.0 - walkSample[2]) / sigmaT[channelIdx];
// Sample our next path segment direction
ray.Direction = walkIdx ?
SampleSphereUniform(walkSample[0], walkSample[1]) : SampleHemisphereCosine(walkSample[0], walkSample[1], -normal);
// Initialize the payload data
payload.rayTHit = FLT_INF;
// Do the next step
TraceRay(_RaytracingAccelerationStructure, RAY_FLAG_FORCE_NON_OPAQUE | RAY_FLAG_SKIP_CLOSEST_HIT_SHADER | RAY_FLAG_CULL_FRONT_FACING_TRIANGLES,
RAYTRACINGRENDERERFLAG_PATH_TRACING, 0, 1, 1, ray, payload);
// Check if we hit something
hit = payload.rayTHit < FLT_INF;
// How much did the ray travel?
float t = hit ? payload.rayTHit : ray.TMax;
// Evaluate the transmittance for the current segment
float3 transmittance = exp(-t * sigmaT);
// Evaluate the pdf for the current segment
float pdf = dot((hit ? transmittance : sigmaT * transmittance), channelWeights);
// Contribute to the throughput
result.throughput *= SafeDivide(hit ? transmittance : sigmaS * transmittance, pdf);
// Compute the next path position
ray.Origin += ray.Direction * t;
// increment the path depth
walkIdx++;
}
while (!hit && walkIdx < MAX_WALK_STEPS);
// Set the exit intersection position and normal
if (!hit)
{
result.exitPosition = position;
result.exitNormal = normal;
result.throughput = diffuseColor;
// By not returning false here, we default to a diffuse BRDF when an intersection is not found;
// this is physically wrong, but may prove more convenient for a user, as results will look
// like diffuse instead of getting slightly darker when the mean free path becomes shorter.
//return false;
}
else
{
result.exitPosition = ray.Origin;
result.exitNormal = payload.value;
}
#ifdef _DOUBLESIDED_ON
// If we are dealing with a thin (double-sided) surface, we randomly flip the output normal half the time
if (isThin)
{
if (GetSample(pixelCoord, _RaytracingSampleIndex, DIM_THIN_NORMAL_FLIP) < 0.5)
result.exitNormal = -result.exitNormal;
result.throughput *= 2.0;
}
#endif
return true;
}
} // namespace SSS
#endif // UNITY_PATH_TRACING_BSDF_INCLUDED