|
|
|
|
|
|
#define VBUFFER_SLICE_COUNT 128 |
|
|
|
#endif |
|
|
|
|
|
|
|
#define SUPPORT_ASYMMETRY 1 // Support asymmetric phase functions |
|
|
|
#define SUPPORT_PUNCTUAL_LIGHTS 1 // Punctual lights contribute to fog lighting |
|
|
|
|
|
|
|
#define GROUP_SIZE_1D 8 |
|
|
|
|
|
|
// Implementation |
|
|
|
//-------------------------------------------------------------------------------------------------- |
|
|
|
|
|
|
|
// A ray with a single origin and two directions: |
|
|
|
// one pointing at the center of the voxel, and one jittered in screen space. |
|
|
|
float3 strataDirWS; // Normalized, tile-stratified |
|
|
|
float3 centerDirWS; // Normalized, tile-centered |
|
|
|
float strataDirInvViewZ; // 1 / ViewSpace(strataDirWS).z |
|
|
|
float twoDirRatioViewZ; // ViewSpace(strataDirWS).z / ViewSpace(centerDirWS).z |
|
|
|
float3 jitterDirWS; // Normalized, voxel-jittered in the screen space |
|
|
|
float3 centerDirWS; // Normalized, voxel-centered in the screen space |
|
|
|
float jitterDirInvViewZ; // 1 / ViewSpace(jitterDirWS).z |
|
|
|
float twoDirRatioViewZ; // ViewSpace(jitterDirWS).z / ViewSpace(centerDirWS).z |
|
|
|
// Returns a point along the stratified direction. |
|
|
|
float ConvertLinearDepthToJitterRayDist(DualRay ray, float z) |
|
|
|
{ |
|
|
|
return z * ray.jitterDirInvViewZ; |
|
|
|
} |
|
|
|
|
|
|
|
float ConvertJitterDistToCenterRayDist(DualRay ray, float t) |
|
|
|
{ |
|
|
|
return t * ray.twoDirRatioViewZ; |
|
|
|
} |
|
|
|
|
|
|
|
// Returns a point along the jittered direction. |
|
|
|
return ray.originWS + t * ray.strataDirWS; |
|
|
|
return ray.originWS + t * ray.jitterDirWS; |
|
|
|
// Returns a point along the centered direction. It has a special property: |
|
|
|
// ViewSpace(GetPointAtDistance(ray, t)).z = ViewSpace(GetCenterAtDistance(ray, t)).z, |
|
|
|
// e.i. both points computed from the same value of 't' reside on the same Z-plane in the view space. |
|
|
|
float3 GetCenterAtDistance(DualRay ray, float t) |
|
|
|
// Returns a point along the centered direction. |
|
|
|
float3 GetCenterAtDistance(DualRay ray, float s) |
|
|
|
t *= ray.twoDirRatioViewZ; // Perform the Z-coordinate conversion |
|
|
|
return ray.originWS + t * ray.centerDirWS; |
|
|
|
return ray.originWS + s * ray.centerDirWS; |
|
|
|
} |
|
|
|
|
|
|
|
struct VoxelLighting |
|
|
|
|
|
|
color, attenuation); |
|
|
|
|
|
|
|
// Important: |
|
|
|
// Ideally, all scattering calculations should use the stratified versions |
|
|
|
// Ideally, all scattering calculations should use the jittered versions |
|
|
|
// of the sample position and the ray direction. However, correct reprojection |
|
|
|
// of asymmetrically scattered lighting (affected by an anisotropic phase |
|
|
|
// function) is not possible. We work around this issue by reprojecting |
|
|
|
|
|
|
float3 coneAxisX = lenMul * light.right; |
|
|
|
float3 coneAxisY = lenMul * light.up; |
|
|
|
|
|
|
|
sampleLight = IntersectRayCone(ray.originWS, ray.strataDirWS, |
|
|
|
sampleLight = IntersectRayCone(ray.originWS, ray.jitterDirWS, |
|
|
|
light.positionWS, light.forward, |
|
|
|
coneAxisX, coneAxisY, |
|
|
|
t0, t1, tEntr, tExit); |
|
|
|
|
|
|
|
|
|
|
float t, distSq, rcpPdf; |
|
|
|
ImportanceSamplePunctualLight(rndVal, light.positionWS, |
|
|
|
ray.originWS, ray.strataDirWS, |
|
|
|
ray.originWS, ray.jitterDirWS, |
|
|
|
tEntr, tExit, t, distSq, rcpPdf, |
|
|
|
hackMinDistSq); |
|
|
|
|
|
|
|
|
|
|
0, L, lightToSample, distances, color, attenuation); |
|
|
|
|
|
|
|
// Important: |
|
|
|
// Ideally, all scattering calculations should use the stratified versions |
|
|
|
// Ideally, all scattering calculations should use the jittered versions |
|
|
|
// of the sample position and the ray direction. However, correct reprojection |
|
|
|
// of asymmetrically scattered lighting (affected by an anisotropic phase |
|
|
|
// function) is not possible. We work around this issue by reprojecting |
|
|
|
|
|
|
float3x3 rotMat = float3x3(light.right, light.up, light.forward); |
|
|
|
|
|
|
|
float3 o = mul(rotMat, ray.originWS - light.positionWS); |
|
|
|
float3 d = mul(rotMat, ray.strataDirWS); |
|
|
|
float3 d = mul(rotMat, ray.jitterDirWS); |
|
|
|
|
|
|
|
float range = light.size.x; |
|
|
|
float3 boxPt0 = float3(-1, -1, 0); |
|
|
|
|
|
|
0, L, lightToSample, distances, color, attenuation); |
|
|
|
|
|
|
|
// Important: |
|
|
|
// Ideally, all scattering calculations should use the stratified versions |
|
|
|
// Ideally, all scattering calculations should use the jittered versions |
|
|
|
// of the sample position and the ray direction. However, correct reprojection |
|
|
|
// of asymmetrically scattered lighting (affected by an anisotropic phase |
|
|
|
// function) is not possible. We work around this issue by reprojecting |
|
|
|
|
|
|
void FillVolumetricLightingBuffer(LightLoopContext context, uint featureFlags, |
|
|
|
PositionInputs posInput, DualRay ray) |
|
|
|
{ |
|
|
|
float n = _VBufferDepthDecodingParams.x + _VBufferDepthDecodingParams.z; |
|
|
|
float z0 = n; // Start integration from the near plane |
|
|
|
float t0 = ray.strataDirInvViewZ * z0; // Convert view space Z to distance along the stratified ray |
|
|
|
float de = rcp(VBUFFER_SLICE_COUNT); // Log-encoded distance between slices |
|
|
|
const float n = _VBufferDepthDecodingParams.x + _VBufferDepthDecodingParams.z; |
|
|
|
const float z0 = n; // Start integration from the near plane |
|
|
|
const float de = rcp(VBUFFER_SLICE_COUNT); // Log-encoded distance between slices |
|
|
|
|
|
|
|
float t0 = ConvertLinearDepthToJitterRayDist(ray, z0); |
|
|
|
|
|
|
|
// The contribution of the ambient probe does not depend on the position, |
|
|
|
// only on the direction and the length of the interval. |
|
|
|
|
|
|
uint3 voxelCoord = uint3(posInput.positionSS, slice); |
|
|
|
|
|
|
|
float e1 = slice * de + de; // (slice + 1) / sliceCount |
|
|
|
#if defined(SHADER_API_METAL) |
|
|
|
// Warning: this compiles, but it's nonsense. Use DecodeLogarithmicDepthGeneralized(). |
|
|
|
float z1 = DecodeLogarithmicDepth(e1, _VBufferDepthDecodingParams); |
|
|
|
#else |
|
|
|
#endif |
|
|
|
float t1 = ray.strataDirInvViewZ * z1; // Convert view space Z to distance along the stratified ray |
|
|
|
float t1 = ConvertLinearDepthToJitterRayDist(ray, z1); |
|
|
|
float dt = t1 - t0; |
|
|
|
|
|
|
|
#ifdef USE_CLUSTERED_LIGHTLIST |
|
|
|
|
|
|
// Compute the -exact- position of the center of the voxel. |
|
|
|
// It's important since the accumulated value of the integral is stored at the center. |
|
|
|
// We will use it for participating media sampling, asymmetric scattering and reprojection. |
|
|
|
float tc = t0 + 0.5 * dt; |
|
|
|
float3 centerWS = GetCenterAtDistance(ray, tc); |
|
|
|
float s = ConvertJitterDistToCenterRayDist(ray, t0 + 0.5 * dt); |
|
|
|
float3 centerWS = GetCenterAtDistance(ray, s); |
|
|
|
// Sample the participating medium at 'tc' (or 'centerWS'). |
|
|
|
// Sample the participating medium at the center of the voxel. |
|
|
|
// We consider it to be constant along the interval [t0, t1] (within the voxel). |
|
|
|
// TODO: piecewise linear. |
|
|
|
float3 scattering = LOAD_TEXTURE3D(_VBufferDensity, voxelCoord).rgb; |
|
|
|
|
|
|
); |
|
|
|
#endif |
|
|
|
#if ENABLE_REPROJECTION |
|
|
|
|
|
|
|
// Reproject the history at 'centerWS'. |
|
|
|
float4 reprojValue = SampleVBuffer(TEXTURE3D_PARAM(_VBufferLightingHistory, s_linear_clamp_sampler), |
|
|
|
centerWS, |
|
|
|
|
|
|
// TODO: doesn't seem to be worth it, removed for now. |
|
|
|
|
|
|
|
// Perform temporal blending. |
|
|
|
// Both radiance values are obtained by integrating over line segments of different length. |
|
|
|
// Blending only makes sense if the length of both intervals is the same. |
|
|
|
// Therefore, the reprojected radiance needs to be rescaled by (frame_dt / reproj_dt). |
|
|
|
// Both radiance values are obtained by integrating over line segments of different length, |
|
|
|
// with potentially different participating media coverage. |
|
|
|
// Blending only makes sense if the voxels are virtually identical. |
|
|
|
// Therefore, we need to rescale the history to make it match the current configuration. |
|
|
|
// In order to do that, we integrate transmittance over the length of the ray interval |
|
|
|
// passing through the center of the voxel. The integral can be interpreted as the amount of |
|
|
|
// isotropically in-scattered radiance from a directional light with unit intensity. |
|
|
|
// We ignore jittering, as we want values from the same voxel to be reporojected without |
|
|
|
// any rescaling. |
|
|
|
float ds = ConvertJitterDistToCenterRayDist(ray, dt); |
|
|
|
float centerTransmInt = TransmittanceIntegralHomogeneousMedium(extinction, ds); |
|
|
|
|
|
|
|
float reprojRcpLen = reprojSuccess ? rcp(reprojValue.a) : 0; |
|
|
|
float lengthScale = dt * reprojRcpLen; |
|
|
|
float reprojScale = reprojSuccess ? (centerTransmInt * rcp(reprojValue.a)) : 0; |
|
|
|
float3 blendedRadiance = (1 - blendFactor) * lighting.radianceNoPhase + blendFactor * lengthScale * reprojRadiance; |
|
|
|
float3 blendedRadiance = (1 - blendFactor) * lighting.radianceNoPhase + blendFactor * reprojScale * reprojRadiance; |
|
|
|
|
|
|
|
// Store the feedback for the voxel. |
|
|
|
// TODO: dynamic lights (which update their position, rotation, cookie or shadow at runtime) |
|
|
|
|
|
|
|
|
|
|
_VBufferLightingFeedback[voxelCoord] = float4(blendedRadiance, dt); |
|
|
|
_VBufferLightingFeedback[voxelCoord] = float4(blendedRadiance, centerTransmInt); |
|
|
|
#if SUPPORT_ASYMMETRY |
|
|
|
#endif |
|
|
|
|
|
|
|
#if SUPPORT_ASYMMETRY |
|
|
|
#else |
|
|
|
float3 blendedRadiance = lighting.radianceNoPhase; |
|
|
|
#endif |
|
|
|
|
|
|
|
|
|
|
|
#if SUPPORT_ASYMMETRY |
|
|
|
float phase = _CornetteShanksConstant; |
|
|
|
#else |
|
|
|
float phase = IsotropicPhaseFunction(); |
|
|
|
#endif |
|
|
|
|
|
|
|
float4 integral = float4(totalRadiance, opticalDepth); |
|
|
|
float phase = _CornetteShanksConstant; |
|
|
|
|
|
|
|
// Integrate the contribution of the probe over the interval. |
|
|
|
// Integral{a, b}{Transmittance(0, t) * L_s(t) dt} = Transmittance(0, a) * Integral{a, b}{Transmittance(0, t - a) * L_s(t) dt}. |
|
|
|
|
|
|
// Compute the optical depth up to the end of the interval. |
|
|
|
opticalDepth += 0.5 * extinction * dt; |
|
|
|
|
|
|
|
z0 = z1; |
|
|
|
t0 = t1; |
|
|
|
|
|
|
|
#ifdef USE_CLUSTERED_LIGHTLIST |
|
|
|
|
|
|
|
|
|
|
float2 centerCoord = voxelCoord + float2(0.5, 0.5); |
|
|
|
#if ENABLE_REPROJECTION |
|
|
|
float2 strataCoord = centerCoord + _VBufferSampleOffset.xy; |
|
|
|
float2 jitterCoord = centerCoord + _VBufferSampleOffset.xy; |
|
|
|
float2 strataCoord = centerCoord; |
|
|
|
float2 jitterCoord = centerCoord; |
|
|
|
// Compute the (tile-centered) ray direction s.t. its ViewSpace(rayDirWS).z = 1. |
|
|
|
// Compute the (voxel-centered in the screen space) ray direction s.t. its ViewSpace(rayDirWS).z = 1. |
|
|
|
// Compute the (tile-stratified) ray direction s.t. its ViewSpace(rayDirWS).z = 1. |
|
|
|
float3 strataDirWS = mul(-float3(strataCoord, 1), (float3x3)_VBufferCoordToViewDirWS); |
|
|
|
float strataDirLenSq = dot(strataDirWS, strataDirWS); |
|
|
|
float strataDirLenRcp = rsqrt(strataDirLenSq); |
|
|
|
float strataDirLen = strataDirLenSq * strataDirLenRcp; |
|
|
|
// Compute the (voxel-jittered in the screen space) ray direction s.t. its ViewSpace(rayDirWS).z = 1. |
|
|
|
float3 jitterDirWS = mul(-float3(jitterCoord, 1), (float3x3)_VBufferCoordToViewDirWS); |
|
|
|
float jitterDirLenSq = dot(jitterDirWS, jitterDirWS); |
|
|
|
float jitterDirLenRcp = rsqrt(jitterDirLenSq); |
|
|
|
float jitterDirLen = jitterDirLenSq * jitterDirLenRcp; |
|
|
|
ray.strataDirWS = strataDirWS * strataDirLenRcp; // Normalize |
|
|
|
ray.jitterDirWS = jitterDirWS * jitterDirLenRcp; // Normalize |
|
|
|
ray.strataDirInvViewZ = strataDirLen; // View space Z |
|
|
|
ray.twoDirRatioViewZ = centerDirLen * strataDirLenRcp; // View space Z ratio |
|
|
|
ray.jitterDirInvViewZ = jitterDirLen; // View space Z |
|
|
|
ray.twoDirRatioViewZ = centerDirLen * jitterDirLenRcp; // View space Z ratio |
|
|
|
|
|
|
|
// TODO |
|
|
|
LightLoopContext context; |
|
|
|