浏览代码

Port SSS to compute with an LDS texture cache

/RenderPassXR_Sandbox
Evgenii Golubev 8 年前
当前提交
98cec915
共有 6 个文件被更改,包括 226 次插入421 次删除
  1. 20
      Assets/ScriptableRenderPipeline/HDRenderPipeline/HDRenderPipeline.cs
  2. 10
      Assets/ScriptableRenderPipeline/HDRenderPipeline/Material/Lit/Lit.hlsl
  3. 532
      Assets/ScriptableRenderPipeline/HDRenderPipeline/Material/Lit/Resources/SubsurfaceScattering.compute
  4. 6
      Assets/ScriptableRenderPipeline/HDRenderPipeline/Material/Lit/Resources/SubsurfaceScattering.shader
  5. 78
      Assets/ScriptableRenderPipeline/HDRenderPipeline/Material/Lit/SubsurfaceScatteringProfile.cs
  6. 1
      Assets/ScriptableRenderPipeline/ShaderLibrary/Common.hlsl

20
Assets/ScriptableRenderPipeline/HDRenderPipeline/HDRenderPipeline.cs


if (sssSettings.useDisneySSS)
{
hdCamera.SetupComputeShader(m_SubsurfaceScatteringCS, cmd);
cmd.SetComputeIntParam( m_SubsurfaceScatteringCS, "_TexturingModeFlags", sssParameters.texturingModeFlags);
cmd.SetComputeVectorArrayParam(m_SubsurfaceScatteringCS, "_WorldScales", sssParameters.worldScales);
cmd.SetComputeVectorArrayParam(m_SubsurfaceScatteringCS, "_FilterKernels", sssParameters.filterKernels);
cmd.SetComputeVectorArrayParam(m_SubsurfaceScatteringCS, "_ShapeParams", sssParameters.shapeParams);
cmd.SetComputeTextureParam(m_SubsurfaceScatteringCS, m_SubsurfaceScatteringKernel, "_GBufferTexture0", m_gbufferManager.GetGBuffers()[0]);
cmd.SetComputeTextureParam(m_SubsurfaceScatteringCS, m_SubsurfaceScatteringKernel, "_GBufferTexture1", m_gbufferManager.GetGBuffers()[1]);
cmd.SetComputeTextureParam(m_SubsurfaceScatteringCS, m_SubsurfaceScatteringKernel, "_GBufferTexture2", m_gbufferManager.GetGBuffers()[2]);

cmd.SetComputeTextureParam(m_SubsurfaceScatteringCS, m_SubsurfaceScatteringKernel, "_HTile", GetHTile());
cmd.SetComputeTextureParam(m_SubsurfaceScatteringCS, m_SubsurfaceScatteringKernel, "_IrradianceSource", m_CameraDiffuseIrradianceBufferRT);
cmd.SetComputeTextureParam(m_SubsurfaceScatteringCS, m_SubsurfaceScatteringKernel, "_CameraColorTexture", m_CameraColorBufferRT);
cmd.SetGlobalTexture("_IrradianceSource", m_CameraDiffuseIrradianceBufferRT); // Cannot set a RT on a material
m_SssHorizontalFilterAndCombinePass.SetFloatArray("_WorldScales", sssParameters.worldScales);
m_SssHorizontalFilterAndCombinePass.SetFloatArray("_FilterKernelsNearField", sssParameters.filterKernelsNearField);
m_SssHorizontalFilterAndCombinePass.SetFloatArray("_FilterKernelsFarField", sssParameters.filterKernelsFarField);
//cmd.SetGlobalTexture("_IrradianceSource", m_CameraDiffuseIrradianceBufferRT); // Cannot set a RT on a material
//m_SssHorizontalFilterAndCombinePass.SetVectorArray("_WorldScales", sssParameters.worldScales);
//m_SssHorizontalFilterAndCombinePass.SetVectorArray("_FilterKernels", sssParameters.filterKernels);
Utilities.DrawFullScreen(cmd, m_SssHorizontalFilterAndCombinePass, m_CameraColorBufferRT, m_CameraDepthStencilBufferRT);
//Utilities.DrawFullScreen(cmd, m_SssHorizontalFilterAndCombinePass, m_CameraColorBufferRT, m_CameraDepthStencilBufferRT);
m_SssVerticalFilterPass.SetFloatArray("_WorldScales", sssParameters.worldScales);
m_SssVerticalFilterPass.SetVectorArray("_WorldScales", sssParameters.worldScales);
m_SssVerticalFilterPass.SetVectorArray("_FilterKernelsBasic", sssParameters.filterKernelsBasic);
m_SssVerticalFilterPass.SetVectorArray("_HalfRcpWeightedVariances", sssParameters.halfRcpWeightedVariances);
Utilities.DrawFullScreen(cmd, m_SssVerticalFilterPass, m_CameraFilteringBufferRT, m_CameraDepthStencilBufferRT);

m_SssHorizontalFilterAndCombinePass.SetFloatArray("_WorldScales", sssParameters.worldScales);
m_SssHorizontalFilterAndCombinePass.SetVectorArray("_WorldScales", sssParameters.worldScales);
m_SssHorizontalFilterAndCombinePass.SetVectorArray("_FilterKernelsBasic", sssParameters.filterKernelsBasic);
m_SssHorizontalFilterAndCombinePass.SetVectorArray("_HalfRcpWeightedVariances", sssParameters.halfRcpWeightedVariances);
Utilities.DrawFullScreen(cmd, m_SssHorizontalFilterAndCombinePass, m_CameraColorBufferRT, m_CameraDepthStencilBufferRT);

10
Assets/ScriptableRenderPipeline/HDRenderPipeline/Material/Lit/Lit.hlsl


bool performPostScatterTexturing = IsBitSet(_TexturingModeFlags, subsurfaceProfile);
bool enableSssAndTransmission = true;
if (0)
#else
if (_EnableSSSAndTransmission != 0) // If we globally disable SSS effect, don't modify diffuseColor
enableSssAndTransmission = false;
#elif (SSS_PASS == 0)
enableSssAndTransmission = _EnableSSSAndTransmission != 0;
if (enableSssAndTransmission) // If we globally disable SSS effect, don't modify diffuseColor
{
// We modify the albedo here as this code is used by all lighting (including light maps and GI).
if (performPostScatterTexturing)

532
Assets/ScriptableRenderPipeline/HDRenderPipeline/Material/Lit/Resources/SubsurfaceScattering.compute


// Inputs & outputs
//--------------------------------------------------------------------------------------------------
float _WorldScales[SSS_N_PROFILES]; // Size of the world unit in meters
float _FilterKernelsNearField[SSS_N_PROFILES][SSS_N_SAMPLES_NEAR_FIELD][2]; // 0 = radius, 1 = reciprocal of the PDF
float _FilterKernelsFarField[SSS_N_PROFILES][SSS_N_SAMPLES_FAR_FIELD][2]; // 0 = radius, 1 = reciprocal of the PDF
float4 _WorldScales[SSS_N_PROFILES]; // Size of the world unit in meters (only the X component is used)
float4 _FilterKernels[SSS_N_PROFILES][SSS_N_SAMPLES_NEAR_FIELD]; // XY = near field, ZW = far field; 0 = radius, 1 = reciprocal of the PDF
DECLARE_GBUFFER_TEXTURE(_GBufferTexture); // Contains the albedo and SSS parameters
TEXTURE2D(_DepthTexture); // Z-buffer

// Implementation
//--------------------------------------------------------------------------------------------------
groupshared float4 textureCache[TEXTURE_CACHE_SIZE_1D][TEXTURE_CACHE_SIZE_1D]; // {irradiance, linearDepth}
groupshared bool processGroup;
bool StencilTest(int2 pixelCoord, float stencilRef)
{
int2 tileCoord = pixelCoord >> 3; // Divide by 8
// Perform the stencil test (reject at the tile rate).
bool passedStencilTest = stencilRef == LOAD_TEXTURE2D(_HTile, tileCoord).r;
[branch] if (passedStencilTest)
{
// Our copy of HTile does not allow to accept at the tile rate.
// Therefore, we have to additionally perform the stencil test at the pixel rate.
passedStencilTest = stencilRef == LOAD_TEXTURE2D(_StencilTexture, pixelCoord).r;
}
return passedStencilTest;
}
float4 LoadSampleFromCacheMemory(int2 cacheCoord)
{
return textureCache[cacheCoord.x][cacheCoord.y];
}
float4 LoadSampleFromVideoMemory(int2 pixelCoord)
{
float3 irradiance = LOAD_TEXTURE2D(_IrradianceSource, pixelCoord).rgb;
float depth = LOAD_TEXTURE2D(_DepthTexture, pixelCoord).r;
return float4(irradiance, LinearEyeDepth(depth, _ZBufferParams));
}
// Returns {irradiance, linearDepth}.
float4 LoadSample(int2 pixelCoord, int2 cacheAnchor)
{
int2 cacheCoord = pixelCoord - cacheAnchor;
bool isInCache = cacheCoord.x >= 0 && cacheCoord.x < TEXTURE_CACHE_SIZE_1D &&
cacheCoord.y >= 0 && cacheCoord.y < TEXTURE_CACHE_SIZE_1D;
[branch] if (isInCache)
{
return LoadSampleFromCacheMemory(cacheCoord);
}
else
{
float stencilRef = STENCILLIGHTINGUSAGE_SPLIT_LIGHTING;
[branch] if (StencilTest(pixelCoord, stencilRef))
{
return LoadSampleFromVideoMemory(pixelCoord);
}
else
{
return float4(0, 0, 0, 0);
}
}
}
// Computes the value of the integrand over a disk: (2 * PI * r) * KernelVal().
// N.b.: the returned value is multiplied by 4. It is irrelevant due to weight renormalization.
float3 KernelValCircle(float r, float3 S)

}
// Computes F(r)/P(r), s.t. r = sqrt(a^2 + b^2).
// Computes F(r)/P(r), s.t. r = sqrt(xy^2 + z^2).
float3 ComputeBilateralWeight(float a2, float b, float mmPerUnit, float3 S, float rcpPdf)
float3 ComputeBilateralWeight(float xy2, float z, float mmPerUnit, float3 S, float rcpPdf)
b = 0;
z = 0;
// Both 'a2' and 'b2' require unit conversion.
float r = sqrt(a2 + b * b) * mmPerUnit;
// Both 'xy2' and 'z' require conversion to millimeters.
float r = sqrt(xy2 + z * z) * mmPerUnit;
// Only 'b2' requires unit conversion.
float r = sqrt(a2 + (b * mmPerUnit) * (b * mmPerUnit));
// Only 'z' requires conversion to millimeters.
float r = sqrt(xy2 + (z * mmPerUnit) * (z * mmPerUnit));
#endif
#if SSS_CLAMP_ARTIFACT

#endif
}
#define SSS_ITER(i, n, kernel, profileID, shapeParam, centerPosUnSS, centerPosVS, \
useTangentPlane, tangentX, tangentY, mmPerUnit, pixelsPerMm, \
totalIrradiance, totalWeight) \
{ \
float r = kernel[profileID][i][0]; \
/* The relative sample position is known at compile time. */ \
float phi = SampleDiskFibonacci(i, n).y; \
float2 vec = r * float2(cos(phi), sin(phi)); \
\
/* Compute the screen-space position and the associated irradiance. */ \
float2 position; float3 irradiance; \
/* Compute the squared distance (in mm) in the screen-aligned plane. */ \
float dXY2; \
\
if (useTangentPlane) \
{ \
/* 'vec' is given relative to the tangent frame. */ \
float3 relPosVS = vec.x * tangentX + vec.y * tangentY; \
float3 positionVS = centerPosVS + relPosVS; \
float4 positionCS = mul(projMatrix, float4(positionVS, 1)); \
float2 positionSS = ComputeScreenSpacePosition(positionCS); \
\
position = positionSS * _ScreenSize.xy; \
irradiance = LOAD_TEXTURE2D(_IrradianceSource, position).rgb; \
dXY2 = dot(relPosVS.xy, relPosVS.xy); \
} \
else \
{ \
/* 'vec' is given directly in screen-space. */ \
position = centerPosUnSS + vec * pixelsPerMm; \
irradiance = LOAD_TEXTURE2D(_IrradianceSource, position).rgb; \
dXY2 = r * r; \
} \
\
/* TODO: see if making this a [branch] improves performance. */ \
[flatten] \
if (any(irradiance)) \
{ \
/* Apply bilateral weighting. */ \
float z = LOAD_TEXTURE2D(_MainDepthTexture, position).r; \
float d = LinearEyeDepth(z, _ZBufferParams); \
float t = d - centerPosVS.z; \
float p = kernel[profileID][i][1]; \
float3 w = ComputeBilateralWeight(dXY2, t, mmPerUnit, shapeParam, p); \
\
totalIrradiance += w * irradiance; \
totalWeight += w; \
} \
else \
{ \
/*************************************************************************/ \
/* The irradiance is 0. This could happen for 3 reasons. */ \
/* Most likely, the surface fragment does not have an SSS material. */ \
/* Alternatively, our sample comes from a region without any geometry. */ \
/* Finally, the surface fragment could be completely shadowed. */ \
/* Our blur is energy-preserving, so 'centerWeight' should be set to 0. */ \
/* We do not terminate the loop since we want to gather the contribution */ \
/* of the remaining samples (e.g. in case of hair covering skin). */ \
/* Note: See comment in the output of deferred.shader */ \
/*************************************************************************/ \
} \
}
void EvaluateSample(uint i, uint n, uint profileID, uint iR, uint iP, uint2 pixelCoord, int2 cacheAnchor,
float3 shapeParam, float3 centerPosVS, float2 mmPerUnit, float2 pixelsPerMm,
bool useTangentPlane, float3 tangentX, float3 tangentY, float4x4 projMatrix,
inout float3 totalIrradiance, inout float3 totalWeight)
{
float r = _FilterKernels[profileID][i][iR];
// The relative sample position is known at the compile time.
float phi = SampleDiskFibonacci(i, n).y;
float2 vec = r * float2(cos(phi), sin(phi));
#define SSS_LOOP(n, kernel, profileID, shapeParam, centerPosUnSS, centerPosVS, \
useTangentPlane, tangentX, tangentY, mmPerUnit, pixelsPerMm, \
totalIrradiance, totalWeight) \
{ \
float centerRadius = kernel[profileID][0][0]; \
float centerRcpPdf = kernel[profileID][0][1]; \
float3 centerWeight = KernelValCircle(centerRadius, shapeParam) * centerRcpPdf; \
\
totalIrradiance = centerWeight * centerIrradiance; \
totalWeight = centerWeight; \
\
/* Integrate over the screen-aligned or tangent plane in the view space. */ \
[unroll] \
for (uint i = 1; i < n; i++) \
{ \
SSS_ITER(i, n, kernel, profileID, shapeParam, centerPosUnSS, centerPosVS, \
useTangentPlane, tangentX, tangentY, mmPerUnit, pixelsPerMm, \
totalIrradiance, totalWeight) \
} \
}
// Compute the screen-space position and the squared distance (in mm) in the image plane.
float2 position; float xy2;
if (useTangentPlane)
{
float3 relPosVS = vec.x * tangentX + vec.y * tangentY;
float3 positionVS = centerPosVS + relPosVS;
float4 positionCS = mul(projMatrix, float4(positionVS, 1));
float2 positionSS = ComputeScreenSpacePosition(positionCS);
bool StencilTest(int2 pixelCoord, float stencilRef)
{
int2 tileCoord = pixelCoord / 8;
position = positionSS * _ScreenSize.xy;
xy2 = dot(relPosVS.xy, relPosVS.xy);
}
else
{
position = pixelCoord + vec * pixelsPerMm;
xy2 = r * r;
}
// Perform the stencil test (reject at the tile rate).
bool passedStencilTest = stencilRef == LOAD_TEXTURE2D(_HTile, tileCoord).r;
float4 sample = LoadSample(position, cacheAnchor);
float3 irradiance = sample.rgb;
float linearDepth = sample.a;
[branch] if (passedStencilTest)
// Check the results of the stencil test.
[flatten] if (linearDepth > 0)
// Our copy of HTile does not allow to accept at the tile rate.
// Therefore, we have to additionally perform the stencil test at the pixel rate.
passedStencilTest = stencilRef == LOAD_TEXTURE2D(_StencilTexture, pixelCoord).r;
// Apply bilateral weighting.
float z = linearDepth - centerPosVS.z;
float p = _FilterKernels[profileID][i][iP];
float3 w = ComputeBilateralWeight(xy2, z, mmPerUnit, shapeParam, p);
totalIrradiance += w * irradiance;
totalWeight += w;
return passedStencilTest;
groupshared float4 textureCache[TEXTURE_CACHE_SIZE_1D][TEXTURE_CACHE_SIZE_1D]; // float4(irradiance, linearDepth)
groupshared bool processGroup;
const uint waveIndex = groupThreadId / 64;
const uint laneIndex = groupThreadId % 64;
const uint quadIndex = laneIndex / 4;
uint waveIndex = groupThreadId / 64;
uint laneIndex = groupThreadId % 64;
uint quadIndex = laneIndex / 4;
const uint mortonCode = groupThreadId;
const uint2 localCoord = DecodeMorton2D(mortonCode);
const uint2 tileAnchor = groupId * GROUP_SIZE_1D;
const uint2 pixelCoord = tileAnchor + localCoord;
const uint2 cacheCoord = localCoord + TEXTURE_CACHE_BORDER;
const uint2 cacheMinPt = tileAnchor - TEXTURE_CACHE_BORDER;
const uint2 cacheMaxPt = cacheMinPt + TEXTURE_CACHE_SIZE_1D;
const float stencilRef = STENCILLIGHTINGUSAGE_SPLIT_LIGHTING;
uint mortonCode = groupThreadId;
uint2 localCoord = DecodeMorton2D(mortonCode);
uint2 tileAnchor = groupId * GROUP_SIZE_1D;
uint2 pixelCoord = tileAnchor + localCoord;
int2 cacheAnchor = tileAnchor - TEXTURE_CACHE_BORDER;
uint2 cacheCoord = localCoord + TEXTURE_CACHE_BORDER;
float stencilRef = STENCILLIGHTINGUSAGE_SPLIT_LIGHTING;
[branch] if (groupThreadId == 0)
{

// Populate the central region of the LDS cache.
textureCache[cacheCoord.x][cacheCoord.y] = cachedValue;
const uint numBorderQuadsPerWave = TEXTURE_CACHE_SIZE_1D / 2 - 1;
const uint halfCacheWidthInQuads = TEXTURE_CACHE_SIZE_1D / 4;
uint numBorderQuadsPerWave = TEXTURE_CACHE_SIZE_1D / 2 - 1;
uint halfCacheWidthInQuads = TEXTURE_CACHE_SIZE_1D / 4;
[branch] if (quadIndex < numBorderQuadsPerWave)
{

break;
}
uint2 cacheCoord2 = (startQuad + quadCoord) * 2 + uint2(laneIndex & 1, (laneIndex >> 1) & 1);
int2 pixelCoord2 = (int2)(tileAnchor + cacheCoord2 - TEXTURE_CACHE_BORDER);
int2 cacheCoord2 = 2 * (startQuad + quadCoord) + int2(laneIndex & 1, (laneIndex >> 1) & 1);
int2 pixelCoord2 = tileAnchor + cacheCoord2 - TEXTURE_CACHE_BORDER;
float3 irradiance2 = LOAD_TEXTURE2D(_IrradianceSource, pixelCoord2).rgb;
float depth2 = LOAD_TEXTURE2D(_DepthTexture, pixelCoord2).r;
cachedValue2 = float4(irradiance2, LinearEyeDepth(depth2, _ZBufferParams));
cachedValue2 = LoadSampleFromVideoMemory(pixelCoord2);
}
// Populate the border region of the LDS cache.

// Wait for the LDS.
GroupMemoryBarrierWithGroupSync();
[branch] if (!passedStencilTest) { return; }
bool isOffScreen = pixelCoord.x >= (uint)_ScreenSize.x || pixelCoord.y >= (uint)_ScreenSize.y;
[branch] if (!passedStencilTest || isOffScreen) { return; }
PositionInputs posInput = GetPositionInput(pixelCoord, _ScreenSize.zw);

float3 cornerPosVS = ComputeViewSpacePosition(cornerPosSS, centerDepth, _InvProjMatrix);
// Rescaling the filter is equivalent to inversely scaling the world.
float mmPerUnit = MILLIMETERS_PER_METER * (_WorldScales[profileID] / distScale);
float mmPerUnit = MILLIMETERS_PER_METER * (_WorldScales[profileID].x / distScale);
float unitsPerMm = rcp(mmPerUnit);
// Compute the view-space dimensions of the pixel as a quad projected onto geometry.

#else
_CameraColorTexture[pixelCoord] += float4(bsdfData.diffuseColor * centerIrradiance, 1);
#endif
return;
return;
const bool useTangentPlane = SSS_USE_TANGENT_PLANE != 0;
bool useTangentPlane = SSS_USE_TANGENT_PLANE != 0;
float4x4 viewMatrix, projMatrix;
GetLeftHandedViewSpaceMatrices(viewMatrix, projMatrix);

#if SSS_DEBUG_NORMAL_VS
// We expect the view-space normal to be front-facing.
if (normalVS.z >= 0) { _CameraColorTexture[pixelCoord] = float4(1, 0, 0, 1); return; }
#endif
// Accumulate filtered irradiance and bilateral weights (for renormalization).
float3 totalIrradiance, totalWeight;
// Use fewer samples for SS regions smaller than 5x5 pixels (rotated by 45 degrees).
[branch] if (maxDistInPixels < SSS_LOD_THRESHOLD)
{
#if SSS_DEBUG_LOD
_CameraColorTexture[pixelCoord] = float4(0.5, 0.5, 0, 1); return;
#else
SSS_LOOP(SSS_N_SAMPLES_FAR_FIELD, _FilterKernelsFarField,
profileID, shapeParam, pixelCoord, centerPosVS,
useTangentPlane, tangentX, tangentY, mmPerUnit, pixelsPerMm,
totalIrradiance, totalWeight)
#endif
}
else
if (normalVS.z >= 0)
#if SSS_DEBUG_LOD
_CameraColorTexture[pixelCoord] = float4(1, 0, 0, 1); return;
#else
SSS_LOOP(SSS_N_SAMPLES_NEAR_FIELD, _FilterKernelsNearField,
profileID, shapeParam, pixelCoord, centerPosVS,
useTangentPlane, tangentX, tangentY, mmPerUnit, pixelsPerMm,
totalIrradiance, totalWeight)
#endif
_CameraColorTexture[pixelCoord] = float4(1, 1, 1, 1);
return;
_CameraColorTexture[pixelCoord] += float4(bsdfData.diffuseColor * totalIrradiance / totalWeight, 1);
// _CameraColorTexture[pixelCoord] = float4((mortonCode % 256) / 255.0, (groupId.x % 256) / 15.0, (groupId.y % 256) / 15.0, 1);
// _CameraColorTexture[pixelCoord] = float4((mortonCode % 256) / 255.0, 0, 0, 1);
// _CameraColorTexture[pixelCoord] = float4((pixelCoord.x % 256) / 255.0, (pixelCoord.y % 256) / 255.0, 0, 1);
// _CameraColorTexture[pixelCoord] = float4(numBorderTexelsPerWave == (16 + 18) ? 1 : 0, 0, 0, 1);
}
/*
float4 Frag(Varyings input) : SV_Target
{
PositionInputs posInput = GetPositionInput(input.positionCS.xy, _ScreenSize.zw);
float3 unused;
// Note: When we are in this SubsurfaceScattering shader we know that we are a SSS material. This shader is strongly coupled with the deferred Lit.shader.
// We can use the material classification facility to help the compiler to know we use SSS material and optimize the code (and don't require to read gbuffer with materialId).
uint featureFlags = MATERIALFEATUREFLAGS_LIT_SSS;
BSDFData bsdfData;
FETCH_GBUFFER(gbuffer, _GBufferTexture, posInput.unPositionSS);
DECODE_FROM_GBUFFER(gbuffer, featureFlags, bsdfData, unused);
int profileID = bsdfData.subsurfaceProfile;
float distScale = bsdfData.subsurfaceRadius;
#ifdef SSS_MODEL_DISNEY
float3 shapeParam = _ShapeParams[profileID].rgb;
float maxDistance = _ShapeParams[profileID].a;
#else
float maxDistance = _FilterKernelsBasic[profileID][SSS_BASIC_N_SAMPLES - 1].a;
#endif
// Take the first (central) sample.
// TODO: copy its neighborhood into LDS.
float2 centerPosition = posInput.unPositionSS;
float3 centerIrradiance = LOAD_TEXTURE2D(_IrradianceSource, centerPosition).rgb;
// Reconstruct the view-space position.
float2 centerPosSS = posInput.positionSS;
float2 cornerPosSS = centerPosSS + 0.5 * _ScreenSize.zw;
float centerDepth = LOAD_TEXTURE2D(_MainDepthTexture, centerPosition).r;
float3 centerPosVS = ComputeViewSpacePosition(centerPosSS, centerDepth, _InvProjMatrix);
float3 cornerPosVS = ComputeViewSpacePosition(cornerPosSS, centerDepth, _InvProjMatrix);
#ifdef SSS_MODEL_DISNEY
// Rescaling the filter is equivalent to inversely scaling the world.
float mmPerUnit = MILLIMETERS_PER_METER * (_WorldScales[profileID] / distScale);
float unitsPerMm = rcp(mmPerUnit);
// Compute the view-space dimensions of the pixel as a quad projected onto geometry.
float2 unitsPerPixel = 2 * abs(cornerPosVS.xy - centerPosVS.xy);
float2 pixelsPerMm = rcp(unitsPerPixel) * unitsPerMm;
// We perform point sampling. Therefore, we can avoid the cost
// of filtering if we stay within the bounds of the current pixel.
// We use the value of 1 instead of 0.5 as an optimization.
// N.b.: our LoD selection algorithm is the same regardless of
// whether we integrate over the tangent plane or not, since we
// don't want the orientation of the tangent plane to create
// divergence of execution across the warp.
float maxDistInPixels = maxDistance * max(pixelsPerMm.x, pixelsPerMm.y);
[branch]
if (distScale == 0 || maxDistInPixels < 1)
{
#if SSS_DEBUG_LOD
return float4(0, 0, 1, 1);
#else
return float4(bsdfData.diffuseColor * centerIrradiance, 1);
#endif
}
const bool useTangentPlane = SSS_USE_TANGENT_PLANE != 0;
float4x4 viewMatrix, projMatrix;
GetLeftHandedViewSpaceMatrices(viewMatrix, projMatrix);
// Compute the tangent frame in view space.
float3 normalVS = mul((float3x3)viewMatrix, bsdfData.normalWS);
float3 tangentX = GetLocalFrame(normalVS)[0] * unitsPerMm;
float3 tangentY = GetLocalFrame(normalVS)[1] * unitsPerMm;
#if SSS_DEBUG_NORMAL_VS
// We expect the view-space normal to be front-facing.
if (normalVS.z >= 0) return float4(1, 0, 0, 1);
#endif
// Accumulate filtered irradiance and bilateral weights (for renormalization).
float3 totalIrradiance, totalWeight;
// Use fewer samples for SS regions smaller than 5x5 pixels (rotated by 45 degrees).
[branch]
if (maxDistInPixels < SSS_LOD_THRESHOLD)
{
#if SSS_DEBUG_LOD
return float4(0.5, 0.5, 0, 1);
#else
SSS_LOOP(SSS_N_SAMPLES_FAR_FIELD, _FilterKernelsFarField,
profileID, shapeParam, centerPosition, centerPosVS,
useTangentPlane, tangentX, tangentY, mmPerUnit, pixelsPerMm,
totalIrradiance, totalWeight)
#endif
}
else
{
#if SSS_DEBUG_LOD
return float4(1, 0, 0, 1);
#else
SSS_LOOP(SSS_N_SAMPLES_NEAR_FIELD, _FilterKernelsNearField,
profileID, shapeParam, centerPosition, centerPosVS,
useTangentPlane, tangentX, tangentY, mmPerUnit, pixelsPerMm,
totalIrradiance, totalWeight)
#endif
}
#else
// Rescaling the filter is equivalent to inversely scaling the world.
float metersPerUnit = _WorldScales[profileID] / distScale * SSS_BASIC_DISTANCE_SCALE;
float centimPerUnit = CENTIMETERS_PER_METER * metersPerUnit;
// Compute the view-space dimensions of the pixel as a quad projected onto geometry.
float2 unitsPerPixel = 2 * abs(cornerPosVS.xy - centerPosVS.xy);
float2 pixelsPerCm = rcp(centimPerUnit * unitsPerPixel);
#endif
// Compute the filtering direction.
#ifdef SSS_FILTER_HORIZONTAL_AND_COMBINE
float2 unitDirection = float2(1, 0);
#else
float2 unitDirection = float2(0, 1);
#endif
// Use more samples for SS regions larger than 5x5 pixels (rotated by 45 degrees).
bool useNearFieldKernel = maxDistInPixels > SSS_LOD_THRESHOLD;
float2 scaledDirection = pixelsPerCm * unitDirection;
float phi = 0; // Random rotation; unused for now
float2x2 rotationMatrix = float2x2(cos(phi), -sin(phi), sin(phi), cos(phi));
float2 rotatedDirection = mul(rotationMatrix, scaledDirection);
#if SSS_DEBUG_LOD
_CameraColorTexture[pixelCoord] = useNearFieldKernel ? float4(1, 0, 0, 1) : float4(0.5, 0.5, 0, 1);
return;
#endif
// Load (1 / (2 * WeightedVariance)) for bilateral weighting.
#if RBG_BILATERAL_WEIGHTS
float3 halfRcpVariance = _HalfRcpWeightedVariances[profileID].rgb;
#else
float halfRcpVariance = _HalfRcpWeightedVariances[profileID].a;
#endif
// Compute the indices used to access the individual components of the float4 of the kernel.
uint iR = useNearFieldKernel ? 0 : 2; // radius
uint iP = useNearFieldKernel ? 1 : 3; // rcp(pdf)
#ifndef SSS_FILTER_HORIZONTAL_AND_COMBINE
bsdfData.diffuseColor = float3(1, 1, 1);
#endif
float centerRadius = _FilterKernels[profileID][0][iR];
float centerRcpPdf = _FilterKernels[profileID][0][iP];
float3 centerWeight = KernelValCircle(centerRadius, shapeParam) * centerRcpPdf;
// Take the first (central) sample.
float2 samplePosition = posInput.unPositionSS;
float3 sampleWeight = _FilterKernelsBasic[profileID][0].rgb;
float3 sampleIrradiance = LOAD_TEXTURE2D(_IrradianceSource, samplePosition).rgb;
// Accumulate filtered irradiance and bilateral weights (for renormalization).
float3 totalIrradiance = centerWeight * centerIrradiance;
float3 totalWeight = centerWeight;
// We perform point sampling. Therefore, we can avoid the cost
// of filtering if we stay within the bounds of the current pixel.
// We use the value of 1 instead of 0.5 as an optimization.
float maxDistInPixels = maxDistance * max(pixelsPerCm.x, pixelsPerCm.y);
int i, n; // Declare once to avoid the warning from the Unity shader compiler.
[branch]
if (distScale == 0 || maxDistInPixels < 1)
{
#if SSS_DEBUG_LOD
return float4(0, 0, 1, 1);
#else
return float4(bsdfData.diffuseColor * sampleIrradiance, 1);
#endif
}
#if SSS_DEBUG_LOD
return float4(0.5, 0.5, 0, 1);
#endif
[unroll]
for (i = 1, n = SSS_N_SAMPLES_FAR_FIELD; i < n; i++)
{
// Integrate over the image or tangent plane in the view space.
EvaluateSample(i, n, profileID, iR, iP, pixelCoord, cacheAnchor,
shapeParam, centerPosVS, mmPerUnit, pixelsPerMm,
useTangentPlane, tangentX, tangentY, projMatrix,
totalIrradiance, totalWeight);
}
// Accumulate filtered irradiance and bilateral weights (for renormalization).
float3 totalIrradiance = sampleWeight * sampleIrradiance;
float3 totalWeight = sampleWeight;
[unroll]
for (int i = 1; i < SSS_BASIC_N_SAMPLES; i++)
{
samplePosition = posInput.unPositionSS + rotatedDirection * _FilterKernelsBasic[profileID][i].a;
sampleWeight = _FilterKernelsBasic[profileID][i].rgb;
sampleIrradiance = LOAD_TEXTURE2D(_IrradianceSource, samplePosition).rgb;
[flatten]
if (any(sampleIrradiance))
{
// Apply bilateral weighting.
// Ref #1: Skin Rendering by Pseudo–Separable Cross Bilateral Filtering.
// Ref #2: Separable SSS, Supplementary Materials, Section E.
float rawDepth = LOAD_TEXTURE2D(_MainDepthTexture, samplePosition).r;
float sampleDepth = LinearEyeDepth(rawDepth, _ZBufferParams);
float zDistance = centimPerUnit * sampleDepth - (centimPerUnit * centerPosVS.z);
sampleWeight *= exp(-zDistance * zDistance * halfRcpVariance);
totalIrradiance += sampleWeight * sampleIrradiance;
totalWeight += sampleWeight;
}
else
{
// The irradiance is 0. This could happen for 3 reasons.
// Most likely, the surface fragment does not have an SSS material.
// Alternatively, our sample comes from a region without any geometry.
// Finally, the surface fragment could be completely shadowed.
// Our blur is energy-preserving, so 'centerWeight' should be set to 0.
// We do not terminate the loop since we want to gather the contribution
// of the remaining samples (e.g. in case of hair covering skin).
}
}
#endif
[branch] if (!useNearFieldKernel)
{
_CameraColorTexture[pixelCoord] += float4(bsdfData.diffuseColor * totalIrradiance / totalWeight, 1);
return;
}
return float4(bsdfData.diffuseColor * totalIrradiance / totalWeight, 1);
}
ENDHLSL
}
[unroll]
for (i = SSS_N_SAMPLES_FAR_FIELD, n = SSS_N_SAMPLES_NEAR_FIELD; i < n; i++)
{
// Integrate over the image or tangent plane in the view space.
EvaluateSample(i, n, profileID, iR, iP, pixelCoord, cacheAnchor,
shapeParam, centerPosVS, mmPerUnit, pixelsPerMm,
useTangentPlane, tangentX, tangentY, projMatrix,
totalIrradiance, totalWeight);
Fallback Off
}
*/
_CameraColorTexture[pixelCoord] += float4(bsdfData.diffuseColor * totalIrradiance / totalWeight, 1);
}

