HairUpdateStrands.hlsl 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. // Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
  2. // SPDX-FileCopyrightText: 2026 Jorrit Rouwe
  3. // SPDX-License-Identifier: MIT
  4. #include "HairUpdateStrandsBindings.h"
  5. #include "HairCommon.h"
  6. void ApplyLRA(float3 inX0, float inMaxDist, JPH_IN_OUT(JPH_HairPosition) ioV0)
  7. {
  8. float3 delta = ioV0.mPosition - inX0;
  9. float delta_len_sq = dot(delta, delta);
  10. if (delta_len_sq > JPH_Square(inMaxDist))
  11. ioV0.mPosition = inX0 + delta * inMaxDist / sqrt(delta_len_sq);
  12. }
  13. void ApplyStretchShear(JPH_IN_OUT(JPH_HairPosition) ioV0, JPH_IN_OUT(JPH_HairPosition) ioV1, float inLength01, float inInvMass0, float inInvMass1, JPH_IN(JPH_HairMaterial) inMaterial)
  14. {
  15. // Inertia of a thin rod of length L ~ m * L^2, we take the maximum mass of the two vertices
  16. float rod_inv_mass = min(inInvMass0, inInvMass1) / inMaterial.mInertiaMultiplier; // / L^2, which we'll apply later
  17. // Equation 37 from "Position and Orientation Based Cosserat Rods" - Kugelstadt and Schoemer - SIGGRAPH 2016
  18. float denom = inInvMass0 + inInvMass1 + 4.0f * rod_inv_mass /* / L^2 * L^2 cancels */ + inMaterial.mStretchComplianceInvDeltaTimeSq;
  19. if (denom >= 1.0e-12f)
  20. {
  21. float3 x0 = ioV0.mPosition;
  22. float3 x1 = ioV1.mPosition;
  23. JPH_Quat rotation1 = ioV0.mRotation;
  24. float3 d3 = JPH_QuatRotateAxisZ(rotation1);
  25. float3 delta = (x1 - x0 - d3 * inLength01) / denom;
  26. ioV0.mPosition = x0 + inInvMass0 * delta;
  27. ioV1.mPosition = x1 - inInvMass1 * delta;
  28. // q * e3_bar = q * (0, 0, -1, 0) = [-qy, qx, -qw, qz]
  29. JPH_Quat q_e3_bar = JPH_Quat(-rotation1.y, rotation1.x, -rotation1.w, rotation1.z);
  30. rotation1 += (2.0f * rod_inv_mass / inLength01 /* / L^2 * L => / L */) * JPH_QuatImaginaryMulQuat(delta, q_e3_bar);
  31. ioV0.mRotation = normalize(rotation1);
  32. }
  33. }
  34. void ApplyBendTwist(JPH_IN_OUT(JPH_HairPosition) ioV0, JPH_IN_OUT(JPH_HairPosition) ioV1, JPH_Quat inOmega0, float inLength01, float inLength12, float inStrandFraction1, float inInvMass0, float inInvMass1, float inInvMass2, JPH_IN(JPH_HairMaterial) inMaterial)
  35. {
  36. // Inertia of a thin rod of length L ~ m * L^2, we take the maximum mass of the two vertices
  37. float rod_inv_mass = min(inInvMass0, inInvMass1) / (inMaterial.mInertiaMultiplier * JPH_Square(inLength01));
  38. float rod2_inv_mass = min(inInvMass1, inInvMass2) / (inMaterial.mInertiaMultiplier * JPH_Square(inLength12));
  39. // Calculate multiplier for the bend compliance based on strand fraction
  40. float fraction = inStrandFraction1 * 3.0f;
  41. uint idx = uint(fraction);
  42. fraction = fraction - float(idx);
  43. float multiplier = inMaterial.mBendComplianceMultiplier[idx] * (1.0f - fraction) + inMaterial.mBendComplianceMultiplier[idx + 1] * fraction;
  44. // Equation 40 from "Position and Orientation Based Cosserat Rods" - Kugelstadt and Schoemer - SIGGRAPH 2016
  45. float denom = rod_inv_mass + rod2_inv_mass + inMaterial.mBendComplianceInvDeltaTimeSq * multiplier;
  46. if (denom >= 1.0e-12f)
  47. {
  48. JPH_Quat rotation1 = ioV0.mRotation;
  49. JPH_Quat rotation2 = ioV1.mRotation;
  50. JPH_Quat omega = JPH_QuatMulQuat(JPH_QuatConjugate(rotation1), rotation2);
  51. JPH_Quat omega_min_omega0 = omega - inOmega0;
  52. JPH_Quat omega_plus_omega0 = omega + inOmega0;
  53. // Take the shortest of the two rotations
  54. JPH_Quat delta_omega = dot(omega_plus_omega0, omega_plus_omega0) < dot(omega_min_omega0, omega_min_omega0) ? omega_plus_omega0 : omega_min_omega0;
  55. delta_omega /= denom;
  56. delta_omega.w = 0; // Scalar part needs to be zero because the real part of the Darboux vector doesn't vanish, see text between eq. 39 and 40.
  57. JPH_Quat delta_rod2 = rod2_inv_mass * JPH_QuatMulQuat(rotation1, delta_omega);
  58. rotation1 += rod_inv_mass * JPH_QuatMulQuat(rotation2, delta_omega);
  59. rotation2 -= delta_rod2;
  60. ioV0.mRotation = normalize(rotation1);
  61. ioV1.mRotation = normalize(rotation2);
  62. }
  63. }
  64. JPH_SHADER_FUNCTION_BEGIN(void, main, cHairPerStrandBatch, 1, 1)
  65. JPH_SHADER_PARAM_THREAD_ID(tid)
  66. JPH_SHADER_FUNCTION_END
  67. {
  68. // Check if this is a valid strand
  69. uint strand_idx = tid.x;
  70. if (strand_idx >= cNumStrands)
  71. return;
  72. uint strand_vtx_count = GetStrandVertexCount(gStrandVertexCounts, strand_idx);
  73. // Load the material
  74. JPH_HairMaterial material = gMaterials[GetStrandMaterialIndex(gStrandMaterialIndex, strand_idx)];
  75. // Load the first two vertices
  76. uint vtx_idx_to_load = strand_idx;
  77. float inv_mass0 = GetVertexInvMass(gVerticesFixed, vtx_idx_to_load);
  78. float length0 = gInitialLengths[vtx_idx_to_load];
  79. JPH_HairPosition v0 = gPositions[vtx_idx_to_load];
  80. // LRA: Vertex everything is attached to
  81. float3 x0 = gInitialPositions[vtx_idx_to_load];
  82. // LRA: Tracks the distance from the first vertex
  83. float max_dist = length0;
  84. vtx_idx_to_load += cNumStrands;
  85. float inv_mass1 = GetVertexInvMass(gVerticesFixed, vtx_idx_to_load);
  86. float strand_fraction1 = GetVertexStrandFraction(gStrandFractions, vtx_idx_to_load);
  87. float length1 = gInitialLengths[vtx_idx_to_load];
  88. JPH_HairPosition v1 = gPositions[vtx_idx_to_load];
  89. // Process 2nd vertex
  90. if (material.mEnableLRA && inv_mass1 > 0.0f)
  91. ApplyLRA(x0, max_dist, v1);
  92. max_dist += length1;
  93. uint vtx_idx_to_retire = strand_idx;
  94. for (uint vtx = 2; vtx < strand_vtx_count; ++vtx)
  95. {
  96. // Get the initial rotation difference from the middle vertex
  97. JPH_Quat omega0 = JPH_QuatDecompress(gOmega0s[vtx_idx_to_load]);
  98. // Load the next vertex
  99. vtx_idx_to_load += cNumStrands;
  100. float inv_mass2 = GetVertexInvMass(gVerticesFixed, vtx_idx_to_load);
  101. float strand_fraction2 = GetVertexStrandFraction(gStrandFractions, vtx_idx_to_load);
  102. float length2 = gInitialLengths[vtx_idx_to_load];
  103. JPH_HairPosition v2 = gPositions[vtx_idx_to_load];
  104. // Process newly added vertex
  105. if (material.mEnableLRA && inv_mass2 > 0.0f)
  106. ApplyLRA(x0, max_dist, v2);
  107. max_dist += length2;
  108. // Stitched mode as per Strand-based Hair System - Pedersen - SIGGRAPH 2022
  109. ApplyStretchShear(v1, v2, length1, inv_mass1, inv_mass2, material);
  110. ApplyStretchShear(v0, v1, length0, inv_mass0, inv_mass1, material);
  111. ApplyBendTwist(v0, v1, omega0, length0, length1, strand_fraction1, inv_mass0, inv_mass1, inv_mass2, material);
  112. // Retire vertex
  113. gPositions[vtx_idx_to_retire] = v0;
  114. vtx_idx_to_retire += cNumStrands;
  115. // Shift the vertices
  116. inv_mass0 = inv_mass1;
  117. inv_mass1 = inv_mass2;
  118. strand_fraction1 = strand_fraction2;
  119. length0 = length1;
  120. length1 = length2;
  121. v0 = v1;
  122. v1 = v2;
  123. }
  124. // Retire 2nd to last vertex
  125. gPositions[vtx_idx_to_retire] = v0;
  126. vtx_idx_to_retire += cNumStrands;
  127. // Cannot calculate rotation for last vertex, take the 2nd last
  128. v1.mRotation = v0.mRotation;
  129. // Retire last vertex
  130. gPositions[vtx_idx_to_retire] = v1;
  131. }