162 lines
6.1 KiB
HLSL
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
|