6
Assets/ScriptableRenderPipeline/HDRenderPipeline/Material/Lit/Resources/SubsurfaceScattering.shader


//-------------------------------------------------------------------------------------
float _WorldScales[SSS_N_PROFILES]; // Size of the world unit in meters
float4 _WorldScales[SSS_N_PROFILES]; // Size of the world unit in meters (only the X component is used)
#ifdef SSS_MODEL_DISNEY
float _FilterKernelsNearField[SSS_N_PROFILES][SSS_N_SAMPLES_NEAR_FIELD][2]; // 0 = radius, 1 = reciprocal of the PDF
float _FilterKernelsFarField[SSS_N_PROFILES][SSS_N_SAMPLES_FAR_FIELD][2]; // 0 = radius, 1 = reciprocal of the PDF

#ifdef SSS_MODEL_DISNEY
// Rescaling the filter is equivalent to inversely scaling the world.
float mmPerUnit = MILLIMETERS_PER_METER * (_WorldScales[profileID] / distScale);
float mmPerUnit = MILLIMETERS_PER_METER * (_WorldScales[profileID].x / distScale);
float unitsPerMm = rcp(mmPerUnit);
// Compute the view-space dimensions of the pixel as a quad projected onto geometry.

}
#else
// Rescaling the filter is equivalent to inversely scaling the world.
float metersPerUnit = _WorldScales[profileID] / distScale * SSS_BASIC_DISTANCE_SCALE;
float metersPerUnit = _WorldScales[profileID].x / distScale * SSS_BASIC_DISTANCE_SCALE;
float centimPerUnit = CENTIMETERS_PER_METER * metersPerUnit;
// Compute the view-space dimensions of the pixel as a quad projected onto geometry.
float2 unitsPerPixel = 2 * abs(cornerPosVS.xy - centerPosVS.xy);

