// Copyright Epic Games, Inc. All Rights Reserved. #include "../Common.ush" #include "../SceneTextureParameters.ush" #include "../DeferredShadingCommon.ush" #include "../ShadowFilteringCommon.ush" #include "HairStrandsDeepShadowCommon.ush" #include "HairStrandsCommon.ush" #include "HairStrandsDeepTransmittanceCommon.ush" #include "HairStrandsVisibilityCommon.ush" #include "HairStrandsVoxelPageCommon.ush" #define DEBUG_ENABLE 0 #if DEBUG_ENABLE #include "../ShaderPrint.ush" #endif //DEBUG_ENABLE #ifndef MAX_HAIR_MACROGROUP_COUNT #error MAX_HAIR_MACROGROUP_COUNT needs to be defined #endif //////////////////////////////////////////////////////////////////////////////////////////////////// // Utils function & structs struct FTransmittanceSettings { int2 AtlasResolution; float4x4 TranslatedWorldToLightTransform; float4 AtlasScaleBias; float3 LightDirection; // Direction from world position to the light float3 TranslatedLightPosition; bool bIsDirectional; float DeepShadowDepthBiasScale; float DeepShadowDensityScale; float TransmittanceKernelApertureInDegree; float3 Random; uint DebugMode; uint TransmittanceKernelType; }; float3 GetViewVector(float3 InTranslatedWorldPosition) { float3 CameraToPixel = GetCameraVectorFromTranslatedWorldPosition(InTranslatedWorldPosition); float3 V = -CameraToPixel; return V; } float3 GetTranslatedWorldPosition(float2 UV, float SceneDepth) { const float2 ScreenPosition = (UV - View.ScreenPositionScaleBias.wz) / View.ScreenPositionScaleBias.xy; return mul(float4(GetScreenPositionForProjectionType(ScreenPosition, SceneDepth), SceneDepth, 1), View.ScreenToTranslatedWorld).xyz; } //////////////////////////////////////////////////////////////////////////////////////////////// // Defines // Need to match FDeepShadowData::MaxMacroGroupCount in HairRendering.h #if SHADER_TRANSMITTANCE_DEEPSHADOW || SHADER_TRANSMITTANCE_VOXEL #if PERMUTATION_GROUP_SIZE == 64 #define TRANSMITTANCE_GROUP_SIZE 64 #elif PERMUTATION_GROUP_SIZE == 32 #define TRANSMITTANCE_GROUP_SIZE 32 #else #error Unknown group size #endif #endif //////////////////////////////////////////////////////////////////////////////////////////////// // Deep shadow #if SHADER_TRANSMITTANCE_DEEPSHADOW StructuredBuffer DeepShadow_AtlasSlotIndexBuffer; StructuredBuffer DeepShadow_ViewInfoBuffer; uint2 DeepShadow_AtlasResolution; uint LightChannelMask; float4 ShadowChannelMask; float4 TranslatedLightPosition_LightDirection; float DeepShadow_DepthBiasScale; float DeepShadow_DensityScale; float LightRadius; float DeepShadow_KernelAperture; float4 DeepShadow_LayerDepths; uint DeepShadow_KernelType; uint DeepShadow_DebugMode; Texture2D DeepShadow_FrontDepthTexture; Texture2D DeepShadow_DomTexture; Buffer IndirectArgsBuffer; RWBuffer OutTransmittanceMask; FHairTransmittanceMask ComputeTransmittance( float3 TranslatedWorldPosition, FTransmittanceSettings Settings, float4 InLayerDepths, Texture2D FrontDepthTexture, Texture2D DomTexture) { FHairTransmittanceMask Out = InitHairTransmittanceMask(); { // LightSpacePosition is 'TranslatedWorldPosition' in LightSpace (TexelCoord.XY / NDC Depth.Z) const float3 LightSpacePosition = ToLightPosition(TranslatedWorldPosition, Settings.TranslatedWorldToLightTransform) * float3(Settings.AtlasScaleBias.xy, 1) + float3(Settings.AtlasScaleBias.zw, 0); float DepthBias = 0; if (Settings.DeepShadowDepthBiasScale > 0) { #if 0 const float DephtBiasSlope = 6; const float DefaultDepthBias = LAYER_DEPTH0 * Settings.DeepShadowDepthBiasScale; DepthBias = (CosLightAngle*DephtBiasSlope + 1) * DefaultDepthBias; #else DepthBias = InLayerDepths[0] * Settings.DeepShadowDepthBiasScale; #endif } // Compute the number of hair count between light & shading point if (Settings.TransmittanceKernelType == 4) { //Out = SampleDOM_PCF_Accurate(LightSpacePosition.xyz, DepthBias, InLayerDepths, FrontDepthTexture, DomTexture, GBuffer, SinLightAngle, HairLUTTexture); Out.HairCount = 0; Out.Visibility = 1; } else { float HairCount = 0; if (Settings.TransmittanceKernelType == 3) { HairCount = SampleDOM_PCSS(LightSpacePosition.xyz, Settings.AtlasResolution, DepthBias, InLayerDepths, FrontDepthTexture, DomTexture, Settings.TransmittanceKernelApertureInDegree, Settings.Random.xy); } else if (Settings.TransmittanceKernelType == 2) { HairCount = SampleDOM_PCF(LightSpacePosition.xyz, DepthBias, InLayerDepths, FrontDepthTexture, DomTexture); } else if (Settings.TransmittanceKernelType == 1) { HairCount = SampleDOM_PCF2x2(LightSpacePosition.xyz, DepthBias, InLayerDepths, FrontDepthTexture, DomTexture); } else { HairCount = 0; } Out.HairCount = HairCount * Settings.DeepShadowDensityScale; Out.Visibility = 1; } if (Settings.DebugMode != 0) { Out.HairCount = 1; } } return Out; } [numthreads(TRANSMITTANCE_GROUP_SIZE, 1, 1)] void MainCS(uint2 DispatchThreadId : SV_DispatchThreadID) { // Note: Use a fixed group count width (HAIR_VISIBILITY_GROUP_COUNT_WIDTH) for avoiding loading the indirect args (to avoid dep. memory fetch) const uint SampleIndex = DispatchThreadId.x + DispatchThreadId.y * TRANSMITTANCE_GROUP_SIZE * HAIR_VISIBILITY_GROUP_COUNT_WIDTH; const uint MaxVisibilityNodeCount = HairStrands.HairSampleCount[0]; if (SampleIndex >= MaxVisibilityNodeCount) { return; } const uint2 PixelCoord = HairStrands.HairSampleCoords[SampleIndex]; const float2 UV = (PixelCoord + float2(0.5f, 0.5f)) / float2(View.BufferSizeAndInvSize.xy); const float3 TransmittanceRandom = float3(InterleavedGradientNoise(PixelCoord, 1), InterleavedGradientNoise(PixelCoord, 2), InterleavedGradientNoise(PixelCoord, 3)); { const FHairSample Sample = UnpackHairSample(HairStrands.HairSampleData[SampleIndex]); if ((LightChannelMask & Sample.LightChannelMask) == 0) { OutTransmittanceMask[SampleIndex] = InitNullPackedHairTransmittanceMask(); return; } const uint MacroGroupIndex = clamp(Sample.MacroGroupId, 0, MAX_HAIR_MACROGROUP_COUNT - 1); const uint AtlasSlotIndex = DeepShadow_AtlasSlotIndexBuffer[MacroGroupIndex]; const FDeepShadowViewInfo ShadowViewInfo = DeepShadow_ViewInfoBuffer[AtlasSlotIndex]; const float4x4 TranslatedWorldToLightTransform = ShadowViewInfo.TranslatedWorldToClip; const float4 AtlasScaleBias = ShadowViewInfo.AtlasScaleBias * float4(DeepShadow_AtlasResolution, DeepShadow_AtlasResolution); const float SceneDepth = ConvertFromDeviceZ(Sample.Depth); const float3 TranslatedWorldPosition = GetTranslatedWorldPosition(UV, SceneDepth); FTransmittanceSettings Settings; Settings.AtlasResolution = DeepShadow_AtlasResolution; Settings.TranslatedWorldToLightTransform = TranslatedWorldToLightTransform; Settings.AtlasScaleBias = AtlasScaleBias; Settings.bIsDirectional = IsLightDirectional(TranslatedLightPosition_LightDirection); Settings.LightDirection = GetTranslatedLightDirection(TranslatedLightPosition_LightDirection, TranslatedWorldPosition); Settings.TranslatedLightPosition = GetTranslatedLightPosition(TranslatedLightPosition_LightDirection, TranslatedWorldPosition); Settings.DeepShadowDepthBiasScale = DeepShadow_DepthBiasScale; Settings.DeepShadowDensityScale = DeepShadow_DensityScale; Settings.TransmittanceKernelApertureInDegree = DeepShadow_KernelAperture; Settings.TransmittanceKernelType = DeepShadow_KernelType; Settings.Random = TransmittanceRandom; Settings.DebugMode = DeepShadow_DebugMode; const FHairTransmittanceMask TransmittanceMask = ComputeTransmittance(TranslatedWorldPosition, Settings, DeepShadow_LayerDepths, DeepShadow_FrontDepthTexture, DeepShadow_DomTexture); OutTransmittanceMask[SampleIndex] = PackTransmittanceMask(TransmittanceMask); } } #endif // #if SHADER_TRANSMITTANCE_DEEPSHADOW //////////////////////////////////////////////////////////////////////////////////////////////// // Voxel volume #if SHADER_TRANSMITTANCE_VOXEL #if PERMUTATION_TRAVERSAL == 1 #define VOXEL_TRAVERSAL_TYPE VOXEL_TRAVERSAL_LINEAR_MIPMAP #else #define VOXEL_TRAVERSAL_TYPE VOXEL_TRAVERSAL_LINEAR #endif #define VOXEL_TRAVERSAL_DEBUG 0 #include "HairStrandsVoxelPageTraversal.ush" #include "../LightGridCommon.ush" #if VOXEL_TRAVERSAL_DEBUG #include "../ShaderPrint.ush" void PrintInfo(uint Index, uint2 PixelCoord, FHairTransmittanceMask TransmittanceMask) { #if 0 const uint2 OutputResolution = uint2(1024, 1024); float2 Origin = (PixelCoord) / float2(OutputResolution); Origin.y += (Index % 8) * 2 * ShaderPrintUniform.FontSize.w; FShaderPrintContext Context = InitShaderPrintContext(true, Origin); Print(Context, TEXT("Hair Count ")); Print(Context, TransmittanceMask.HairCount); Newline(Context); Print(Context, TEXT("Visibility ")); Print(Context, TransmittanceMask.Visibility); Newline(Context); #endif } #endif // VOXEL_TRAVERSAL_DEBUG uint LightChannelMask; float4 TranslatedLightPosition_LightDirection; float LightRadius; #if PERMUTATION_ONE_PASS Texture2D ShadowMaskBitsTexture; #else Texture2D RayMarchMaskTexture; float4 ShadowChannelMask; #endif Buffer IndirectArgsBuffer; RWBuffer OutTransmittanceMask; #include "../VirtualShadowMaps/VirtualShadowMapLightGrid.ush" #define USE_BLUE_NOISE 1 #include "../BlueNoise.ush" DEFINE_HAIR_BLUE_NOISE_JITTER_FUNCTION float3 InternalGetHairVoxelJitter(uint2 PixelCoord, uint SampleIndex, uint MaxSampleCount, uint TimeIndex, uint JitterMode) { // Blue noise is cheaper to compute and h #if USE_BLUE_NOISE float3 RandomSample = GetHairBlueNoiseJitter(PixelCoord, SampleIndex, MaxSampleCount, JitterMode > 1 ? 0 : TimeIndex).xyz; #else float3 RandomSample = GetHairVoxelJitter(PixelCoord, View.StateFrameIndexMod8, JitterMode); #endif return JitterMode > 0 ? RandomSample : float3(0,0,0); } #include "../SSRT/SSRTRayCast.ush" #include "../HZB.ush" uint bUseScreenTrace; float ScreenTraceLength; // TODO: Merge with the version in HairStrandsEnvironementLighting.usf float GetRayVisibility(float3 TranslatedWorldPosition,float SceneDepth, float3 RayDirection, uint2 PixelCoord, uint PixelRayIndex, uint NumPixelSamples) { const float SlopeCompareToleranceScale = 0.5f; const float MaxScreenTraceFraction = ScreenTraceLength * 2.0f / (float)View.ViewSizeAndInvSize.x; float TraceDistance = MaxScreenTraceFraction * 2.0 * GetScreenRayLengthMultiplierForProjectionType(SceneDepth).x; float DepthThresholdScale = View.ProjectionDepthThicknessScale; const float PositionBias = 0.1f; const float3 TranslatedWorldStartPosition = TranslatedWorldPosition + RayDirection * PositionBias; FSSRTCastingSettings CastSettings = CreateDefaultCastSettings(); CastSettings.bStopWhenUncertain = true; bool bHit = false; float Level; float3 HitUVz; bool bRayWasClipped; uint NumSteps = 4; float StartMipLevel = 0; float RayRoughness = .2f; uint2 NoiseCoord = PixelCoord * uint2(NumPixelSamples, 1) + uint2(PixelRayIndex, 0); float StepOffset = VirtualVoxel.JitterMode > 0 ? BlueNoiseScalar(PixelCoord, View.StateFrameIndex) : 0; FSSRTRay Ray = InitScreenSpaceRayFromWorldSpace( TranslatedWorldStartPosition, RayDirection, /* WorldTMax = */ TraceDistance, /* SceneDepth = */ SceneDepth, /* SlopeCompareToleranceScale */ SlopeCompareToleranceScale * DepthThresholdScale * (float)NumSteps, /* bExtendRayToScreenBorder = */ false, /* out */ bRayWasClipped); bool bUncertain; float3 DebugOutput; CastScreenSpaceRay( ClosestHZBTexture, ClosestHZBTextureSampler, StartMipLevel, CastSettings, Ray, RayRoughness, NumSteps, StepOffset - .9f, HZBUvFactorAndInvFactor, false, /* out */ DebugOutput, /* out */ HitUVz, /* out */ Level, /* out */ bHit, /* out */ bUncertain); bHit = bHit && !bUncertain; return bHit ? 0.0f : 1.0f; } FHairTransmittanceMask ComputeTransmittanceVoxel( const uint2 PixelCoord, const float PixelRadius, const float SceneDepth, float3 TranslatedWorldPosition, uint MacroGroupId, FTransmittanceSettings Settings, bool bDebugEnabled) { // Cone opening - Area light transmission is disabled as it is too noisy for now const float TanLightAngle = 0; // Settings.bIsDirectional ? 0 : LightRadius / length(Settings.TranslatedLightPosition - TranslatedWorldPosition); const FVirtualVoxelNodeDesc NodeDesc = UnpackVoxelNode(VirtualVoxel.NodeDescBuffer[MacroGroupId], VirtualVoxel.PageResolution); FVirtualVoxelCommonDesc CommonDesc; CommonDesc.PageCountResolution = VirtualVoxel.PageCountResolution; CommonDesc.PageTextureResolution= VirtualVoxel.PageTextureResolution; CommonDesc.PageResolution = VirtualVoxel.PageResolution; CommonDesc.PageResolutionLog2 = VirtualVoxel.PageResolutionLog2; // Compute the number of hair count between light & shading point const float DistanceThreshold = 100000; float AvgHairCount = 0; float AvgHairVisility = 0; #if PERMUTATION_SUPERSAMPLING const uint SampleCount = 8; for (uint SampleIt = 0; SampleIt < SampleCount; ++SampleIt) #else const uint SampleCount = 1; const uint SampleIt = 0; #endif { float3 SampleRandom = InternalGetHairVoxelJitter(PixelCoord, SampleIt, SampleCount, View.StateFrameIndexMod8, VirtualVoxel.JitterMode); const float PositionBiasScale = 0.5f; const float3 DepthBias = NodeDesc.VoxelWorldSize * (VirtualVoxel.DepthBiasScale_Transmittance * Settings.LightDirection + PositionBiasScale*(SampleRandom*2-1)); const float3 SampleTranslatedWorldPosition = TranslatedWorldPosition + DepthBias; FHairTraversalSettings TraversalSettings = InitHairTraversalSettings(); TraversalSettings.DensityScale = VirtualVoxel.DensityScale_Transmittance; TraversalSettings.CountThreshold = GetOpaqueVoxelValue(); TraversalSettings.DistanceThreshold = DistanceThreshold; TraversalSettings.bDebugEnabled = bDebugEnabled; TraversalSettings.SteppingScale = VirtualVoxel.SteppingScale_Transmittance; TraversalSettings.Random = SampleRandom; TraversalSettings.TanConeAngle = TanLightAngle; TraversalSettings.PixelRadius = PixelRadius; const FHairTraversalResult Result = ComputeHairCountVirtualVoxel( SampleTranslatedWorldPosition, Settings.TranslatedLightPosition, CommonDesc, NodeDesc, VirtualVoxel.PageIndexBuffer, VirtualVoxel.PageTexture, TraversalSettings); InitHairTraversalResult(); if (bUseScreenTrace) { const float HairCountPerScreenSpaceIntersection = 1.5f; // Arbitrary value, visually tweaked const float ScreenTraceVis = GetRayVisibility(TranslatedWorldPosition, SceneDepth, Settings.LightDirection, PixelCoord, SampleIt, SampleCount); AvgHairCount += saturate(1.f-ScreenTraceVis) * HairCountPerScreenSpaceIntersection; } AvgHairCount += Result.HairCount; AvgHairVisility += Result.Visibility; } #if PERMUTATION_SUPERSAMPLING AvgHairCount /= max(1u, SampleCount); AvgHairVisility /= max(1u, SampleCount); #endif FHairTransmittanceMask Out; Out.HairCount = AvgHairCount; Out.Visibility = AvgHairVisility; return Out; } [numthreads(TRANSMITTANCE_GROUP_SIZE, 1, 1)] void MainCS(uint2 DispatchThreadId : SV_DispatchThreadID) #if PERMUTATION_ONE_PASS { const uint SampleIndex = DispatchThreadId.x + DispatchThreadId.y * TRANSMITTANCE_GROUP_SIZE * HAIR_VISIBILITY_GROUP_COUNT_WIDTH; const uint MaxVisibilityNodeCount = HairStrands.HairSampleCount[0]; if (SampleIndex >= MaxVisibilityNodeCount) { return; } const FHairSample Sample = UnpackHairSample(HairStrands.HairSampleData[SampleIndex]); const uint MacroGroupIndex = clamp(Sample.MacroGroupId, 0, MAX_HAIR_MACROGROUP_COUNT - 1); const float SceneDepth = ConvertFromDeviceZ(Sample.Depth); const uint2 PixelCoord = HairStrands.HairSampleCoords[SampleIndex]; const float2 UV = (PixelCoord.xy + float2(0.5f, 0.5f)) / float2(View.BufferSizeAndInvSize.xy); const float PixelRadius = ConvertGivenDepthRadiusForProjectionType(VirtualVoxel.HairCoveragePixelRadiusAtDepth1, SceneDepth); float3 TranslatedWorldPosition = GetTranslatedWorldPosition(UV, SceneDepth); if (View.StereoPassIndex == 1) { TranslatedWorldPosition += VirtualVoxel.TranslatedWorldOffsetStereoCorrection; } const uint4 PackedShadowMask = ShadowMaskBitsTexture[PixelCoord]; const float3 Random = GetHairVoxelJitter(PixelCoord.xy, View.StateFrameIndexMod8, VirtualVoxel.JitterMode); const uint2 LocalPixelPos = PixelCoord; // Use VSM light grid index remapping to ensure we compute transmittance value only for shadowed light // The logic to compute light index needs to be identical as in DeferredLightPixelShader.usf / ClusteredDeferredShadingPixelShader.usf const FCulledLightsGridHeader CulledLightGridHeader = VirtualShadowMapGetLightsGridHeader(LocalPixelPos, SceneDepth); const uint LightCount = min(GetPackedShadowMaskMaxLightCount(), CulledLightGridHeader.NumLights); LOOP for (uint LightIndex = 0; LightIndex < LightCount; ++LightIndex) { const uint OutputIndex = SampleIndex + MaxVisibilityNodeCount * LightIndex; // The Virtual Shadow Remap stores directional lights first const FLocalLightData LightData = VirtualShadowMapGetLocalLightData(CulledLightGridHeader, LightIndex); const float3 TranslatedLightPosition = UnpackLightTranslatedWorldPosition(LightData); // Early out when the shadow mask bits are zero if (UnpackShadowMask(PackedShadowMask, LightIndex) == 0) { OutTransmittanceMask[OutputIndex] = PackTransmittanceMask(InitHairTransmittanceMask()); continue; } // Early out when mismatching light channel if ((Sample.LightChannelMask & UnpackLightingChannelMask(LightData)) == 0) { OutTransmittanceMask[OutputIndex] = InitNullPackedHairTransmittanceMask(); continue; } FTransmittanceSettings Settings; Settings.bIsDirectional = false; Settings.LightDirection = normalize(TranslatedLightPosition - TranslatedWorldPosition); Settings.TranslatedLightPosition = TranslatedLightPosition; Settings.DeepShadowDensityScale = 0; // Use virtual voxel struct value Settings.DeepShadowDepthBiasScale = 0; // Use virtual voxel struct value Settings.Random = Random; #if DEBUG_ENABLE const bool bDebugEnabled = PixelCoord.x == GetCursorPos().x && PixelCoord.y == GetCursorPos().y; #else const bool bDebugEnabled = false; #endif const FHairTransmittanceMask Out = ComputeTransmittanceVoxel( PixelCoord, PixelRadius, SceneDepth, TranslatedWorldPosition, MacroGroupIndex, Settings, bDebugEnabled); OutTransmittanceMask[OutputIndex] = PackTransmittanceMask(Out); } } #else // PERMUTATION_ONE_PASS { const uint SampleIndex = DispatchThreadId.x + DispatchThreadId.y * TRANSMITTANCE_GROUP_SIZE * HAIR_VISIBILITY_GROUP_COUNT_WIDTH; const uint MaxVisibilityNodeCount = HairStrands.HairSampleCount[0]; if (SampleIndex >= MaxVisibilityNodeCount) { return; } const uint2 PixelCoord = HairStrands.HairSampleCoords[SampleIndex]; if (dot(RayMarchMaskTexture.Load(uint3(PixelCoord, 0)), ShadowChannelMask) == 0) { // This pixel is already fully shadowed opaque. (RayMarchMaskTexture is computed using the closest sample as of today) OutTransmittanceMask[SampleIndex] = InitNullPackedHairTransmittanceMask(); return; } #if VOXEL_TRAVERSAL_DEBUG const bool bDebugEnabled = PixelCoord.x == GetCursorPos().x && PixelCoord.y == GetCursorPos().y; #else const bool bDebugEnabled = false; #endif const float2 UV = (PixelCoord.xy + float2(0.5f, 0.5f)) / float2(View.BufferSizeAndInvSize.xy); { const FHairSample Sample = UnpackHairSample(HairStrands.HairSampleData[SampleIndex]); if ((LightChannelMask & Sample.LightChannelMask) == 0) { OutTransmittanceMask[SampleIndex] = InitNullPackedHairTransmittanceMask(); return; } const float SceneDepth = ConvertFromDeviceZ(Sample.Depth); float3 TranslatedWorldPosition = GetTranslatedWorldPosition(UV, SceneDepth); if (View.StereoPassIndex == 1) { TranslatedWorldPosition += VirtualVoxel.TranslatedWorldOffsetStereoCorrection; } const uint MacroGroupIndex = clamp(Sample.MacroGroupId, 0, MAX_HAIR_MACROGROUP_COUNT - 1); FTransmittanceSettings Settings; Settings.bIsDirectional = IsLightDirectional(TranslatedLightPosition_LightDirection); Settings.LightDirection = GetTranslatedLightDirection(TranslatedLightPosition_LightDirection, TranslatedWorldPosition); Settings.TranslatedLightPosition = GetTranslatedLightPosition(TranslatedLightPosition_LightDirection, TranslatedWorldPosition); Settings.DeepShadowDensityScale = 0; // Use virtual voxel struct value Settings.DeepShadowDepthBiasScale = 0; // Use virtual voxel struct value Settings.Random = GetHairVoxelJitter(PixelCoord.xy, View.StateFrameIndexMod8, VirtualVoxel.JitterMode); const float PixelRadius = ConvertGivenDepthRadiusForProjectionType(VirtualVoxel.HairCoveragePixelRadiusAtDepth1, SceneDepth); const FHairTransmittanceMask Out = ComputeTransmittanceVoxel(PixelCoord, PixelRadius, SceneDepth, TranslatedWorldPosition, MacroGroupIndex, Settings, bDebugEnabled); #if VOXEL_TRAVERSAL_DEBUG if (bDebugEnabled) { PrintInfo(SampleIndex, PixelCoord, Out); } #endif OutTransmittanceMask[SampleIndex] = PackTransmittanceMask(Out); } } #endif // PERMUTATION_ONE_PASS #endif // SHADER_TRANSMITTANCE_VOXEL //////////////////////////////////////////////////////////////////////////////////////////////// #if SHADER_CLEAR uint ElementCount; RWBuffer OutputMask; [numthreads(64, 1, 1)] void MainCS(uint2 DispatchThreadId : SV_DispatchThreadID) { const uint Index = DispatchThreadId.x; if (Index >= ElementCount) { return; } OutputMask[Index] = PackTransmittanceMask(InitHairTransmittanceMask()); } #endif // SHADER_CLEAR