浏览代码

Support asymmetry with reprojection

/main
Evgenii Golubev 7 年前
当前提交
c1c33a32
共有 3 个文件被更改,包括 143 次插入93 次删除
  1. 2
      ScriptableRenderPipeline/Core/CoreRP/ShaderLibrary/Common.hlsl
  2. 9
      ScriptableRenderPipeline/Core/CoreRP/ShaderLibrary/VolumeRendering.hlsl
  3. 225
      ScriptableRenderPipeline/HDRenderPipeline/HDRP/Lighting/Volumetrics/Resources/VolumetricLighting.compute

2
ScriptableRenderPipeline/Core/CoreRP/ShaderLibrary/Common.hlsl


// 'z' is the view-space Z position (linear depth).
// saturate() the output of the function to clamp them to the [0, 1] range.
// encodingParams = { n, log2(f/n), 1/n, 1/log2(f/n) }
// TODO: plot and modify the distribution to be a little more linear.
float EncodeLogarithmicDepth(float z, float4 encodingParams)
{
return log2(max(0, z * encodingParams.z)) * encodingParams.w;

// saturate(d) to clamp the output of the function to the [n, f] range.
// encodingParams = { n, log2(f/n), 1/n, 1/log2(f/n) }
// TODO: plot and modify the distribution to be a little more linear.
float DecodeLogarithmicDepth(float d, float4 encodingParams)
{
return encodingParams.x * exp2(d * encodingParams.y);

9
ScriptableRenderPipeline/Core/CoreRP/ShaderLibrary/VolumeRendering.hlsl


real HenyeyGreensteinPhasePartVarying(real asymmetry, real cosTheta)
{
real g = asymmetry;
real f = rsqrt(1 + g * g - 2 * g * cosTheta); // x^(-1/2)
return pow(abs(1 + g * g - 2 * g * cosTheta), -1.5);
return f * f * f; // x^(-3/2)
}
real HenyeyGreensteinPhaseFunction(real asymmetry, real cosTheta)

real CornetteShanksPhasePartVarying(real asymmetry, real cosTheta)
{
real g = asymmetry;
real f = rsqrt(1 + g * g - 2 * g * cosTheta); // x^(-1/2)
real h = (1 + cosTheta * cosTheta);
return (1 + cosTheta * cosTheta) * pow(abs(1 + g * g - 2 * g * cosTheta), -1.5);
return h * (f * f * f); // h * f^(-3/2)
}
// A better approximation of the Mie phase function.

real x = 1 - exp(-extinction * intervalLength);
// Avoid division by 0.
real rcpExt = extinction != 0 ? rcp(extinction) : 0;
real rcpExt = (extinction != 0) ? rcp(extinction) : 0;
weight = x * rcpExt;
offset = -log(1 - rndVal * x) * rcpExt;

225
ScriptableRenderPipeline/HDRenderPipeline/HDRP/Lighting/Volumetrics/Resources/VolumetricLighting.compute


#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 debug
#define DEBUG_REPROJECTION 0
// #pragma enable_d3d11_debug_symbols
#include "../../../ShaderPass/ShaderPass.cs.hlsl"
#define SHADERPASS SHADERPASS_VOLUMETRIC_LIGHTING

#define VBUFFER_TILE_SIZE 4
#define VBUFFER_SLICE_COUNT 256
#endif
#define SUPPORT_ASYMMETRY 1 // Support asymmetric phase functions
#define GROUP_SIZE_1D 16
#define GROUP_SIZE_2D (GROUP_SIZE_1D * GROUP_SIZE_1D)

// Implementation
//--------------------------------------------------------------------------------------------------
#define HG 0
struct Ray
struct DualRay
float3 directionWS; // Normalized, stratified
float ratioLenToZ; // 1 / ViewSpaceZ
float3 centerDirWS; // Not normalized, centered
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 GetPointAtDistance(Ray ray, float t)
// Returns a point along the stratified direction.
float3 GetPointAtDistance(DualRay ray, float t)
return ray.originWS + t * ray.directionWS;
return ray.originWS + t * ray.strataDirWS;
float3 GetCenterAtDistance(Ray ray, float t)
// 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
struct VoxelLighting
{
float3 radianceComplete;
float3 radianceNoPhase;
};
float3 EvaluateVoxelLighting(LightLoopContext context, uint featureFlags, PositionInputs posInput,
Ray 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 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
float3 voxelRadiance = 0;
VoxelLighting lighting;
ZERO_INITIALIZE(VoxelLighting, lighting);
BakeLightingData unused; // Unused for now, so define once

EvaluateLight_Directional(context, posInput, light, unused, 0, L,
color, attenuation);
float cosTheta = dot(L, ray.directionWS);
#if HG
float phase = HenyeyGreensteinPhasePartVarying(asymmetry, cosTheta);
#else
// 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);
#endif
float intensity = attenuation * (phase * weight);
float intensity = attenuation * weight;
voxelRadiance += intensity * color;
lighting.radianceNoPhase += intensity * color;
lighting.radianceComplete += phase * intensity * color;
}
}

do
{
float tMin = max(t0, ray.ratioLenToZ * clusterDepths[cluster]);
float tMin = max(t0, ray.strataDirInvViewZ * clusterDepths[cluster]);
tMax = min(t1, ray.ratioLenToZ * clusterDepths[1]);
tMax = min(t1, ray.strataDirInvViewZ * clusterDepths[1]);
}
#else
float tMin = t0;

float3 coneAxisX = lenMul * light.right;
float3 coneAxisY = lenMul * light.up;
sampleLight = IntersectRayCone(ray.originWS, ray.directionWS,
sampleLight = IntersectRayCone(ray.originWS, ray.strataDirWS,
light.positionWS, light.forward,
coneAxisX, coneAxisY,
tMin, tMax, tEntr, tExit);

float t, distSq, rcpPdf;
ImportanceSamplePunctualLight(rndVal, light.positionWS,
ray.originWS, ray.directionWS,
ray.originWS, ray.strataDirWS,
tEntr, tExit, t, distSq, rcpPdf,
hackMinDistSq);

EvaluateLight_Punctual(context, posInput, light, unused, 0, L, lightToSample,
distances, color, attenuation);
float cosTheta = dot(L, ray.directionWS);
#if HG
float phase = HenyeyGreensteinPhasePartVarying(asymmetry, cosTheta);
#else
float phase = CornetteShanksPhasePartVarying(asymmetry, cosTheta);
#endif
// 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 * (phase * rcpPdf);
float intensity = attenuation * rcpPdf;
voxelRadiance += color * intensity;
lighting.radianceNoPhase += intensity * color;
lighting.radianceComplete += phase * intensity * color;
}
light = FetchLight(lightStart, min(++i, last));

float3x3 rotMat = float3x3(light.right, light.up, light.forward);
float3 o = mul(rotMat, ray.originWS - light.positionWS);
float3 d = mul(rotMat, ray.directionWS);
float3 d = mul(rotMat, ray.strataDirWS);
float range = light.size.x;
float3 boxPt0 = float3(-1, -1, 0);

EvaluateLight_Punctual(context, posInput, light, unused, 0, L, lightToSample,
distances, color, attenuation);
float cosTheta = dot(L, ray.directionWS);
#if HG
float phase = HenyeyGreensteinPhasePartVarying(asymmetry, cosTheta);
#else
float phase = CornetteShanksPhasePartVarying(asymmetry, cosTheta);
#endif
// 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 * (phase * weight);
float intensity = attenuation * weight;
voxelRadiance += intensity * color;
lighting.radianceNoPhase += intensity * color;
lighting.radianceComplete += phase * intensity * color;
}
}
}

} while ((cluster < 2) && (clusterIndices[0] != clusterIndices[1]));
#endif // LIGHTLOOP_TILE_PASS
return voxelRadiance;
return lighting;
PositionInputs posInput, Ray ray)
PositionInputs posInput, DualRay ray)
float z0 = _VBufferDepthEncodingParams.x; // Start integration from the near plane
float t0 = ray.ratioLenToZ * z0;
float de = rcp(VBUFFER_SLICE_COUNT); // Log-encoded distance between slices
float z0 = _VBufferDepthEncodingParams.x; // 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
float3 totalRadiance = 0;
float opticalDepth = 0;