78
Assets/ScriptableRenderPipeline/HDRenderPipeline/Material/Lit/SubsurfaceScatteringProfile.cs


public int numProfiles; // Excluding the neutral profile
public SubsurfaceScatteringProfile[] profiles;
// Below are the cached values.
[NonSerialized] public uint texturingModeFlags; // 1 bit/profile; 0 = PreAndPostScatter, 1 = PostScatter
[NonSerialized] public uint transmissionFlags; // 2 bit/profile; 0 = inf. thick, 1 = thin, 2 = regular
[NonSerialized] public int texturingModeFlags; // 1 bit/profile; 0 = PreAndPostScatter, 1 = PostScatter
[NonSerialized] public int transmissionFlags; // 2 bit/profile; 0 = inf. thick, 1 = thin, 2 = regular
[NonSerialized] public float[] worldScales; // Size of the world unit in meters
[NonSerialized] public Vector4[] worldScales; // Size of the world unit in meters (only the X component is used)
[NonSerialized] public float[] filterKernelsNearField; // 0 = radius, 1 = reciprocal of the PDF
[NonSerialized] public float[] filterKernelsFarField; // 0 = radius, 1 = reciprocal of the PDF
[NonSerialized] public Vector4[] filterKernels; // XY = near field, ZW = far field; 0 = radius, 1 = reciprocal of the PDF
// Old SSS Model >>>
public bool useDisneySSS;
[NonSerialized] public Vector4[] halfRcpWeightedVariances;

