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.
211 lines
8.5 KiB
211 lines
8.5 KiB
// Matrix used to move the samples from world space to local area light space in order to get the UV mapping values
|
|
float4x4 _RaytracingAreaWorldToLocal;
|
|
|
|
struct MISSamplingInput
|
|
{
|
|
// Value used for the importance sampling
|
|
float2 noiseValue;
|
|
// Value that defines the probability of going for either area light or BRDF sampling
|
|
float brdfProb;
|
|
// Flag that defines which MIS technique we should be using
|
|
float mis;
|
|
// Local to world value of the local pixel
|
|
float3x3 localToWorld;
|
|
// Roughness of the current pixel
|
|
float roughness;
|
|
// Position of the current pixel
|
|
float3 positionWS;
|
|
// View direction of the current pixel
|
|
float3 viewWS;
|
|
// Rectangle position
|
|
float3 rectWSPos;
|
|
// Dimensions of the rectangle area light
|
|
float2 rectDimension;
|
|
};
|
|
|
|
struct MISSamplingOuput
|
|
{
|
|
// Sampled direction
|
|
float3 dir;
|
|
// Sampled position
|
|
float3 pos;
|
|
// PDF of the brdf technique
|
|
float brdfPDF;
|
|
// PDF of the light technique
|
|
float lightPDF;
|
|
// UV coordinate on the light
|
|
float2 sampleUV;
|
|
};
|
|
|
|
struct LightSamplingOutput
|
|
{
|
|
// Sampled direction
|
|
float3 dir;
|
|
// Sampled position
|
|
float3 pos;
|
|
// PDF of the light technique
|
|
float lightPDF;
|
|
};
|
|
|
|
#define ANALYTIC_RADIANCE_THRESHOLD 1e-4
|
|
|
|
// The approach here is that on a grid pattern, every pixel is using the opposite technique of his direct neighbor and every sample the technique used changes
|
|
void EvaluateMISTechnique(inout MISSamplingInput samplingInput)
|
|
{
|
|
if (samplingInput.noiseValue.x <= samplingInput.brdfProb)
|
|
{
|
|
samplingInput.mis = 0.0;
|
|
samplingInput.noiseValue.x /= samplingInput.brdfProb;
|
|
}
|
|
else
|
|
{
|
|
samplingInput.mis = 1.0;
|
|
samplingInput.noiseValue.x = (samplingInput.noiseValue.x - samplingInput.brdfProb) / (1.0 - samplingInput.brdfProb);
|
|
}
|
|
}
|
|
|
|
void InitSphericalQuad(LightData areaLightData, float3 positionWS, out SphQuad squad)
|
|
{
|
|
// Dimension of the area light
|
|
float halfWidth = areaLightData.size.x * 0.5;
|
|
float halfHeight = areaLightData.size.y * 0.5;
|
|
|
|
// Compute the world space position of the center of the lightlight
|
|
float3 areaLightPosWS = areaLightData.positionRWS;
|
|
|
|
// Let's first compute the position of the rectangle's corners in world space
|
|
float3 v0 = areaLightPosWS + areaLightData.right * halfWidth + areaLightData.up * halfHeight;
|
|
float3 v1 = areaLightPosWS + areaLightData.right * halfWidth + areaLightData.up * -halfHeight;
|
|
float3 v2 = areaLightPosWS + areaLightData.right * -halfWidth + areaLightData.up * -halfHeight;
|
|
float3 v3 = areaLightPosWS + areaLightData.right * -halfWidth + areaLightData.up * halfHeight;
|
|
|
|
float3 ex = v1 - v0;
|
|
float3 ey = v3 - v0;
|
|
|
|
SphQuadInit(v0, ex, ey, positionWS, squad);
|
|
}
|
|
|
|
bool InitSphericalQuad(LightData light, float3 positionWS, float3 normalWS, inout SphQuad squad)
|
|
{
|
|
ZERO_INITIALIZE(SphQuad, squad);
|
|
|
|
// Dimension of the area light
|
|
float halfWidth = light.size.x * 0.5;
|
|
float halfHeight = light.size.y * 0.5;
|
|
|
|
// Compute the world space position of the center of the lightlight
|
|
float3 areaLightPosWS = light.positionRWS;
|
|
|
|
// Let's evaluate if the point can potentially receive lighting from this point
|
|
float3x3 lightToWorld = float3x3(light.right, light.up, light.forward);
|
|
// Convert the point to the local space of the area light
|
|
float3 positionLS = mul(positionWS - areaLightPosWS, transpose(lightToWorld));
|
|
|
|
// Let's first compute the position of the rectangle's corners in world space
|
|
float3 v0 = areaLightPosWS + light.right * halfWidth + light.up * halfHeight;
|
|
float3 v1 = areaLightPosWS + light.right * halfWidth + light.up * -halfHeight;
|
|
float3 v2 = areaLightPosWS + light.right * -halfWidth + light.up * -halfHeight;
|
|
float3 v3 = areaLightPosWS + light.right * -halfWidth + light.up * halfHeight;
|
|
|
|
// Make sure that this point may have light contributions
|
|
float d = -dot(normalWS, positionWS);
|
|
if (positionLS.z <= 0.0 || (positionLS.z > 0.0 && ((dot(normalWS, v0) + d < 0) && (dot(normalWS, v1) + d < 0) && (dot(normalWS, v2) + d < 0) && (dot(normalWS, v3) + d < 0))))
|
|
return false;
|
|
|
|
float3 ex = v1 - v0;
|
|
float3 ey = v3 - v0;
|
|
|
|
SphQuadInit(v0, ex, ey, positionWS, squad);
|
|
return true;
|
|
}
|
|
|
|
float EvalBrdfPDF(MISSamplingInput misInput, float3 L)
|
|
{
|
|
// Compute the specular PDF
|
|
float3 H = normalize(L + misInput.viewWS );
|
|
float NdotH = dot(misInput.localToWorld[2], H);
|
|
float LdotH = dot(L, H);
|
|
return D_GGX(NdotH, misInput.roughness) * NdotH / (4.0 * LdotH);
|
|
}
|
|
|
|
void brdfSampleMIS(MISSamplingInput misInput, out float3 direction, out float pdf)
|
|
{
|
|
// Specular BRDF sampling
|
|
float NdotL, NdotH, VdotH;
|
|
SampleGGXDir(misInput.noiseValue, misInput.viewWS, misInput.localToWorld, misInput.roughness, direction, NdotL, NdotH, VdotH);
|
|
|
|
// Evaluate the pdf for this sample
|
|
pdf = EvalBrdfPDF(misInput, direction);
|
|
}
|
|
|
|
// Here we decided to use a "Damier" pattern to define which importance sampling technique to use for the MIS
|
|
bool GenerateMISSample(inout MISSamplingInput misInput, SphQuad squad, float3 viewVector, inout MISSamplingOuput misSamplingOutput)
|
|
{
|
|
// Flag that defines if this sample is valid
|
|
bool validity = false;
|
|
|
|
if (misInput.mis < 0.5f)
|
|
{
|
|
// Compute the output light direction
|
|
brdfSampleMIS(misInput, misSamplingOutput.dir, misSamplingOutput.brdfPDF);
|
|
|
|
// First we need to figure out if this sample touches the area light otherwise it is not a valid sample
|
|
float t;
|
|
validity = IntersectPlane(misInput.positionWS, misSamplingOutput.dir, misInput.rectWSPos, squad.z, t);
|
|
|
|
if (validity)
|
|
{
|
|
// Let's compute the sample pos
|
|
misSamplingOutput.pos = misInput.positionWS + t * misSamplingOutput.dir;
|
|
|
|
// The next question is: This the sample point inside the triangle? To do that for the moment we move it to the local space of the light and see if its distance to the center of the light
|
|
// is coherent with the dimensions of the light
|
|
float4 lsPoint = mul(_RaytracingAreaWorldToLocal, float4(misSamplingOutput.pos, 1.0)) * 2.0;
|
|
validity = abs(lsPoint.x) < misInput.rectDimension.x && abs(lsPoint.y) < misInput.rectDimension.y;
|
|
if (validity)
|
|
{
|
|
// Compute the uv on the light
|
|
misSamplingOutput.sampleUV = float2((lsPoint.x + misInput.rectDimension.x) / (2.0 * misInput.rectDimension.x), (lsPoint.y + misInput.rectDimension.y) / (2.0 * misInput.rectDimension.y));
|
|
// Compute the Light PDF
|
|
misSamplingOutput.lightPDF = 1.0f / squad.S;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
validity = true;
|
|
|
|
misSamplingOutput.pos = SphQuadSample(squad, misInput.noiseValue.x, misInput.noiseValue.y);
|
|
misSamplingOutput.dir = normalize(misSamplingOutput.pos - misInput.positionWS);
|
|
|
|
misSamplingOutput.brdfPDF = EvalBrdfPDF(misInput, misSamplingOutput.dir);
|
|
// Compute the Light PDF
|
|
misSamplingOutput.lightPDF = 1.0f / squad.S;
|
|
// Compute the uv on the light
|
|
float4 lsPoint = mul(_RaytracingAreaWorldToLocal, float4(misSamplingOutput.pos, 1.0)) * 2.0;
|
|
misSamplingOutput.sampleUV = float2((lsPoint.x + misInput.rectDimension.x) / (2.0 * misInput.rectDimension.x), (lsPoint.y + misInput.rectDimension.y) / (2.0 * misInput.rectDimension.y));
|
|
}
|
|
return validity;
|
|
}
|
|
|
|
void GenerateLightSample(float3 positionWS, float2 theSample, SphQuad squad, float3 viewVector, out LightSamplingOutput lightSamplingOutput)
|
|
{
|
|
lightSamplingOutput.pos = SphQuadSample(squad, theSample.x, theSample.y);
|
|
lightSamplingOutput.dir = normalize(lightSamplingOutput.pos - positionWS);
|
|
lightSamplingOutput.lightPDF = 1.0f / squad.S;
|
|
}
|
|
|
|
bool EvaluateMISProbabilties(DirectLighting lighting, float perceptualRoughness, out float brdfProb)
|
|
{
|
|
// Compute the magnitude of both the diffuse and specular terms
|
|
const float diffRadiance = Luminance(lighting.diffuse);
|
|
const float specRadiance = Luminance(lighting.specular);
|
|
const float totalRadiance = diffRadiance + specRadiance;
|
|
|
|
// The exact factor to attenuate the brdf probability using the perceptualRoughness has been experimentally defined. It requires
|
|
// an other pass to see if we can improve the quality if we use an other mapping (roughness instead of perceptualRoughness for instance)
|
|
brdfProb = specRadiance / max(totalRadiance, ANALYTIC_RADIANCE_THRESHOLD);
|
|
brdfProb = lerp(brdfProb, 0.0, perceptualRoughness);
|
|
|
|
return totalRadiance > ANALYTIC_RADIANCE_THRESHOLD;
|
|
}
|