浏览代码

Implement the soft voxelization hack

/main
Evgenii Golubev 7 年前
当前提交
68e4eace
共有 3 个文件被更改,包括 104 次插入107 次删除
  1. 165
      ScriptableRenderPipeline/HDRenderPipeline/HDRP/Lighting/Volumetrics/VolumeVoxelization.compute
  2. 45
      ScriptableRenderPipeline/HDRenderPipeline/HDRP/Lighting/Volumetrics/VolumetricLighting.compute
  3. 1
      ScriptableRenderPipeline/HDRenderPipeline/HDRP/Lighting/Volumetrics/VolumetricLighting.cs

165
ScriptableRenderPipeline/HDRenderPipeline/HDRP/Lighting/Volumetrics/VolumeVoxelization.compute


#define VBUFFER_SLICE_COUNT 128
#endif
#define SOFT_VOXELIZATION
#define GROUP_SIZE_1D 8
//--------------------------------------------------------------------------------------------------

//--------------------------------------------------------------------------------------------------
void FillVolumetricDensityBuffer(uint2 voxelCoord, float3 rayOriginWS, float3 rayUnDirWS,
float4 planeEquationUp, float4 planeEquationRight,
float3 planeNormalFwd, float faceExtent)
float3 voxelAxisRight, float3 voxelAxisUp, float3 voxelAxisForward)
{
float n = _VBufferDepthDecodingParams.x + _VBufferDepthDecodingParams.z;
float z0 = n; // Start the computation from the near plane

float z1 = DecodeLogarithmicDepthGeneralized(e1, _VBufferDepthDecodingParams);
#endif
float z = z0 + 0.5 * (z1 - z0);
float halfDZ = 0.5 * (z1 - z0);
float z = z0 + halfDZ;
float4 planeEquationForward = float4(planeNormalFwd, dot(-planeNormalFwd, voxelCenterWS));
float4 planes[3] = { planeEquationRight, planeEquationUp, planeEquationForward };
// If the box overlaps all 3 planes, it overlaps the center of the voxel.
// Otherwise, we have to determine partial coverage.
// We approximate the voxel with a parallelepiped with a square front face.
float voxelExtents[3] = { faceExtent * z, faceExtent * z, 0.5 * (z1 - z0) };
_VBufferDensity[uint3(voxelCoord, slice)] = 0;
float3 voxelScattering = _GlobalScattering;
float voxelExtinction = _GlobalExtinction;

float3 obb_forward = cross(obb.up, obb.right);
float3x3 obbFrame = float3x3(obb.right, obb.up, cross(obb.up, obb.right));
float3 obbExtents = float3(obb.extentX, obb.extentY, obb.extentZ);
float3 voxelCenterBoxRelative = voxelCenterWS - obb.center;
float3 voxelCenterBoxLocal = float3(dot(obb.right, voxelCenterBoxRelative),
dot(obb.up, voxelCenterBoxRelative),
dot(obb_forward, voxelCenterBoxRelative));
float3 voxelCenterBS = mul(voxelCenterWS - obb.center, transpose(obbFrame));
float3 voxelCenterUV = voxelCenterBS / obbExtents;
#ifdef SOFT_VOXELIZATION
// We need to determine which is the face closest to 'voxelCenterBS'.
// TODO: use v_cubeid_f32.
float minFaceDist = abs(obbExtents.x - abs(voxelCenterBS.x));
uint axisIndex; float faceDist;
faceDist = abs(obbExtents.y - abs(voxelCenterBS.y));
axisIndex = (faceDist < minFaceDist) ? 1 : 0;
minFaceDist = min(faceDist, minFaceDist);
faceDist = abs(obbExtents.z - abs(voxelCenterBS.z));
axisIndex = (faceDist < minFaceDist) ? 2 : axisIndex;
float3 N = float3(axisIndex == 0 ? 1 : 0, axisIndex == 1 ? 1 : 0, axisIndex == 2 ? 1 : 0);
// We have determined the normal of the closest face.
// We now have to construct the diagonal of the voxel with the longest extent along this normal.
float3 minDiagPointBS, maxDiagPointBS;
// Compute the point inside the box closest to the voxel center.
float3 closestPointBoxLocal = float3(CopySign(min(obb.extentX, abs(voxelCenterBoxLocal.x)), voxelCenterBoxLocal.x),
CopySign(min(obb.extentY, abs(voxelCenterBoxLocal.y)), voxelCenterBoxLocal.y),
CopySign(min(obb.extentZ, abs(voxelCenterBoxLocal.z)), voxelCenterBoxLocal.z));
float3 voxelAxisRightBS = mul(voxelAxisRight, transpose(obbFrame));
float3 voxelAxisUpBS = mul(voxelAxisUp, transpose(obbFrame));
float3 voxelAxisForwardBS = mul(voxelAxisForward, transpose(obbFrame));
// Convert it to world-space coordinates.
float3 closestPointWS = obb.center + obb.right * closestPointBoxLocal.x
+ obb.up * closestPointBoxLocal.y
+ obb_forward * closestPointBoxLocal.z;
// Start at the center of the voxel.
minDiagPointBS = maxDiagPointBS = voxelCenterBS;
// The farthest point is on the opposite side of the box.
float3 farthestPointWS = 2 * obb.center - closestPointWS;
bool normalFwd = dot(voxelAxisForwardBS, N) >= 0;
float mulForward = normalFwd ? halfDZ : -halfDZ;
float mulMin = normalFwd ? z0 : z1;
float mulMax = normalFwd ? z1 : z0;
// Compute the fractional overlap between the voxel and the box.
float overlapFraction = 1;
minDiagPointBS -= mulForward * voxelAxisForwardBS;
maxDiagPointBS += mulForward * voxelAxisForwardBS;
for (uint p = 0; p < 3; p++)
{
float3 N = planes[p].xyz;
float d = planes[p].w;
float mulUp = dot(voxelAxisUpBS, N) >= 0 ? 1 : -1;
// Compute the signed distance from both points to the plane.
// Positive distance -> point in front of the plane.
// Negative distance -> point behind the plane.
float signedDistanceClosest = dot(float4(closestPointWS, 1), planes[p]);
float signedDistanceFarthest = dot(float4(farthestPointWS, 1), planes[p]);
minDiagPointBS -= (mulMin * mulUp) * voxelAxisUpBS;
maxDiagPointBS += (mulMax * mulUp) * voxelAxisUpBS;
bool overlap = abs(signedDistanceClosest) <= voxelExtents[i];
float mulRight = dot(voxelAxisRightBS, N) >= 0 ? 1 : -1;
overlapFraction *= overlap ? 1 : 0;
minDiagPointBS -= (mulMin * mulRight) * voxelAxisRightBS;
maxDiagPointBS += (mulMax * mulRight) * voxelAxisRightBS;
// // Max projection of the half-diagonal onto the normal (always positive).
// float maxHalfDiagProj = obb.extentX * abs(dot(N, obb.right))
// + obb.extentY * abs(dot(N, obb.up))
// + obb.extentZ * abs(dot(N, obb_forward));
// We want to determine the fractional overlap of the diagonal and the box.
float3 diagOriginBS = minDiagPointBS;
float3 diagUnDirBS = maxDiagPointBS - minDiagPointBS;
// float centerToPlaneDist = dot(N, obb.center) + d;
float tEntr, tExit;
// // Compute min/max distances from the plane to the box.
// float minBoxToPlaneDist = abs(centerToPlaneDist) - maxHalfDiagProj;
// float maxBoxToPlaneDist = abs(centerToPlaneDist) + maxHalfDiagProj;
IntersectRayAABB(diagOriginBS, diagUnDirBS,
-obbExtents, obbExtents,
0, 1,
tEntr, tExit);
// // Check whether the plane overlaps the box.
// bool overlap = minBoxToPlaneDist <= 0;
float overlapFraction = tExit - tEntr;
// float dMin = minBoxToPlaneDist;
// float dMax = maxBoxToPlaneDist;
// float vExt = voxelExtents[p];
// float iExt = rcp(vExt);
#else // SOFT_VOXELIZATION
// // Simplify:
// // if (overlap)
// // overlapFraction *= saturate((min(dMax, vExt) + min(-dMin, vExt)) / (2 * vExt));
// // else
// // overlapFraction *= saturate((min(dMax, vExt) - min( dMin, vExt)) / (2 * vExt));
bool overlap = abs(voxelCenterUV.x) <= 1 &&
abs(voxelCenterUV.y) <= 1 &&
abs(voxelCenterUV.z) <= 1;
// float a = min(1, dMax * iExt);
// float b = min(1, abs(dMin) * iExt);
float overlapFraction = overlap ? 1 : 0;
// overlapFraction *= saturate(0.5 * (a + (overlap ? b : -b)));
// overlapFraction *= overlap ? 1 : 0;
}
#endif // SOFT_VOXELIZATION
if (overlapFraction > 0)
{

_VBufferDensity[uint3(voxelCoord, slice)] = float4(voxelExtinction, voxelScattering);
_VBufferDensity[uint3(voxelCoord, slice)] = float4(voxelScattering, voxelExtinction);
z0 = z1;
}

return;
}
// Perform semi-conservative solid voxelization with partial coverage.
// See "A Topological Approach to Voxelization" by Samuli Laine, 5.2.1.
// The intersection target is rather efficient (3 planes), and, as Samuli notes,
// can work for inputs other than 1D primitives.
// Reminder: our voxel is a skewed pyramid frustum.
// Reminder: our voxel is a skewed pyramid frustum with square front and back faces.
// Compute two orthogonal directions.
// Compute 3x orthogonal directions.
// Compute 2x ray directions s.t. its ViewSpace(rayDirWS).z = 1.
// Compute 3x ray directions s.t. its ViewSpace(rayDirWS).z = 1.
// Construct 3x plane normals.
float3 planeNormalFwd = GetViewForwardDir();
float3 planeNormalUp = normalize(cross(centerDirWS, leftDirWS));
float3 planeNormalRight = normalize(cross(centerDirWS, upDirWS));
// Compute the axes of the voxel.
float3 voxelAxisForward = centerDirWS;
float3 voxelAxisUp = 0.5 * (upDirWS - centerDirWS);
float3 voxelAxisRight = 0.5 * (centerDirWS - leftDirWS);
// Compose 2x plane equations (they pass through the camera).
// The 3rd plane equation depends on the slice, so we'll have to update it inside the loop.
float3 cameraPositionWS = GetCurrentViewPosition();
float4 planeEquationUp = float4(planeNormalUp, dot(-planeNormalUp, cameraPositionWS));
float4 planeEquationRight = float4(planeNormalRight, dot(-planeNormalRight, cameraPositionWS));
// We approximate the voxel with a parallelepiped with a square front face.
// Compute the extents (half-dimensions) of the front face on the vs_Z=1 plane.
// TODO: directly compute the inverse.
// TODO: precompute and load this value from the constant buffer. It's a constant!
float faceExtent = 0.5 * distance(leftDirWS, centerDirWS);
FillVolumetricDensityBuffer(voxelCoord, cameraPositionWS, centerDirWS,
planeEquationUp, planeEquationRight,
planeNormalFwd, faceExtent);
FillVolumetricDensityBuffer(voxelCoord, GetCurrentViewPosition(), centerDirWS,
voxelAxisRight, voxelAxisUp, voxelAxisForward);
}

