|
|
|
|
|
|
// Definitions |
|
|
|
//-------------------------------------------------------------------------------------------------- |
|
|
|
|
|
|
|
#define PRESET_ULTRA 0 |
|
|
|
|
|
|
|
#if PRESET_ULTRA |
|
|
|
#ifdef PRESET_ULTRA |
|
|
|
// E.g. for 1080p: (1920/4)x(1080/4)x(256) = 33,177,600 voxels |
|
|
|
#define VBUFFER_TILE_SIZE 4 |
|
|
|
#define VBUFFER_SLICE_COUNT 256 |
|
|
|
|
|
|
#define VBUFFER_SLICE_COUNT 128 |
|
|
|
#endif |
|
|
|
#endif // PRESET_ULTRA |
|
|
|
|
|
|
|
#pragma kernel VolumetricLightingAllLights VolumetricLighting=VolumetricLightingAllLights LIGHTLOOP_SINGLE_PASS |
|
|
|
#pragma kernel VolumetricLightingClustered VolumetricLighting=VolumetricLightingClustered LIGHTLOOP_TILE_PASS USE_CLUSTERED_LIGHTLIST |
|
|
|
|
|
|
float ratioZtoLen; // ViewSpaceZ |
|
|
|
}; |
|
|
|
|
|
|
|
float3 GetPointAtDistance(Ray ray, float t) |
|
|
|
{ |
|
|
|
return ray.originWS + t * ray.directionWS; |
|
|
|
} |
|
|
|
|
|
|
|
void FillVolumetricLightingBuffer(Ray cameraRay, uint2 voxelCoord, uint2 tileCoord) |
|
|
|
void FillVolumetricLightingBuffer(Ray ray, uint2 voxelCoord, uint2 tileCoord) |
|
|
|
{ |
|
|
|
LightLoopContext context; |
|
|
|
// ZERO_INITIALIZE(LightLoopContext, context); |
|
|
|
|
|
|
float4 depthParams = _VBufferDepthEncodingParams; |
|
|
|
|
|
|
|
float z0 = depthParams.x; // View space Z coordinate of the near plane |
|
|
|
float t0 = cameraRay.ratioLenToZ * z0; // Distance to the near plane |
|
|
|
float t0 = z0 * ray.ratioLenToZ; // Distance to the near plane |
|
|
|
float de = rcp(VBUFFER_SLICE_COUNT); // Log-encoded distance between slices |
|
|
|
|
|
|
|
float3 totalRadiance = 0; |
|
|
|
|
|
|
|
|
|
|
#ifdef LIGHTLOOP_TILE_PASS |
|
|
|
// Our voxel is not necessarily completely inside a single light cluster. |
|
|
|
uint clusterIndices[2]; |
|
|
|
float clusterDistances[2]; |
|
|
|
// TODO: the clustered code could be made faster & simpler. |
|
|
|
clusterIndices[0] = GetLightClusterIndex(tileCoord, z0); |
|
|
|
clusterDistances[0] = GetLightClusterMinDepthVS(tileCoord, clusterIndices[0]) * ray.ratioLenToZ; |
|
|
|
#endif // LIGHTLOOP_TILE_PASS |
|
|
|
|
|
|
|
for (uint s = 0; s < sliceCountHack; s++) |
|
|
|
for (uint slice = 0; slice < sliceCountHack; slice++) |
|
|
|
float e1 = s * de + de; // (s + 1) / sliceCount |
|
|
|
float e1 = slice * de + de; // (slice + 1) / sliceCount |
|
|
|
float t1 = cameraRay.ratioLenToZ * z1; |
|
|
|
float t1 = ray.ratioLenToZ * z1; |
|
|
|
// TODO: low-discrepancy point set. |
|
|
|
float t = t0 + 0.5 * dt; |
|
|
|
float z = cameraRay.ratioZtoLen * t; |
|
|
|
float3 positionWS = cameraRay.originWS + t * cameraRay.directionWS; |
|
|
|
// Compute the position of the center of the voxel. |
|
|
|
// We will use it for participating media sampling and reprojection. |
|
|
|
float tc = t0 + 0.5 * dt; |
|
|
|
float3 centerWS = GetPointAtDistance(ray, tc); |
|
|
|
// Get volume properties at 't'. |
|
|
|
// Sample the participating media at 'tc' (or 'centerWS'). |
|
|
|
// We consider it to be constant along the interval [t0, t1] (within the voxel). |
|
|
|
// TODO: use a low-discrepancy point set. |
|
|
|
float rndVal = 0.5; |
|
|
|
|
|
|
|
float tOffset, weight; |
|
|
|
ImportanceSampleHomogeneousMedia(extinction, dt, rndVal, tOffset, weight); |
|
|
|
|
|
|
|
float t = t0 + tOffset; |
|
|
|
float3 positionWS = GetPointAtDistance(ray, t); |
|
|
|
|
|
|
|
for (uint i = 0; i < _DirectionalLightCount; ++i) |
|
|
|
{ |
|
|
|
// Fetch the light. |
|
|
|
|
|
|
float intensity = 1; |
|
|
|
float intensity = weight; |
|
|
|
float3 color = lightData.color; |
|
|
|
|
|
|
|
[branch] if (lightData.shadowIndex >= 0) |
|
|
|
|
|
|
intensity *= shadow; |
|
|
|
} |
|
|
|
|
|
|
|
// Note: no global fog attenuation along shadow rays for directional lights. |
|
|
|
// Note: no fog attenuation along shadow rays for directional lights. |
|
|
|
|
|
|
|
[branch] if (lightData.cookieIndex >= 0) |
|
|
|
{ |
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (featureFlags & LIGHTFEATUREFLAGS_PUNCTUAL) |
|
|
|
#ifdef LIGHTLOOP_TILE_PASS |
|
|
|
// TODO: the clustered code could be made faster & simpler. |
|
|
|
clusterIndices[1] = GetLightClusterIndex(tileCoord, z1); |
|
|
|
clusterDistances[1] = GetLightClusterMinDepthVS(tileCoord, clusterIndices[1]) * ray.ratioLenToZ; |
|
|
|
|
|
|
|
// Loop over 1 or 2 light clusters. |
|
|
|
for (int cluster = 0; cluster < 2; cluster++) |
|
|
|
uint punctualLightCount; |
|
|
|
float tMin = max(t0, clusterDistances[cluster]); |
|
|
|
float tMax = t1; |
|
|
|
#ifdef LIGHTLOOP_TILE_PASS |
|
|
|
PositionInputs posInput; |
|
|
|
posInput.depthVS = z; |
|
|
|
posInput.unTileCoord = tileCoord; |
|
|
|
|
|
|
|
// TODO: do not refetch between consecutive samples if possible. |
|
|
|
uint punctualLightStart; |
|
|
|
GetCountAndStartCluster(posInput, LIGHTCATEGORY_PUNCTUAL, punctualLightStart, punctualLightCount); |
|
|
|
#else |
|
|
|
punctualLightCount = _PunctualLightCount; |
|
|
|
#endif |
|
|
|
if (cluster == 0 && clusterDistances[0] != clusterDistances[1]) |
|
|
|
{ |
|
|
|
tMax = min(t1, clusterDistances[1]); |
|
|
|
} |
|
|
|
#else |
|
|
|
float tMin = t0; |
|
|
|
float tMax = t1; |
|
|
|
#endif // LIGHTLOOP_TILE_PASS |
|
|
|
for (uint i = 0; i < punctualLightCount; ++i) |
|
|
|
if (featureFlags & LIGHTFEATUREFLAGS_PUNCTUAL) |
|
|
|
uint punctualLightCount; |
|
|
|
|
|
|
|
uint punctualLightIndex = FetchIndex(punctualLightStart, i); |
|
|
|
uint punctualLightStart; |
|
|
|
GetCountAndStartCluster(tileCoord, clusterIndices[cluster], LIGHTCATEGORY_PUNCTUAL, |
|
|
|
punctualLightStart, punctualLightCount); |
|
|
|
uint punctualLightIndex = i; |
|
|
|
#endif |
|
|
|
punctualLightCount = _PunctualLightCount; |
|
|
|
#endif // LIGHTLOOP_TILE_PASS |
|
|
|
// Fetch the light. |
|
|
|
LightData lightData = _LightDatas[punctualLightIndex]; |
|
|
|
int lightType = lightData.lightType; |
|
|
|
for (uint i = 0; i < punctualLightCount; ++i) |
|
|
|
{ |
|
|
|
#ifdef LIGHTLOOP_TILE_PASS |
|
|
|
uint punctualLightIndex = FetchIndex(punctualLightStart, i); |
|
|
|
#else |
|
|
|
uint punctualLightIndex = i; |
|
|
|
#endif // LIGHTLOOP_TILE_PASS |
|
|
|
float3 lightToSample = positionWS - lightData.positionWS; |
|
|
|
float distSq = dot(lightToSample, lightToSample); |
|
|
|
float dist = sqrt(distSq); |
|
|
|
float3 L = lightToSample * -rsqrt(distSq); |
|
|
|
float clampedDistSq = max(distSq, 0.5 * dt); // Hack to reduce undersampling |
|
|
|
float intensity = GetPunctualShapeAttenuation(lightData, L, clampedDistSq); |
|
|
|
float3 color = lightData.color; |
|
|
|
// Fetch the light. |
|
|
|
LightData lightData = _LightDatas[punctualLightIndex]; |
|
|
|
int lightType = lightData.lightType; |
|
|
|
|
|
|
|
// TODO... |
|
|
|
if (lightType != GPULIGHTTYPE_POINT) continue; |
|
|
|
|
|
|
|
float t, rcpPdf; |
|
|
|
ImportanceSamplePunctualLight(lightData.positionWS, ray.originWS, ray.directionWS, |
|
|
|
tMin, tMax, rndVal, t, rcpPdf); |
|
|
|
|
|
|
|
float3 positionWS = GetPointAtDistance(ray, t); |
|
|
|
|
|
|
|
// TODO: we could compute this data in ImportanceSamplePunctualLight(). |
|
|
|
float3 lightToSample = positionWS - lightData.positionWS; |
|
|
|
float distSq = dot(lightToSample, lightToSample); |
|
|
|
float dist = sqrt(distSq); |
|
|
|
float3 L = lightToSample * -rsqrt(distSq); |
|
|
|
float intensity = GetPunctualShapeAttenuation(lightData, L, distSq); |
|
|
|
float3 color = lightData.color; |
|
|
|
|
|
|
|
// TODO: heterogeneous media. |
|
|
|
intensity *= TransmittanceHomogeneousMedia(extinction, dist); |
|
|
|
|
|
|
|
[branch] if (lightData.shadowIndex >= 0) |
|
|
|
{ |
|
|
|
// TODO: make projector lights cast shadows. |
|
|
|
float shadow = GetPunctualShadowAttenuation(context.shadowContext, positionWS, |
|
|
|
float3(0, 0, 0), lightData.shadowIndex, float4(L, dist)); |
|
|
|
intensity *= Transmittance(OpticalDepthHomogeneous(extinction, dist)); |
|
|
|
intensity *= lerp(1, shadow, lightData.shadowDimmer); |
|
|
|
} |
|
|
|
[branch] if (lightData.shadowIndex >= 0) |
|
|
|
{ |
|
|
|
// TODO: make projector lights cast shadows. |
|
|
|
float shadow = GetPunctualShadowAttenuation(context.shadowContext, positionWS, |
|
|
|
float3(0, 0, 0), lightData.shadowIndex, float4(L, dist)); |
|
|
|
// Projector lights always have cookies, so we can perform clipping inside the if(). |
|
|
|
[branch] if (lightData.cookieIndex >= 0) |
|
|
|
{ |
|
|
|
float4 cookie = EvaluateCookie_Punctual(context, lightData, lightToSample); |
|
|
|
|
|
|
|
color *= cookie.rgb; |
|
|
|
intensity *= cookie.a; |
|
|
|
} |
|
|
|
intensity *= lerp(1, shadow, lightData.shadowDimmer); |
|
|
|
} |
|
|
|
// Compute transmittance from 't0' to 't'. |
|
|
|
float transmittance = TransmittanceHomogeneousMedia(extinction, t - t0); |
|
|
|
// Projector lights always have cookies, so we can perform clipping inside the if(). |
|
|
|
[branch] if (lightData.cookieIndex >= 0) |
|
|
|
{ |
|
|
|
float4 cookie = EvaluateCookie_Punctual(context, lightData, lightToSample); |
|
|
|
intensity *= transmittance * rcpPdf; |
|
|
|
color *= scattering; |
|
|
|
color *= cookie.rgb; |
|
|
|
intensity *= cookie.a; |
|
|
|
// Compute the amount of in-scattered radiance. |
|
|
|
sampleRadiance += color * intensity; |
|
|
|
|
|
|
|
// Compute the amount of in-scattered radiance. |
|
|
|
sampleRadiance += color * intensity; |
|
|
|
#ifdef LIGHTLOOP_TILE_PASS |
|
|
|
// The voxel is completely inside the light cluster. |
|
|
|
if (clusterIndices[0] == clusterIndices[1]) break; |
|
|
|
#endif // LIGHTLOOP_TILE_PASS |
|
|
|
// We consider the volume to be homogeneous along the interval (within the voxel). |
|
|
|
// Integral{a, b}{Transmittance(0, x) dx} = Transmittance(0, a) * Integral{0, b - a}{Transmittance(a, a + x) dx}. |
|
|
|
float transmittance = Transmittance(opticalDepth) * TransmittanceIntegralHomogeneous(extinction, dt); |
|
|
|
// Compute the transmittance up to the start of the interval. |
|
|
|
float transmittance = Transmittance(opticalDepth); |
|
|
|
// Compute the optical depth up to the sample. |
|
|
|
opticalDepth += extinction * (t - t0); |
|
|
|
// Integral{a, b}{Transmittance(0, t) * Li(t) dt} = Transmittance(0, a) * Integral{a, b}{Transmittance(0, t - a) * Li(t) dt}. |
|
|
|
// We multiply by the scattering coefficient per light. |
|
|
|
totalRadiance += (transmittance * IsotropicPhaseFunction()) * sampleRadiance; |
|
|
|
// We consider the lighting (and the volume properties) to be constant along the interval (within the voxel). |
|
|
|
totalRadiance += (transmittance * IsotropicPhaseFunction()) * scattering * sampleRadiance; |
|
|
|
// Compute the optical depth up to the center of the interval. |
|
|
|
opticalDepth += 0.5 * extinction * dt; |
|
|
|
// Store the voxel data. TODO: reprojection using camera motion vectors. |
|
|
|
_VBufferLighting[uint3(voxelCoord, s)] = float4(totalRadiance, opticalDepth); |
|
|
|
// Store the voxel data. TODO: reprojection of 'tc' (or 'centerWS'). |
|
|
|
_VBufferLighting[uint3(voxelCoord, slice)] = float4(totalRadiance, opticalDepth); |
|
|
|
opticalDepth += extinction * (t1 - t); |
|
|
|
opticalDepth += 0.5 * extinction * dt; |
|
|
|
|
|
|
|
#ifdef LIGHTLOOP_TILE_PASS |
|
|
|
clusterIndices[0] = clusterIndices[1]; |
|
|
|
clusterDistances[0] = clusterDistances[1]; |
|
|
|
#endif // LIGHTLOOP_TILE_PASS |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
// TODO: use a low-discrepancy point set. |
|
|
|
float2 sampleCoord = voxelCoord + 0.5; |
|
|
|
|
|
|
|
// Compute the unnormalized ray direction s.t. its ViewSpaceZ = 1. |
|
|
|
// Compute the ray direction s.t. its ViewSpaceZ = 1. |
|
|
|
Ray cameraRay; |
|
|
|
cameraRay.originWS = GetCurrentViewPosition(); |
|
|
|
cameraRay.ratioLenToZ = sqrt(dot(dir, dir)); |
|
|
|
cameraRay.ratioZtoLen = rsqrt(dot(dir, dir)); |
|
|
|
cameraRay.directionWS = dir * cameraRay.ratioZtoLen; |
|
|
|
Ray ray; |
|
|
|
ray.originWS = GetCurrentViewPosition(); |
|
|
|
ray.ratioLenToZ = sqrt(dot(dir, dir)); |
|
|
|
ray.ratioZtoLen = rsqrt(dot(dir, dir)); |
|
|
|
ray.directionWS = dir * ray.ratioZtoLen; |
|
|
|
FillVolumetricLightingBuffer(cameraRay, voxelCoord, tileCoord); |
|
|
|
FillVolumetricLightingBuffer(ray, voxelCoord, tileCoord); |
|
|
|
} |