{
float e1 = slice * de + de; // (slice + 1) / sliceCount
float z1 = DecodeLogarithmicDepth(e1, _VBufferDepthEncodingParams);
float t1 = ray.ratioLenToZ * z1;
float t1 = ray.strataDirInvViewZ * z1; // Convert view space Z to distance along the stratified ray
float dt = t1 - t0;
#ifdef LIGHTLOOP_TILE_PASS

// 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 and reprojection.
// We will use it for participating media sampling, asymmetric scattering and reprojection.
float tc = t0 + 0.5 * dt;
float3 centerWS = GetCenterAtDistance(ray, tc);

float rndVal = 0.5;
#endif
float3 voxelRadiance = EvaluateVoxelLighting(context, featureFlags, posInput,
ray, t0, t1, dt, rndVal, extinction, asymmetry
#ifdef LIGHTLOOP_TILE_PASS
, clusterIndices, clusterDepths);
#else
);
#endif
VoxelLighting lighting = EvaluateVoxelLighting(context, featureFlags, posInput, centerWS,
ray, t0, t1, dt, rndVal, extinction, asymmetry
#ifdef LIGHTLOOP_TILE_PASS
, clusterIndices, clusterDepths);
#else
);
#endif
#if (SUPPORT_ASYMMETRY == 0)
lighting.radianceComplete = lighting.radianceNoPhase;
#endif
#if ENABLE_REPROJECTION
// Reproject the history at 'centerWS'.

