2021-09-09 20:42:29 -04:00

162 lines
6.1 KiB
HLSL

#ifndef UNITY_SUB_SURFACE_INCLUDED
#define UNITY_SUB_SURFACE_INCLUDED
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/Raytracing/Shaders/SubSurface/RayTracingIntersectionSubSurface.hlsl"
// Data for the sub-surface walk
struct ScatteringResult
{
bool hit;
float3 outputPosition;
float3 outputNormal;
float3 outputDirection;
float3 outputDiffuse;
float3 outputThroughput;
};
// This function does the remapping from scattering color and distance to sigmaS and sigmaT
void RemapSubSurfaceScatteringParameters(float3 albedo, float3 radius, out float3 sigmaS, out float3 sigmaT)
{
float3 a = 1.0 - exp(albedo * (-5.09406 + albedo * (2.61188 - albedo * 4.31805)));
float3 s = 1.9 - albedo + 3.5 * (albedo - 0.8) * (albedo - 0.8);
sigmaT = 1.0 / max(radius * s, 1e-16);
sigmaS = sigmaT * a;
}
// This function allows us to pick a color channel
int GetChannel(float u1, float3 channelWeight)
{
if (channelWeight.x > u1)
return 0;
if ((channelWeight.x + channelWeight.y) > u1)
return 1;
return 2;
}
// Safe division to avoid nans
float3 SafeDivide(float3 val0, float3 val1)
{
float3 result;
result.x = val1.x != 0.0 ? val0.x / val1.x : 0.0;
result.y = val1.y != 0.0 ? val0.y / val1.y : 0.0;
result.z = val1.z != 0.0 ? val0.z / val1.z : 0.0;
return result;
}
void ScatteringWalk(float3 normalWS, float3 diffuseColor, float3 subSurfaceColor, uint2 pixelCoord, int rayCount, float3 positionWS, inout ScatteringResult scatteringResult)
{
// Initialize our variable
scatteringResult.outputThroughput = float3(1.0, 1.0, 1.0);
scatteringResult.hit = false;
// Remap from our user-friendly parameters to and sigmaS and sigmaT
float3 sigmaS, sigmaT;
RemapSubSurfaceScatteringParameters(diffuseColor, subSurfaceColor, sigmaS, sigmaT);
// Initialize the intersection structure
RayIntersectionSubSurface internalRayIntersection;
ZERO_INITIALIZE(RayIntersectionSubSurface, internalRayIntersection);
internalRayIntersection.pixelCoord = pixelCoord;
// Initialize the walk parameters
RayDesc internalRayDesc;
internalRayDesc.TMin = 0.0;
int maxWalkSteps = 16;
int walkIdx = 0;
float3 currentPathPosition = positionWS;
float3 transmittance;
while (!scatteringResult.hit && walkIdx < maxWalkSteps)
{
// Samples the random numbers for the direction
float dir0Rnd = GetBNDSequenceSample(pixelCoord, rayCount, 4 * walkIdx + 0);
float dir1Rnd = GetBNDSequenceSample(pixelCoord, rayCount, 4 * walkIdx + 1);
// Samples the random numbers for the distance
float dstRndSample = GetBNDSequenceSample(pixelCoord, rayCount, 4 * walkIdx + 2);
// Random number used to do channel selection
float channelSelection = GetBNDSequenceSample(pixelCoord, rayCount, 4 * walkIdx + 3);
// Compute the per-channel weight
float3 weights = scatteringResult.outputThroughput * SafeDivide(sigmaS, sigmaT);
// Normalize our weights
float channelSum = weights.x + weights.y + weights.z;
float3 channelWeight = SafeDivide(weights, channelSum);
// Evaluate what channel we should be using for this sample
int channelIdx = GetChannel(channelSelection, channelWeight);
// Fetch sigmaT
float currentSigmaT = sigmaT[channelIdx];
// Evaluate the length of our steps
internalRayDesc.TMax = -log(1.0f - dstRndSample) / currentSigmaT;
if (walkIdx != 0)
{
internalRayDesc.Direction = normalize(SampleSphereUniform(dir0Rnd, dir1Rnd));
internalRayDesc.Origin = currentPathPosition;
}
else
{
// If we just started the walk, the surface is considered back-Lambertian
internalRayDesc.Direction = normalize(SampleHemisphereCosine(dir0Rnd, dir1Rnd, -normalWS));
internalRayDesc.Origin = positionWS - normalWS * _RaytracingRayBias;
}
// Initialize the intersection data
internalRayIntersection.t = -1.0;
internalRayIntersection.outNormal = 0.0;
// Do the next step
// TODO: Maybe include only the subsurface meshes.
TraceRay(_RaytracingAccelerationStructure, RAY_FLAG_FORCE_OPAQUE | RAY_FLAG_CULL_FRONT_FACING_TRIANGLES,
RAYTRACINGRENDERERFLAG_OPAQUE, 0, 1, 0, internalRayDesc, internalRayIntersection);
// Define if we did a hit
scatteringResult.hit = internalRayIntersection.t > 0.0;
// How much did the ray travel?
float t = scatteringResult.hit ? internalRayIntersection.t : internalRayDesc.TMax;
// Evaluate the transmittance for the current segment
transmittance = exp(-t * sigmaT);
// Evaluate the pdf for the current segment
float pdf = dot((scatteringResult.hit ? transmittance : sigmaT * transmittance), channelWeight);
// Contribute to the throughput
scatteringResult.outputThroughput *= SafeDivide(scatteringResult.hit ? transmittance : sigmaS * transmittance, pdf);
// FIXME: The following multiplication by diffuseColor looks rather fishy...
// This should probably not be done at all, and definitely not on select path lengths.
// If we exit right away, the diffuse color is the throughput value
if (scatteringResult.hit && walkIdx == 0)
scatteringResult.outputThroughput *= diffuseColor;
// Compute the next path position
currentPathPosition = currentPathPosition + internalRayDesc.Direction * t;
scatteringResult.outputNormal = internalRayIntersection.outNormal;
// increment the path depth
walkIdx++;
}
// If we did not hit (we could connect) but for the moment we kill the path
if (!scatteringResult.hit)
scatteringResult.outputThroughput = float3(0.0, 0.0, 0.0);
else
{
scatteringResult.outputPosition = currentPathPosition;
scatteringResult.outputDirection = internalRayDesc.Direction;
scatteringResult.outputDiffuse = internalRayIntersection.outIndirectDiffuse;
}
}
#endif // UNITY_SUB_SURFACE_INCLUDED