您最多选择25个主题
主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
619 行
30 KiB
619 行
30 KiB
//--------------------------------------------------------------------------------------------------
|
|
// Definitions
|
|
//--------------------------------------------------------------------------------------------------
|
|
|
|
#pragma kernel VolumetricLightingAllLights VolumetricLighting=VolumetricLightingAllLights ENABLE_REPROJECTION=0 LIGHTLOOP_SINGLE_PASS
|
|
#pragma kernel VolumetricLightingAllLightsReproj VolumetricLighting=VolumetricLightingAllLightsReproj ENABLE_REPROJECTION=1 LIGHTLOOP_SINGLE_PASS
|
|
#pragma kernel VolumetricLightingClustered VolumetricLighting=VolumetricLightingClustered ENABLE_REPROJECTION=0 LIGHTLOOP_TILE_PASS USE_CLUSTERED_LIGHTLIST
|
|
#pragma kernel VolumetricLightingClusteredReproj VolumetricLighting=VolumetricLightingClusteredReproj ENABLE_REPROJECTION=1 LIGHTLOOP_TILE_PASS USE_CLUSTERED_LIGHTLIST
|
|
|
|
// #pragma enable_d3d11_debug_symbols
|
|
|
|
#include "../../../ShaderPass/ShaderPass.cs.hlsl"
|
|
#define SHADERPASS SHADERPASS_VOLUMETRIC_LIGHTING
|
|
|
|
#include "../../../ShaderConfig.cs.hlsl"
|
|
#if (SHADEROPTIONS_VOLUMETRIC_LIGHTING_PRESET == 1)
|
|
// E.g. for 1080p: (1920/8)x(1080/8)x(128) = 4,147,200 voxels
|
|
#define VBUFFER_TILE_SIZE 8
|
|
#define VBUFFER_SLICE_COUNT 64
|
|
#else
|
|
// E.g. for 1080p: (1920/4)x(1080/4)x(256) = 33,177,600 voxels
|
|
#define VBUFFER_TILE_SIZE 4
|
|
#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 16
|
|
#define GROUP_SIZE_2D (GROUP_SIZE_1D * GROUP_SIZE_1D)
|
|
|
|
#if (SHADEROPTIONS_VOLUMETRIC_LIGHTING_PRESET != 0) // Switch between the full and the empty shader
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Included headers
|
|
//--------------------------------------------------------------------------------------------------
|
|
|
|
#include "CoreRP/ShaderLibrary/Common.hlsl"
|
|
#include "CoreRP/ShaderLibrary/Filtering.hlsl"
|
|
#include "CoreRP/ShaderLibrary/VolumeRendering.hlsl"
|
|
#include "CoreRP/ShaderLibrary/SpaceFillingCurves.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"
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// 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
|
|
|
|
// TODO: avoid creating another Constant Buffer...
|
|
CBUFFER_START(UnityVolumetricLighting)
|
|
float4 _VBufferSampleOffset; // {x, y, z}, w = rendered frame count
|
|
float4x4 _VBufferCoordToViewDirWS; // Actually just 3x3, but Unity can only set 4x4
|
|
float _CornetteShanksConstant; // CornetteShanksPhasePartConstant(_GlobalAsymmetry)
|
|
CBUFFER_END
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Implementation
|
|
//--------------------------------------------------------------------------------------------------
|
|
|
|
struct DualRay
|
|
{
|
|
float3 originWS;
|
|
float3 strataDirWS; // Normalized, tile-stratified
|
|
float3 centerDirWS; // Normalized, tile-centered
|
|
float strataDirInvViewZ; // 1 / ViewSpace(strataDirWS).z
|
|
float twoDirRatioViewZ; // ViewSpace(strataDirWS).z / ViewSpace(centerDirWS).z
|
|
};
|
|
|
|
// Returns a point along the stratified direction.
|
|
float3 GetPointAtDistance(DualRay ray, float t)
|
|
{
|
|
return ray.originWS + t * ray.strataDirWS;
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
t *= ray.twoDirRatioViewZ; // Perform the Z-coordinate conversion
|
|
return ray.originWS + t * 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 asymmetry
|
|
#ifdef LIGHTLOOP_TILE_PASS
|
|
, uint clusterIndices[2], float clusterDepths[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;
|
|
EvaluateLight_Directional(context, posInput, light, unused, 0, L,
|
|
color, attenuation);
|
|
|
|
// Important:
|
|
// Ideally, all scattering calculations should use the stratified 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 asymmetry no longer benefits from temporal averaging,
|
|
// and any temporal instability of asymmetry causes causes visible jitter.
|
|
// In order to stabilize the image, we use the voxel center for all
|
|
// asymmetry-related calculations.
|
|
float cosTheta = dot(L, ray.centerDirWS);
|
|
float phase = CornetteShanksPhasePartVarying(asymmetry, cosTheta);
|
|
|
|
// Note: the 'weight' accounts for transmittance from 't0' to 't'.
|
|
float intensity = 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
|
|
|
|
#ifdef LIGHTLOOP_TILE_PASS
|
|
// Loop over 1 or 2 light clusters.
|
|
int cluster = 0;
|
|
do
|
|
{
|
|
float tMin = max(t0, ray.strataDirInvViewZ * clusterDepths[cluster]);
|
|
float tMax = t1;
|
|
|
|
if (cluster == 0 && (clusterIndices[0] != clusterIndices[1]))
|
|
{
|
|
tMax = min(t1, ray.strataDirInvViewZ * clusterDepths[1]);
|
|
}
|
|
#else
|
|
float tMin = t0;
|
|
float tMax = t1;
|
|
#endif // LIGHTLOOP_TILE_PASS
|
|
|
|
if (featureFlags & LIGHTFEATUREFLAGS_PUNCTUAL)
|
|
{
|
|
uint lightCount, lightStart;
|
|
|
|
#ifdef LIGHTLOOP_TILE_PASS
|
|
GetCountAndStartCluster(posInput.tileCoord, clusterIndices[cluster], LIGHTCATEGORY_PUNCTUAL,
|
|
lightStart, lightCount);
|
|
#else
|
|
lightCount = _PunctualLightCount;
|
|
lightStart = 0;
|
|
#endif // LIGHTLOOP_TILE_PASS
|
|
|
|
if (lightCount > 0)
|
|
{
|
|
LightData light = FetchLight(lightStart, 0);
|
|
|
|
uint i = 0, last = lightCount - 1;
|
|
|
|
// Box lights require special handling (see the next while loop).
|
|
while (i <= last && light.lightType != GPULIGHTTYPE_PROJECTOR_BOX)
|
|
{
|
|
float tEntr = tMin;
|
|
float tExit = tMax;
|
|
|
|
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.strataDirWS,
|
|
light.positionWS, light.forward,
|
|
coneAxisX, coneAxisY,
|
|
tMin, tMax, 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.strataDirWS,
|
|
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;
|
|
EvaluateLight_Punctual(context, posInput, light, unused, 0, L, lightToSample,
|
|
distances, color, attenuation);
|
|
|
|
// Important:
|
|
// Ideally, all scattering calculations should use the stratified 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 asymmetry no longer benefits from temporal averaging,
|
|
// and any temporal instability of asymmetry causes causes visible jitter.
|
|
// In order to stabilize the image, we use the voxel center for all
|
|
// asymmetry-related calculations.
|
|
float3 centerL = light.positionWS - centerWS;
|
|
float cosTheta = dot(centerL, ray.centerDirWS) * rsqrt(dot(centerL, centerL));
|
|
float phase = CornetteShanksPhasePartVarying(asymmetry, cosTheta);
|
|
|
|
float intensity = 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;
|
|
}
|
|
|
|
light = FetchLight(lightStart, min(++i, last));
|
|
}
|
|
|
|
while (i <= last) // GPULIGHTTYPE_PROJECTOR_BOX
|
|
{
|
|
light = FetchLight(lightStart, min(++i, last));
|
|
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.strataDirWS);
|
|
|
|
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, tMin, tMax, 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;
|
|
EvaluateLight_Punctual(context, posInput, light, unused, 0, L, lightToSample,
|
|
distances, color, attenuation);
|
|
|
|
// Important:
|
|
// Ideally, all scattering calculations should use the stratified 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 asymmetry no longer benefits from temporal averaging,
|
|
// and any temporal instability of asymmetry causes causes visible jitter.
|
|
// In order to stabilize the image, we use the voxel center for all
|
|
// asymmetry-related calculations.
|
|
float3 centerL = light.positionWS - centerWS;
|
|
float cosTheta = dot(centerL, ray.centerDirWS) * rsqrt(dot(centerL, centerL));
|
|
float phase = CornetteShanksPhasePartVarying(asymmetry, cosTheta);
|
|
|
|
// Note: the 'weight' accounts for transmittance from 'tEntr' to 't'.
|
|
float intensity = 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 LIGHTLOOP_TILE_PASS
|
|
cluster++;
|
|
// Check whether the voxel is completely inside the light cluster.
|
|
} while ((cluster < 2) && (clusterIndices[0] != clusterIndices[1]));
|
|
#endif // LIGHTLOOP_TILE_PASS
|
|
|
|
return lighting;
|
|
}
|
|
|
|
// Computes the in-scattered radiance along the ray.
|
|
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
|
|
|
|
// 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.
|
|
float3 probeInScatteredRadiance = SampleSH9(_AmbientProbeCoeffs, ray.centerDirWS);
|
|
|
|
float3 totalRadiance = 0;
|
|
float opticalDepth = 0;
|
|
|
|
#ifdef LIGHTLOOP_TILE_PASS
|
|
// Our voxel is not necessarily completely inside a single light cluster.
|
|
// Note that Z-binning can solve this problem, as we can iterate over all Z-bins
|
|
// to compute min/max light indices, and then use this range for the entire slice.
|
|
uint clusterIndices[2];
|
|
float clusterDepths[2];
|
|
clusterIndices[0] = GetLightClusterIndex(posInput.tileCoord, z0);
|
|
clusterDepths[0] = GetLightClusterMinLinearDepth(posInput.tileCoord, clusterIndices[0]);
|
|
#endif // LIGHTLOOP_TILE_PASS
|
|
|
|
#if defined(SHADER_API_METAL)
|
|
[fastopt]
|
|
for (uint slice = 0; slice < VBUFFER_SLICE_COUNT; slice++)
|
|
#else
|
|
uint sliceCountHack = max(VBUFFER_SLICE_COUNT, (uint)_VBufferDepthEncodingParams.w); // Prevent unrolling...
|
|
|
|
// TODO: replace 'sliceCountHack' with VBUFFER_SLICE_COUNT when the shader compiler bug is fixed.
|
|
for (uint slice = 0; slice < sliceCountHack; slice++)
|
|
#endif
|
|
{
|
|
float e1 = slice * de + de; // (slice + 1) / sliceCount
|
|
#if defined(SHADER_API_METAL)
|
|
float z1 = DecodeLogarithmicDepth(e1, _VBufferDepthDecodingParams);
|
|
#else
|
|
float z1 = DecodeLogarithmicDepthGeneralized(e1, _VBufferDepthDecodingParams);
|
|
#endif
|
|
float t1 = ray.strataDirInvViewZ * z1; // Convert view space Z to distance along the stratified ray
|
|
float dt = t1 - t0;
|
|
|
|
#ifdef LIGHTLOOP_TILE_PASS
|
|
clusterIndices[1] = GetLightClusterIndex(posInput.tileCoord, z1);
|
|
clusterDepths[1] = GetLightClusterMinLinearDepth(posInput.tileCoord, clusterIndices[1]);
|
|
#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 tc = t0 + 0.5 * dt;
|
|
float3 centerWS = GetCenterAtDistance(ray, tc);
|
|
|
|
// Sample the participating medium at 'tc' (or 'centerWS').
|
|
// We consider it to be constant along the interval [t0, t1] (within the voxel).
|
|
// TODO: piecewise linear.
|
|
float3 scattering = _GlobalScattering;
|
|
float extinction = max(_GlobalExtinction, FLT_MIN); // Avoid NaNs
|
|
float asymmetry = _GlobalAsymmetry;
|
|
|
|
// TODO: define a function ComputeGlobalFogCoefficients(float3 centerWS),
|
|
// which allows procedural definition of extinction and scattering.
|
|
|
|
#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, asymmetry
|
|
#ifdef LIGHTLOOP_TILE_PASS
|
|
, clusterIndices, clusterDepths);
|
|
#else
|
|
);
|
|
#endif
|
|
#if ENABLE_REPROJECTION
|
|
// Reproject the history at 'centerWS'.
|
|
float2 reprojPosNDC = ComputeNormalizedDeviceCoordinates(centerWS, _PrevViewProjMatrix);
|
|
float reprojZ = mul(_PrevViewProjMatrix, float4(centerWS, 1)).w;
|
|
float4 reprojValue = SampleVBuffer(TEXTURE3D_PARAM(_VBufferLightingHistory, s_trilinear_clamp_sampler),
|
|
false, reprojPosNDC, reprojZ,
|
|
_VBufferSliceCount.xy,
|
|
_VBufferDepthEncodingParams,
|
|
_VBufferDepthDecodingParams);
|
|
|
|
// 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.
|
|
// 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).
|
|
// Important: reprojection must be performed without the phase function! Otherwise,
|
|
// some kind of per-light angle correction is required, which is intractable in practice.
|
|
bool reprojSuccess = reprojValue.a != 0;
|
|
float blendFactor = reprojSuccess ? historyWeight : 0;
|
|
float reprojRcpLen = reprojSuccess ? rcp(reprojValue.a) : 0;
|
|
float lengthScale = dt * reprojRcpLen;
|
|
float3 reprojRadiance = reprojValue.rgb;
|
|
float3 blendedRadiance = (1 - blendFactor) * lighting.radianceNoPhase + blendFactor * lengthScale * 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.
|
|
// to the history buffer. This will cause them to alias, but it is the only way
|
|
// to prevent ghosting.
|
|
_VBufferLightingFeedback[uint3(posInput.positionSS, slice)] = float4(blendedRadiance, dt);
|
|
|
|
#if SUPPORT_ASYMMETRY
|
|
// 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;
|
|
#endif // SUPPORT_ASYMMETRY
|
|
|
|
#else // ENABLE_REPROJECTION
|
|
|
|
#if SUPPORT_ASYMMETRY
|
|
float3 blendedRadiance = lighting.radianceComplete;
|
|
#else // SUPPORT_ASYMMETRY
|
|
float3 blendedRadiance = lighting.radianceNoPhase;
|
|
#endif // SUPPORT_ASYMMETRY
|
|
|
|
#endif // ENABLE_REPROJECTION
|
|
|
|
// Compute the transmittance from the camera to 't0'.
|
|
float transmittance = Transmittance(opticalDepth);
|
|
|
|
#if SUPPORT_ASYMMETRY
|
|
float phase = _CornetteShanksConstant;
|
|
#else
|
|
float phase = IsotropicPhaseFunction();
|
|
#endif
|
|
|
|
// 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.
|
|
_VBufferLightingIntegral[uint3(posInput.positionSS, slice)] = float4(totalRadiance, opticalDepth);
|
|
|
|
// Compute the optical depth up to the end of the interval.
|
|
opticalDepth += 0.5 * extinction * dt;
|
|
|
|
t0 = t1;
|
|
|
|
#ifdef LIGHTLOOP_TILE_PASS
|
|
clusterIndices[0] = clusterIndices[1];
|
|
clusterDepths[0] = clusterDepths[1];
|
|
#endif // LIGHTLOOP_TILE_PASS
|
|
}
|
|
}
|
|
|
|
[numthreads(GROUP_SIZE_2D, 1, 1)]
|
|
void VolumetricLighting(uint2 groupId : SV_GroupID,
|
|
uint groupThreadId : SV_GroupThreadID)
|
|
{
|
|
// Perform compile-time checks.
|
|
if (!IsPower2(VBUFFER_TILE_SIZE) || !IsPower2(TILE_SIZE_CLUSTERED)) return;
|
|
|
|
// Note: any factor of 64 is a suitable wave size for our algorithm.
|
|
uint waveIndex = WaveReadFirstLane(groupThreadId / 64);
|
|
uint laneIndex = groupThreadId % 64;
|
|
uint quadIndex = laneIndex / 4;
|
|
|
|
// Arrange threads in the Morton order to optimally match the memory layout of GCN tiles.
|
|
uint2 groupCoord = DecodeMorton2D(groupThreadId);
|
|
uint2 groupOffset = groupId * GROUP_SIZE_1D;
|
|
uint2 voxelCoord = groupOffset + groupCoord;
|
|
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 = WaveReadFirstLane(tileCoord);
|
|
}
|
|
|
|
UNITY_BRANCH if (voxelCoord.x >= (uint)_VBufferResolution.x ||
|
|
voxelCoord.y >= (uint)_VBufferResolution.y)
|
|
{
|
|
return;
|
|
}
|
|
|
|
float2 centerCoord = voxelCoord + 0.5;
|
|
#if ENABLE_REPROJECTION
|
|
float2 strataCoord = centerCoord + _VBufferSampleOffset.xy;
|
|
#else
|
|
float2 strataCoord = centerCoord;
|
|
#endif
|
|
|
|
// 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 (tile-centered) 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;
|
|
|
|
DualRay ray;
|
|
|
|
ray.originWS = GetCurrentViewPosition();
|
|
ray.strataDirWS = strataDirWS * strataDirLenRcp; // Normalize
|
|
ray.centerDirWS = centerDirWS * centerDirLenRcp; // Normalize
|
|
ray.strataDirInvViewZ = strataDirLen; // View space Z
|
|
ray.twoDirRatioViewZ = centerDirLen * strataDirLenRcp; // View space Z ratio
|
|
|
|
// TODO
|
|
LightLoopContext context;
|
|
context.shadowContext = InitShadowContext();
|
|
uint featureFlags = 0xFFFFFFFF;
|
|
|
|
PositionInputs posInput = GetPositionInput(voxelCoord, _VBufferResolution.zw, tileCoord);
|
|
|
|
FillVolumetricLightingBuffer(context, featureFlags, posInput, ray);
|
|
}
|
|
|
|
#else
|
|
|
|
[numthreads(GROUP_SIZE_2D, 1, 1)]
|
|
void VolumetricLighting(uint2 groupId : SV_GroupID,
|
|
uint groupThreadId : SV_GroupThreadID)
|
|
{
|
|
// Reduce compile times if the feature is disabled.
|
|
}
|
|
|
|
#endif // SHADEROPTIONS_VOLUMETRIC_LIGHTING_PRESET
|