您最多选择25个主题
主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
656 行
32 KiB
656 行
32 KiB
//--------------------------------------------------------------------------------------------------
|
|
// Definitions
|
|
//--------------------------------------------------------------------------------------------------
|
|
|
|
#pragma kernel VolumetricLightingBruteforceMQ VolumetricLighting=VolumetricLightingBruteforceMQ VL_PRESET_MQ ENABLE_REPROJECTION=0 LIGHTLOOP_SINGLE_PASS
|
|
#pragma kernel VolumetricLightingBruteforceReprojMQ VolumetricLighting=VolumetricLightingBruteforceReprojMQ VL_PRESET_MQ ENABLE_REPROJECTION=1 LIGHTLOOP_SINGLE_PASS
|
|
#pragma kernel VolumetricLightingClusteredMQ VolumetricLighting=VolumetricLightingClusteredMQ VL_PRESET_MQ ENABLE_REPROJECTION=0 LIGHTLOOP_TILE_PASS USE_CLUSTERED_LIGHTLIST
|
|
#pragma kernel VolumetricLightingClusteredReprojMQ VolumetricLighting=VolumetricLightingClusteredReprojMQ VL_PRESET_MQ ENABLE_REPROJECTION=1 LIGHTLOOP_TILE_PASS USE_CLUSTERED_LIGHTLIST
|
|
#pragma kernel VolumetricLightingBruteforceHQ VolumetricLighting=VolumetricLightingBruteforceHQ VL_PRESET_HQ ENABLE_REPROJECTION=0 LIGHTLOOP_SINGLE_PASS
|
|
#pragma kernel VolumetricLightingBruteforceReprojHQ VolumetricLighting=VolumetricLightingBruteforceReprojHQ VL_PRESET_HQ ENABLE_REPROJECTION=1 LIGHTLOOP_SINGLE_PASS
|
|
#pragma kernel VolumetricLightingClusteredHQ VolumetricLighting=VolumetricLightingClusteredHQ VL_PRESET_HQ ENABLE_REPROJECTION=0 LIGHTLOOP_TILE_PASS USE_CLUSTERED_LIGHTLIST
|
|
#pragma kernel VolumetricLightingClusteredReprojHQ VolumetricLighting=VolumetricLightingClusteredReprojHQ VL_PRESET_HQ ENABLE_REPROJECTION=1 LIGHTLOOP_TILE_PASS USE_CLUSTERED_LIGHTLIST
|
|
|
|
// #pragma enable_d3d11_debug_symbols
|
|
|
|
#ifdef VL_PRESET_MQ
|
|
// E.g. for 1080p: (1920/8)x(1080/8)x(64) = 2,073,600 voxels
|
|
#define VBUFFER_TILE_SIZE 8
|
|
#endif
|
|
#ifdef VL_PRESET_HQ
|
|
// E.g. for 1080p: (1920/4)x(1080/4)x(128) = 16,588,800 voxels
|
|
#define VBUFFER_TILE_SIZE 4
|
|
#endif
|
|
|
|
#define GROUP_SIZE_1D 8
|
|
#define SUPPORT_PUNCTUAL_LIGHTS 1 // Punctual lights contribute to fog lighting
|
|
#define SHADOW_USE_ONLY_VIEW_BASED_BIASING 1 // We don't use normal biasing as it is not available when doing volumetric
|
|
|
|
#include "../../ShaderPass/ShaderPass.cs.hlsl"
|
|
#define SHADERPASS SHADERPASS_VOLUMETRIC_LIGHTING
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Included headers
|
|
//--------------------------------------------------------------------------------------------------
|
|
|
|
#include "CoreRP/ShaderLibrary/Common.hlsl"
|
|
#include "CoreRP/ShaderLibrary/Color.hlsl"
|
|
#include "CoreRP/ShaderLibrary/Filtering.hlsl"
|
|
#include "CoreRP/ShaderLibrary/VolumeRendering.hlsl"
|
|
|
|
#include "../../ShaderVariables.hlsl"
|
|
#include "VolumetricLighting.cs.hlsl"
|
|
#include "VBuffer.hlsl"
|
|
|
|
#define UNITY_MATERIAL_VOLUMETRIC // Define before including Lighting.hlsl and Material.hlsl
|
|
#include "../Lighting.hlsl" // Includes Material.hlsl
|
|
#include "../LightEvaluation.hlsl"
|
|
|
|
#pragma only_renderers d3d11 ps4 xboxone vulkan metal switch
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Inputs & outputs
|
|
//--------------------------------------------------------------------------------------------------
|
|
|
|
RW_TEXTURE3D(float4, _VBufferLightingIntegral); // RGB = radiance, A = optical depth
|
|
RW_TEXTURE3D(float4, _VBufferLightingFeedback); // RGB = radiance, A = interval length
|
|
TEXTURE3D(_VBufferLightingHistory); // RGB = radiance, A = interval length
|
|
TEXTURE3D(_VBufferDensity); // RGB = sqrt(scattering), A = sqrt(extinction)
|
|
|
|
// TODO: avoid creating another Constant Buffer...
|
|
CBUFFER_START(UnityVolumetricLighting)
|
|
float4x4 _VBufferCoordToViewDirWS; // Actually just 3x3, but Unity can only set 4x4
|
|
float4 _VBufferSampleOffset; // Not used by this shader
|
|
float _CornetteShanksConstant; // Not used by this shader
|
|
uint _NumVisibleDensityVolumes;
|
|
CBUFFER_END
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Implementation
|
|
//--------------------------------------------------------------------------------------------------
|
|
|
|
// A ray with a single origin and two directions:
|
|
// one pointing at the center of the voxel, and one jittered in screen space.
|
|
struct DualRay
|
|
{
|
|
float3 originWS;
|
|
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
|
|
};
|
|
|
|
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.
|
|
float3 GetPointAtDistance(DualRay ray, float t)
|
|
{
|
|
return ray.originWS + t * ray.jitterDirWS;
|
|
}
|
|
|
|
// Returns a point along the centered direction.
|
|
float3 GetCenterAtDistance(DualRay ray, float s)
|
|
{
|
|
return ray.originWS + s * ray.centerDirWS;
|
|
}
|
|
|
|
struct VoxelLighting
|
|
{
|
|
float3 radianceComplete;
|
|
float3 radianceNoPhase;
|
|
};
|
|
|
|
// Computes the light integral (in-scattered radiance) within the voxel.
|
|
// Multiplication by the scattering coefficient and the phase function is performed outside.
|
|
VoxelLighting EvaluateVoxelLighting(LightLoopContext context, uint featureFlags, PositionInputs posInput, float3 centerWS,
|
|
DualRay ray, float t0, float t1, float dt, float rndVal, float extinction, float anisotropy
|
|
#ifdef USE_CLUSTERED_LIGHTLIST
|
|
, uint lightClusters[2])
|
|
#else
|
|
)
|
|
#endif
|
|
{
|
|
VoxelLighting lighting;
|
|
ZERO_INITIALIZE(VoxelLighting, lighting);
|
|
|
|
BakeLightingData unused; // Unused for now, so define once
|
|
|
|
if (featureFlags & LIGHTFEATUREFLAGS_DIRECTIONAL)
|
|
{
|
|
float tOffset, weight;
|
|
ImportanceSampleHomogeneousMedium(rndVal, extinction, dt, tOffset, weight);
|
|
|
|
float t = t0 + tOffset;
|
|
posInput.positionWS = GetPointAtDistance(ray, t);
|
|
|
|
for (uint i = 0; i < _DirectionalLightCount; ++i)
|
|
{
|
|
// Fetch the light.
|
|
DirectionalLightData light = _DirectionalLightDatas[i];
|
|
float3 L = -light.forward; // Lights point backwards in Unity
|
|
|
|
float3 color; float attenuation;
|
|
light.contactShadowIndex = -1; // Disable shadow contact if any
|
|
// Note: We provide a normal of 0 here that work with the NdotL >= 0 test inside the EvaluateLight_Punctual call
|
|
EvaluateLight_Directional(context, posInput, light, unused, 0, L,
|
|
color, attenuation);
|
|
|
|
// Important:
|
|
// 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
|
|
// lighting not affected by the phase function. This basically removes
|
|
// the phase function from the temporal integration process. It is a hack.
|
|
// The downside is that anisotropy no longer benefits from temporal averaging,
|
|
// and any temporal instability of anisotropy causes causes visible jitter.
|
|
// In order to stabilize the image, we use the voxel center for all
|
|
// anisotropy-related calculations.
|
|
float cosTheta = dot(L, ray.centerDirWS);
|
|
float phase = CornetteShanksPhasePartVarying(anisotropy, cosTheta);
|
|
|
|
// Note: the 'weight' accounts for transmittance from 't0' to 't'.
|
|
float intensity = light.volumetricDimmer * attenuation * weight;
|
|
|
|
// Compute the amount of in-scattered radiance.
|
|
lighting.radianceNoPhase += intensity * color;
|
|
lighting.radianceComplete += phase * intensity * color;
|
|
}
|
|
}
|
|
|
|
#if (SUPPORT_PUNCTUAL_LIGHTS == 0)
|
|
return lighting;
|
|
#endif
|
|
|
|
if (featureFlags & LIGHTFEATUREFLAGS_PUNCTUAL)
|
|
{
|
|
#ifdef USE_CLUSTERED_LIGHTLIST
|
|
// Iterate over all lights within 2 (not necessarily unique) clusters overlapping the voxel along Z.
|
|
// We need to skip duplicates, but it's not too difficult since lights are sorted by index.
|
|
uint lightStarts[2], lightCounts[2];
|
|
|
|
for (uint k = 0; k < 2; k++)
|
|
{
|
|
GetCountAndStartCluster(posInput.tileCoord, lightClusters[k], LIGHTCATEGORY_PUNCTUAL,
|
|
lightStarts[k], lightCounts[k]);
|
|
|
|
}
|
|
|
|
uint i = 0, j = 0;
|
|
|
|
if (i < lightCounts[0] || j < lightCounts[1])
|
|
{
|
|
// At least one of the clusters is non-empty.
|
|
uint lightIndices[2];
|
|
|
|
// Fetch two initial indices from both clusters.
|
|
lightIndices[0] = FetchIndexWithBoundsCheck(lightStarts[0], lightCounts[0], i);
|
|
lightIndices[1] = FetchIndexWithBoundsCheck(lightStarts[1], lightCounts[1], j);
|
|
|
|
// Process all punctual lights except for box lights (which are technically not even punctual).
|
|
do
|
|
{
|
|
// Process lights in order.
|
|
uint lightIndex = min(lightIndices[0], lightIndices[1]);
|
|
|
|
#else // USE_CLUSTERED_LIGHTLIST
|
|
{
|
|
uint lightIndex = 0;
|
|
|
|
// Process all punctual lights except for box lights (which are technically not even punctual).
|
|
for (; lightIndex < _PunctualLightCount; lightIndex++)
|
|
{
|
|
#endif // USE_CLUSTERED_LIGHTLIST
|
|
|
|
LightData light = _LightDatas[lightIndex];
|
|
|
|
// Process box lights in a separate loop.
|
|
if (light.lightType == GPULIGHTTYPE_PROJECTOR_BOX) { break; }
|
|
|
|
float tEntr = t0;
|
|
float tExit = t1;
|
|
|
|
bool sampleLight = true;
|
|
|
|
// Perform ray-cone intersection for pyramid and spot lights.
|
|
if (light.lightType != GPULIGHTTYPE_POINT)
|
|
{
|
|
float lenMul = 1;
|
|
|
|
if (light.lightType == GPULIGHTTYPE_PROJECTOR_PYRAMID)
|
|
{
|
|
// 'light.right' and 'light.up' vectors are pre-scaled on the CPU
|
|
// s.t. if you were to place them at the distance of 1 directly in front
|
|
// of the light, they would give you the "footprint" of the light.
|
|
// For spot lights, the cone fit is exact.
|
|
// For pyramid lights, however, this is the "inscribed" cone
|
|
// (contained within the pyramid), and we want to intersect
|
|
// the "escribed" cone (which contains the pyramid).
|
|
// Therefore, we have to scale the radii by the sqrt(2).
|
|
lenMul = rsqrt(2);
|
|
}
|
|
|
|
float3 coneAxisX = lenMul * light.right;
|
|
float3 coneAxisY = lenMul * light.up;
|
|
|
|
sampleLight = IntersectRayCone(ray.originWS, ray.jitterDirWS,
|
|
light.positionWS, light.forward,
|
|
coneAxisX, coneAxisY,
|
|
t0, t1, tEntr, tExit);
|
|
}
|
|
|
|
if (sampleLight)
|
|
{
|
|
// We are unable to adequately sample features larger
|
|
// than the half of the length of the integration interval
|
|
// divided by the number of temporal samples (7).
|
|
// Therefore, we apply this hack to reduce flickering.
|
|
float hackMinDistSq = Sq(dt * (0.5 / 7));
|
|
|
|
float t, distSq, rcpPdf;
|
|
ImportanceSamplePunctualLight(rndVal, light.positionWS,
|
|
ray.originWS, ray.jitterDirWS,
|
|
tEntr, tExit, t, distSq, rcpPdf,
|
|
hackMinDistSq);
|
|
|
|
posInput.positionWS = GetPointAtDistance(ray, t);
|
|
|
|
float3 lightToSample = posInput.positionWS - light.positionWS;
|
|
float distRcp = rsqrt(distSq);
|
|
float dist = distSq * distRcp;
|
|
float distProj = dot(lightToSample, light.forward);
|
|
float4 distances = float4(dist, distSq, distRcp, distProj);
|
|
float3 L = -lightToSample * distRcp;
|
|
|
|
float3 color; float attenuation;
|
|
light.contactShadowIndex = -1; // Disable shadow contact if any
|
|
// Note: We provide a normal of 0 here that work with the NdotL >= 0 test inside the EvaluateLight_Punctual call
|
|
EvaluateLight_Punctual(context, posInput, light, unused,
|
|
0, L, lightToSample, distances,
|
|
color, attenuation);
|
|
|
|
// Important:
|
|
// 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
|
|
// lighting not affected by the phase function. This basically removes
|
|
// the phase function from the temporal integration process. It is a hack.
|
|
// The downside is that anisotropy no longer benefits from temporal averaging,
|
|
// and any temporal instability of anisotropy causes causes visible jitter.
|
|
// In order to stabilize the image, we use the voxel center for all
|
|
// anisotropy-related calculations.
|
|
float3 centerL = light.positionWS - centerWS;
|
|
float cosTheta = dot(centerL, ray.centerDirWS) * rsqrt(dot(centerL, centerL));
|
|
float phase = CornetteShanksPhasePartVarying(anisotropy, cosTheta);
|
|
|
|
float intensity = light.volumetricDimmer * attenuation * rcpPdf;
|
|
|
|
// Compute transmittance from 't0' to 't'.
|
|
intensity *= TransmittanceHomogeneousMedium(extinction, t - t0);
|
|
|
|
// Compute the amount of in-scattered radiance.
|
|
lighting.radianceNoPhase += intensity * color;
|
|
lighting.radianceComplete += phase * intensity * color;
|
|
}
|
|
|
|
#ifndef USE_CLUSTERED_LIGHTLIST
|
|
}
|
|
|
|
// Process all box lights.
|
|
for (; lightIndex < _PunctualLightCount; lightIndex++)
|
|
{
|
|
#else // USE_CLUSTERED_LIGHTLIST
|
|
|
|
// Advance to the next light in one (or both at the same time) clusters.
|
|
if (lightIndex == lightIndices[0])
|
|
{
|
|
i++;
|
|
lightIndices[0] = FetchIndexWithBoundsCheck(lightStarts[0], lightCounts[0], i);
|
|
}
|
|
|
|
if (lightIndex == lightIndices[1])
|
|
{
|
|
j++;
|
|
lightIndices[1] = FetchIndexWithBoundsCheck(lightStarts[1], lightCounts[1], j);
|
|
}
|
|
} while (i < lightCounts[0] || j < lightCounts[1]);
|
|
|
|
// Process all box lights.
|
|
while (i < lightCounts[0] || j < lightCounts[1])
|
|
{
|
|
// Process lights in order.
|
|
uint lightIndex = min(lightIndices[0], lightIndices[1]);
|
|
|
|
#endif // USE_CLUSTERED_LIGHTLIST
|
|
|
|
LightData light = _LightDatas[lightIndex];
|
|
light.lightType = GPULIGHTTYPE_PROJECTOR_BOX;
|
|
|
|
// Convert the box light from OBB to AABB.
|
|
// 'light.right' and 'light.up' vectors are pre-scaled on the CPU by (2/w) and (2/h).
|
|
float3x3 rotMat = float3x3(light.right, light.up, light.forward);
|
|
|
|
float3 o = mul(rotMat, ray.originWS - light.positionWS);
|
|
float3 d = mul(rotMat, ray.jitterDirWS);
|
|
|
|
float range = light.size.x;
|
|
float3 boxPt0 = float3(-1, -1, 0);
|
|
float3 boxPt1 = float3( 1, 1, range);
|
|
|
|
float tEntr, tExit;
|
|
|
|
if (IntersectRayAABB(o, d, boxPt0, boxPt1, t0, t1, tEntr, tExit))
|
|
{
|
|
float tOffset, weight;
|
|
ImportanceSampleHomogeneousMedium(rndVal, extinction, tExit - tEntr, tOffset, weight);
|
|
|
|
float t = tEntr + tOffset;
|
|
posInput.positionWS = GetPointAtDistance(ray, t);
|
|
|
|
float3 L = -light.forward;
|
|
float3 lightToSample = posInput.positionWS - light.positionWS;
|
|
float distProj = dot(lightToSample, light.forward);
|
|
float4 distances = float4(1, 1, 1, distProj);
|
|
|
|
float3 color; float attenuation;
|
|
light.contactShadowIndex = -1; // Disable shadow contact if any
|
|
// Note: We provide a normal of 0 here that work with the NdotL >= 0 test inside the EvaluateLight_Punctual call
|
|
EvaluateLight_Punctual( context, posInput, light, unused,
|
|
0, L, lightToSample, distances,
|
|
color, attenuation);
|
|
|
|
// Important:
|
|
// 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
|
|
// lighting not affected by the phase function. This basically removes
|
|
// the phase function from the temporal integration process. It is a hack.
|
|
// The downside is that anisotropy no longer benefits from temporal averaging,
|
|
// and any temporal instability of anisotropy causes causes visible jitter.
|
|
// In order to stabilize the image, we use the voxel center for all
|
|
// anisotropy-related calculations.
|
|
float3 centerL = light.positionWS - centerWS;
|
|
float cosTheta = dot(centerL, ray.centerDirWS) * rsqrt(dot(centerL, centerL));
|
|
float phase = CornetteShanksPhasePartVarying(anisotropy, cosTheta);
|
|
|
|
// Note: the 'weight' accounts for transmittance from 'tEntr' to 't'.
|
|
float intensity = light.volumetricDimmer * attenuation * weight;
|
|
|
|
// Compute transmittance from 't0' to 'tEntr'.
|
|
intensity *= TransmittanceHomogeneousMedium(extinction, tEntr - t0);
|
|
|
|
// Compute the amount of in-scattered radiance.
|
|
lighting.radianceNoPhase += intensity * color;
|
|
lighting.radianceComplete += phase * intensity * color;
|
|
}
|
|
|
|
#ifdef USE_CLUSTERED_LIGHTLIST
|
|
// Advance to the next light in one (or both at the same time) clusters.
|
|
if (lightIndex == lightIndices[0])
|
|
{
|
|
i++;
|
|
lightIndices[0] = FetchIndexWithBoundsCheck(lightStarts[0], lightCounts[0], i);
|
|
}
|
|
|
|
if (lightIndex == lightIndices[1])
|
|
{
|
|
j++;
|
|
lightIndices[1] = FetchIndexWithBoundsCheck(lightStarts[1], lightCounts[1], j);
|
|
}
|
|
#endif // USE_CLUSTERED_LIGHTLIST
|
|
}
|
|
}
|
|
}
|
|
|
|
return lighting;
|
|
}
|
|
|
|
// Computes the in-scattered radiance along the ray.
|
|
void FillVolumetricLightingBuffer(LightLoopContext context, uint featureFlags,
|
|
PositionInputs posInput, DualRay ray)
|
|
{
|
|
const float n = _VBufferDepthDecodingParams.x + _VBufferDepthDecodingParams.z;
|
|
const float z0 = n; // Start integration from the near plane
|
|
const float de = _VBufferSliceCount.y; // 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.
|
|
// SampleSH9() evaluates the 3-band SH in a given direction.
|
|
// The probe is already pre-convolved with the phase function.
|
|
float3 probeInScatteredRadiance = SampleSH9(_AmbientProbeCoeffs, ray.centerDirWS);
|
|
|
|
float3 totalRadiance = 0;
|
|
float opticalDepth = 0;
|
|
|
|
#ifdef USE_CLUSTERED_LIGHTLIST
|
|
// The voxel can overlap up to 2 light clusters along Z, so we have to iterate over both.
|
|
// TODO: implement Z-binning which makes Z-range queries easy.
|
|
uint lightClusters[2];
|
|
lightClusters[0] = GetLightClusterIndex(posInput.tileCoord, z0);
|
|
#endif // USE_CLUSTERED_LIGHTLIST
|
|
|
|
for (uint slice = 0; slice < (uint)_VBufferSliceCount.x; slice++)
|
|
{
|
|
uint3 voxelCoord = uint3(posInput.positionSS, slice);
|
|
|
|
float e1 = slice * de + de; // (slice + 1) / sliceCount
|
|
float z1 = DecodeLogarithmicDepthGeneralized(e1, _VBufferDepthDecodingParams);
|
|
float t1 = ConvertLinearDepthToJitterRayDist(ray, z1);
|
|
float dt = t1 - t0;
|
|
|
|
#ifdef USE_CLUSTERED_LIGHTLIST
|
|
lightClusters[1] = GetLightClusterIndex(posInput.tileCoord, z1);
|
|
#endif
|
|
|
|
// 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 s = ConvertJitterDistToCenterRayDist(ray, t0 + 0.5 * dt);
|
|
float3 centerWS = GetCenterAtDistance(ray, s);
|
|
|
|
// 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;
|
|
float extinction = LOAD_TEXTURE3D(_VBufferDensity, voxelCoord).a;
|
|
float anisotropy = _GlobalAnisotropy;
|
|
|
|
// Prevent division by 0.
|
|
extinction = max(extinction, FLT_MIN);
|
|
|
|
#if ENABLE_REPROJECTION
|
|
// This is a sequence of 7 equidistant numbers from 1/14 to 13/14.
|
|
// Each of them is the centroid of the interval of length 2/14.
|
|
float rndVal = _VBufferSampleOffset.z;
|
|
#else
|
|
float rndVal = 0.5;
|
|
#endif
|
|
|
|
VoxelLighting lighting = EvaluateVoxelLighting(context, featureFlags, posInput, centerWS,
|
|
ray, t0, t1, dt, rndVal, extinction, anisotropy
|
|
#ifdef USE_CLUSTERED_LIGHTLIST
|
|
, lightClusters);
|
|
#else
|
|
);
|
|
#endif
|
|
#if ENABLE_REPROJECTION
|
|
|
|
// Reproject the history at 'centerWS'.
|
|
float4 reprojValue = SampleVBuffer(TEXTURE3D_PARAM(_VBufferLightingHistory, s_linear_clamp_sampler),
|
|
centerWS,
|
|
_PrevViewProjMatrix,
|
|
_VBufferPrevResolution,
|
|
_VBufferPrevSliceCount.xy,
|
|
_VBufferPrevUvScaleAndLimit.xy,
|
|
_VBufferPrevUvScaleAndLimit.zw,
|
|
_VBufferPrevDepthEncodingParams,
|
|
_VBufferPrevDepthDecodingParams,
|
|
false, false, true);
|
|
|
|
// Compute the exponential moving average over 'n' frames:
|
|
// X = (1 - a) * ValueAtFrame[n] + a * AverageOverPreviousFrames.
|
|
// We want each sample to be uniformly weighted by (1 / n):
|
|
// X = (1 / n) * Sum{i from 1 to n}{ValueAtFrame[i]}.
|
|
// Therefore, we get:
|
|
// (1 - a) = (1 / n) => a = (1 - 1 / n) = (n - 1) / n,
|
|
// X = (1 / n) * ValueAtFrame[n] + (1 - 1 / n) * AverageOverPreviousFrames.
|
|
// Why does it work? We need to make the following assumption:
|
|
// AverageOverPreviousFrames ≈ AverageOverFrames[n - 1].
|
|
// AverageOverFrames[n - 1] = (1 / (n - 1)) * Sum{i from 1 to n - 1}{ValueAtFrame[i]}.
|
|
// This implies that the reprojected (accumulated) value has mostly converged.
|
|
// X = (1 / n) * ValueAtFrame[n] + ((n - 1) / n) * (1 / (n - 1)) * Sum{i from 1 to n - 1}{ValueAtFrame[i]}.
|
|
// X = (1 / n) * ValueAtFrame[n] + (1 / n) * Sum{i from 1 to n - 1}{ValueAtFrame[i]}.
|
|
// X = Sum{i from 1 to n}{ValueAtFrame[i] / n}.
|
|
float numFrames = 7;
|
|
float frameWeight = 1 / numFrames;
|
|
float historyWeight = 1 - frameWeight;
|
|
|
|
// The accuracy of the integral linearly decreases with the length of the interval.
|
|
// Therefore, reprojecting longer intervals should result in a lower confidence.
|
|
// 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,
|
|
// 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.
|
|
// Important: reprojection must be performed without the phase function! Otherwise,
|
|
// some kind of per-light angle correction is required, which is intractable in practice.
|
|
float ds = ConvertJitterDistToCenterRayDist(ray, dt);
|
|
float centerTransmInt = TransmittanceIntegralHomogeneousMedium(extinction, ds);
|
|
|
|
bool reprojSuccess = reprojValue.a != 0;
|
|
float blendFactor = reprojSuccess ? historyWeight : 0;
|
|
float reprojScale = reprojSuccess ? (centerTransmInt * rcp(reprojValue.a)) : 0;
|
|
float3 reprojRadiance = reprojValue.rgb;
|
|
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)
|
|
// do not support reprojection and should neither read nor write to the history buffer.
|
|
// This will cause them to alias, but it is the only way to prevent ghosting.
|
|
|
|
_VBufferLightingFeedback[voxelCoord] = float4(blendedRadiance, centerTransmInt);
|
|
|
|
// Extrapolate the influence of the phase function on the results of the current frame.
|
|
// Use max() to prevent division by 0.
|
|
float3 phaseCurrFrame = lighting.radianceComplete * rcp(max(lighting.radianceNoPhase, FLT_MIN));
|
|
blendedRadiance *= phaseCurrFrame;
|
|
|
|
#else // ENABLE_REPROJECTION
|
|
float3 blendedRadiance = lighting.radianceComplete;
|
|
#endif // ENABLE_REPROJECTION
|
|
|
|
// Compute the transmittance from the camera to 't0'.
|
|
float transmittance = Transmittance(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}.
|
|
float3 probeRadiance = probeInScatteredRadiance * TransmittanceIntegralHomogeneousMedium(extinction, dt);
|
|
|
|
totalRadiance += transmittance * scattering * (phase * blendedRadiance + probeRadiance);
|
|
|
|
// Compute the optical depth up to the center of the interval.
|
|
opticalDepth += 0.5 * extinction * dt;
|
|
|
|
// Store the voxel data.
|
|
// Note: for correct filtering, the data has to be stored in the perceptual space.
|
|
// This means storing the tone mapped radiance and transmittance instead of optical depth.
|
|
// See "A Fresh Look at Generalized Sampling", p. 51.
|
|
// TODO: re-enable tone mapping after implementing pre-exposure.
|
|
_VBufferLightingIntegral[voxelCoord] = float4(/*FastTonemap*/(totalRadiance), Transmittance(opticalDepth));
|
|
|
|
// Compute the optical depth up to the end of the interval.
|
|
opticalDepth += 0.5 * extinction * dt;
|
|
|
|
t0 = t1;
|
|
|
|
#ifdef USE_CLUSTERED_LIGHTLIST
|
|
lightClusters[0] = lightClusters[1];
|
|
#endif
|
|
}
|
|
}
|
|
|
|
[numthreads(GROUP_SIZE_1D, GROUP_SIZE_1D, 1)]
|
|
void VolumetricLighting(uint2 groupId : SV_GroupID,
|
|
uint2 groupThreadId : SV_GroupThreadID)
|
|
{
|
|
// Perform compile-time checks.
|
|
if (!IsPower2(VBUFFER_TILE_SIZE) || !IsPower2(TILE_SIZE_CLUSTERED)) return;
|
|
|
|
uint2 groupOffset = groupId * GROUP_SIZE_1D;
|
|
uint2 voxelCoord = groupOffset + groupThreadId;
|
|
uint2 tileCoord = voxelCoord * VBUFFER_TILE_SIZE / TILE_SIZE_CLUSTERED;
|
|
|
|
uint voxelsPerClusterTile = Sq((uint)(TILE_SIZE_CLUSTERED / VBUFFER_TILE_SIZE));
|
|
|
|
if (voxelsPerClusterTile >= 64)
|
|
{
|
|
// TODO: this is a compile-time test, make sure the compiler actually scalarizes.
|
|
tileCoord = groupOffset * VBUFFER_TILE_SIZE / TILE_SIZE_CLUSTERED;
|
|
}
|
|
|
|
UNITY_BRANCH
|
|
if (voxelCoord.x >= (uint)_VBufferResolution.x ||
|
|
voxelCoord.y >= (uint)_VBufferResolution.y)
|
|
{
|
|
return;
|
|
}
|
|
|
|
float2 centerCoord = voxelCoord + float2(0.5, 0.5);
|
|
#if ENABLE_REPROJECTION
|
|
float2 jitterCoord = centerCoord + _VBufferSampleOffset.xy;
|
|
#else
|
|
float2 jitterCoord = centerCoord;
|
|
#endif
|
|
|
|
// TODO: avoid 2x matrix multiplications by precomputing the world-space offset on the Z=1 plane.
|
|
// Compute the (voxel-centered in the screen space) ray direction s.t. its ViewSpace(rayDirWS).z = 1.
|
|
float3 centerDirWS = mul(-float3(centerCoord, 1), (float3x3)_VBufferCoordToViewDirWS);
|
|
float centerDirLenSq = dot(centerDirWS, centerDirWS);
|
|
float centerDirLenRcp = rsqrt(centerDirLenSq);
|
|
float centerDirLen = centerDirLenSq * centerDirLenRcp;
|
|
|
|
// 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;
|
|
|
|
DualRay ray;
|
|
|
|
ray.originWS = GetCurrentViewPosition();
|
|
ray.jitterDirWS = jitterDirWS * jitterDirLenRcp; // Normalize
|
|
ray.centerDirWS = centerDirWS * centerDirLenRcp; // Normalize
|
|
ray.jitterDirInvViewZ = jitterDirLen; // View space Z
|
|
ray.twoDirRatioViewZ = centerDirLen * jitterDirLenRcp; // View space Z ratio
|
|
|
|
// TODO
|
|
LightLoopContext context;
|
|
context.sampleReflection = 0;
|
|
context.shadowContext = InitShadowContext();
|
|
context.contactShadow = 1.0;
|
|
uint featureFlags = 0xFFFFFFFF;
|
|
|
|
PositionInputs posInput = GetPositionInput(voxelCoord, _VBufferResolution.zw, tileCoord);
|
|
|
|
FillVolumetricLightingBuffer(context, featureFlags, posInput, ray);
|
|
}
|