您最多选择25个主题 主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 
 

324 行
13 KiB

//--------------------------------------------------------------------------------------------------
// Definitions
//--------------------------------------------------------------------------------------------------
#pragma kernel VolumetricLightingAllLights VolumetricLighting=VolumetricLightingAllLights LIGHTLOOP_SINGLE_PASS
#pragma kernel VolumetricLightingClustered VolumetricLighting=VolumetricLightingClustered LIGHTLOOP_TILE_PASS USE_CLUSTERED_LIGHTLIST
#pragma enable_d3d11_debug_symbols
#include "../../../ShaderPass/ShaderPass.cs.hlsl"
#define SHADERPASS SHADERPASS_VOLUMETRIC_LIGHTING
#define GROUP_SIZE_1D 16
#define GROUP_SIZE_2D (GROUP_SIZE_1D * GROUP_SIZE_1D)
//--------------------------------------------------------------------------------------------------
// Included headers
//--------------------------------------------------------------------------------------------------
#include "../../../../Core/ShaderLibrary/Common.hlsl"
#include "../../../../Core/ShaderLibrary/SpaceFillingCurves.hlsl"
#include "../../../../Core/ShaderLibrary/VolumeRendering.hlsl"
#include "../VolumetricLighting.cs.hlsl"
#define UNITY_MATERIAL_LIT // Need to be defined before including Material.hlsl
#include "../../../ShaderVariables.hlsl"
#include "../../../Lighting/Lighting.hlsl" // This includes Material.hlsl
//--------------------------------------------------------------------------------------------------
// Inputs & outputs
//--------------------------------------------------------------------------------------------------
RW_TEXTURE3D(float4, _VBufferLighting); // RGB = radiance, A = optical depth
TEXTURE3D(_VBufferLightingPrev); // RGB = radiance, A = optical depth
CBUFFER_START(UnityVolumetricLighting)
float4x4 _VBufferCoordToViewDirWS; // Actually just 3x3, but Unity can only set 4x4
CBUFFER_END
//--------------------------------------------------------------------------------------------------
// Implementation
//--------------------------------------------------------------------------------------------------
struct Ray
{
float3 originWS;
float3 directionWS; // Normalized
float ratioLenToZ; // 1 / ViewSpaceZ
};
float3 GetPointAtDistance(Ray ray, float t)
{
return ray.originWS + t * ray.directionWS;
}
// Computes the in-scattered radiance along the ray.
void FillVolumetricLightingBuffer(Ray ray, uint2 voxelCoord, uint2 tileCoord)
{
LightLoopContext context;
// ZERO_INITIALIZE(LightLoopContext, context);
context.shadowContext = InitShadowContext();
uint featureFlags = 0xFFFFFFFF; // TODO
float4 depthParams = _VBufferDepthEncodingParams;
float z0 = 0, t0 = 0; // Start at the origin of the ray
float de = rcp(VBUFFER_SLICE_COUNT); // Log-encoded distance between slices
float3 totalRadiance = 0;
float opticalDepth = 0;
uint sliceCountHack = max(VBUFFER_SLICE_COUNT, (uint)depthParams.x); // Prevent unrolling...
#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(tileCoord, z0);
clusterDepths[0] = GetLightClusterMinLinearDepth(tileCoord, clusterIndices[0]);
#endif // LIGHTLOOP_TILE_PASS
// TODO: replace 'sliceCountHack' with VBUFFER_SLICE_COUNT when the shader compiler bug is fixed.
for (uint slice = 0; slice < sliceCountHack; slice++)
{
float e1 = slice * de + de; // (slice + 1) / sliceCount
float z1 = DecodeLogarithmicDepth(e1, depthParams);
float t1 = ray.ratioLenToZ * z1;
float dt = t1 - t0;
// Compute the position of the center of the voxel.
// We will use it for participating media sampling and reprojection.
float tc = t0 + 0.5 * dt;
float3 centerWS = GetPointAtDistance(ray, tc);
// Sample the participating medium at 'tc' (or 'centerWS').
// We consider it to be constant along the interval [t0, t1] (within the voxel).
float3 scattering = _GlobalFog_Scattering;
float extinction = _GlobalFog_Extinction;
// TODO: use a low-discrepancy point set.
float rndVal = 0.5;
float3 sampleRadiance = 0;
if (featureFlags & LIGHTFEATUREFLAGS_DIRECTIONAL)
{
float tOffset, weight;
ImportanceSampleHomogeneousMedium(rndVal, extinction, dt, tOffset, weight);
float t = t0 + tOffset;
float3 positionWS = GetPointAtDistance(ray, t);
for (uint i = 0; i < _DirectionalLightCount; ++i)
{
// Fetch the light.
DirectionalLightData lightData = _DirectionalLightDatas[i];
float3 L = -lightData.forward; // Lights point backwards in Unity
float intensity = weight;
float3 color = lightData.color;
[branch] if (lightData.shadowIndex >= 0)
{
float shadow = GetDirectionalShadowAttenuation(context.shadowContext, positionWS,
float3(0, 0, 0), lightData.shadowIndex, L);
intensity *= shadow;
}
// Note: no fog attenuation along shadow rays for directional lights.
[branch] if (lightData.cookieIndex >= 0)
{
float3 lightToSample = positionWS - lightData.positionWS;
float3 cookie = EvaluateCookie_Directional(context, lightData, lightToSample);
color *= cookie.rgb;
}
// Compute the amount of in-scattered radiance.
sampleRadiance += color * intensity;
}
}
#ifdef LIGHTLOOP_TILE_PASS
clusterIndices[1] = GetLightClusterIndex(tileCoord, z1);
clusterDepths[1] = GetLightClusterMinLinearDepth(tileCoord, clusterIndices[1]);
// Loop over 1 or 2 light clusters.
for (int cluster = 0; cluster < 2; cluster++)
{
float tMin = max(t0, ray.ratioLenToZ * clusterDepths[cluster]);
float tMax = t1;
if (cluster == 0 && (clusterIndices[0] != clusterIndices[1]))
{
tMax = min(t1, ray.ratioLenToZ * clusterDepths[1]);
}
#else
float tMin = t0;
float tMax = t1;
#endif // LIGHTLOOP_TILE_PASS
if (featureFlags & LIGHTFEATUREFLAGS_PUNCTUAL)
{
uint punctualLightStart;
uint punctualLightCount;
#ifdef LIGHTLOOP_TILE_PASS
GetCountAndStartCluster(tileCoord, clusterIndices[cluster], LIGHTCATEGORY_PUNCTUAL,
punctualLightStart, punctualLightCount);
#else
punctualLightStart = 0;
punctualLightCount = _PunctualLightCount;
#endif // LIGHTLOOP_TILE_PASS
// TODO: since lights are sorted, make a while loop per light type.
for (uint i = 0; i < punctualLightCount; ++i)
{
LightData lightData = FetchLight(punctualLightStart, i);
int lightType = lightData.lightType;
// TODO...
if (lightType != GPULIGHTTYPE_POINT && lightType != GPULIGHTTYPE_SPOT) continue;
float tEntr, tExit;
if (lightType == GPULIGHTTYPE_SPOT)
{
if (!ConeRayIntersect(ray.originWS, ray.directionWS,
lightData.positionWS, lightData.forward,
lightData.right, lightData.up,
tMin, tMax, tEntr, tExit))
{
continue;
}
}
else
{
tEntr = tMin;
tExit = tMax;
}
float t, distSq, rcpPdf;
ImportanceSamplePunctualLight(rndVal, lightData.positionWS,
ray.originWS, ray.directionWS,
tEntr, tExit, t, distSq, rcpPdf);
float3 positionWS = GetPointAtDistance(ray, t);
// TODO: we could compute this data in ImportanceSamplePunctualLight().
float3 lightToSample = positionWS - lightData.positionWS;
float dist = sqrt(distSq);
float3 L = lightToSample * -rsqrt(distSq);
float intensity = GetPunctualShapeAttenuation(lightData, L, distSq);
float3 color = lightData.color;
// TODO: heterogeneous medium.
intensity *= TransmittanceHomogeneousMedium(extinction, dist);
[branch] if (lightData.shadowIndex >= 0)
{
// TODO: make projector lights cast shadows.
float shadow = GetPunctualShadowAttenuation(context.shadowContext, positionWS,
float3(0, 0, 0), lightData.shadowIndex, float4(L, dist));
intensity *= lerp(1, shadow, lightData.shadowDimmer);
}
// Projector lights always have cookies, so we can perform clipping inside the if().
[branch] if (lightData.cookieIndex >= 0)
{
float4 cookie = EvaluateCookie_Punctual(context, lightData, lightToSample);
color *= cookie.rgb;
intensity *= cookie.a;
}
// Compute transmittance from 't0' to 't'.
float transmittance = TransmittanceHomogeneousMedium(extinction, t - t0);
intensity *= transmittance * rcpPdf;
// Compute the amount of in-scattered radiance.
sampleRadiance += color * intensity;
}
}
#ifdef LIGHTLOOP_TILE_PASS
// The voxel is completely inside the light cluster.
if (clusterIndices[0] == clusterIndices[1]) break;
}
clusterIndices[0] = clusterIndices[1];
clusterDepths[0] = clusterDepths[1];
#endif // LIGHTLOOP_TILE_PASS
// Compute the transmittance up to the start of the interval.
float transmittance = Transmittance(opticalDepth);
// Integral{a, b}{Transmittance(0, t) * Li(t) dt} = Transmittance(0, a) * Integral{a, b}{Transmittance(0, t - a) * Li(t) dt}.
totalRadiance += (transmittance * IsotropicPhaseFunction()) * scattering * sampleRadiance;
// Compute the optical depth up to the center of the interval.
opticalDepth += 0.5 * extinction * dt;
// Store the voxel data. TODO: reprojection of 'tc' (or 'centerWS').
_VBufferLighting[uint3(voxelCoord, slice)] = float4(totalRadiance, opticalDepth);
// Compute the optical depth up to the end of the interval.
opticalDepth += 0.5 * extinction * dt;
t0 = t1;
}
}
[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);
}
[branch] if (voxelCoord.x >= (uint)_VBufferResolutionAndScale.x ||
voxelCoord.y >= (uint)_VBufferResolutionAndScale.y)
{
return;
}
// TODO: use a low-discrepancy point set.
float2 sampleCoord = voxelCoord + 0.5;
// Compute the ray direction s.t. its ViewSpaceZ = 1.
float3 dir = -mul(float3(sampleCoord, 1), (float3x3)_VBufferCoordToViewDirWS);
float lenSq = dot(dir, dir);
float lenRcp = rsqrt(lenSq);
float len = lenSq * lenRcp;
Ray ray;
ray.originWS = GetCurrentViewPosition();
ray.ratioLenToZ = len;
ray.directionWS = dir * lenRcp;
FillVolumetricLightingBuffer(ray, voxelCoord, tileCoord);
}