// Copyright Epic Games, Inc. All Rights Reserved. #include "../Common.ush" #include "../ShaderPrint.ush" #include "../MonteCarlo.ush" #include "HairStrandsVisibilityCommon.ush" #include "HairCardsTextureCommon.ush" ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #if SHADER_VERTEX_RECT || SHADER_PIXEL_RECT uint SampleCount; int2 Atlas_Resolution; int2 Atlas_RectOffset; int2 Atlas_RectResolution; int2 Tile_Coord; int2 Tile_Count; int2 Tile_Resolution; float3 Raster_MinBound; float3 Raster_MaxBound; float3 Raster_AxisX; // Not normalized, contains AABB (full) length float3 Raster_AxisY; // Not normalized, contains AABB (full) length float3 Raster_AxisZ; // Not normalized, contains AABB (full) length // Offset to the first strands vertex // Count of all vertex belonging to this cluster uint Curve_VertexOffset; uint Curve_VertexCount; uint Curve_TotalVertexCount; Buffer Curve_PositionBuffer; Buffer Curve_AttributeBuffer; float GetPixelWorldRadius() { return select(IsOrthoProjection(), 0.5f, 0.5f * max(length(Raster_AxisX) / float(Atlas_RectResolution.x), length(Raster_AxisY) / float(Atlas_RectResolution.y))); } #endif #if SHADER_VERTEX_RECT #include "HairStrandsVertexFactoryCommon.ush" void MainVS( uint VertexId : SV_VertexID, out float4 OutPosition : SV_Position, nointerpolation out uint OutVertexId : SEG_ID) { const int VertexIndex = Curve_VertexOffset + (VertexId / 6); OutVertexId = VertexIndex; int V0_1 = max(VertexIndex - 1, 0); int V0_0 = max(VertexIndex + 0, 0); int V1_0 = max(VertexIndex + 1, 0); int V1_1 = max(VertexIndex + 2, 0); V0_1 = min(V0_1, Curve_TotalVertexCount - 1); V0_0 = min(V0_0, Curve_TotalVertexCount - 1); V1_0 = min(V1_0, Curve_TotalVertexCount - 1); V1_1 = min(V1_1, Curve_TotalVertexCount - 1); const float4 Data0_1 = Curve_PositionBuffer[V0_1]; const float4 Data0 = Curve_PositionBuffer[V0_0]; const float4 Data1 = Curve_PositionBuffer[V1_0]; const float4 Data1_1 = Curve_PositionBuffer[V1_1]; const float3 CP0_1 = Data0_1.xyz; const float3 CP0 = Data0.xyz; const float3 CP1 = Data1.xyz; const float3 CP1_1 = Data1_1.xyz; const float R0 = Data0.w; const float R1 = Data1.w; // CP0 - - - - - -> B // 0 ---2 4 | // | / / | | // | / / | | // | / / | | // 1 3----5 CP1 v T const uint LocalVertexId = (VertexId % 6); const bool bBottom = (LocalVertexId % 2); const bool bLeft = LocalVertexId == 0 || LocalVertexId == 1 || LocalVertexId == 3; float3 CP = 0; float R = 0; float Sign = bLeft ? -1 : 1; if (bBottom) { CP = CP0; R = R0; } else { CP = CP1; R = R1; } float3 CP_T = 0; if (bBottom) { const float3 CP_T0 = Data0_1.w > 0 ? CP0.xyz - CP0_1.xyz : 0; const float3 CP_T1 = CP1.xyz - CP0.xyz; CP_T = normalize(CP_T1 + CP_T0); } else { const float3 CP_T0 = CP1.xyz - CP0.xyz; const float3 CP_T1 = Data1.w > 0 ? CP1_1.xyz - CP1.xyz : 0; CP_T = normalize(CP_T1 + CP_T0); } float2 UV = 0; UV.x = bLeft ? 0.f : 1.f; UV.y = bBottom ? 0.f : 1.f; const float3 CP_B = normalize(cross(CP_T, Raster_AxisZ)); // Clamp poly strip to be at least 2 pixels wide const float PixelRadius = GetPixelWorldRadius(); const float RadiusScale = 2; const float RasterRadius = max(R, PixelRadius * RadiusScale); const float3 WorldPosition = CP.xyz + CP_B * RasterRadius * Sign + CP_T * RasterRadius * Sign; const float3 NormPos = (WorldPosition - Raster_MinBound) / (Raster_MaxBound - Raster_MinBound); const float3 AxisX = normalize(Raster_AxisX); const float3 AxisY = normalize(Raster_AxisY); const float3 AxisZ = normalize(Raster_AxisZ); const float SignX = dot(AxisX, float3(1, 1, 1)) > 0 ? 1 : -1; const float SignY = dot(AxisY, float3(1, 1, 1)) > 0 ? 1 : -1; const float SignZ = dot(AxisZ, float3(1, 1, 1)) > 0 ? 1 : -1; float3 NDCPos = 0; NDCPos.x = dot(AxisX, NormPos) * 2 + (SignX>0 ? -1 : 1); NDCPos.y = dot(AxisY, NormPos) * 2 + (SignY>0 ? -1 : 1); NDCPos.z = dot(AxisZ, NormPos) * (SignZ>0 ? 1 : -1); // Cull invalid segment OutPosition = float4(NDCPos, 1); if (R0 == 0) { OutPosition = float4(INFINITE_FLOAT, INFINITE_FLOAT, INFINITE_FLOAT, 1); } } #endif // VERTEXSHADER ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #if SHADER_PIXEL_RECT #include "HairStrandsVertexFactoryCommon.ush" #define DEBUG_ENABLE 0 // Two output type: // 0 = Depth/Tangent/Attributes // 1 = Coverage void MainPS( in float4 SvPosition : SV_Position, nointerpolation in uint VertexId : SEG_ID, #if PERMUTATION_OUTPUT == 0 out float4 OutDepth : SV_Target0, out float4 OutTangent : SV_Target1, out float4 OutAttribute : SV_Target2 #else out float4 OutCoverage : SV_Target0 #endif ) { float2 InRectUV = float2(SvPosition.xy - Atlas_RectOffset) / float2(Atlas_RectResolution); InRectUV.y = 1 - InRectUV.y; #if PERMUTATION_OUTPUT == 0 float4 OutCoverage = 0; #else float4 OutDepth = 0; float4 OutTangent = 0; float4 OutAttribute = 0; #endif OutCoverage = 0; OutDepth = 0; OutTangent = float4(InRectUV, 0, 0); OutAttribute = 0; const float3 Center = (Raster_MaxBound + Raster_MinBound) * 0.5f; const float3 CardWorldPos = (InRectUV.x * 2 - 1) * Raster_AxisX * 0.5f + (InRectUV.y * 2 - 1) * Raster_AxisY * 0.5f + -0.5f * Raster_AxisZ + Center; const int2 PixelCoord = int2(SvPosition.xy); #if DEBUG_ENABLE bool bDebugEnable = VertexId <= 10; // false;// all(PixelCoord == int2(1600, 10)); if (bDebugEnable) { const float4 Color = float4(1, 1, 0, 1); AddAABBWS(Raster_MinBound, Raster_MaxBound, Color); } #endif const float3 RayO = CardWorldPos; const float3 RayD = normalize(Raster_AxisZ); const float MaxDistance = 10; #if DEBUG_ENABLE if (bDebugEnable) { const float4 Color = float4(1, 0, 0, 1); AddLineWS(RayO, RayO + RayD * MaxDistance, Color, Color); } #endif const float FarDistance = 100000; float ClosestDepth = FarDistance; float3 ClosestTangent = 0; float4 ClosestAttribute = 0; //for (uint VertexIt = 0; VertexIt < Curve_VertexCount-1; ++VertexIt) { const uint VertexIndex0 = clamp(VertexId, 0, Curve_TotalVertexCount - 1); const uint VertexIndex1 = clamp(VertexId+1, 0, Curve_TotalVertexCount - 1); const float4 Data0 = Curve_PositionBuffer[VertexIndex0]; const float4 Data1 = Curve_PositionBuffer[VertexIndex1]; const float4 Attributes0 = Curve_AttributeBuffer[VertexIndex0]; const float4 Attributes1 = Curve_AttributeBuffer[VertexIndex1]; const float3 CP0 = Data0.xyz; const float3 CP1 = Data1.xyz; const float R0 = Data0.w; const float R1 = Data1.w; // Invalid selction, i.e., a segment connecting the end of a strands to the beginning of another strands. if (Data0.w == 0) discard; #if DEBUG_ENABLE if (bDebugEnable) { const float4 Color = float4(0, 1, 0, 1); AddLineWS(CP0, CP1, Color, Color); } #endif const float PixelRadius = GetPixelWorldRadius(); const float3 WorldTangent = normalize(CP1 - CP0); const float3 LocalTangent = float3( dot(WorldTangent, Raster_AxisX), dot(WorldTangent, Raster_AxisY), dot(WorldTangent, -Raster_AxisZ)); for (uint SampleIt = 0; SampleIt < SampleCount; ++SampleIt) { const float2 U = Hammersley(SampleIt, SampleCount, 0) * 2 - 1; const float3 Jitter = U.x * (0.5f * Raster_AxisX / float(Atlas_RectResolution.x)) + U.y * (0.5f * Raster_AxisY / float(Atlas_RectResolution.y)); const float3 RayP0 = RayO + Jitter; const float3 RayP1 = RayO + Jitter + RayD * MaxDistance; #if DEBUG_ENABLE if (bDebugEnable) { const float4 Color = float4(1, 0, 0, 1); AddLineWS(RayP0, RayP1, Color, Color); } #endif // Scale radius in order to avoid having 0 coverage when hair are too thin float VertexU = 0; const float RadiusScaleFactor = 1.5f; const float Distance = Intersection(CP0, CP1, RayP0, RayP1, R0, VertexU) * MaxDistance; if (Distance > 0 && Distance < ClosestDepth) { OutCoverage += saturate(R0* RadiusScaleFactor / PixelRadius); ClosestTangent = LocalTangent; ClosestDepth = saturate(Distance / length(RayP1 - RayP0)); ClosestAttribute= lerp(Attributes0, Attributes1, VertexU); } } } OutCoverage /= SampleCount; const float3 NColor = (ClosestTangent + 1) * 0.5f; OutTangent = float4(NColor, 0); OutDepth = ClosestDepth < FarDistance ? ClosestDepth : 0; OutAttribute = ClosestAttribute; OutCoverage.w = 1; OutTangent.w = 1; OutDepth.w = 1; OutAttribute.w = 1; #if PERMUTATION_OUTPUT == 0 if (OutCoverage.x <= 0) { discard; } #endif } #endif // SHADER_PIXEL_RECT