// 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.
float3 blendedRadiance = (1 - blendFactor) * voxelRadiance + blendFactor * lengthScale * reprojRadiance;
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)

_VBufferLightingFeedback[uint3(posInput.positionSS, slice)] = float4(blendedRadiance, dt);
// Extrapolate the influence of the phase function on the results of the current frame.
// TODO: how to ensure we do not divide by 0?
float3 phaseCurrFrame = lighting.radianceComplete / lighting.radianceNoPhase;
blendedRadiance *= phaseCurrFrame;
float3 blendedRadiance = voxelRadiance;
#endif
#if DEBUG_REPROJECTION
if (distance(voxelRadiance, reprojValue.rgb) > 0.1) blendedRadiance = float3(1000, 0, 0);
#endif
float3 blendedRadiance = lighting.radianceComplete;
#endif // ENABLE_REPROJECTION
#if HG
float phase = HenyeyGreensteinPhasePartConstant(asymmetry);
#if SUPPORT_ASYMMETRY
float phase = CornetteShanksPhasePartConstant(asymmetry);
float phase = CornetteShanksPhasePartConstant(asymmetry);
float phase = IsotropicPhaseFunction();
#endif
// Integral{a, b}{Transmittance(0, t) * L_s(t) dt} = Transmittance(0, a) * Integral{a, b}{Transmittance(0, t - a) * L_s(t) dt}.

float2 centerCoord = voxelCoord + 0.5;
#if ENABLE_REPROJECTION
float2 sampleCoord = centerCoord + _VBufferSampleOffset.xy;
float2 strataCoord = centerCoord + _VBufferSampleOffset.xy;
float2 sampleCoord = centerCoord;
float2 strataCoord = centerCoord;
// Compute the (stratified) ray direction s.t. its ViewSpaceZ = 1.
float3 rayDir = mul(-float3(sampleCoord, 1), (float3x3)_VBufferCoordToViewDirWS);
float lenSq = dot(rayDir, rayDir);
float lenRcp = rsqrt(lenSq);
float len = lenSq * lenRcp;
// 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;
#if ENABLE_REPROJECTION
// Compute the ray direction which passes through the center of the voxel s.t. its ViewSpaceZ = 1.
float3 rayCenterDir = mul(-float3(centerCoord, 1), (float3x3)_VBufferCoordToViewDirWS);
#else
float3 rayCenterDir = rayDir;
#endif
DualRay ray;
Ray ray;
ray.originWS = GetCurrentViewPosition();
ray.ratioLenToZ = len;
ray.directionWS = rayDir * lenRcp;
ray.centerDirWS = rayCenterDir * lenRcp;
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;

正在加载...
取消
保存