worldScales = null;
shapeParams = null;
transmissionTints = null;
filterKernelsNearField = null;
filterKernelsFarField = null;
filterKernels = null;
// Old SSS Model >>>
useDisneySSS = true;
halfRcpWeightedVariances = null;

if (worldScales == null || worldScales.Length != SssConstants.SSS_N_PROFILES)
{
worldScales = new float[SssConstants.SSS_N_PROFILES];
worldScales = new Vector4[SssConstants.SSS_N_PROFILES];
}
if (shapeParams == null || shapeParams.Length != SssConstants.SSS_N_PROFILES)

transmissionTints = new Vector4[SssConstants.SSS_N_PROFILES];
}
const int filterKernelsNearFieldLen = 2 * SssConstants.SSS_N_PROFILES * SssConstants.SSS_N_SAMPLES_NEAR_FIELD;
if (filterKernelsNearField == null || filterKernelsNearField.Length != filterKernelsNearFieldLen)
const int filterKernelsNearFieldLen = SssConstants.SSS_N_PROFILES * SssConstants.SSS_N_SAMPLES_NEAR_FIELD;
if (filterKernels == null || filterKernels.Length != filterKernelsNearFieldLen)
filterKernelsNearField = new float[filterKernelsNearFieldLen];
}
const int filterKernelsFarFieldLen = 2 * SssConstants.SSS_N_PROFILES * SssConstants.SSS_N_SAMPLES_FAR_FIELD;
if (filterKernelsFarField == null || filterKernelsFarField.Length != filterKernelsFarFieldLen)
{
filterKernelsFarField = new float[filterKernelsFarFieldLen];
filterKernels = new Vector4[filterKernelsNearFieldLen];
}
// Old SSS Model >>>

