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.
1220 lines
42 KiB
1220 lines
42 KiB
#ifndef UNITY_PATH_TRACING_LIGHT_INCLUDED
|
|
#define UNITY_PATH_TRACING_LIGHT_INCLUDED
|
|
|
|
// This is just because it need to be defined, shadow maps are not used.
|
|
#define SHADOW_LOW
|
|
|
|
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/Lighting/Lighting.hlsl"
|
|
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/Lighting/LightLoop/CookieSampling.hlsl"
|
|
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/Lighting/LightLoop/LightLoopDef.hlsl"
|
|
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/Lighting/LightEvaluation.hlsl"
|
|
|
|
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/Raytracing/Shaders/ShaderVariablesRaytracingLightLoop.hlsl"
|
|
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/Raytracing/Shaders/Shadows/SphericalQuad.hlsl"
|
|
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/Raytracing/Shaders/Common/AtmosphericScatteringRayTracing.hlsl"
|
|
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/PathTracing/Shaders/PathTracingSampling.hlsl"
|
|
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/PathTracing/Shaders/PathTracingSkySampling.hlsl"
|
|
|
|
// Define this to use the Ray Tracing light cluster
|
|
#define USE_LIGHT_CLUSTER
|
|
|
|
#ifdef USE_LIGHT_CLUSTER
|
|
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/Raytracing/Shaders/RayTracingLightCluster.hlsl"
|
|
#endif
|
|
|
|
// How many lights (at most) do we support at one given shading point
|
|
// FIXME: hardcoded limits are evil, this LightList should instead be put together in C#
|
|
#define MAX_LOCAL_LIGHT_COUNT SHADEROPTIONS_PATH_TRACING_MAX_LIGHT_COUNT
|
|
#define MAX_DISTANT_LIGHT_COUNT 4
|
|
|
|
#define DELTA_PDF 1000000.0
|
|
#define DOT_PRODUCT_EPSILON 0.001
|
|
|
|
#define SAMPLE_SOLID_ANGLE
|
|
|
|
// Supports punctual, spot, rect area and directional lights, in addition to one sky (aka environment)
|
|
struct LightList
|
|
{
|
|
uint localCount;
|
|
uint localPointCount;
|
|
uint localIndex[MAX_LOCAL_LIGHT_COUNT];
|
|
float localWeight;
|
|
|
|
uint distantCount;
|
|
uint distantIndex[MAX_DISTANT_LIGHT_COUNT];
|
|
float distantWeight;
|
|
|
|
uint skyCount; // 0 or 1
|
|
float skyWeight;
|
|
|
|
#ifdef USE_LIGHT_CLUSTER
|
|
uint cellIndex;
|
|
#endif
|
|
};
|
|
|
|
bool IsAreaLightActive(LightData lightData, float3 position, float3 normal)
|
|
{
|
|
float3 lightToPosition = position - lightData.positionRWS;
|
|
|
|
#ifndef USE_LIGHT_CLUSTER
|
|
// Check light range first
|
|
if (Length2(lightToPosition) > Sq(lightData.range))
|
|
return false;
|
|
#endif
|
|
|
|
// If this is tube light, we're done
|
|
if (lightData.lightType == GPULIGHTTYPE_TUBE)
|
|
return true;
|
|
|
|
// Check that the shading position is in front of the light
|
|
float lightCos = dot(lightToPosition, lightData.forward);
|
|
if (lightCos < 0.0)
|
|
return false;
|
|
|
|
// Check that at least part of the light is above the tangent plane
|
|
float lightTangentDist = dot(normal, lightToPosition);
|
|
if (4.0 * lightTangentDist * abs(lightTangentDist) > Sq(lightData.size.x) + Sq(lightData.size.y))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool IsPointLightActive(LightData lightData, float3 position, float3 normal)
|
|
{
|
|
float3 lightToPosition = position - lightData.positionRWS;
|
|
|
|
if(lightData.lightType != GPULIGHTTYPE_PROJECTOR_BOX)
|
|
{
|
|
#ifndef USE_LIGHT_CLUSTER
|
|
// Check light range first
|
|
if (Length2(lightToPosition) > Sq(lightData.range))
|
|
return false;
|
|
#endif
|
|
|
|
// Check that at least part of the light is above the tangent plane
|
|
float lightTangentDist = dot(normal, lightToPosition);
|
|
if (lightTangentDist * abs(lightTangentDist) > lightData.size.x)
|
|
return false;
|
|
}
|
|
|
|
// If this is an omni-directional point light, we're done
|
|
if (lightData.lightType == GPULIGHTTYPE_POINT)
|
|
return true;
|
|
|
|
// Check that we are on the right side of the light plane
|
|
float z = dot(lightToPosition, lightData.forward);
|
|
if (z < 0.0)
|
|
return false;
|
|
|
|
if (lightData.lightType == GPULIGHTTYPE_SPOT)
|
|
{
|
|
// Offset the light position towards the back, to account for the radius,
|
|
// then check whether we are still within the dilated cone angle
|
|
float sinTheta2 = 1.0 - Sq(lightData.angleOffset / lightData.angleScale);
|
|
float3 lightRadiusOffset = sqrt(lightData.size.x / sinTheta2) * lightData.forward;
|
|
float lightCos = dot(normalize(lightToPosition + lightRadiusOffset), lightData.forward);
|
|
|
|
return lightCos * lightData.angleScale + lightData.angleOffset > 0.0;
|
|
}
|
|
|
|
// Our light type is either BOX or PYRAMID
|
|
float x = abs(dot(lightToPosition, lightData.right));
|
|
float y = abs(dot(lightToPosition, lightData.up));
|
|
|
|
return (lightData.lightType == GPULIGHTTYPE_PROJECTOR_BOX) ?
|
|
x < 1.0 && y < 1.0 : // BOX
|
|
x < z && y < z; // PYRAMID
|
|
}
|
|
|
|
bool IsDistantLightActive(DirectionalLightData lightData, float3 normal)
|
|
{
|
|
return dot(normal, lightData.forward) <= sin(lightData.angularDiameter * 0.5);
|
|
}
|
|
|
|
LightList CreateLightList(float3 position, float3 normal, uint lightLayers = RENDERING_LAYERS_MASK,
|
|
bool withPoint = true, bool withArea = true, bool withDistant = true,
|
|
float3 lightPosition = FLT_MAX)
|
|
{
|
|
LightList list = (LightList)0;
|
|
uint i;
|
|
|
|
// First take care of local lights (point, area)
|
|
if (withPoint || withArea)
|
|
{
|
|
uint localPointCount, localCount;
|
|
|
|
#ifdef USE_LIGHT_CLUSTER
|
|
if (PointInsideCluster(position))
|
|
{
|
|
list.cellIndex = GetClusterCellIndex(position);
|
|
localPointCount = GetPunctualLightEndIndexInClusterCell(list.cellIndex);
|
|
localCount = GetAreaLightEndIndexInClusterCell(list.cellIndex);
|
|
}
|
|
else
|
|
{
|
|
localPointCount = 0;
|
|
localCount = 0;
|
|
}
|
|
#else
|
|
localPointCount = _WorldPunctualLightCount;
|
|
localCount = _WorldPunctualLightCount + _WorldAreaLightCount;
|
|
#endif
|
|
|
|
// Do we have an imposed local light (identificed by position), for volumetric scattering?
|
|
bool forceLightPosition = (lightPosition.x != FLT_MAX);
|
|
|
|
// First point lights (including spot lights)
|
|
if (withPoint)
|
|
{
|
|
for (i = 0; i < localPointCount && list.localPointCount < MAX_LOCAL_LIGHT_COUNT; i++)
|
|
{
|
|
#ifdef USE_LIGHT_CLUSTER
|
|
const LightData lightData = FetchClusterLightIndex(list.cellIndex, i);
|
|
#else
|
|
const LightData lightData = _WorldLightDatas[i];
|
|
#endif
|
|
|
|
if (forceLightPosition && any(lightPosition - lightData.positionRWS))
|
|
continue;
|
|
|
|
if (IsMatchingLightLayer(lightData.lightLayers, lightLayers) && IsPointLightActive(lightData, position, normal))
|
|
list.localIndex[list.localPointCount++] = i;
|
|
}
|
|
|
|
list.localCount = list.localPointCount;
|
|
}
|
|
|
|
// Then rect area lights
|
|
if (withArea)
|
|
{
|
|
for (i = localPointCount; i < localCount && list.localCount < MAX_LOCAL_LIGHT_COUNT; i++)
|
|
{
|
|
#ifdef USE_LIGHT_CLUSTER
|
|
const LightData lightData = FetchClusterLightIndex(list.cellIndex, i);
|
|
#else
|
|
const LightData lightData = _WorldLightDatas[i];
|
|
#endif
|
|
|
|
if (forceLightPosition && any(lightPosition - lightData.positionRWS))
|
|
continue;
|
|
|
|
if (IsMatchingLightLayer(lightData.lightLayers, lightLayers) && IsAreaLightActive(lightData, position, normal))
|
|
list.localIndex[list.localCount++] = i;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Then filter the active distant lights (directional)
|
|
list.distantCount = 0;
|
|
|
|
if (withDistant)
|
|
{
|
|
for (i = 0; i < _DirectionalLightCount && list.distantCount < MAX_DISTANT_LIGHT_COUNT; i++)
|
|
{
|
|
if (IsMatchingLightLayer(_DirectionalLightDatas[i].lightLayers, lightLayers) && IsDistantLightActive(_DirectionalLightDatas[i], normal))
|
|
list.distantIndex[list.distantCount++] = i;
|
|
}
|
|
}
|
|
|
|
// And finally the sky light
|
|
list.skyCount = withDistant && IsSkyEnabled() && IsSkySamplingEnabled() ? 1 : 0;
|
|
|
|
// Compute the weights, used for the lights PDF (we split 50/50 between local and distant+sky)
|
|
list.localWeight = list.localCount ? (list.distantCount + list.skyCount ? 0.5 : 1.0) : 0.0;
|
|
float nonLocalWeight = 1.0 - list.localWeight;
|
|
list.distantWeight = list.distantCount ? (list.skyCount ? 0.5 * nonLocalWeight : nonLocalWeight) : 0.0;
|
|
list.skyWeight = nonLocalWeight - list.distantWeight;
|
|
|
|
return list;
|
|
}
|
|
|
|
uint GetLightCount(LightList list)
|
|
{
|
|
return list.localCount + list.distantCount + list.skyCount;
|
|
}
|
|
|
|
LightData GetLocalLightData(LightList list, uint i)
|
|
{
|
|
#ifdef USE_LIGHT_CLUSTER
|
|
return FetchClusterLightIndex(list.cellIndex, list.localIndex[i]);
|
|
#else
|
|
return _WorldLightDatas[list.localIndex[i]];
|
|
#endif
|
|
}
|
|
|
|
LightData GetLocalLightData(LightList list, float inputSample)
|
|
{
|
|
return GetLocalLightData(list, (uint)(inputSample * list.localCount));
|
|
}
|
|
|
|
DirectionalLightData GetDistantLightData(LightList list, uint i)
|
|
{
|
|
return _DirectionalLightDatas[list.distantIndex[i]];
|
|
}
|
|
|
|
DirectionalLightData GetDistantLightData(LightList list, float inputSample)
|
|
{
|
|
return GetDistantLightData(list, (uint)(inputSample * list.distantCount));
|
|
}
|
|
|
|
float GetLocalLightWeight(LightList list)
|
|
{
|
|
return list.localWeight / list.localCount;
|
|
}
|
|
|
|
float GetDistantLightWeight(LightList list)
|
|
{
|
|
return list.distantWeight / list.distantCount;
|
|
}
|
|
|
|
float GetSkyLightWeight(LightList list)
|
|
{
|
|
return list.skyWeight;
|
|
}
|
|
|
|
#define PTLIGHT_LOCAL 0
|
|
#define PTLIGHT_DISTANT 1
|
|
#define PTLIGHT_SKY 2
|
|
|
|
uint PickLightType(LightList list, inout float theSample)
|
|
{
|
|
if (theSample < list.localWeight)
|
|
{
|
|
// We pick local lighting
|
|
theSample /= list.localWeight;
|
|
return PTLIGHT_LOCAL;
|
|
}
|
|
|
|
if (theSample < list.localWeight + list.distantWeight)
|
|
{
|
|
// We pick distant lighting
|
|
theSample = (theSample - list.localWeight) / list.distantWeight;
|
|
return PTLIGHT_DISTANT;
|
|
}
|
|
|
|
// Otherwise, sky lighting
|
|
theSample = (theSample - list.distantWeight - list.localWeight) / list.skyWeight;
|
|
return PTLIGHT_SKY;
|
|
}
|
|
|
|
float3 GetPunctualEmission(LightData lightData, float3 position, float3 outgoingDir, float dist)
|
|
{
|
|
float3 emission = lightData.color;
|
|
|
|
// Punctual attenuation
|
|
float4 distances = float4(dist, Sq(dist), rcp(dist), -dist * dot(outgoingDir, lightData.forward));
|
|
if(lightData.lightType == GPULIGHTTYPE_PROJECTOR_BOX)
|
|
{
|
|
//Note that we won't use GetPunctualLightVectors for non-Box cases to avoid calling ModifyDistancesForFillLighting
|
|
//We ensure the distances z and w coordinates are the same as for GetPunctualLightVectors for Box lights
|
|
distances.z = 1.0;
|
|
distances.w = dist;
|
|
}
|
|
emission *= PunctualLightAttenuation(distances, lightData.rangeAttenuationScale, lightData.rangeAttenuationBias, lightData.angleScale, lightData.angleOffset);
|
|
|
|
#ifndef LIGHT_EVALUATION_NO_COOKIE
|
|
if (lightData.cookieMode != COOKIEMODE_NONE)
|
|
{
|
|
LightLoopContext context;
|
|
float3 lightToSample = - dist * outgoingDir;
|
|
if(lightData.lightType == GPULIGHTTYPE_PROJECTOR_BOX)
|
|
{
|
|
lightToSample = position - lightData.positionRWS;
|
|
}
|
|
emission *= EvaluateCookie_Punctual(context, lightData, lightToSample).rgb;
|
|
}
|
|
#endif
|
|
|
|
return emission;
|
|
}
|
|
|
|
float3 GetDirectionalEmission(DirectionalLightData lightData, float3 positionRWS)
|
|
{
|
|
float3 emission = lightData.color;
|
|
|
|
#if SHADEROPTIONS_PRECOMPUTED_ATMOSPHERIC_ATTENUATION
|
|
// Nothing to do here
|
|
#else
|
|
// Physical sky emission color code, adapted from EvaluateLight_Directional()
|
|
if (asint(lightData.distanceFromCamera) >= 0)
|
|
emission *= EvaluateSunColorAttenuation(positionRWS - _PlanetCenterPosition, -lightData.forward);
|
|
#endif
|
|
|
|
#ifndef LIGHT_EVALUATION_NO_COOKIE
|
|
if (lightData.cookieMode != COOKIEMODE_NONE)
|
|
{
|
|
LightLoopContext context;
|
|
float3 lightToSample = positionRWS - lightData.positionRWS;
|
|
emission *= EvaluateCookie_Directional(context, lightData, lightToSample);
|
|
}
|
|
#endif
|
|
|
|
return emission;
|
|
}
|
|
|
|
float3 GetAreaEmission(LightData lightData, float centerU, float centerV, float sqDist)
|
|
{
|
|
float3 emission = lightData.color;
|
|
|
|
// Range windowing (see LightLoop.cs to understand why it is written this way)
|
|
if (lightData.rangeAttenuationBias == 1.0)
|
|
emission *= SmoothDistanceWindowing(sqDist, rcp(Sq(lightData.range)), lightData.rangeAttenuationBias);
|
|
|
|
#ifndef LIGHT_EVALUATION_NO_COOKIE
|
|
if (lightData.cookieMode != COOKIEMODE_NONE)
|
|
{
|
|
float2 uv = float2(0.5 - centerU, 0.5 + centerV);
|
|
emission *= SampleCookie2D(uv, lightData.cookieScaleOffset);
|
|
}
|
|
#endif
|
|
|
|
return emission;
|
|
}
|
|
|
|
float3 GetLightTransmission(float3 transmission, float shadowOpacity)
|
|
{
|
|
return lerp(float3(1.0, 1.0, 1.0), transmission, shadowOpacity);
|
|
}
|
|
|
|
bool SampleRectAreaLight(LightList lightList, LightData lightData,
|
|
float3 inputSample,
|
|
float3 position,
|
|
float3 normal,
|
|
bool isSpherical,
|
|
out float3 outgoingDir,
|
|
out float3 value,
|
|
out float pdf,
|
|
out float dist)
|
|
{
|
|
// The lights have already been filtered for "IsActive" in CreateLightList
|
|
/*
|
|
if (!IsAreaLightActive(lightData, position, normal))
|
|
return false;
|
|
*/
|
|
|
|
// Initialize out values
|
|
outgoingDir = 0;
|
|
value = 0;
|
|
pdf = 0;
|
|
dist = 0;
|
|
|
|
float3 lightCenter = lightData.positionRWS;
|
|
|
|
#ifndef SAMPLE_SOLID_ANGLE
|
|
// Generate a point on the surface of the light
|
|
float centerU = inputSample.x - 0.5;
|
|
float centerV = inputSample.y - 0.5;
|
|
float3 lightSamplePos = lightCenter + centerU * lightData.size.x * lightData.right + centerV * lightData.size.y * lightData.up;
|
|
|
|
// And the corresponding direction
|
|
outgoingDir = lightSamplePos - position;
|
|
float sqDist = Length2(outgoingDir);
|
|
dist = sqrt(sqDist);
|
|
outgoingDir /= dist;
|
|
|
|
if (!isSpherical && dot(normal, outgoingDir) < DOT_PRODUCT_EPSILON)
|
|
return false;
|
|
|
|
float cosTheta = -dot(outgoingDir, lightData.forward);
|
|
if (cosTheta < DOT_PRODUCT_EPSILON)
|
|
return false;
|
|
|
|
float lightArea = length(cross(lightData.size.x * lightData.right, lightData.size.y * lightData.up));
|
|
|
|
value = GetAreaEmission(lightData, centerU, centerV, sqDist);
|
|
pdf = GetLocalLightWeight(lightList) * sqDist / (lightArea * cosTheta);
|
|
#else
|
|
// Solid angle sampling
|
|
float u = inputSample.x;
|
|
float v = inputSample.y;
|
|
|
|
SphQuad squad;
|
|
lightCenter = lightCenter - 0.5 * lightData.size.x * lightData.right;
|
|
lightCenter = lightCenter - 0.5 * lightData.size.y * lightData.up;
|
|
SphQuadInit(lightCenter, lightData.size.x * lightData.right, lightData.size.y * lightData.up, position, squad);
|
|
|
|
// Generate sample
|
|
// TODO: Move this validity check into the common quad initialization function
|
|
if (squad.S < 0.00001 || isnan(squad.S))
|
|
return false;
|
|
|
|
// 1. compute ’cu’
|
|
float au = u * squad.S + squad.k;
|
|
float fu = (cos(au) * squad.b0 - squad.b1) / sin(au);
|
|
float cu = 1 / sqrt(fu * fu + squad.b0sq);// *(fu > 0 ? +1 : -1);
|
|
cu = (fu > 0.0f) ? cu : -cu;
|
|
cu = clamp(cu, -1, 1); // avoid NaNs
|
|
|
|
// 2. compute ’xu’
|
|
float xu = -(cu * squad.z0) / sqrt(1 - cu * cu);
|
|
xu = clamp(xu, squad.x0, squad.x1); // avoid Infs
|
|
|
|
// 3. compute ’yv’
|
|
float d = sqrt(xu * xu + squad.z0sq);
|
|
float h0 = squad.y0 / sqrt(d * d + squad.y0sq);
|
|
float h1 = squad.y1 / sqrt(d * d + squad.y1sq);
|
|
float hv = h0 + v * (h1 - h0);
|
|
float hv2 = hv * hv;
|
|
float eps = 0.0001;
|
|
float yv = (hv2 < 1.0 - eps) ? (hv * d) / sqrt(1.0 - hv2) : squad.y1;
|
|
|
|
// 4. transform (xu,yv,z0) to world coords
|
|
float3 lightSamplePos = (squad.o + xu * squad.x + yv * squad.y + squad.z0 * squad.z);
|
|
|
|
// TODO: We should use this function, but we need xu and yv below for cookie evaluation
|
|
// float3 lightSamplePos = SphQuadSample(squad, u, v);
|
|
|
|
outgoingDir = lightSamplePos - position;
|
|
float sqDist = Length2(outgoingDir);
|
|
dist = sqrt(sqDist);
|
|
outgoingDir /= dist;
|
|
|
|
u = (xu - squad.x0)/(squad.x1 - squad.x0) - 0.5;
|
|
v = (yv - squad.y0)/(squad.y1 - squad.y0) - 0.5;
|
|
value = GetAreaEmission(lightData, u, v, sqDist); // TODO: add rcpPdf term here from lightlist when that PR lands
|
|
pdf = GetLocalLightWeight(lightList) / squad.S;
|
|
|
|
if (!isSpherical && dot(normal, outgoingDir) < DOT_PRODUCT_EPSILON)
|
|
return false;
|
|
|
|
float cosTheta = -dot(outgoingDir, lightData.forward);
|
|
if (cosTheta < DOT_PRODUCT_EPSILON)
|
|
return false;
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
bool SampleTubeAreaLight(LightList lightList, LightData lightData,
|
|
float3 inputSample,
|
|
float3 position,
|
|
float3 normal,
|
|
bool isSpherical,
|
|
out float3 outgoingDir,
|
|
out float3 value,
|
|
out float pdf,
|
|
out float dist)
|
|
{
|
|
// initialize out values to avoid warnings
|
|
outgoingDir = 0;
|
|
value = 0;
|
|
pdf = 0;
|
|
dist = 0;
|
|
|
|
float3 lightCenter = lightData.positionRWS;
|
|
float lightLength = lightData.size.x;
|
|
|
|
// Generate a point on the line
|
|
// TODO : equiangular sampling might be better than just uniformly sampling along the line.
|
|
float centerU = inputSample.x - 0.5;
|
|
float3 lightSamplePos = lightCenter + centerU * lightLength * lightData.right;
|
|
|
|
// And the corresponding direction
|
|
outgoingDir = lightSamplePos - position;
|
|
float sqDist = Length2(outgoingDir);
|
|
dist = sqrt(sqDist);
|
|
outgoingDir /= dist;
|
|
|
|
if (!isSpherical && dot(normal, outgoingDir) < DOT_PRODUCT_EPSILON)
|
|
return false;
|
|
|
|
float sinTheta = abs(dot(outgoingDir, lightData.right));
|
|
if (sinTheta > sqrt(1.0-DOT_PRODUCT_EPSILON*DOT_PRODUCT_EPSILON))
|
|
return false;
|
|
float cosTheta = sqrt(1.0-sinTheta*sinTheta);
|
|
|
|
// The multiplication by 2 is explained by the fact that we are dealing with a tube, although with radius -> 0
|
|
// So the energy coming to a point is actually coming from the half arc of the tube facing the point.
|
|
// This means the total intensity is multiplied by Integral{-PI/2 to PI/2, cos phi} = 2, phi being the
|
|
// angle of the tube surface normal along that half arc. This is empirically verified by comparing visual
|
|
// lighting intensities between the rasterized and path traced versions of the tube area light.
|
|
value = GetAreaEmission(lightData, centerU, 0, sqDist) * 2 * cosTheta / sqDist;
|
|
pdf = GetLocalLightWeight(lightList) / lightLength;
|
|
|
|
// Multiply both value and pdf by DELTA_PDF to give more MIS weight for the light,
|
|
// as the line light is ignored in EvaluateLight due to its inifinitesimal surface.
|
|
value *= DELTA_PDF;
|
|
pdf *= DELTA_PDF;
|
|
|
|
return true;
|
|
}
|
|
|
|
float2 GetDiscAreaLightCookieUV(LightData lightData, float3 posOnDisk)
|
|
{
|
|
float lightDiameter = 2*lightData.size.x;
|
|
float centerU = dot(posOnDisk, lightData.right) / (lightDiameter * Length2(lightData.right));
|
|
float centerV = dot(posOnDisk, lightData.up) / (lightDiameter * Length2(lightData.up));
|
|
return float2(centerU, centerV);
|
|
}
|
|
|
|
bool SampleDiscAreaLight(LightList lightList, LightData lightData,
|
|
float3 inputSample,
|
|
float3 position,
|
|
float3 normal,
|
|
bool isSpherical,
|
|
out float3 outgoingDir,
|
|
out float3 value,
|
|
out float pdf,
|
|
out float dist)
|
|
{
|
|
float3 lightCenter = lightData.positionRWS;
|
|
float3 lightSamplePos;
|
|
float3 lightNormal; // This is ignored
|
|
float4x4 lightToWorld = float4x4(float4(lightData.right, 0.0), float4(lightData.up, 0.0), float4(lightData.forward, 0.0), float4(lightCenter, 1.0));
|
|
float lightRadius = lightData.size.x;
|
|
|
|
SampleDisk(inputSample.xy, lightToWorld, lightRadius, pdf, lightSamplePos, lightNormal);
|
|
|
|
// And the corresponding direction
|
|
outgoingDir = lightSamplePos - position;
|
|
float sqDist = Length2(outgoingDir);
|
|
dist = sqrt(sqDist);
|
|
outgoingDir /= dist;
|
|
|
|
if (!isSpherical && dot(normal, outgoingDir) < DOT_PRODUCT_EPSILON)
|
|
return false;
|
|
|
|
float cosTheta = -dot(outgoingDir, lightData.forward);
|
|
if (cosTheta < DOT_PRODUCT_EPSILON)
|
|
return false;
|
|
|
|
float3 diskPos = lightSamplePos-lightCenter;
|
|
float2 centerUV = GetDiscAreaLightCookieUV(lightData, diskPos);
|
|
|
|
value = GetAreaEmission(lightData, centerUV.x, centerUV.y, sqDist);
|
|
// 1/(Light area) has already been taken into account in the pdf returned by SampleDisk
|
|
pdf *= GetLocalLightWeight(lightList) * sqDist / cosTheta;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool SamplePunctualLight(LightList lightList, LightData lightData,
|
|
float3 inputSample,
|
|
float3 position,
|
|
float3 normal,
|
|
bool isSpherical,
|
|
out float3 outgoingDir,
|
|
out float3 value,
|
|
out float pdf,
|
|
out float dist)
|
|
{
|
|
// Direction from shading point to light position
|
|
outgoingDir = lightData.positionRWS - position;
|
|
float sqDist = Length2(outgoingDir);
|
|
dist = sqrt(sqDist);
|
|
outgoingDir /= dist;
|
|
|
|
if (lightData.size.x > 0.0) // Stores the square radius
|
|
{
|
|
float3x3 localFrame = GetLocalFrame(outgoingDir);
|
|
SampleCone(inputSample.xy, sqrt(1.0 / (1.0 + lightData.size.x / sqDist)), outgoingDir, pdf); // computes rcpPdf
|
|
|
|
outgoingDir = outgoingDir.x * localFrame[0] + outgoingDir.y * localFrame[1] + outgoingDir.z * localFrame[2];
|
|
pdf = min(rcp(pdf), DELTA_PDF);
|
|
}
|
|
else
|
|
{
|
|
// DELTA_PDF represents 1 / area, where the area is infinitesimal
|
|
pdf = DELTA_PDF;
|
|
}
|
|
|
|
if(lightData.lightType == GPULIGHTTYPE_PROJECTOR_BOX)
|
|
{
|
|
outgoingDir = -lightData.forward;
|
|
dist = dot(outgoingDir, lightData.positionRWS - position);
|
|
}
|
|
|
|
if (!isSpherical && dot(normal, outgoingDir) < DOT_PRODUCT_EPSILON)
|
|
return false;
|
|
|
|
value = GetPunctualEmission(lightData, position, outgoingDir, dist) * pdf;
|
|
pdf = GetLocalLightWeight(lightList) * pdf;
|
|
|
|
return true;
|
|
}
|
|
|
|
void SampleDistanceLight(LightList lightList, DirectionalLightData lightData,
|
|
float3 inputSample,
|
|
float3 position,
|
|
out float3 outgoingDir,
|
|
out float3 value,
|
|
out float pdf)
|
|
{
|
|
if (lightData.angularDiameter > 0.0)
|
|
{
|
|
SampleCone(inputSample.xy, cos(lightData.angularDiameter * 0.5), outgoingDir, pdf); // computes rcpPdf
|
|
value = GetDirectionalEmission(lightData, position) / pdf;
|
|
pdf = GetDistantLightWeight(lightList) / pdf;
|
|
outgoingDir = normalize(outgoingDir.x * normalize(lightData.right) + outgoingDir.y * normalize(lightData.up) - outgoingDir.z * lightData.forward);
|
|
}
|
|
else
|
|
{
|
|
value = GetDirectionalEmission(lightData, position) * DELTA_PDF;
|
|
pdf = GetDistantLightWeight(lightList) * DELTA_PDF;
|
|
outgoingDir = -lightData.forward;
|
|
}
|
|
}
|
|
|
|
bool SampleLights(LightList lightList,
|
|
float3 inputSample,
|
|
float3 position,
|
|
float3 normal,
|
|
bool isVolume,
|
|
out float3 outgoingDir,
|
|
out float3 value,
|
|
out float pdf,
|
|
out float dist,
|
|
out float shadowOpacity)
|
|
{
|
|
// initialize out values to avoid warnings on early return calls
|
|
outgoingDir = 0;
|
|
value = 0;
|
|
pdf = 0;
|
|
dist = 0;
|
|
shadowOpacity = 0;
|
|
|
|
if (!GetLightCount(lightList))
|
|
return false;
|
|
|
|
// Are we lighting a spherical (e.g. volume) or a hemi-spherical distribution (e.g. opaque surface)?
|
|
const bool isSpherical = isVolume || !any(normal);
|
|
|
|
// Stochastically pick one type of light to sample
|
|
const uint lightType = PickLightType(lightList, inputSample.z);
|
|
if (lightType == PTLIGHT_LOCAL)
|
|
{
|
|
// Pick a local light from the list
|
|
LightData lightData = GetLocalLightData(lightList, inputSample.z);
|
|
|
|
switch (lightData.lightType)
|
|
{
|
|
case GPULIGHTTYPE_RECTANGLE:
|
|
if (!SampleRectAreaLight(lightList, lightData, inputSample, position, normal, isSpherical, outgoingDir, value, pdf, dist))
|
|
return false;
|
|
break;
|
|
|
|
case GPULIGHTTYPE_TUBE:
|
|
if (!SampleTubeAreaLight(lightList, lightData, inputSample, position, normal, isSpherical, outgoingDir, value, pdf, dist))
|
|
return false;
|
|
break;
|
|
|
|
case GPULIGHTTYPE_DISC:
|
|
if (!SampleDiscAreaLight(lightList, lightData, inputSample, position, normal, isSpherical, outgoingDir, value, pdf, dist))
|
|
return false;
|
|
break;
|
|
|
|
default:
|
|
if (!SamplePunctualLight(lightList, lightData, inputSample, position, normal, isSpherical, outgoingDir, value, pdf, dist))
|
|
return false;
|
|
break;
|
|
}
|
|
|
|
if (isVolume)
|
|
{
|
|
value *= lightData.volumetricLightDimmer;
|
|
shadowOpacity = lightData.volumetricShadowDimmer;
|
|
}
|
|
else
|
|
{
|
|
value *= lightData.lightDimmer;
|
|
shadowOpacity = lightData.shadowDimmer;
|
|
}
|
|
|
|
#ifndef LIGHT_EVALUATION_NO_HEIGHT_FOG
|
|
ApplyFogAttenuation(position, outgoingDir, dist, value);
|
|
#endif
|
|
}
|
|
else // Distant or Sky light
|
|
{
|
|
if (lightType == PTLIGHT_DISTANT)
|
|
{
|
|
// Pick a distant light from the list
|
|
DirectionalLightData lightData = GetDistantLightData(lightList, inputSample.z);
|
|
|
|
SampleDistanceLight(lightList, lightData, inputSample, position, outgoingDir, value, pdf);
|
|
|
|
if (isVolume)
|
|
{
|
|
value *= lightData.volumetricLightDimmer;
|
|
shadowOpacity = lightData.volumetricShadowDimmer;
|
|
}
|
|
else
|
|
{
|
|
value *= lightData.lightDimmer;
|
|
shadowOpacity = lightData.shadowDimmer;
|
|
}
|
|
}
|
|
else // lightType == PTLIGHT_SKY
|
|
{
|
|
float2 uv = SampleSky(inputSample.xy);
|
|
outgoingDir = MapUVToSkyDirection(uv);
|
|
value = GetSkyValue(outgoingDir);
|
|
pdf = GetSkyLightWeight(lightList) * GetSkyPDFFromValue(value);
|
|
|
|
shadowOpacity = 1.0;
|
|
}
|
|
|
|
if (!isSpherical && (dot(normal, outgoingDir) < DOT_PRODUCT_EPSILON))
|
|
return false;
|
|
|
|
dist = FLT_INF;
|
|
|
|
#ifndef LIGHT_EVALUATION_NO_HEIGHT_FOG
|
|
ApplyFogAttenuation(position, outgoingDir, value);
|
|
#endif
|
|
}
|
|
|
|
return any(value) && any(pdf);
|
|
}
|
|
|
|
void EvaluateRectAreaLight(LightList lightList,
|
|
LightData lightData,
|
|
float3 rayOrigin,
|
|
float3 rayDirection,
|
|
float t,
|
|
float cosTheta,
|
|
float3 hitPosition, // Hit position is relative to lightCenter
|
|
inout float3 value,
|
|
inout float pdf)
|
|
{
|
|
// Then check if we are within the rectangle bounds
|
|
float centerU = dot(hitPosition, lightData.right) / (lightData.size.x * Length2(lightData.right));
|
|
float centerV = dot(hitPosition, lightData.up) / (lightData.size.y * Length2(lightData.up));
|
|
if (abs(centerU) < 0.5 && abs(centerV) < 0.5)
|
|
{
|
|
float3 lightCenter = lightData.positionRWS;
|
|
float t2 = Sq(t);
|
|
float3 lightValue = GetAreaEmission(lightData, centerU, centerV, t2);
|
|
#ifndef LIGHT_EVALUATION_NO_HEIGHT_FOG
|
|
ApplyFogAttenuation(rayOrigin, rayDirection, t, lightValue);
|
|
#endif
|
|
|
|
#ifndef SAMPLE_SOLID_ANGLE
|
|
float lightArea = length(cross(lightData.size.x * lightData.right, lightData.size.y * lightData.up));
|
|
value += lightValue;
|
|
pdf += GetLocalLightWeight(lightList) * t2 / (lightArea * cosTheta);
|
|
#else
|
|
float3 position = rayOrigin;
|
|
|
|
SphQuad squad;
|
|
lightCenter = lightCenter - 0.5 * lightData.size.x * lightData.right;
|
|
lightCenter = lightCenter - 0.5 * lightData.size.y * lightData.up;
|
|
SphQuadInit(lightCenter, lightData.size.x * lightData.right, lightData.size.y * lightData.up, position, squad);
|
|
|
|
// TODO: Move this validity check into the common quad initialization function
|
|
if (!(squad.S < 0.00001 || isnan(squad.S)))
|
|
{
|
|
value += lightValue;
|
|
pdf += GetLocalLightWeight(lightList) / squad.S;
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void EvaluateDiscAreaLight(LightList lightList,
|
|
LightData lightData,
|
|
float3 rayOrigin,
|
|
float3 rayDirection,
|
|
float t,
|
|
float cosTheta,
|
|
float3 hitPosition, // Hit position is relative to lightCenter
|
|
inout float3 value,
|
|
inout float pdf)
|
|
{
|
|
float lightRadius = lightData.size.x;
|
|
float lightRadiusSquared = lightRadius*lightRadius;
|
|
|
|
// Then check if we are within the disc bounds
|
|
if (Length2(hitPosition) < lightRadiusSquared)
|
|
{
|
|
float2 centerUV = GetDiscAreaLightCookieUV(lightData, hitPosition);
|
|
float t2 = Sq(t);
|
|
float3 lightValue = GetAreaEmission(lightData, centerUV.x, centerUV.y, t2);
|
|
#ifndef LIGHT_EVALUATION_NO_HEIGHT_FOG
|
|
ApplyFogAttenuation(rayOrigin, rayDirection, t, lightValue);
|
|
#endif
|
|
|
|
float lightArea = PI * lightRadiusSquared;
|
|
value += lightValue;
|
|
pdf += GetLocalLightWeight(lightList) * t2 / (lightArea * cosTheta);
|
|
}
|
|
}
|
|
|
|
void EvaluateLights(LightList lightList,
|
|
float3 rayOrigin,
|
|
float3 rayDirection,
|
|
float rayHit,
|
|
out float3 value,
|
|
out float pdf)
|
|
{
|
|
value = 0.0;
|
|
pdf = 0.0;
|
|
|
|
uint i;
|
|
|
|
// First local lights (area lights only, as we consider the probability of hitting a point light neglectable)
|
|
for (i = lightList.localPointCount; i < lightList.localCount; i++)
|
|
{
|
|
LightData lightData = GetLocalLightData(lightList, i);
|
|
// Similarly, tube lights are line shaped so have no surface so neglect them as well
|
|
if (lightData.lightType == GPULIGHTTYPE_TUBE)
|
|
continue;
|
|
|
|
float t = rayHit;
|
|
float cosTheta = -dot(rayDirection, lightData.forward);
|
|
float3 lightCenter = lightData.positionRWS;
|
|
|
|
// Check if we hit the light plane, at a distance below our tMax (coming from indirect computation)
|
|
if (cosTheta > 0.0 && IntersectPlane(rayOrigin, rayDirection, lightCenter, lightData.forward, t))
|
|
{
|
|
if (t < rayHit)
|
|
{
|
|
float3 hitVec = rayOrigin + t * rayDirection - lightCenter;
|
|
|
|
if (lightData.lightType == GPULIGHTTYPE_RECTANGLE)
|
|
{
|
|
EvaluateRectAreaLight(lightList, lightData, rayOrigin, rayDirection, t, cosTheta, hitVec, value, pdf);
|
|
}
|
|
else
|
|
{
|
|
EvaluateDiscAreaLight(lightList, lightData, rayOrigin, rayDirection, t, cosTheta, hitVec, value, pdf);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Then distant lights
|
|
for (i = 0; i < lightList.distantCount; i++)
|
|
{
|
|
DirectionalLightData lightData = GetDistantLightData(lightList, i);
|
|
|
|
if (lightData.angularDiameter > 0.0 && rayHit >= FLT_INF)
|
|
{
|
|
float cosHalfAngle = cos(lightData.angularDiameter * 0.5);
|
|
float cosTheta = -dot(rayDirection, lightData.forward);
|
|
if (cosTheta >= cosHalfAngle)
|
|
{
|
|
float3 lightValue = GetDirectionalEmission(lightData, rayDirection);
|
|
#ifndef LIGHT_EVALUATION_NO_HEIGHT_FOG
|
|
ApplyFogAttenuation(rayOrigin, rayDirection, lightValue);
|
|
#endif
|
|
float rcpPdf = TWO_PI * (1.0 - cosHalfAngle);
|
|
value += lightValue / rcpPdf;
|
|
pdf += GetDistantLightWeight(lightList) / rcpPdf;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Then sky light
|
|
if (lightList.skyCount && rayHit >= FLT_INF)
|
|
{
|
|
float3 skyValue = GetSkyValue(rayDirection);
|
|
pdf += GetSkyLightWeight(lightList) * GetSkyPDFFromValue(skyValue);
|
|
#ifndef LIGHT_EVALUATION_NO_HEIGHT_FOG
|
|
ApplyFogAttenuation(rayOrigin, rayDirection, skyValue);
|
|
#endif
|
|
value += skyValue;
|
|
}
|
|
}
|
|
|
|
// Functions used by volumetric sampling
|
|
|
|
bool GetSphereInterval(float3 lightToRayOrigin, float radius, float3 rayDirection, out float tMin, out float tMax)
|
|
{
|
|
// initialize out values to avoid warnings on early return
|
|
tMin = 0;
|
|
tMax = 0;
|
|
|
|
// We consider Direction to be normalized => a = 1
|
|
float b = 2.0 * dot(rayDirection, lightToRayOrigin);
|
|
float c = Length2(lightToRayOrigin) - Sq(radius);
|
|
|
|
float2 t;
|
|
if (!SolveQuadraticEquation(1.0, b, c, t))
|
|
return false;
|
|
|
|
tMin = max(t.x, 0.0);
|
|
tMax = max(t.y, 0.0);
|
|
|
|
return tMin < tMax;
|
|
}
|
|
|
|
bool GetAreaLightInterval(LightData lightData, float3 rayOrigin, float3 rayDirection, out float tMin, out float tMax)
|
|
{
|
|
// initialize out values to avoid warnings on early return
|
|
tMin = 0;
|
|
tMax = 0;
|
|
|
|
if (lightData.volumetricLightDimmer < 0.001)
|
|
return false;
|
|
|
|
float3 lightToRayOrigin = rayOrigin - lightData.positionRWS;
|
|
|
|
if (!GetSphereInterval(lightToRayOrigin, lightData.range, rayDirection, tMin, tMax))
|
|
return false;
|
|
|
|
// If this is tube light, we're done
|
|
if (lightData.lightType == GPULIGHTTYPE_TUBE)
|
|
return true;
|
|
|
|
float LdotD = dot(lightData.forward, rayDirection);
|
|
float t = -dot(lightData.forward, lightToRayOrigin) / LdotD;
|
|
if (LdotD > 0.0)
|
|
tMin = max(tMin, t);
|
|
else
|
|
tMax = min(tMax, t);
|
|
|
|
return tMin < tMax;
|
|
}
|
|
|
|
void Sort(inout float x, inout float y)
|
|
{
|
|
if (x > y) Swap(x, y);
|
|
}
|
|
|
|
void GetFrontInterval(float oz, float dz, float t1, float t2, inout float tMin, inout float tMax)
|
|
{
|
|
bool t1Valid = oz + t1 * dz > 0.0;
|
|
bool t2Valid = oz + t2 * dz > 0.0;
|
|
|
|
if (t1Valid)
|
|
{
|
|
if (t2Valid)
|
|
{
|
|
tMin = max(t1, tMin);
|
|
tMax = min(t2, tMax);
|
|
}
|
|
else
|
|
{
|
|
tMax = min(t1, tMax);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
tMin = t2Valid ? max(t2, tMin) : tMax;
|
|
}
|
|
}
|
|
|
|
bool GetPointLightInterval(LightData lightData, float3 rayOrigin, float3 rayDirection, out float tMin, out float tMax)
|
|
{
|
|
// initialize out values to avoid warnings on early return
|
|
tMin = 0;
|
|
tMax = 0;
|
|
|
|
if (lightData.volumetricLightDimmer < 0.001)
|
|
return false;
|
|
|
|
float3 lightToRayOrigin = rayOrigin - lightData.positionRWS;
|
|
|
|
if (lightData.lightType != GPULIGHTTYPE_PROJECTOR_BOX)
|
|
{
|
|
if (!GetSphereInterval(lightToRayOrigin, lightData.range, rayDirection, tMin, tMax))
|
|
return false;
|
|
}
|
|
|
|
// This is just a point light (no spot cone angle)
|
|
if (lightData.lightType == GPULIGHTTYPE_POINT)
|
|
return true;
|
|
|
|
// We are dealing with either a cone, a pyramid or a box
|
|
float3 localOrigin = float3(dot(lightToRayOrigin, lightData.right),
|
|
dot(lightToRayOrigin, lightData.up),
|
|
dot(lightToRayOrigin, lightData.forward));
|
|
float3 localDirection = float3(dot(rayDirection, lightData.right),
|
|
dot(rayDirection, lightData.up),
|
|
dot(rayDirection, lightData.forward));
|
|
|
|
if (lightData.lightType == GPULIGHTTYPE_PROJECTOR_BOX)
|
|
{
|
|
float tStart = 0;
|
|
float tEnd = rcp(FLT_EPS);
|
|
IntersectRayAABB(localOrigin, (localDirection), float3(-1, -1, 0), float3(1,1,lightData.range),
|
|
tStart, tEnd, tMin, tMax);
|
|
|
|
}
|
|
else if (lightData.lightType == GPULIGHTTYPE_PROJECTOR_PYRAMID)
|
|
{
|
|
// Compute intersections with planes x=-z and x=z
|
|
float tx1 = -(localOrigin.x - localOrigin.z) / (localDirection.x - localDirection.z);
|
|
float tx2 = -(localOrigin.x + localOrigin.z) / (localDirection.x + localDirection.z);
|
|
Sort(tx1, tx2);
|
|
|
|
// Check validity of the intersections (we want them only in front of the light)
|
|
GetFrontInterval(localOrigin.z, localDirection.z, tx1, tx2, tMin, tMax);
|
|
|
|
if (tMin < tMax)
|
|
{
|
|
// Compute intersections with planes y=-1 and y=1
|
|
float ty1 = -(localOrigin.y - localOrigin.z) / (localDirection.y - localDirection.z);
|
|
float ty2 = -(localOrigin.y + localOrigin.z) / (localDirection.y + localDirection.z);
|
|
Sort(ty1, ty2);
|
|
|
|
// Check validity of the intersections (we want them only in front of the light)
|
|
GetFrontInterval(localOrigin.z, localDirection.z, ty1, ty2, tMin, tMax);
|
|
}
|
|
}
|
|
else // lightData.lightType == GPULIGHTTYPE_SPOT
|
|
{
|
|
float cosTheta2 = Sq(lightData.angleOffset / lightData.angleScale);
|
|
|
|
// Offset light origin to account for light radius
|
|
localOrigin.z += sqrt(lightData.size.x / (1.0 - cosTheta2));
|
|
|
|
// Account for non-normalized local basis
|
|
float3 normalizedLocalOrigin = float3(localOrigin.x / Length2(lightData.right),
|
|
localOrigin.y / Length2(lightData.up),
|
|
localOrigin.z);
|
|
|
|
float a = Sq(localDirection.z) - cosTheta2;
|
|
float b = 2.0 * (localOrigin.z * localDirection.z - dot(normalizedLocalOrigin, localDirection) * cosTheta2);
|
|
float c = Sq(localOrigin.z) - dot(normalizedLocalOrigin, localOrigin) * cosTheta2;
|
|
|
|
float2 t;
|
|
if (!SolveQuadraticEquation(a, b, c, t))
|
|
return false;
|
|
|
|
// Check validity of the intersections (we want them only in front of the light)
|
|
GetFrontInterval(localOrigin.z, localDirection.z, t.x, t.y, tMin, tMax);
|
|
}
|
|
|
|
return tMin < tMax;
|
|
}
|
|
|
|
// This function has been deprecated in favor of PickLocalLightInterval() right below
|
|
// float GetLocalLightsInterval(float3 rayOrigin, float3 rayDirection, out float tMin, out float tMax)
|
|
// {
|
|
// tMin = FLT_MAX;
|
|
// tMax = 0.0;
|
|
|
|
// float tLightMin, tLightMax;
|
|
|
|
// // First process point lights
|
|
// uint i = 0, n = _WorldPunctualLightCount, localCount = 0;
|
|
// for (; i < n; i++)
|
|
// {
|
|
// if (GetPointLightInterval(_WorldLightDatas[i], rayOrigin, rayDirection, tLightMin, tLightMax))
|
|
// {
|
|
// tMin = min(tMin, tLightMin);
|
|
// tMax = max(tMax, tLightMax);
|
|
// localCount++;
|
|
// }
|
|
// }
|
|
|
|
// // Then area lights
|
|
// n += _WorldAreaLightCount;
|
|
// for (; i < n; i++)
|
|
// {
|
|
// if (GetAreaLightInterval(_WorldLightDatas[i], rayOrigin, rayDirection, tLightMin, tLightMax))
|
|
// {
|
|
// tMin = min(tMin, tLightMin);
|
|
// tMax = max(tMax, tLightMax);
|
|
// localCount++;
|
|
// }
|
|
// }
|
|
|
|
// uint lightCount = localCount + _DirectionalLightCount;
|
|
|
|
// return lightCount ? float(localCount) / lightCount : -1.0;
|
|
// }
|
|
|
|
float GetLocalLightWeight(LightData lightData, float3 rayOrigin, float3 rayDirection, float tMin, float tMax)
|
|
{
|
|
float tDist = clamp(dot(lightData.positionRWS - rayOrigin, rayDirection), tMin, tMax);
|
|
float3 vDist = rayOrigin + tDist * rayDirection - lightData.positionRWS;
|
|
|
|
// By offsetting the square distance by 1.0, we reduce the range of the weight to ]0.0, 1.0],
|
|
// while avoiding a singularity when distance goes towards 0.0.
|
|
float distSq = 1.0 + Length2(vDist);
|
|
|
|
return rcp(distSq);
|
|
}
|
|
|
|
float PickLocalLightInterval(float3 rayOrigin, float3 rayDirection, inout float inputSample, out float3 lightPosition, out float lightWeight, out float tMin, out float tMax)
|
|
{
|
|
tMin = FLT_MAX;
|
|
tMax = 0.0;
|
|
|
|
float tLightMin, tLightMax;
|
|
float wLight, wSum = 0.0;
|
|
|
|
// First process point lights
|
|
uint i = 0, n = 0, localCount = 0;
|
|
|
|
n += _WorldPunctualLightCount;
|
|
for (; i < n; i++)
|
|
{
|
|
if (GetPointLightInterval(_WorldLightDatas[i], rayOrigin, rayDirection, tLightMin, tLightMax))
|
|
{
|
|
wLight = GetLocalLightWeight(_WorldLightDatas[i], rayOrigin, rayDirection, tLightMin, tLightMax);
|
|
|
|
if (wLight > 0.0)
|
|
{
|
|
wSum += wLight;
|
|
wLight /= wSum;
|
|
|
|
if (inputSample < wLight)
|
|
{
|
|
lightPosition = _WorldLightDatas[i].positionRWS;
|
|
lightWeight = wLight;
|
|
tMin = tLightMin;
|
|
tMax = tLightMax;
|
|
|
|
inputSample = RescaleSampleUnder(inputSample, wLight);
|
|
}
|
|
else
|
|
{
|
|
lightWeight *= 1.0 - wLight;
|
|
|
|
inputSample = RescaleSampleOver(inputSample, wLight);
|
|
}
|
|
|
|
localCount++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Then area lights
|
|
n += _WorldAreaLightCount;
|
|
for (; i < n; i++)
|
|
{
|
|
if (GetAreaLightInterval(_WorldLightDatas[i], rayOrigin, rayDirection, tLightMin, tLightMax))
|
|
{
|
|
wLight = GetLocalLightWeight(_WorldLightDatas[i], rayOrigin, rayDirection, tLightMin, tLightMax);
|
|
|
|
if (wLight > 0.0)
|
|
{
|
|
wSum += wLight;
|
|
wLight /= wSum;
|
|
|
|
if (inputSample < wLight)
|
|
{
|
|
lightPosition = _WorldLightDatas[i].positionRWS;
|
|
lightWeight = wLight;
|
|
tMin = tLightMin;
|
|
tMax = tLightMax;
|
|
|
|
inputSample = RescaleSampleUnder(inputSample, wLight);
|
|
}
|
|
else
|
|
{
|
|
lightWeight *= 1.0 - wLight;
|
|
|
|
inputSample = RescaleSampleOver(inputSample, wLight);
|
|
}
|
|
|
|
localCount++;
|
|
}
|
|
}
|
|
}
|
|
|
|
uint lightCount = localCount + _DirectionalLightCount + (IsSkyEnabled() && IsSkySamplingEnabled() ? 1 : 0);
|
|
|
|
return lightCount ? float(localCount) / lightCount : -1.0;
|
|
}
|
|
|
|
LightList CreateLightList(float3 position, bool sampleLocalLights, float3 lightPosition = FLT_MAX)
|
|
{
|
|
return CreateLightList(position, 0.0, RENDERING_LAYERS_MASK, sampleLocalLights, sampleLocalLights, !sampleLocalLights, lightPosition);
|
|
}
|
|
|
|
#endif // UNITY_PATH_TRACING_LIGHT_INCLUDED
|