45
ScriptableRenderPipeline/HDRenderPipeline/HDRP/Lighting/Volumetrics/VolumetricLighting.compute


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)

for (uint slice = 0; slice < sliceCountHack; slice++)
#endif
{
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().

// 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
// TODO: sample the accurate extinction values for lights.
float3 scattering = LOAD_TEXTURE3D(_VBufferDensity, voxelCoord).rgb;
float extinction = LOAD_TEXTURE3D(_VBufferDensity, voxelCoord).a;
float asymmetry = _GlobalAsymmetry;
// TODO: define a function ComputeGlobalFogCoefficients(float3 centerWS),

);
#endif
#if ENABLE_REPROJECTION
float4 feedback = 0;
// Reproject the history at 'centerWS'.
float2 reprojPosNDC = ComputeNormalizedDeviceCoordinates(centerWS, _PrevViewProjMatrix);
float reprojZ = mul(_PrevViewProjMatrix, float4(centerWS, 1)).w;

// 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 (extinction > 0)
{
feedback = float4(blendedRadiance, dt);
}
_VBufferLightingFeedback[voxelCoord] = feedback;
#if SUPPORT_ASYMMETRY
// Extrapolate the influence of the phase function on the results of the current frame.

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);
float4 integral = float4(totalRadiance, opticalDepth);
totalRadiance += transmittance * scattering * (phase * blendedRadiance + probeRadiance);
if (extinction > 0)
{
// 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);
// Compute the optical depth up to the center of the interval.
opticalDepth += 0.5 * extinction * dt;
totalRadiance += transmittance * scattering * (phase * blendedRadiance + probeRadiance);
// Store the voxel data.
_VBufferLightingIntegral[uint3(posInput.positionSS, slice)] = float4(totalRadiance, opticalDepth);
// Compute the optical depth up to the center of the interval.
opticalDepth += 0.5 * extinction * dt;
// Compute the optical depth up to the end of the interval.
opticalDepth += 0.5 * extinction * dt;
integral = float4(totalRadiance, opticalDepth);
// Compute the optical depth up to the end of the interval.
opticalDepth += 0.5 * extinction * dt;
}
// Store the voxel data.
_VBufferLightingIntegral[voxelCoord] = integral;
t0 = t1;

1
ScriptableRenderPipeline/HDRenderPipeline/HDRP/Lighting/Volumetrics/VolumetricLighting.cs


cmd.SetComputeMatrixParam( m_VolumetricLightingCS, HDShaderIDs._VBufferCoordToViewDirWS, transform);
cmd.SetComputeVectorParam( m_VolumetricLightingCS, HDShaderIDs._VBufferSampleOffset, offset);
cmd.SetComputeFloatParam( m_VolumetricLightingCS, HDShaderIDs._CornetteShanksConstant, CornetteShanksPhasePartConstant(asymmetry));
cmd.SetComputeTextureParam(m_VolumetricLightingCS, kernel, HDShaderIDs._VBufferDensity, vBuffer.GetDensityBuffer()); // Read
cmd.SetComputeTextureParam(m_VolumetricLightingCS, kernel, HDShaderIDs._VBufferLightingIntegral, vBuffer.GetLightingIntegralBuffer()); // Write
if (enableReprojection)
{

正在加载...
取消
保存