if (i >= numProfiles || profiles[i] == null)
{
// Pink transmission
transmissionFlags |= (uint)1 << i * 2;
transmissionFlags |= 1 << i * 2;
worldScales[i] = 1.0f;
worldScales[i] = Vector4.one;
filterKernelsNearField[2 * (n * i + j) + 0] = 0.0f;
filterKernelsNearField[2 * (n * i + j) + 1] = 1.0f;
}
for (int j = 0, n = SssConstants.SSS_N_SAMPLES_FAR_FIELD; j < n; j++)
{
filterKernelsFarField[2 * (n * i + j) + 0] = 0.0f;
filterKernelsFarField[2 * (n * i + j) + 1] = 1.0f;
filterKernels[n * i + j].x = 0.0f;
filterKernels[n * i + j].y = 1.0f;
filterKernels[n * i + j].z = 0.0f;
filterKernels[n * i + j].w = 1.0f;
}
// Old SSS Model >>>

Debug.Assert(numProfiles < 16, "Transmission flags (32-bit integer) cannot support more than 16 profiles.");
texturingModeFlags |= (uint)profiles[i].texturingMode << i;
transmissionFlags |= (uint)profiles[i].transmissionMode << i * 2;
texturingModeFlags |= (int)profiles[i].texturingMode << i;
transmissionFlags |= (int)profiles[i].transmissionMode << i * 2;
thicknessRemaps[i] = new Vector4(profiles[i].thicknessRemap.x, profiles[i].thicknessRemap.y - profiles[i].thicknessRemap.x, 0.0f, 0.0f);
worldScales[i] = profiles[i].worldScale;
shapeParams[i] = profiles[i].shapeParameter;
shapeParams[i].w = profiles[i].maxRadius;
transmissionTints[i] = profiles[i].transmissionTint;
thicknessRemaps[i] = new Vector4(profiles[i].thicknessRemap.x, profiles[i].thicknessRemap.y - profiles[i].thicknessRemap.x, 0.0f, 0.0f);
worldScales[i] = new Vector4(profiles[i].worldScale, 0, 0, 0);
shapeParams[i] = profiles[i].shapeParameter;
shapeParams[i].w = profiles[i].maxRadius;
transmissionTints[i] = profiles[i].transmissionTint;
filterKernelsNearField[2 * (n * i + j) + 0] = profiles[i].filterKernelNearField[j].x;
filterKernelsNearField[2 * (n * i + j) + 1] = profiles[i].filterKernelNearField[j].y;
filterKernels[n * i + j].x = profiles[i].filterKernelNearField[j].x;
filterKernels[n * i + j].y = profiles[i].filterKernelNearField[j].y;
filterKernelsFarField[2 * (n * i + j) + 0] = profiles[i].filterKernelFarField[j].x;
filterKernelsFarField[2 * (n * i + j) + 1] = profiles[i].filterKernelFarField[j].y;
filterKernels[n * i + j].z = profiles[i].filterKernelFarField[j].x;
filterKernels[n * i + j].w = profiles[i].filterKernelFarField[j].y;
}
// Old SSS Model >>>

{
int i = SssConstants.SSS_NEUTRAL_PROFILE_ID;
worldScales[i] = 1.0f;
worldScales[i] = Vector4.one;
filterKernelsNearField[2 * (n * i + j) + 0] = 0.0f;
filterKernelsNearField[2 * (n * i + j) + 1] = 1.0f;
}
for (int j = 0, n = SssConstants.SSS_N_SAMPLES_FAR_FIELD; j < n; j++)
{
filterKernelsFarField[2 * (n * i + j) + 0] = 0.0f;
filterKernelsFarField[2 * (n * i + j) + 1] = 1.0f;
filterKernels[n * i + j].x = 0.0f;
filterKernels[n * i + j].y = 1.0f;
filterKernels[n * i + j].z = 0.0f;
filterKernels[n * i + j].w = 1.0f;
}
// Old SSS Model >>>

1
Assets/ScriptableRenderPipeline/ShaderLibrary/Common.hlsl


#define INV_FOUR_PI 0.07957747155
#define HALF_PI 1.57079632679
#define INV_HALF_PI 0.636619772367
#define INFINITY asfloat(0x7F800000)
#define FLT_EPSILON 1.192092896e-07 // Smallest positive number, such that 1.0 + FLT_EPSILON != 1.0
#define FLT_MIN 1.175494351e-38 // Minimum representable positive floating-point number

正在加载...
取消
保存