This commit is contained in:
2026-03-04 00:50:15 -08:00
parent 9126175569
commit 4211317c03
569 changed files with 122194 additions and 0 deletions

View File

@ -0,0 +1,42 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2026 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include "HairApplyDeltaTransformBindings.h"
#include "HairCommon.h"
JPH_SHADER_FUNCTION_BEGIN(void, main, cHairPerVertexBatch, 1, 1)
JPH_SHADER_PARAM_THREAD_ID(tid)
JPH_SHADER_FUNCTION_END
{
// Check if this is a valid vertex
uint vtx = tid.x + cNumStrands; // Skip the root of each strand, it's fixed
if (vtx >= cNumVertices)
return;
if (IsVertexFixed(gVerticesFixed, vtx))
return;
// Load the material
uint strand_idx = vtx % cNumStrands;
JPH_HairMaterial material = gMaterials[GetStrandMaterialIndex(gStrandMaterialIndex, strand_idx)];
// Load the vertex
float strand_fraction = GetVertexStrandFraction(gStrandFractions, vtx);
JPH_HairPosition pos = gPositions[vtx];
JPH_HairVelocity vel = gVelocities[vtx];
// Transform the position so that it stays in the same place in world space (if influence is 1)
float influence = GradientSamplerSample(material.mWorldTransformInfluence, strand_fraction);
pos.mPosition += influence * (JPH_Mat44Mul3x4Vec3(cDeltaTransform, pos.mPosition) - pos.mPosition);
// Linear interpolate the rotation based on the influence
pos.mRotation = normalize(JPH_QuatMulQuat(influence * cDeltaTransformQuat + float4(0, 0, 0, 1.0f - influence), pos.mRotation));
// Transform velocities
vel.mVelocity = JPH_Mat44Mul3x3Vec3(cDeltaTransform, vel.mVelocity);
vel.mAngularVelocity = JPH_Mat44Mul3x3Vec3(cDeltaTransform, vel.mAngularVelocity);
// Write back vertex
gPositions[vtx] = pos;
gVelocities[vtx] = vel;
}

View File

@ -0,0 +1,14 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2026 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include "HairStructs.h"
JPH_SHADER_BIND_BEGIN(JPH_HairApplyDeltaTransform)
JPH_SHADER_BIND_BUFFER(JPH_uint, gVerticesFixed)
JPH_SHADER_BIND_BUFFER(JPH_uint, gStrandFractions)
JPH_SHADER_BIND_BUFFER(JPH_uint, gStrandMaterialIndex)
JPH_SHADER_BIND_BUFFER(JPH_HairMaterial, gMaterials)
JPH_SHADER_BIND_RW_BUFFER(JPH_HairPosition, gPositions)
JPH_SHADER_BIND_RW_BUFFER(JPH_HairVelocity, gVelocities)
JPH_SHADER_BIND_END(JPH_HairApplyDeltaTransform)

View File

@ -0,0 +1,19 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2026 Jorrit Rouwe
// SPDX-License-Identifier: MIT
void ApplyGlobalPose(JPH_IN_OUT(JPH_HairPosition) ioPos, float3 inRestPosition, JPH_Quat inRestOrientation, JPH_IN(JPH_HairGlobalPoseTransform) inGlobalPoseTransform, JPH_IN(JPH_HairMaterial) inMaterial, float inStrandFraction)
{
// LERP between stored global pose and global pose skinned to the scalp
float skin_factor = GradientSamplerSample(inMaterial.mSkinGlobalPose, inStrandFraction);
float3 in_position = inRestPosition;
in_position += skin_factor * (inGlobalPoseTransform.mPosition + JPH_QuatMulVec3(inGlobalPoseTransform.mRotation, in_position) - in_position);
JPH_Quat in_rotation = inRestOrientation;
in_rotation += skin_factor * (JPH_QuatMulQuat(inGlobalPoseTransform.mRotation, in_rotation) - in_rotation);
// LERP between simulated position and skinned position
float pose_factor = GradientSamplerSample(inMaterial.mGlobalPose, inStrandFraction);
ioPos.mPosition += pose_factor * (in_position - ioPos.mPosition);
ioPos.mRotation += pose_factor * (in_rotation - ioPos.mRotation);
ioPos.mRotation = normalize(ioPos.mRotation);
}

View File

@ -0,0 +1,38 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2026 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include "HairApplyGlobalPoseBindings.h"
#include "HairCommon.h"
#include "HairApplyGlobalPose.h"
JPH_SHADER_FUNCTION_BEGIN(void, main, cHairPerVertexBatch, 1, 1)
JPH_SHADER_PARAM_THREAD_ID(tid)
JPH_SHADER_FUNCTION_END
{
// Check if this is a valid vertex
uint vtx = tid.x + cNumStrands; // Skip the root of each strand, it's fixed
if (vtx >= cNumVertices)
return;
if (IsVertexFixed(gVerticesFixed, vtx))
return;
// Load the material
uint strand_idx = vtx % cNumStrands;
JPH_HairMaterial material = gMaterials[GetStrandMaterialIndex(gStrandMaterialIndex, strand_idx)];
// Load the vertex
float strand_fraction = GetVertexStrandFraction(gStrandFractions, vtx);
float3 initial_pos = gInitialPositions[vtx];
float4 initial_bishop = JPH_QuatDecompress(gInitialBishops[vtx]);
JPH_HairGlobalPoseTransform global_pose_transform = gGlobalPoseTransforms[strand_idx];
// Only apply global pose
JPH_HairPosition pos;
pos.mPosition = float3(0, 0, 0);
pos.mRotation = float4(0, 0, 0, 0);
ApplyGlobalPose(pos, initial_pos, initial_bishop, global_pose_transform, material, strand_fraction);
// Write back vertex
gPositions[vtx] = pos;
}

View File

@ -0,0 +1,16 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2026 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include "HairStructs.h"
JPH_SHADER_BIND_BEGIN(JPH_HairApplyGlobalPose)
JPH_SHADER_BIND_BUFFER(JPH_uint, gVerticesFixed)
JPH_SHADER_BIND_BUFFER(JPH_uint, gStrandFractions)
JPH_SHADER_BIND_BUFFER(JPH_float3, gInitialPositions)
JPH_SHADER_BIND_BUFFER(JPH_uint, gInitialBishops)
JPH_SHADER_BIND_BUFFER(JPH_uint, gStrandMaterialIndex)
JPH_SHADER_BIND_BUFFER(JPH_HairMaterial, gMaterials)
JPH_SHADER_BIND_BUFFER(JPH_HairGlobalPoseTransform, gGlobalPoseTransforms)
JPH_SHADER_BIND_RW_BUFFER(JPH_HairPosition, gPositions)
JPH_SHADER_BIND_END(JPH_HairApplyGlobalPose)

View File

@ -0,0 +1,114 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2026 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include "HairCalculateCollisionPlanesBindings.h"
#include "HairCommon.h"
JPH_SHADER_FUNCTION_BEGIN(void, main, cHairPerVertexBatch, 1, 1)
JPH_SHADER_PARAM_THREAD_ID(tid)
JPH_SHADER_FUNCTION_END
{
// Check if this is a valid vertex
uint vtx = tid.x + cNumStrands; // Skip the root of each strand, it's fixed
if (vtx >= cNumVertices)
return;
// Load the vertex
float3 pos = gPositions[vtx].mPosition;
// Start with a plane that is far away (i.e. no collision)
JPH_HairCollisionPlane collision_plane;
collision_plane.mPlane = float4(1, 0, 0, 1.0e6f);
collision_plane.mShapeIndex = 0;
float largest_penetration = -1.0e6f;
// Loop over all shapes
uint current_idx = 0;
uint current_plane = 0;
for (uint current_shape_idx = 0;; ++current_shape_idx)
{
// Find most facing plane
float max_distance = -1.0e6f;
float3 max_plane_normal = float3(0, 0, 0);
uint max_plane_face_info = 0;
// Get number of faces in this shape
uint nf = gShapeIndices[current_idx++];
if (nf == 0)
break;
for (uint f = 0; f < nf; ++f)
{
// Get the plane
JPH_Plane plane = gShapePlanes[current_plane++];
float distance = JPH_PlaneSignedDistance(plane, pos);
if (distance > max_distance)
{
max_distance = distance;
max_plane_normal = JPH_PlaneGetNormal(plane);
max_plane_face_info = current_idx;
}
// Skip over vertex start and end
current_idx += 2;
}
// Project point onto that plane, in local space to the vertex
float3 closest_point = -max_distance * max_plane_normal;
// Check edges if we're outside the hull (when inside we know the closest face is also the closest point to the surface)
bool is_outside = max_distance > 0.0f;
if (is_outside)
{
// Loop over edges
float closest_point_dist_sq = 1.0e12f;
uint vi = gShapeIndices[max_plane_face_info];
uint vi_end = gShapeIndices[max_plane_face_info + 1];
float3 p1 = gShapeVertices[gShapeIndices[vi_end - 1]];
for (; vi < vi_end; ++vi)
{
// Get edge points
float3 p2 = gShapeVertices[gShapeIndices[vi]];
// Check if the position is outside the edge (if not, the face will be closer)
float3 p1_p2 = p2 - p1;
float3 p1_pos = p1 - pos;
float3 edge_normal = cross(p1_p2, max_plane_normal);
if (dot(edge_normal, p1_pos) <= 0.0f)
{
// Get closest point on edge
float3 closest = JPH_GetClosestPointOnLine(p1_pos, p1_p2);
float distance_sq = dot(closest, closest);
if (distance_sq < closest_point_dist_sq)
{
closest_point_dist_sq = distance_sq;
closest_point = closest;
}
}
// Cycle vertex
p1 = p2;
}
}
// Check if this is the largest penetration
float3 normal = -closest_point;
float normal_length = length(normal);
float penetration = normal_length;
if (is_outside)
penetration = -penetration;
else
normal = -normal;
if (penetration > largest_penetration)
{
// Calculate contact plane
normal = normal_length > 0.0f? normal / normal_length : max_plane_normal;
collision_plane.mPlane = JPH_PlaneFromPointAndNormal(pos + closest_point, normal);
collision_plane.mShapeIndex = current_shape_idx;
largest_penetration = penetration;
}
}
gCollisionPlanes[vtx] = collision_plane;
}

View File

@ -0,0 +1,13 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2026 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include "HairStructs.h"
JPH_SHADER_BIND_BEGIN(JPH_HairCalculateCollisionPlanes)
JPH_SHADER_BIND_BUFFER(JPH_HairPosition, gPositions)
JPH_SHADER_BIND_BUFFER(JPH_Plane, gShapePlanes)
JPH_SHADER_BIND_BUFFER(JPH_float3, gShapeVertices)
JPH_SHADER_BIND_BUFFER(JPH_uint, gShapeIndices)
JPH_SHADER_BIND_RW_BUFFER(JPH_HairCollisionPlane, gCollisionPlanes)
JPH_SHADER_BIND_END(JPH_HairCalculateCollisionPlanes)

View File

@ -0,0 +1,16 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2026 Jorrit Rouwe
// SPDX-License-Identifier: MIT
float3 SkinRenderVertex(uint inVertexIndex)
{
// Calculating resulting render position
float3 out_position = float3(0, 0, 0);
for (uint idx = inVertexIndex * cHairNumSVertexInfluences, idx_end = idx + cHairNumSVertexInfluences; idx < idx_end; ++idx)
{
JPH_HairSVertexInfluence inf = gSVertexInfluences[idx];
JPH_HairPosition sim_vtx = gPositions[inf.mVertexIndex];
out_position += inf.mWeight * (sim_vtx.mPosition + JPH_QuatMulVec3(sim_vtx.mRotation, inf.mRelativePosition));
}
return out_position;
}

View File

@ -0,0 +1,22 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2026 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include "HairCalculateRenderPositionsBindings.h"
#include "HairCommon.h"
#include "HairCalculateRenderPositions.h"
JPH_SHADER_FUNCTION_BEGIN(void, main, cHairPerRenderVertexBatch, 1, 1)
JPH_SHADER_PARAM_THREAD_ID(tid)
JPH_SHADER_FUNCTION_END
{
// Check if this is a valid vertex
uint vtx = tid.x;
if (vtx >= cNumRenderVertices)
return;
float3 out_position = SkinRenderVertex(vtx);
// Copy the vertex position to the output buffer
gRenderPositions[vtx] = out_position;
}

View File

@ -0,0 +1,16 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2026 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include "HairStructs.h"
// Overridable output type
#ifndef JPH_SHADER_BIND_RENDER_POSITIONS
#define JPH_SHADER_BIND_RENDER_POSITIONS(name) JPH_SHADER_BIND_RW_BUFFER(JPH_float3, name)
#endif
JPH_SHADER_BIND_BEGIN(JPH_HairCalculateRenderPositions)
JPH_SHADER_BIND_BUFFER(JPH_HairSVertexInfluence, gSVertexInfluences)
JPH_SHADER_BIND_BUFFER(JPH_HairPosition, gPositions)
JPH_SHADER_BIND_RENDER_POSITIONS(gRenderPositions)
JPH_SHADER_BIND_END(JPH_HairCalculateRenderPositions)

View File

@ -0,0 +1,56 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2026 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include "ShaderMath.h"
#include "ShaderMat44.h"
#include "ShaderQuat.h"
#include "ShaderPlane.h"
#include "ShaderVec3.h"
// The density and velocity fields are stored in fixed point while accumulating, this constant converts from float to fixed point.
JPH_SHADER_CONSTANT(int, cFloatToFixed, 1 << 10)
JPH_SHADER_CONSTANT(float, cFixedToFloat, 1.0f / float(cFloatToFixed))
bool IsVertexFixed(JPH_SHADER_BUFFER(JPH_uint) inVertexFixed, uint inVertexIndex)
{
return (inVertexFixed[inVertexIndex >> 5] & (1u << (inVertexIndex & 31))) != 0;
}
float GetVertexInvMass(JPH_SHADER_BUFFER(JPH_uint) inVertexFixed, uint inVertexIndex)
{
return IsVertexFixed(inVertexFixed, inVertexIndex)? 0.0f : 1.0f;
}
float GetVertexStrandFraction(JPH_SHADER_BUFFER(JPH_uint) inStrandFractions, uint inVertexIndex)
{
return ((inStrandFractions[inVertexIndex >> 2] >> ((inVertexIndex & 3) << 3)) & 0xff) * (1.0f / 255.0f);
}
uint GetStrandVertexCount(JPH_SHADER_BUFFER(JPH_uint) inStrandVertexCounts, uint inStrandIndex)
{
return (inStrandVertexCounts[inStrandIndex >> 2] >> ((inStrandIndex & 3) << 3)) & 0xff;
}
uint GetStrandMaterialIndex(JPH_SHADER_BUFFER(JPH_uint) inStrandMaterialIndex, uint inStrandIndex)
{
return (inStrandMaterialIndex[inStrandIndex >> 2] >> ((inStrandIndex & 3) << 3)) & 0xff;
}
float GradientSamplerSample(float4 inSampler, float inStrandFraction)
{
return min(inSampler.w, max(inSampler.z, inSampler.y + inStrandFraction * inSampler.x));
}
void GridPositionToIndexAndFraction(float3 inPosition, JPH_OUT(uint3) outIndex, JPH_OUT(float3) outFraction)
{
// Get position in grid space
float3 grid_pos = min(max(inPosition - cGridOffset, float3(0, 0, 0)) * cGridScale, cGridSizeMin1);
outIndex = min(uint3(grid_pos), cGridSizeMin2);
outFraction = grid_pos - float3(outIndex);
}
uint GridIndexToBufferIndex(uint3 inIndex)
{
return inIndex.x + inIndex.y * cGridStride.y + inIndex.z * cGridStride.z;
}

View File

@ -0,0 +1,50 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2026 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include "HairGridAccumulateBindings.h"
#include "HairCommon.h"
void AtomicAddVelocityAndDensity(uint inIndex, int4 inValue)
{
JPH_AtomicAdd(gVelocityAndDensity[inIndex].x, inValue.x);
JPH_AtomicAdd(gVelocityAndDensity[inIndex].y, inValue.y);
JPH_AtomicAdd(gVelocityAndDensity[inIndex].z, inValue.z);
JPH_AtomicAdd(gVelocityAndDensity[inIndex].w, inValue.w);
}
JPH_SHADER_FUNCTION_BEGIN(void, main, cHairPerVertexBatch, 1, 1)
JPH_SHADER_PARAM_THREAD_ID(tid)
JPH_SHADER_FUNCTION_END
{
// Check that we are processing a valid vertex
uint vtx = tid.x + cNumStrands; // Skip the root of each strand, it's fixed
if (vtx >= cNumVertices)
return;
if (IsVertexFixed(gVerticesFixed, vtx))
return;
// Convert position to grid index and fraction
uint3 index;
float3 ma;
GridPositionToIndexAndFraction(gPositions[vtx].mPosition, index, ma);
float3 a = float3(1, 1, 1) - ma;
// Get velocity
float4 velocity_and_density = float4(gVelocities[vtx].mVelocity, 1) * cFloatToFixed;
// Calculate contribution of density and velocity for each cell
uint3 stride = cGridStride;
uint adr_000 = GridIndexToBufferIndex(index);
uint adr_100 = adr_000 + 1;
uint adr_010 = adr_000 + stride.y;
uint adr_110 = adr_010 + 1;
AtomicAddVelocityAndDensity(adr_000, (int4)round( a.x * a.y * a.z * velocity_and_density));
AtomicAddVelocityAndDensity(adr_100, (int4)round(ma.x * a.y * a.z * velocity_and_density));
AtomicAddVelocityAndDensity(adr_010, (int4)round( a.x * ma.y * a.z * velocity_and_density));
AtomicAddVelocityAndDensity(adr_110, (int4)round(ma.x * ma.y * a.z * velocity_and_density));
AtomicAddVelocityAndDensity(adr_000 + stride.z, (int4)round( a.x * a.y * ma.z * velocity_and_density));
AtomicAddVelocityAndDensity(adr_100 + stride.z, (int4)round(ma.x * a.y * ma.z * velocity_and_density));
AtomicAddVelocityAndDensity(adr_010 + stride.z, (int4)round( a.x * ma.y * ma.z * velocity_and_density));
AtomicAddVelocityAndDensity(adr_110 + stride.z, (int4)round(ma.x * ma.y * ma.z * velocity_and_density));
}

View File

@ -0,0 +1,12 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2026 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include "HairStructs.h"
JPH_SHADER_BIND_BEGIN(JPH_HairGridAccumulate)
JPH_SHADER_BIND_BUFFER(JPH_uint, gVerticesFixed)
JPH_SHADER_BIND_BUFFER(JPH_HairPosition, gPositions)
JPH_SHADER_BIND_BUFFER(JPH_HairVelocity, gVelocities)
JPH_SHADER_BIND_RW_BUFFER(JPH_int4, gVelocityAndDensity)
JPH_SHADER_BIND_END(JPH_HairGridAccumulate)

View File

@ -0,0 +1,17 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2026 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include "HairGridClearBindings.h"
#include "HairCommon.h"
JPH_SHADER_FUNCTION_BEGIN(void, main, cHairPerGridCellBatch, 1, 1)
JPH_SHADER_PARAM_THREAD_ID(tid)
JPH_SHADER_FUNCTION_END
{
uint index = tid.x;
if (index >= cNumGridPoints)
return;
gVelocityAndDensity[index] = int4(0, 0, 0, 0);
}

View File

@ -0,0 +1,9 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2026 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include "HairStructs.h"
JPH_SHADER_BIND_BEGIN(JPH_HairGridClear)
JPH_SHADER_BIND_RW_BUFFER(JPH_int4, gVelocityAndDensity)
JPH_SHADER_BIND_END(JPH_HairGridClear)

View File

@ -0,0 +1,26 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2026 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include "HairGridNormalizeBindings.h"
#include "HairCommon.h"
JPH_SHADER_FUNCTION_BEGIN(void, main, cHairPerGridCellBatch, 1, 1)
JPH_SHADER_PARAM_THREAD_ID(tid)
JPH_SHADER_FUNCTION_END
{
uint index = tid.x;
if (index >= cNumGridPoints)
return;
// Convert from fixed point back to float and divide velocity by density to get average velocity
float4 v = (float4)gVelocityAndDensity[index] * cFixedToFloat;
float density = v.w;
if (density > 1.0e-12f)
{
v.x /= density;
v.y /= density;
v.z /= density;
}
gVelocityAndDensity[index] = asint(v);
}

View File

@ -0,0 +1,9 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2026 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include "HairStructs.h"
JPH_SHADER_BIND_BEGIN(JPH_HairGridNormalize)
JPH_SHADER_BIND_RW_BUFFER(JPH_int4, gVelocityAndDensity)
JPH_SHADER_BIND_END(JPH_HairGridNormalize)

View File

@ -0,0 +1,88 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2026 Jorrit Rouwe
// SPDX-License-Identifier: MIT
float DeltaDensity(uint inIndex)
{
return gVelocityAndDensity[inIndex].w - gNeutralDensity[inIndex];
}
void ApplyGrid(JPH_IN(JPH_HairPosition) inPos, JPH_IN_OUT(JPH_HairVelocity) ioVel, JPH_IN(JPH_HairMaterial) inMaterial, float inStrandFraction)
{
if (!inMaterial.mEnableGrid)
return;
// Convert position to grid index and fraction
uint3 index;
float3 ma;
GridPositionToIndexAndFraction(inPos.mPosition, index, ma);
float3 a = float3(1, 1, 1) - ma;
// Get average velocity at the vertex position (trilinear sample)
float3 velocity;
uint3 stride = cGridStride;
uint adr_000 = GridIndexToBufferIndex(index);
uint adr_100 = adr_000 + 1;
uint adr_010 = adr_000 + stride.y;
uint adr_110 = adr_010 + 1;
velocity = gVelocityAndDensity[adr_000].xyz * ( a.x * a.y * a.z);
velocity += gVelocityAndDensity[adr_100].xyz * (ma.x * a.y * a.z);
velocity += gVelocityAndDensity[adr_010].xyz * ( a.x * ma.y * a.z);
velocity += gVelocityAndDensity[adr_110].xyz * (ma.x * ma.y * a.z);
velocity += gVelocityAndDensity[adr_000 + stride.z].xyz * ( a.x * a.y * ma.z);
velocity += gVelocityAndDensity[adr_100 + stride.z].xyz * (ma.x * a.y * ma.z);
velocity += gVelocityAndDensity[adr_010 + stride.z].xyz * ( a.x * ma.y * ma.z);
velocity += gVelocityAndDensity[adr_110 + stride.z].xyz * (ma.x * ma.y * ma.z);
// Drive towards the average velocity of the cell
ioVel.mVelocity += GradientSamplerSample(inMaterial.mGridVelocityFactor, inStrandFraction) * (velocity - ioVel.mVelocity);
// Calculate force to go towards neutral density
// Based on eq 3 of Volumetric Methods for Simulation and Rendering of Hair - Lena Petrovic, Mark Henne and John Anderson
float dd000 = DeltaDensity(adr_000);
float dd100 = DeltaDensity(adr_100);
float dd010 = DeltaDensity(adr_010);
float dd110 = DeltaDensity(adr_110);
float dd001 = DeltaDensity(adr_000 + stride.z);
float dd101 = DeltaDensity(adr_100 + stride.z);
float dd011 = DeltaDensity(adr_010 + stride.z);
float dd111 = DeltaDensity(adr_110 + stride.z);
float3 force = float3(
a.y * a.z * (dd000 - dd100)
+ ma.y * a.z * (dd010 - dd110)
+ a.y * ma.z * (dd001 - dd101)
+ ma.y * ma.z * (dd011 - dd111),
a.x * a.z * (dd000 - dd010)
+ ma.x * a.z * (dd100 - dd110)
+ a.x * ma.z * (dd001 - dd011)
+ ma.x * ma.z * (dd101 - dd111),
a.x * a.y * (dd000 - dd001)
+ ma.x * a.y * (dd100 - dd101)
+ a.x * ma.y * (dd010 - dd011)
+ ma.x * ma.y * (dd110 - dd111));
ioVel.mVelocity += inMaterial.mGridDensityForceFactor * force * cDeltaTime; // / mass, but mass is 1
}
void Integrate(JPH_IN_OUT(JPH_HairPosition) ioPos, JPH_IN(JPH_HairVelocity) inVel, JPH_IN(JPH_HairMaterial) inMaterial, float inStrandFraction)
{
JPH_HairVelocity vel = inVel;
// Gravity
vel.mVelocity += cSubStepGravity * GradientSamplerSample(inMaterial.mGravityFactor, inStrandFraction);
// Damping
vel.mVelocity *= inMaterial.mExpLinearDampingDeltaTime;
vel.mAngularVelocity *= inMaterial.mExpAngularDampingDeltaTime;
// Integrate position
ioPos.mPosition += vel.mVelocity * cDeltaTime;
// Integrate rotation
JPH_Quat rotation = ioPos.mRotation;
JPH_Quat delta_rotation = cHalfDeltaTime * JPH_QuatImaginaryMulQuat(vel.mAngularVelocity, rotation);
ioPos.mRotation = normalize(rotation + delta_rotation);
}

View File

@ -0,0 +1,35 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2026 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include "HairIntegrateBindings.h"
#include "HairCommon.h"
#include "HairIntegrate.h"
JPH_SHADER_FUNCTION_BEGIN(void, main, cHairPerVertexBatch, 1, 1)
JPH_SHADER_PARAM_THREAD_ID(tid)
JPH_SHADER_FUNCTION_END
{
// Check if this is a valid vertex
uint vtx = tid.x + cNumStrands; // Skip the root of each strand, it's fixed
if (vtx >= cNumVertices)
return;
if (IsVertexFixed(gVerticesFixed, vtx))
return;
// Load the material
uint strand_idx = vtx % cNumStrands;
JPH_HairMaterial material = gMaterials[GetStrandMaterialIndex(gStrandMaterialIndex, strand_idx)];
// Load the vertex
float strand_fraction = GetVertexStrandFraction(gStrandFractions, vtx);
JPH_HairPosition pos = gPositions[vtx];
JPH_HairVelocity vel = gVelocities[vtx];
// Update previous position
gPreviousPositions[vtx] = pos;
ApplyGrid(pos, vel, material, strand_fraction);
Integrate(pos, vel, material, strand_fraction);
gPositions[vtx] = pos;
}

View File

@ -0,0 +1,17 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2026 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include "HairStructs.h"
JPH_SHADER_BIND_BEGIN(JPH_HairIntegrate)
JPH_SHADER_BIND_BUFFER(JPH_uint, gVerticesFixed)
JPH_SHADER_BIND_BUFFER(JPH_uint, gStrandFractions)
JPH_SHADER_BIND_BUFFER(JPH_float, gNeutralDensity)
JPH_SHADER_BIND_BUFFER(JPH_float4, gVelocityAndDensity)
JPH_SHADER_BIND_BUFFER(JPH_uint, gStrandMaterialIndex)
JPH_SHADER_BIND_BUFFER(JPH_HairMaterial, gMaterials)
JPH_SHADER_BIND_BUFFER(JPH_HairVelocity, gVelocities)
JPH_SHADER_BIND_RW_BUFFER(JPH_HairPosition, gPositions)
JPH_SHADER_BIND_RW_BUFFER(JPH_HairPosition, gPreviousPositions)
JPH_SHADER_BIND_END(JPH_HairIntegrate)

View File

@ -0,0 +1,50 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2026 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include "HairSkinRootsBindings.h"
#include "HairCommon.h"
JPH_SHADER_FUNCTION_BEGIN(void, main, cHairPerStrandBatch, 1, 1)
JPH_SHADER_PARAM_THREAD_ID(tid)
JPH_SHADER_FUNCTION_END
{
// Check if this is a valid strand
uint strand_idx = tid.x;
if (strand_idx >= cNumStrands)
return;
JPH_HairSkinPoint sp = gSkinPoints[strand_idx];
// Get the vertices of the attached triangle
uint tri_idx = sp.mTriangleIndex * 3;
float3 v0 = JPH_Mat44Mul3x3Vec3(cScalpToHead, gScalpVertices[gScalpTriangles[tri_idx + 0]]);
float3 v1 = JPH_Mat44Mul3x3Vec3(cScalpToHead, gScalpVertices[gScalpTriangles[tri_idx + 1]]);
float3 v2 = JPH_Mat44Mul3x3Vec3(cScalpToHead, gScalpVertices[gScalpTriangles[tri_idx + 2]]);
JPH_HairPosition root;
// Set the position of the root
root.mPosition = sp.mU * v0 + sp.mV * v1 + (1.0f - sp.mU - sp.mV) * v2 + cScalpToHead[3].xyz;
// Get tangent vector
float3 tangent = normalize(v1 - v0);
// Get normal of the triangle
float3 normal = normalize(cross(tangent, v2 - v0));
// Calculate basis for the triangle
float3 binormal = cross(tangent, normal);
JPH_Quat triangle_basis = JPH_QuatFromMat33(normal, binormal, tangent);
// Calculate the new Bishop frame of the root
root.mRotation = JPH_QuatMulQuat(triangle_basis, JPH_QuatDecompress(sp.mToBishop));
gPositions[strand_idx] = root;
// Calculate the transform that transforms the stored global pose to the space of the skinned root of the strand
JPH_HairGlobalPoseTransform transform;
transform.mRotation = JPH_QuatMulQuat(root.mRotation, JPH_QuatConjugate(JPH_QuatDecompress(gInitialBishops[strand_idx])));
transform.mPosition = root.mPosition - JPH_QuatMulVec3(transform.mRotation, gInitialPositions[strand_idx]);
gGlobalPoseTransforms[strand_idx] = transform;
}

View File

@ -0,0 +1,23 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2026 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include "HairStructs.h"
// Overridable input types
#ifndef JPH_SHADER_BIND_SCALP_VERTICES
#define JPH_SHADER_BIND_SCALP_VERTICES(name) JPH_SHADER_BIND_BUFFER(JPH_float3, name)
#endif
#ifndef JPH_SHADER_BIND_SCALP_TRIANGLES
#define JPH_SHADER_BIND_SCALP_TRIANGLES(name) JPH_SHADER_BIND_BUFFER(JPH_uint, name)
#endif
JPH_SHADER_BIND_BEGIN(JPH_HairSkinRoots)
JPH_SHADER_BIND_BUFFER(JPH_HairSkinPoint, gSkinPoints)
JPH_SHADER_BIND_SCALP_VERTICES(gScalpVertices)
JPH_SHADER_BIND_SCALP_TRIANGLES(gScalpTriangles)
JPH_SHADER_BIND_BUFFER(JPH_float3, gInitialPositions)
JPH_SHADER_BIND_BUFFER(JPH_uint, gInitialBishops)
JPH_SHADER_BIND_RW_BUFFER(JPH_HairPosition, gPositions)
JPH_SHADER_BIND_RW_BUFFER(JPH_HairGlobalPoseTransform, gGlobalPoseTransforms)
JPH_SHADER_BIND_END(JPH_HairSkinRoots)

View File

@ -0,0 +1,26 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2026 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include "HairSkinVerticesBindings.h"
#include "HairCommon.h"
JPH_SHADER_FUNCTION_BEGIN(void, main, cHairPerVertexBatch, 1, 1)
JPH_SHADER_PARAM_THREAD_ID(tid)
JPH_SHADER_FUNCTION_END
{
// Check if this is a valid vertex
uint vtx = tid.x;
if (vtx >= cNumSkinVertices)
return;
// Skin the vertex
float3 v = float3(0, 0, 0);
for (uint w = vtx * cNumSkinWeightsPerVertex, w_end = w + cNumSkinWeightsPerVertex; w < w_end; ++w)
{
JPH_HairSkinWeight sw = gScalpSkinWeights[w];
if (sw.mWeight > 0.0f)
v += sw.mWeight * JPH_Mat44Mul3x4Vec3(gScalpJointMatrices[sw.mJointIdx], gScalpVertices[vtx]);
}
gScalpVerticesOut[vtx] = v;
}

View File

@ -0,0 +1,12 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2026 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include "HairStructs.h"
JPH_SHADER_BIND_BEGIN(JPH_HairSkinVertices)
JPH_SHADER_BIND_BUFFER(JPH_float3, gScalpVertices)
JPH_SHADER_BIND_BUFFER(JPH_HairSkinWeight, gScalpSkinWeights)
JPH_SHADER_BIND_BUFFER(JPH_Mat44, gScalpJointMatrices)
JPH_SHADER_BIND_RW_BUFFER(JPH_float3, gScalpVerticesOut)
JPH_SHADER_BIND_END(JPH_HairSkinVertices)

View File

@ -0,0 +1,120 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2026 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include "ShaderCore.h"
// Prevent including this file multiple times unless we're generating bindings
#if !defined(HAIR_STRUCTS_H) || defined(JPH_SHADER_GENERATE_WRAPPER)
#ifndef JPH_SHADER_GENERATE_WRAPPER
#define HAIR_STRUCTS_H
#endif
JPH_SUPPRESS_WARNING_PUSH
JPH_SUPPRESS_WARNINGS
JPH_SHADER_CONSTANT(int, cHairPerVertexBatch, 64)
JPH_SHADER_CONSTANT(int, cHairPerGridCellBatch, 32)
JPH_SHADER_CONSTANT(int, cHairPerStrandBatch, 32)
JPH_SHADER_CONSTANT(int, cHairPerRenderVertexBatch, 128)
JPH_SHADER_CONSTANT(int, cHairNumSVertexInfluences, 3)
JPH_SHADER_STRUCT_BEGIN(JPH_HairSkinWeight)
JPH_SHADER_STRUCT_MEMBER(JPH_uint, JointIdx)
JPH_SHADER_STRUCT_MEMBER(JPH_float, Weight)
JPH_SHADER_STRUCT_END(JPH_HairSkinWeight)
JPH_SHADER_STRUCT_BEGIN(JPH_HairSkinPoint)
JPH_SHADER_STRUCT_MEMBER(JPH_uint, TriangleIndex) ///< Index of triangle in mScalpVertices to which this skin point is attached
JPH_SHADER_STRUCT_MEMBER(JPH_float, U) ///< Barycentric u coordinate of skin point
JPH_SHADER_STRUCT_MEMBER(JPH_float, V) ///< Barycentric v coordinate of skin point
JPH_SHADER_STRUCT_MEMBER(JPH_uint, ToBishop) ///< Compressed quaternion to rotate the frame defined by the triangle normal and the first edge to the Bishop frame of the first vertex of the strand
JPH_SHADER_STRUCT_END(JPH_HairSkinPoint)
JPH_SHADER_STRUCT_BEGIN(JPH_HairGlobalPoseTransform)
JPH_SHADER_STRUCT_MEMBER(JPH_float3, Position)
JPH_SHADER_STRUCT_MEMBER(JPH_Quat, Rotation)
JPH_SHADER_STRUCT_END(JPH_HairGlobalPoseTransform)
JPH_SHADER_STRUCT_BEGIN(JPH_HairSVertexInfluence)
JPH_SHADER_STRUCT_MEMBER(JPH_uint, VertexIndex) ///< Index in mSimVertices that indicates to which simulated vertex this vertex is attached.
JPH_SHADER_STRUCT_MEMBER(JPH_float3, RelativePosition) ///< Position in local space from the simulated vertex to the render vertex
JPH_SHADER_STRUCT_MEMBER(JPH_float, Weight) ///< Influence weight, 0 = not attached, 1 = fully attached
JPH_SHADER_STRUCT_END(JPH_HairSVertexInfluence)
JPH_SHADER_STRUCT_BEGIN(JPH_HairPosition)
JPH_SHADER_STRUCT_MEMBER(JPH_float3, Position)
JPH_SHADER_STRUCT_MEMBER(JPH_Quat, Rotation)
JPH_SHADER_STRUCT_END(JPH_HairPosition)
JPH_SHADER_STRUCT_BEGIN(JPH_HairVelocity)
JPH_SHADER_STRUCT_MEMBER(JPH_float3, Velocity)
JPH_SHADER_STRUCT_MEMBER(JPH_float3, AngularVelocity)
JPH_SHADER_STRUCT_END(JPH_HairVelocity)
JPH_SHADER_STRUCT_BEGIN(JPH_HairMaterial)
JPH_SHADER_STRUCT_MEMBER(JPH_float4, WorldTransformInfluence)
JPH_SHADER_STRUCT_MEMBER(JPH_float4, GlobalPose)
JPH_SHADER_STRUCT_MEMBER(JPH_float4, SkinGlobalPose)
JPH_SHADER_STRUCT_MEMBER(JPH_float4, GravityFactor)
JPH_SHADER_STRUCT_MEMBER(JPH_float4, HairRadius)
JPH_SHADER_STRUCT_MEMBER(JPH_float4, BendComplianceMultiplier)
JPH_SHADER_STRUCT_MEMBER(JPH_float4, GridVelocityFactor)
JPH_SHADER_STRUCT_MEMBER(JPH_uint, EnableCollision)
JPH_SHADER_STRUCT_MEMBER(JPH_uint, EnableLRA)
JPH_SHADER_STRUCT_MEMBER(JPH_uint, EnableGrid)
JPH_SHADER_STRUCT_MEMBER(JPH_float, Friction)
JPH_SHADER_STRUCT_MEMBER(JPH_float, ExpLinearDampingDeltaTime)
JPH_SHADER_STRUCT_MEMBER(JPH_float, ExpAngularDampingDeltaTime)
JPH_SHADER_STRUCT_MEMBER(JPH_float, BendComplianceInvDeltaTimeSq)
JPH_SHADER_STRUCT_MEMBER(JPH_float, StretchComplianceInvDeltaTimeSq)
JPH_SHADER_STRUCT_MEMBER(JPH_float, GridDensityForceFactor)
JPH_SHADER_STRUCT_MEMBER(JPH_float, InertiaMultiplier)
JPH_SHADER_STRUCT_MEMBER(JPH_float, MaxLinearVelocitySq)
JPH_SHADER_STRUCT_MEMBER(JPH_float, MaxAngularVelocitySq)
JPH_SHADER_STRUCT_END(JPH_HairMaterial)
JPH_SHADER_STRUCT_BEGIN(JPH_HairCollisionPlane)
JPH_SHADER_STRUCT_MEMBER(JPH_Plane, Plane)
JPH_SHADER_STRUCT_MEMBER(JPH_uint, ShapeIndex)
JPH_SHADER_STRUCT_END(JPH_HairCollisionPlane)
JPH_SHADER_STRUCT_BEGIN(JPH_HairCollisionShape)
JPH_SHADER_STRUCT_MEMBER(JPH_float3, CenterOfMass)
JPH_SHADER_STRUCT_MEMBER(JPH_float3, LinearVelocity)
JPH_SHADER_STRUCT_MEMBER(JPH_float3, AngularVelocity)
JPH_SHADER_STRUCT_END(JPH_HairCollisionShape)
// Note: The order was chosen to match the struct between C++ and HLSL.
JPH_SHADER_CONSTANTS_BEGIN(JPH_HairUpdateContext, gContext)
JPH_SHADER_CONSTANTS_MEMBER(JPH_uint, NumStrands)
JPH_SHADER_CONSTANTS_MEMBER(JPH_uint, NumVertices)
JPH_SHADER_CONSTANTS_MEMBER(JPH_uint, NumGridPoints)
JPH_SHADER_CONSTANTS_MEMBER(JPH_uint, NumRenderVertices)
JPH_SHADER_CONSTANTS_MEMBER(JPH_uint3, GridSizeMin2)
JPH_SHADER_CONSTANTS_MEMBER(JPH_float, TwoDivDeltaTime)
JPH_SHADER_CONSTANTS_MEMBER(JPH_float3, GridSizeMin1)
JPH_SHADER_CONSTANTS_MEMBER(JPH_float, DeltaTime)
JPH_SHADER_CONSTANTS_MEMBER(JPH_float3, GridOffset)
JPH_SHADER_CONSTANTS_MEMBER(JPH_float, HalfDeltaTime)
JPH_SHADER_CONSTANTS_MEMBER(JPH_float3, GridScale)
JPH_SHADER_CONSTANTS_MEMBER(JPH_float, InvDeltaTimeSq)
JPH_SHADER_CONSTANTS_MEMBER(JPH_float3, SubStepGravity)
JPH_SHADER_CONSTANTS_MEMBER(JPH_uint, NumSkinVertices)
JPH_SHADER_CONSTANTS_MEMBER(JPH_uint3, GridStride)
JPH_SHADER_CONSTANTS_MEMBER(JPH_uint, NumSkinWeightsPerVertex)
JPH_SHADER_CONSTANTS_MEMBER(JPH_Mat44, DeltaTransform)
JPH_SHADER_CONSTANTS_MEMBER(JPH_Mat44, ScalpToHead)
JPH_SHADER_CONSTANTS_MEMBER(JPH_Quat, DeltaTransformQuat)
JPH_SHADER_CONSTANTS_END(JPH_HairUpdateContext)
// Note: The order was chosen to match the struct between C++ and HLSL.
JPH_SHADER_CONSTANTS_BEGIN(JPH_HairIterationContext, gIterationContext)
JPH_SHADER_CONSTANTS_MEMBER(JPH_float, AccumulatedDeltaTime) ///< = Iteration * DeltaTime
JPH_SHADER_CONSTANTS_MEMBER(JPH_float, IterationFraction) ///< = 1 / (NumIterations - Iteration) or the fraction to apply to get from current to target for this iteration step
JPH_SHADER_CONSTANTS_END(JPH_HairIterationContext)
JPH_SUPPRESS_WARNING_POP
#endif // !defined(HAIR_STRUCTS_H) || defined(JPH_SHADER_GENERATE_WRAPPER)

View File

@ -0,0 +1,28 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2026 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include "HairTeleportBindings.h"
#include "HairCommon.h"
JPH_SHADER_FUNCTION_BEGIN(void, main, cHairPerVertexBatch, 1, 1)
JPH_SHADER_PARAM_THREAD_ID(tid)
JPH_SHADER_FUNCTION_END
{
// Check if this is a valid vertex
uint vtx = tid.x;
if (vtx >= cNumVertices)
return;
// Initialize position based on the initial vertex data
JPH_HairPosition pos;
pos.mPosition = gInitialPositions[vtx];
pos.mRotation = JPH_QuatDecompress(gInitialBishops[vtx]);
gPositions[vtx] = pos;
// Initialize velocity to zero
JPH_HairVelocity vel;
vel.mVelocity = float3(0, 0, 0);
vel.mAngularVelocity = float3(0, 0, 0);
gVelocities[vtx] = vel;
}

View File

@ -0,0 +1,12 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2026 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include "HairStructs.h"
JPH_SHADER_BIND_BEGIN(JPH_HairTeleport)
JPH_SHADER_BIND_BUFFER(JPH_float3, gInitialPositions)
JPH_SHADER_BIND_BUFFER(JPH_uint, gInitialBishops)
JPH_SHADER_BIND_RW_BUFFER(JPH_HairPosition, gPositions)
JPH_SHADER_BIND_RW_BUFFER(JPH_HairVelocity, gVelocities)
JPH_SHADER_BIND_END(JPH_HairTeleport)

View File

@ -0,0 +1,30 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2026 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include "HairUpdateRootsBindings.h"
#include "HairCommon.h"
JPH_SHADER_FUNCTION_BEGIN(void, main, cHairPerStrandBatch, 1, 1)
JPH_SHADER_PARAM_THREAD_ID(tid)
JPH_SHADER_FUNCTION_END
{
// Check if this is a valid strand
uint strand_idx = tid.x;
if (strand_idx >= cNumStrands)
return;
float inv_fraction = 1.0f - cIterationFraction;
JPH_HairPosition pos = gPositions[strand_idx];
JPH_HairPosition target_pos = gTargetPositions[strand_idx];
pos.mPosition = pos.mPosition * inv_fraction + target_pos.mPosition * cIterationFraction;
pos.mRotation = normalize(pos.mRotation * inv_fraction + target_pos.mRotation * cIterationFraction);
gPositions[strand_idx] = pos;
JPH_HairGlobalPoseTransform transf = gGlobalPoseTransforms[strand_idx];
JPH_HairGlobalPoseTransform target_transf = gTargetGlobalPoseTransforms[strand_idx];
transf.mPosition = transf.mPosition * inv_fraction + target_transf.mPosition * cIterationFraction;
transf.mRotation = normalize(transf.mRotation * inv_fraction + target_transf.mRotation * cIterationFraction);
gGlobalPoseTransforms[strand_idx] = transf;
}

View File

@ -0,0 +1,12 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2026 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include "HairStructs.h"
JPH_SHADER_BIND_BEGIN(JPH_HairUpdateRoots)
JPH_SHADER_BIND_BUFFER(JPH_HairPosition, gTargetPositions)
JPH_SHADER_BIND_BUFFER(JPH_HairGlobalPoseTransform, gTargetGlobalPoseTransforms)
JPH_SHADER_BIND_RW_BUFFER(JPH_HairPosition, gPositions)
JPH_SHADER_BIND_RW_BUFFER(JPH_HairGlobalPoseTransform, gGlobalPoseTransforms)
JPH_SHADER_BIND_END(JPH_HairUpdateRoots)

View File

@ -0,0 +1,154 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2026 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include "HairUpdateStrandsBindings.h"
#include "HairCommon.h"
void ApplyLRA(float3 inX0, float inMaxDist, JPH_IN_OUT(JPH_HairPosition) ioV0)
{
float3 delta = ioV0.mPosition - inX0;
float delta_len_sq = dot(delta, delta);
if (delta_len_sq > JPH_Square(inMaxDist))
ioV0.mPosition = inX0 + delta * inMaxDist / sqrt(delta_len_sq);
}
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)
{
// Inertia of a thin rod of length L ~ m * L^2, we take the maximum mass of the two vertices
float rod_inv_mass = min(inInvMass0, inInvMass1) / inMaterial.mInertiaMultiplier; // / L^2, which we'll apply later
// Equation 37 from "Position and Orientation Based Cosserat Rods" - Kugelstadt and Schoemer - SIGGRAPH 2016
float denom = inInvMass0 + inInvMass1 + 4.0f * rod_inv_mass /* / L^2 * L^2 cancels */ + inMaterial.mStretchComplianceInvDeltaTimeSq;
if (denom >= 1.0e-12f)
{
float3 x0 = ioV0.mPosition;
float3 x1 = ioV1.mPosition;
JPH_Quat rotation1 = ioV0.mRotation;
float3 d3 = JPH_QuatRotateAxisZ(rotation1);
float3 delta = (x1 - x0 - d3 * inLength01) / denom;
ioV0.mPosition = x0 + inInvMass0 * delta;
ioV1.mPosition = x1 - inInvMass1 * delta;
// q * e3_bar = q * (0, 0, -1, 0) = [-qy, qx, -qw, qz]
JPH_Quat q_e3_bar = JPH_Quat(-rotation1.y, rotation1.x, -rotation1.w, rotation1.z);
rotation1 += (2.0f * rod_inv_mass / inLength01 /* / L^2 * L => / L */) * JPH_QuatImaginaryMulQuat(delta, q_e3_bar);
ioV0.mRotation = normalize(rotation1);
}
}
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)
{
// Inertia of a thin rod of length L ~ m * L^2, we take the maximum mass of the two vertices
float rod_inv_mass = min(inInvMass0, inInvMass1) / (inMaterial.mInertiaMultiplier * JPH_Square(inLength01));
float rod2_inv_mass = min(inInvMass1, inInvMass2) / (inMaterial.mInertiaMultiplier * JPH_Square(inLength12));
// Calculate multiplier for the bend compliance based on strand fraction
float fraction = inStrandFraction1 * 3.0f;
uint idx = uint(fraction);
fraction = fraction - float(idx);
float multiplier = inMaterial.mBendComplianceMultiplier[idx] * (1.0f - fraction) + inMaterial.mBendComplianceMultiplier[idx + 1] * fraction;
// Equation 40 from "Position and Orientation Based Cosserat Rods" - Kugelstadt and Schoemer - SIGGRAPH 2016
float denom = rod_inv_mass + rod2_inv_mass + inMaterial.mBendComplianceInvDeltaTimeSq * multiplier;
if (denom >= 1.0e-12f)
{
JPH_Quat rotation1 = ioV0.mRotation;
JPH_Quat rotation2 = ioV1.mRotation;
JPH_Quat omega = JPH_QuatMulQuat(JPH_QuatConjugate(rotation1), rotation2);
JPH_Quat omega_min_omega0 = omega - inOmega0;
JPH_Quat omega_plus_omega0 = omega + inOmega0;
// Take the shortest of the two rotations
JPH_Quat delta_omega = dot(omega_plus_omega0, omega_plus_omega0) < dot(omega_min_omega0, omega_min_omega0) ? omega_plus_omega0 : omega_min_omega0;
delta_omega /= denom;
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.
JPH_Quat delta_rod2 = rod2_inv_mass * JPH_QuatMulQuat(rotation1, delta_omega);
rotation1 += rod_inv_mass * JPH_QuatMulQuat(rotation2, delta_omega);
rotation2 -= delta_rod2;
ioV0.mRotation = normalize(rotation1);
ioV1.mRotation = normalize(rotation2);
}
}
JPH_SHADER_FUNCTION_BEGIN(void, main, cHairPerStrandBatch, 1, 1)
JPH_SHADER_PARAM_THREAD_ID(tid)
JPH_SHADER_FUNCTION_END
{
// Check if this is a valid strand
uint strand_idx = tid.x;
if (strand_idx >= cNumStrands)
return;
uint strand_vtx_count = GetStrandVertexCount(gStrandVertexCounts, strand_idx);
// Load the material
JPH_HairMaterial material = gMaterials[GetStrandMaterialIndex(gStrandMaterialIndex, strand_idx)];
// Load the first two vertices
uint vtx_idx_to_load = strand_idx;
float inv_mass0 = GetVertexInvMass(gVerticesFixed, vtx_idx_to_load);
float length0 = gInitialLengths[vtx_idx_to_load];
JPH_HairPosition v0 = gPositions[vtx_idx_to_load];
// LRA: Vertex everything is attached to
float3 x0 = gInitialPositions[vtx_idx_to_load];
// LRA: Tracks the distance from the first vertex
float max_dist = length0;
vtx_idx_to_load += cNumStrands;
float inv_mass1 = GetVertexInvMass(gVerticesFixed, vtx_idx_to_load);
float strand_fraction1 = GetVertexStrandFraction(gStrandFractions, vtx_idx_to_load);
float length1 = gInitialLengths[vtx_idx_to_load];
JPH_HairPosition v1 = gPositions[vtx_idx_to_load];
// Process 2nd vertex
if (material.mEnableLRA && inv_mass1 > 0.0f)
ApplyLRA(x0, max_dist, v1);
max_dist += length1;
uint vtx_idx_to_retire = strand_idx;
for (uint vtx = 2; vtx < strand_vtx_count; ++vtx)
{
// Get the initial rotation difference from the middle vertex
JPH_Quat omega0 = JPH_QuatDecompress(gOmega0s[vtx_idx_to_load]);
// Load the next vertex
vtx_idx_to_load += cNumStrands;
float inv_mass2 = GetVertexInvMass(gVerticesFixed, vtx_idx_to_load);
float strand_fraction2 = GetVertexStrandFraction(gStrandFractions, vtx_idx_to_load);
float length2 = gInitialLengths[vtx_idx_to_load];
JPH_HairPosition v2 = gPositions[vtx_idx_to_load];
// Process newly added vertex
if (material.mEnableLRA && inv_mass2 > 0.0f)
ApplyLRA(x0, max_dist, v2);
max_dist += length2;
// Stitched mode as per Strand-based Hair System - Pedersen - SIGGRAPH 2022
ApplyStretchShear(v1, v2, length1, inv_mass1, inv_mass2, material);
ApplyStretchShear(v0, v1, length0, inv_mass0, inv_mass1, material);
ApplyBendTwist(v0, v1, omega0, length0, length1, strand_fraction1, inv_mass0, inv_mass1, inv_mass2, material);
// Retire vertex
gPositions[vtx_idx_to_retire] = v0;
vtx_idx_to_retire += cNumStrands;
// Shift the vertices
inv_mass0 = inv_mass1;
inv_mass1 = inv_mass2;
strand_fraction1 = strand_fraction2;
length0 = length1;
length1 = length2;
v0 = v1;
v1 = v2;
}
// Retire 2nd to last vertex
gPositions[vtx_idx_to_retire] = v0;
vtx_idx_to_retire += cNumStrands;
// Cannot calculate rotation for last vertex, take the 2nd last
v1.mRotation = v0.mRotation;
// Retire last vertex
gPositions[vtx_idx_to_retire] = v1;
}

View File

@ -0,0 +1,17 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2026 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include "HairStructs.h"
JPH_SHADER_BIND_BEGIN(JPH_HairUpdateStrands)
JPH_SHADER_BIND_BUFFER(JPH_uint, gVerticesFixed)
JPH_SHADER_BIND_BUFFER(JPH_uint, gStrandFractions)
JPH_SHADER_BIND_BUFFER(JPH_float3, gInitialPositions)
JPH_SHADER_BIND_BUFFER(JPH_uint, gOmega0s)
JPH_SHADER_BIND_BUFFER(JPH_float, gInitialLengths)
JPH_SHADER_BIND_BUFFER(JPH_uint, gStrandVertexCounts)
JPH_SHADER_BIND_BUFFER(JPH_uint, gStrandMaterialIndex)
JPH_SHADER_BIND_BUFFER(JPH_HairMaterial, gMaterials)
JPH_SHADER_BIND_RW_BUFFER(JPH_HairPosition, gPositions)
JPH_SHADER_BIND_END(JPH_HairUpdateStrands)

View File

@ -0,0 +1,64 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2026 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include "HairApplyGlobalPose.h"
void ApplyCollisionAndUpdateVelocity(uint inVtx, JPH_IN_OUT(JPH_HairPosition) ioPos, JPH_IN(JPH_HairPosition) inPreviousPos, JPH_IN(JPH_HairMaterial) inMaterial, float inStrandFraction, JPH_OUT(JPH_HairVelocity) outVel)
{
// Update velocities
outVel.mVelocity = (ioPos.mPosition - inPreviousPos.mPosition) / cDeltaTime;
outVel.mAngularVelocity = cTwoDivDeltaTime * JPH_QuatMulQuat(ioPos.mRotation, JPH_QuatConjugate(inPreviousPos.mRotation)).xyz;
if (inMaterial.mEnableCollision)
{
// Calculate closest point on the collision plane
JPH_HairCollisionPlane plane = gCollisionPlanes[inVtx];
float distance_to_plane = JPH_PlaneSignedDistance(plane.mPlane, ioPos.mPosition);
float3 contact_normal = JPH_PlaneGetNormal(plane.mPlane);
float3 point_on_plane = ioPos.mPosition - distance_to_plane * contact_normal;
// Calculate how much the plane moved in this time step
JPH_HairCollisionShape shape = gCollisionShapes[plane.mShapeIndex];
float3 plane_velocity = shape.mLinearVelocity + cross(shape.mAngularVelocity, point_on_plane - shape.mCenterOfMass);
float plane_movement = dot(plane_velocity, contact_normal) * cAccumulatedDeltaTime;
float projected_distance = -distance_to_plane + plane_movement + GradientSamplerSample(inMaterial.mHairRadius, inStrandFraction);
if (projected_distance > 0.0f)
{
// Resolve penetration
ioPos.mPosition += contact_normal * projected_distance;
// Only update velocity when moving towards each other
float3 v_relative = outVel.mVelocity - plane_velocity;
float v_relative_dot_normal = dot(contact_normal, v_relative);
if (v_relative_dot_normal < 0.0f)
{
// Calculate normal and tangential velocity (equation 30)
float3 v_normal = contact_normal * v_relative_dot_normal;
float3 v_tangential = v_relative - v_normal;
float v_tangential_length = length(v_tangential);
// Apply friction as described in Detailed Rigid Body Simulation with Extended Position Based Dynamics - Matthias Muller et al. (modified equation 31)
if (v_tangential_length > 0.0f)
outVel.mVelocity -= v_tangential * min(inMaterial.mFriction * projected_distance / (v_tangential_length * cDeltaTime), 1.0f);
// Apply restitution of zero (equation 35)
outVel.mVelocity -= v_normal;
}
}
}
}
void LimitVelocity(JPH_IN_OUT(JPH_HairVelocity) ioVel, JPH_IN(JPH_HairMaterial) inMaterial)
{
// Limit linear velocity
float linear_velocity_sq = dot(ioVel.mVelocity, ioVel.mVelocity);
if (linear_velocity_sq > inMaterial.mMaxLinearVelocitySq)
ioVel.mVelocity *= sqrt(inMaterial.mMaxLinearVelocitySq / linear_velocity_sq);
// Limit angular velocity
float angular_velocity_sq = dot(ioVel.mAngularVelocity, ioVel.mAngularVelocity);
if (angular_velocity_sq > inMaterial.mMaxAngularVelocitySq)
ioVel.mAngularVelocity *= sqrt(inMaterial.mMaxAngularVelocitySq / angular_velocity_sq);
}

View File

@ -0,0 +1,40 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2026 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include "HairUpdateVelocityBindings.h"
#include "HairCommon.h"
#include "HairUpdateVelocity.h"
JPH_SHADER_FUNCTION_BEGIN(void, main, cHairPerVertexBatch, 1, 1)
JPH_SHADER_PARAM_THREAD_ID(tid)
JPH_SHADER_FUNCTION_END
{
// Check if this is a valid vertex
uint vtx = tid.x + cNumStrands; // Skip the root of each strand, it's fixed
if (vtx >= cNumVertices)
return;
if (IsVertexFixed(gVerticesFixed, vtx))
return;
// Load the material
uint strand_idx = vtx % cNumStrands;
JPH_HairMaterial material = gMaterials[GetStrandMaterialIndex(gStrandMaterialIndex, strand_idx)];
// Load the vertex
float strand_fraction = GetVertexStrandFraction(gStrandFractions, vtx);
float3 initial_pos = gInitialPositions[vtx];
float4 initial_bishop = JPH_QuatDecompress(gInitialBishops[vtx]);
JPH_HairGlobalPoseTransform global_pose_transform = gGlobalPoseTransforms[strand_idx];
JPH_HairPosition pos = gPositions[vtx];
JPH_HairPosition prev_pos = gPreviousPositions[vtx];
JPH_HairVelocity vel;
ApplyGlobalPose(pos, initial_pos, initial_bishop, global_pose_transform, material, strand_fraction);
ApplyCollisionAndUpdateVelocity(vtx, pos, prev_pos, material, strand_fraction, vel);
LimitVelocity(vel, material);
// Write back vertex
gPositions[vtx] = pos;
gVelocities[vtx] = vel;
}

View File

@ -0,0 +1,20 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2026 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include "HairStructs.h"
JPH_SHADER_BIND_BEGIN(JPH_HairUpdateVelocity)
JPH_SHADER_BIND_BUFFER(JPH_uint, gVerticesFixed)
JPH_SHADER_BIND_BUFFER(JPH_uint, gStrandFractions)
JPH_SHADER_BIND_BUFFER(JPH_float3, gInitialPositions)
JPH_SHADER_BIND_BUFFER(JPH_uint, gInitialBishops)
JPH_SHADER_BIND_BUFFER(JPH_uint, gStrandMaterialIndex)
JPH_SHADER_BIND_BUFFER(JPH_HairMaterial, gMaterials)
JPH_SHADER_BIND_BUFFER(JPH_HairPosition, gPreviousPositions)
JPH_SHADER_BIND_BUFFER(JPH_HairGlobalPoseTransform, gGlobalPoseTransforms)
JPH_SHADER_BIND_BUFFER(JPH_HairCollisionShape, gCollisionShapes)
JPH_SHADER_BIND_BUFFER(JPH_HairCollisionPlane, gCollisionPlanes)
JPH_SHADER_BIND_RW_BUFFER(JPH_HairPosition, gPositions)
JPH_SHADER_BIND_RW_BUFFER(JPH_HairVelocity, gVelocities)
JPH_SHADER_BIND_END(JPH_HairUpdateVelocity)

View File

@ -0,0 +1,44 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2026 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include "HairUpdateVelocityIntegrateBindings.h"
#include "HairCommon.h"
#include "HairIntegrate.h"
#include "HairUpdateVelocity.h"
JPH_SHADER_FUNCTION_BEGIN(void, main, cHairPerVertexBatch, 1, 1)
JPH_SHADER_PARAM_THREAD_ID(tid)
JPH_SHADER_FUNCTION_END
{
// Check if this is a valid vertex
uint vtx = tid.x + cNumStrands; // Skip the root of each strand, it's fixed
if (vtx >= cNumVertices)
return;
if (IsVertexFixed(gVerticesFixed, vtx))
return;
// Load the material
uint strand_idx = vtx % cNumStrands;
JPH_HairMaterial material = gMaterials[GetStrandMaterialIndex(gStrandMaterialIndex, strand_idx)];
// Load the vertex
float strand_fraction = GetVertexStrandFraction(gStrandFractions, vtx);
float3 initial_pos = gInitialPositions[vtx];
float4 initial_bishop = JPH_QuatDecompress(gInitialBishops[vtx]);
JPH_HairGlobalPoseTransform global_pose_transform = gGlobalPoseTransforms[strand_idx];
JPH_HairPosition pos = gPositions[vtx];
JPH_HairPosition prev_pos = gPreviousPositions[vtx];
// HairUpdateVelocity shader
JPH_HairVelocity vel; // Keeps velocity as a local variable
ApplyGlobalPose(pos, initial_pos, initial_bishop, global_pose_transform, material, strand_fraction);
ApplyCollisionAndUpdateVelocity(vtx, pos, prev_pos, material, strand_fraction, vel);
LimitVelocity(vel, material);
// HairIntegrate shader
gPreviousPositions[vtx] = pos;
ApplyGrid(pos, vel, material, strand_fraction);
Integrate(pos, vel, material, strand_fraction);
gPositions[vtx] = pos;
}

View File

@ -0,0 +1,21 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2026 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include "HairStructs.h"
JPH_SHADER_BIND_BEGIN(JPH_HairUpdateVelocityIntegrate)
JPH_SHADER_BIND_BUFFER(JPH_uint, gVerticesFixed)
JPH_SHADER_BIND_BUFFER(JPH_uint, gStrandFractions)
JPH_SHADER_BIND_BUFFER(JPH_float3, gInitialPositions)
JPH_SHADER_BIND_BUFFER(JPH_uint, gInitialBishops)
JPH_SHADER_BIND_BUFFER(JPH_float, gNeutralDensity)
JPH_SHADER_BIND_BUFFER(JPH_float4, gVelocityAndDensity)
JPH_SHADER_BIND_BUFFER(JPH_uint, gStrandMaterialIndex)
JPH_SHADER_BIND_BUFFER(JPH_HairMaterial, gMaterials)
JPH_SHADER_BIND_BUFFER(JPH_HairGlobalPoseTransform, gGlobalPoseTransforms)
JPH_SHADER_BIND_BUFFER(JPH_HairCollisionShape, gCollisionShapes)
JPH_SHADER_BIND_BUFFER(JPH_HairCollisionPlane, gCollisionPlanes)
JPH_SHADER_BIND_RW_BUFFER(JPH_HairPosition, gPreviousPositions)
JPH_SHADER_BIND_RW_BUFFER(JPH_HairPosition, gPositions)
JPH_SHADER_BIND_END(JPH_HairUpdateVelocityIntegrate)

View File

@ -0,0 +1,139 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2026 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <Jolt/Jolt.h>
#ifdef JPH_USE_CPU_COMPUTE
#include <Jolt/Shaders/HairWrapper.h>
#define JPH_SHADER_NAME HairTeleport
#include <Jolt/Compute/CPU/WrapShaderBegin.h>
#include "HairTeleport.hlsl"
#include <Jolt/Compute/CPU/WrapShaderBindings.h>
#include "HairTeleportBindings.h"
#include <Jolt/Compute/CPU/WrapShaderEnd.h>
#define JPH_SHADER_NAME HairApplyDeltaTransform
#include <Jolt/Compute/CPU/WrapShaderBegin.h>
#include "HairApplyDeltaTransform.hlsl"
#include <Jolt/Compute/CPU/WrapShaderBindings.h>
#include "HairApplyDeltaTransformBindings.h"
#include <Jolt/Compute/CPU/WrapShaderEnd.h>
#define JPH_SHADER_NAME HairSkinVertices
#include <Jolt/Compute/CPU/WrapShaderBegin.h>
#include "HairSkinVertices.hlsl"
#include <Jolt/Compute/CPU/WrapShaderBindings.h>
#include "HairSkinVerticesBindings.h"
#include <Jolt/Compute/CPU/WrapShaderEnd.h>
#define JPH_SHADER_NAME HairSkinRoots
#include <Jolt/Compute/CPU/WrapShaderBegin.h>
#include "HairSkinRoots.hlsl"
#include <Jolt/Compute/CPU/WrapShaderBindings.h>
#include "HairSkinRootsBindings.h"
#include <Jolt/Compute/CPU/WrapShaderEnd.h>
#define JPH_SHADER_NAME HairApplyGlobalPose
#include <Jolt/Compute/CPU/WrapShaderBegin.h>
#include "HairApplyGlobalPose.hlsl"
#include <Jolt/Compute/CPU/WrapShaderBindings.h>
#include "HairApplyGlobalPoseBindings.h"
#include <Jolt/Compute/CPU/WrapShaderEnd.h>
#define JPH_SHADER_NAME HairCalculateCollisionPlanes
#include <Jolt/Compute/CPU/WrapShaderBegin.h>
#include "HairCalculateCollisionPlanes.hlsl"
#include <Jolt/Compute/CPU/WrapShaderBindings.h>
#include "HairCalculateCollisionPlanesBindings.h"
#include <Jolt/Compute/CPU/WrapShaderEnd.h>
#define JPH_SHADER_NAME HairGridClear
#include <Jolt/Compute/CPU/WrapShaderBegin.h>
#include "HairGridClear.hlsl"
#include <Jolt/Compute/CPU/WrapShaderBindings.h>
#include "HairGridClearBindings.h"
#include <Jolt/Compute/CPU/WrapShaderEnd.h>
#define JPH_SHADER_NAME HairGridAccumulate
#include <Jolt/Compute/CPU/WrapShaderBegin.h>
#include "HairGridAccumulate.hlsl"
#include <Jolt/Compute/CPU/WrapShaderBindings.h>
#include "HairGridAccumulateBindings.h"
#include <Jolt/Compute/CPU/WrapShaderEnd.h>
#define JPH_SHADER_NAME HairGridNormalize
#include <Jolt/Compute/CPU/WrapShaderBegin.h>
#include "HairGridNormalize.hlsl"
#include <Jolt/Compute/CPU/WrapShaderBindings.h>
#include "HairGridNormalizeBindings.h"
#include <Jolt/Compute/CPU/WrapShaderEnd.h>
#define JPH_SHADER_NAME HairIntegrate
#include <Jolt/Compute/CPU/WrapShaderBegin.h>
#include "HairIntegrate.hlsl"
#include <Jolt/Compute/CPU/WrapShaderBindings.h>
#include "HairIntegrateBindings.h"
#include <Jolt/Compute/CPU/WrapShaderEnd.h>
#define JPH_SHADER_NAME HairUpdateRoots
#include <Jolt/Compute/CPU/WrapShaderBegin.h>
#include "HairUpdateRoots.hlsl"
#include <Jolt/Compute/CPU/WrapShaderBindings.h>
#include "HairUpdateRootsBindings.h"
#include <Jolt/Compute/CPU/WrapShaderEnd.h>
#define JPH_SHADER_NAME HairUpdateStrands
#include <Jolt/Compute/CPU/WrapShaderBegin.h>
#include "HairUpdateStrands.hlsl"
#include <Jolt/Compute/CPU/WrapShaderBindings.h>
#include "HairUpdateStrandsBindings.h"
#include <Jolt/Compute/CPU/WrapShaderEnd.h>
#define JPH_SHADER_NAME HairUpdateVelocity
#include <Jolt/Compute/CPU/WrapShaderBegin.h>
#include "HairUpdateVelocity.hlsl"
#include <Jolt/Compute/CPU/WrapShaderBindings.h>
#include "HairUpdateVelocityBindings.h"
#include <Jolt/Compute/CPU/WrapShaderEnd.h>
#define JPH_SHADER_NAME HairUpdateVelocityIntegrate
#include <Jolt/Compute/CPU/WrapShaderBegin.h>
#include "HairUpdateVelocityIntegrate.hlsl"
#include <Jolt/Compute/CPU/WrapShaderBindings.h>
#include "HairUpdateVelocityIntegrateBindings.h"
#include <Jolt/Compute/CPU/WrapShaderEnd.h>
#define JPH_SHADER_NAME HairCalculateRenderPositions
#include <Jolt/Compute/CPU/WrapShaderBegin.h>
#include "HairCalculateRenderPositions.hlsl"
#include <Jolt/Compute/CPU/WrapShaderBindings.h>
#include "HairCalculateRenderPositionsBindings.h"
#include <Jolt/Compute/CPU/WrapShaderEnd.h>
JPH_NAMESPACE_BEGIN
void JPH_EXPORT HairRegisterShaders(ComputeSystemCPU *inComputeSystem)
{
JPH_REGISTER_SHADER(inComputeSystem, HairTeleport);
JPH_REGISTER_SHADER(inComputeSystem, HairApplyDeltaTransform);
JPH_REGISTER_SHADER(inComputeSystem, HairSkinVertices);
JPH_REGISTER_SHADER(inComputeSystem, HairSkinRoots);
JPH_REGISTER_SHADER(inComputeSystem, HairApplyGlobalPose);
JPH_REGISTER_SHADER(inComputeSystem, HairCalculateCollisionPlanes);
JPH_REGISTER_SHADER(inComputeSystem, HairGridClear);
JPH_REGISTER_SHADER(inComputeSystem, HairGridAccumulate);
JPH_REGISTER_SHADER(inComputeSystem, HairGridNormalize);
JPH_REGISTER_SHADER(inComputeSystem, HairIntegrate);
JPH_REGISTER_SHADER(inComputeSystem, HairUpdateRoots);
JPH_REGISTER_SHADER(inComputeSystem, HairUpdateStrands);
JPH_REGISTER_SHADER(inComputeSystem, HairUpdateVelocity);
JPH_REGISTER_SHADER(inComputeSystem, HairUpdateVelocityIntegrate);
JPH_REGISTER_SHADER(inComputeSystem, HairCalculateRenderPositions);
}
JPH_NAMESPACE_END
#endif // JPH_USE_CPU_COMPUTE

View File

@ -0,0 +1,17 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2026 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#pragma once
#ifdef JPH_USE_CPU_COMPUTE
JPH_NAMESPACE_BEGIN
class ComputeSystemCPU;
void JPH_EXPORT HairRegisterShaders(ComputeSystemCPU *inComputeSystem);
JPH_NAMESPACE_END
#endif // JPH_USE_CPU_COMPUTE

View File

@ -0,0 +1,85 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2025 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#ifndef JPH_SHADER_OVERRIDE_MACROS
#ifdef __cplusplus
JPH_SUPPRESS_WARNING_PUSH
JPH_SUPPRESS_WARNINGS
using JPH_float = float;
using JPH_float3 = JPH::Float3;
using JPH_float4 = JPH::Float4;
using JPH_uint = JPH::uint32;
using JPH_uint3 = JPH::uint32[3];
using JPH_uint4 = JPH::uint32[4];
using JPH_int = int;
using JPH_int3 = int[3];
using JPH_int4 = int[4];
using JPH_Quat = JPH::Float4;
using JPH_Plane = JPH::Float4;
using JPH_Mat44 = JPH::Float4[4]; // matrix, column major
#define JPH_SHADER_CONSTANT(type, name, value) constexpr type name = value;
#define JPH_SHADER_CONSTANTS_BEGIN(type, name) struct type {
#define JPH_SHADER_CONSTANTS_MEMBER(type, name) type c##name;
#define JPH_SHADER_CONSTANTS_END(type) };
#define JPH_SHADER_BIND_BEGIN(name)
#define JPH_SHADER_BIND_END(name)
#define JPH_SHADER_BIND_BUFFER(type, name)
#define JPH_SHADER_BIND_RW_BUFFER(type, name)
JPH_SUPPRESS_WARNING_POP
#else
#define JPH_SUPPRESS_WARNING_PUSH
#define JPH_SUPPRESS_WARNING_POP
#define JPH_SUPPRESS_WARNINGS
typedef float JPH_float;
typedef float3 JPH_float3;
typedef float4 JPH_float4;
typedef uint JPH_uint;
typedef uint3 JPH_uint3;
typedef uint4 JPH_uint4;
typedef int JPH_int;
typedef int3 JPH_int3;
typedef int4 JPH_int4;
typedef float4 JPH_Quat; // xyz = imaginary part, w = real part
typedef float4 JPH_Plane; // xyz = normal, w = constant
typedef float4 JPH_Mat44[4]; // matrix, column major
#define JPH_SHADER_CONSTANT(type, name, value) static const type name = value;
#define JPH_SHADER_CONSTANTS_BEGIN(type, name) cbuffer name {
#define JPH_SHADER_CONSTANTS_MEMBER(type, name) type c##name;
#define JPH_SHADER_CONSTANTS_END(type) };
#define JPH_SHADER_FUNCTION_BEGIN(return_type, name, group_size_x, group_size_y, group_size_z) \
[numthreads(group_size_x, group_size_y, group_size_z)] \
return_type name(
#define JPH_SHADER_PARAM_THREAD_ID(name) uint3 name : SV_DispatchThreadID
#define JPH_SHADER_FUNCTION_END )
#define JPH_SHADER_BUFFER(type) StructuredBuffer<type>
#define JPH_SHADER_RW_BUFFER(type) RWStructuredBuffer<type>
#define JPH_SHADER_BIND_BEGIN(name)
#define JPH_SHADER_BIND_END(name)
#define JPH_SHADER_BIND_BUFFER(type, name) JPH_SHADER_BUFFER(type) name;
#define JPH_SHADER_BIND_RW_BUFFER(type, name) JPH_SHADER_RW_BUFFER(type) name;
#define JPH_AtomicAdd InterlockedAdd
#endif
#define JPH_SHADER_STRUCT_BEGIN(name) struct name {
#define JPH_SHADER_STRUCT_MEMBER(type, name) type m##name;
#define JPH_SHADER_STRUCT_END(name) };
#define JPH_IN(type) in type
#define JPH_OUT(type) out type
#define JPH_IN_OUT(type) in out type
#endif // JPH_OVERRIDE_SHADER_MACROS

View File

@ -0,0 +1,13 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2025 Jorrit Rouwe
// SPDX-License-Identifier: MIT
inline float3 JPH_Mat44Mul3x4Vec3(JPH_Mat44 inLHS, float3 inRHS)
{
return inLHS[0].xyz * inRHS.x + inLHS[1].xyz * inRHS.y + inLHS[2].xyz * inRHS.z + inLHS[3].xyz;
}
inline float3 JPH_Mat44Mul3x3Vec3(JPH_Mat44 inLHS, float3 inRHS)
{
return inLHS[0].xyz * inRHS.x + inLHS[1].xyz * inRHS.y + inLHS[2].xyz * inRHS.z;
}

View File

@ -0,0 +1,16 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2025 Jorrit Rouwe
// SPDX-License-Identifier: MIT
// Calculate inValue^2
inline float JPH_Square(float inValue)
{
return inValue * inValue;
}
// Get the closest point on a line segment defined by inA + x * inAB for x e [0, 1] to the origin
inline float3 JPH_GetClosestPointOnLine(float3 inA, float3 inAB)
{
float v = clamp(-dot(inA, inAB) / dot(inAB, inAB), 0.0f, 1.0f);
return inA + v * inAB;
}

View File

@ -0,0 +1,18 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2025 Jorrit Rouwe
// SPDX-License-Identifier: MIT
JPH_Plane JPH_PlaneFromPointAndNormal(float3 inPoint, float3 inNormal)
{
return JPH_Plane(inNormal, -dot(inNormal, inPoint));
}
float3 JPH_PlaneGetNormal(JPH_Plane inPlane)
{
return inPlane.xyz;
}
float JPH_PlaneSignedDistance(JPH_Plane inPlane, float3 inPoint)
{
return dot(inPoint, inPlane.xyz) + inPlane.w;
}

View File

@ -0,0 +1,114 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2025 Jorrit Rouwe
// SPDX-License-Identifier: MIT
inline float3 JPH_QuatMulVec3(JPH_Quat inLHS, float3 inRHS)
{
float3 v_xyz = inLHS.xyz;
float3 v_yzx = inLHS.yzx;
float3 q_cross_p = (inRHS.yzx * v_xyz - v_yzx * inRHS).yzx;
float3 q_cross_q_cross_p = (q_cross_p.yzx * v_xyz - v_yzx * q_cross_p).yzx;
float3 v = inLHS.w * q_cross_p + q_cross_q_cross_p;
return inRHS + (v + v);
}
inline JPH_Quat JPH_QuatMulQuat(JPH_Quat inLHS, JPH_Quat inRHS)
{
float x = inLHS.w * inRHS.x + inLHS.x * inRHS.w + inLHS.y * inRHS.z - inLHS.z * inRHS.y;
float y = inLHS.w * inRHS.y - inLHS.x * inRHS.z + inLHS.y * inRHS.w + inLHS.z * inRHS.x;
float z = inLHS.w * inRHS.z + inLHS.x * inRHS.y - inLHS.y * inRHS.x + inLHS.z * inRHS.w;
float w = inLHS.w * inRHS.w - inLHS.x * inRHS.x - inLHS.y * inRHS.y - inLHS.z * inRHS.z;
return JPH_Quat(x, y, z, w);
}
inline JPH_Quat JPH_QuatImaginaryMulQuat(float3 inLHS, JPH_Quat inRHS)
{
float x = +inLHS.x * inRHS.w + inLHS.y * inRHS.z - inLHS.z * inRHS.y;
float y = -inLHS.x * inRHS.z + inLHS.y * inRHS.w + inLHS.z * inRHS.x;
float z = +inLHS.x * inRHS.y - inLHS.y * inRHS.x + inLHS.z * inRHS.w;
float w = -inLHS.x * inRHS.x - inLHS.y * inRHS.y - inLHS.z * inRHS.z;
return JPH_Quat(x, y, z, w);
}
inline float3 JPH_QuatRotateAxisZ(JPH_Quat inRotation)
{
return (inRotation.z + inRotation.z) * inRotation.xyz + (inRotation.w + inRotation.w) * float3(inRotation.y, -inRotation.x, inRotation.w) - float3(0, 0, 1);
}
inline JPH_Quat JPH_QuatConjugate(JPH_Quat inRotation)
{
return JPH_Quat(-inRotation.x, -inRotation.y, -inRotation.z, inRotation.w);
}
inline JPH_Quat JPH_QuatDecompress(uint inValue)
{
const float cOneOverSqrt2 = 0.70710678f;
const uint cNumBits = 9;
const uint cMask = (1u << cNumBits) - 1;
const uint cMaxValue = cMask - 1; // Need odd number of buckets to quantize to or else we can't encode 0
const float cScale = 2.0f * cOneOverSqrt2 / float(cMaxValue);
// Restore two components
float3 v3 = float3(float(inValue & cMask), float((inValue >> cNumBits) & cMask), float((inValue >> (2 * cNumBits)) & cMask)) * cScale - float3(cOneOverSqrt2, cOneOverSqrt2, cOneOverSqrt2);
// Restore the highest component
float4 v = float4(v3, sqrt(max(1.0f - dot(v3, v3), 0.0f)));
// Extract sign
if ((inValue & 0x80000000u) != 0)
v = -v;
// Swizzle the components in place
uint max_element = (inValue >> 29) & 3;
v = max_element == 0? v.wxyz : (max_element == 1? v.xwyz : (max_element == 2? v.xywz : v));
return v;
}
inline JPH_Quat JPH_QuatFromMat33(float3 inCol0, float3 inCol1, float3 inCol2)
{
float tr = inCol0.x + inCol1.y + inCol2.z;
if (tr >= 0.0f)
{
float s = sqrt(tr + 1.0f);
float is = 0.5f / s;
return JPH_Quat(
(inCol1.z - inCol2.y) * is,
(inCol2.x - inCol0.z) * is,
(inCol0.y - inCol1.x) * is,
0.5f * s);
}
else
{
if (inCol0.x > inCol1.y && inCol0.x > inCol2.z)
{
float s = sqrt(inCol0.x - (inCol1.y + inCol2.z) + 1);
float is = 0.5f / s;
return JPH_Quat(
0.5f * s,
(inCol1.x + inCol0.y) * is,
(inCol0.z + inCol2.x) * is,
(inCol1.z - inCol2.y) * is);
}
else if (inCol1.y > inCol2.z)
{
float s = sqrt(inCol1.y - (inCol2.z + inCol0.x) + 1);
float is = 0.5f / s;
return JPH_Quat(
(inCol1.x + inCol0.y) * is,
0.5f * s,
(inCol2.y + inCol1.z) * is,
(inCol2.x - inCol0.z) * is);
}
else
{
float s = sqrt(inCol2.z - (inCol0.x + inCol1.y) + 1);
float is = 0.5f / s;
return JPH_Quat(
(inCol0.z + inCol2.x) * is,
(inCol2.y + inCol1.z) * is,
0.5f * s,
(inCol0.y - inCol1.x) * is);
}
}
}

View File

@ -0,0 +1,28 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2025 Jorrit Rouwe
// SPDX-License-Identifier: MIT
inline float3 JPH_Vec3DecompressUnit(uint inValue)
{
const float cOneOverSqrt2 = 0.70710678f;
const uint cNumBits = 14;
const uint cMask = (1u << cNumBits) - 1;
const uint cMaxValue = cMask - 1; // Need odd number of buckets to quantize to or else we can't encode 0
const float cScale = 2.0f * cOneOverSqrt2 / float(cMaxValue);
// Restore two components
float2 v2 = float2(float(inValue & cMask), float((inValue >> cNumBits) & cMask)) * cScale - float2(cOneOverSqrt2, cOneOverSqrt2);
// Restore the highest component
float3 v = float3(v2, sqrt(max(1.0f - dot(v2, v2), 0.0f)));
// Extract sign
if ((inValue & 0x80000000u) != 0)
v = -v;
// Swizzle the components in place
uint max_element = (inValue >> 29) & 3;
v = max_element == 0? v.zxy : (max_element == 1? v.xzy : v);
return v;
}

View File

@ -0,0 +1,25 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2025 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include "TestComputeBindings.h"
JPH_SHADER_FUNCTION_BEGIN(void, main, cTestComputeGroupSize, 1, 1)
JPH_SHADER_PARAM_THREAD_ID(tid)
JPH_SHADER_FUNCTION_END
{
// Ensure that we do not write out of bounds
if (tid.x >= cNumElements)
return;
if (cUIntValue2 == 0)
{
// First write, uses optional data and tests that the packing of float3/uint3's works
gData[tid.x] = gOptionalData[tid.x] + int(cFloat3Value2.y) + gUploadData[0];
}
else
{
// Read-modify-write gData
gData[tid.x] = (gData[tid.x] + cUIntValue) * cUIntValue2;
}
}

View File

@ -0,0 +1,25 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2025 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include "TestCompute2Bindings.h"
#include "ShaderMat44.h"
#include "ShaderVec3.h"
#include "ShaderQuat.h"
JPH_SHADER_FUNCTION_BEGIN(void, main, cTestCompute2GroupSize, 1, 1)
JPH_SHADER_PARAM_THREAD_ID(tid)
JPH_SHADER_FUNCTION_END
{
TestCompute2Input input = gInput[tid.x];
TestCompute2Output output;
output.mMul3x4Output = JPH_Mat44Mul3x4Vec3(input.mMat44Value, input.mMat44MulValue);
output.mMul3x3Output = JPH_Mat44Mul3x3Vec3(input.mMat44Value, input.mMat44MulValue);
output.mDecompressedVec3 = JPH_Vec3DecompressUnit(input.mCompressedVec3);
output.mDecompressedQuat = JPH_QuatDecompress(input.mCompressedQuat);
gOutput[tid.x] = output;
}

View File

@ -0,0 +1,26 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2025 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include "ShaderCore.h"
JPH_SHADER_CONSTANT(int, cTestCompute2GroupSize, 1)
JPH_SHADER_STRUCT_BEGIN(TestCompute2Input)
JPH_SHADER_STRUCT_MEMBER(JPH_Mat44, Mat44Value)
JPH_SHADER_STRUCT_MEMBER(JPH_float3, Mat44MulValue)
JPH_SHADER_STRUCT_MEMBER(JPH_uint, CompressedVec3)
JPH_SHADER_STRUCT_MEMBER(JPH_uint, CompressedQuat)
JPH_SHADER_STRUCT_END(TestComputeContext)
JPH_SHADER_STRUCT_BEGIN(TestCompute2Output)
JPH_SHADER_STRUCT_MEMBER(JPH_float3, Mul3x4Output)
JPH_SHADER_STRUCT_MEMBER(JPH_float3, Mul3x3Output)
JPH_SHADER_STRUCT_MEMBER(JPH_float3, DecompressedVec3)
JPH_SHADER_STRUCT_MEMBER(JPH_Quat, DecompressedQuat)
JPH_SHADER_STRUCT_END(TestCompute2Output)
JPH_SHADER_BIND_BEGIN(JPH_TestCompute2)
JPH_SHADER_BIND_BUFFER(TestCompute2Input, gInput)
JPH_SHADER_BIND_RW_BUFFER(TestCompute2Output, gOutput)
JPH_SHADER_BIND_END(JPH_TestCompute2)

View File

@ -0,0 +1,21 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2025 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include "ShaderCore.h"
JPH_SHADER_CONSTANT(int, cTestComputeGroupSize, 64)
JPH_SHADER_CONSTANTS_BEGIN(TestComputeContext, gContext)
JPH_SHADER_CONSTANTS_MEMBER(JPH_float3, Float3Value)
JPH_SHADER_CONSTANTS_MEMBER(JPH_uint, UIntValue) // Test that this value packs correctly with the float3 preceding it
JPH_SHADER_CONSTANTS_MEMBER(JPH_float3, Float3Value2)
JPH_SHADER_CONSTANTS_MEMBER(JPH_uint, UIntValue2)
JPH_SHADER_CONSTANTS_MEMBER(JPH_uint, NumElements)
JPH_SHADER_CONSTANTS_END(TestComputeContext)
JPH_SHADER_BIND_BEGIN(JPH_TestCompute)
JPH_SHADER_BIND_BUFFER(JPH_uint, gUploadData)
JPH_SHADER_BIND_BUFFER(JPH_uint, gOptionalData)
JPH_SHADER_BIND_RW_BUFFER(JPH_uint, gData)
JPH_SHADER_BIND_END(JPH_TestCompute)

View File

@ -0,0 +1,23 @@
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
// SPDX-FileCopyrightText: 2026 Jorrit Rouwe
// SPDX-License-Identifier: MIT
#include <Jolt/Jolt.h>
#ifdef JPH_USE_CPU_COMPUTE
#define JPH_SHADER_NAME TestCompute
#include <Jolt/Compute/CPU/WrapShaderBegin.h>
#include "TestCompute.hlsl"
#include <Jolt/Compute/CPU/WrapShaderBindings.h>
#include "TestComputeBindings.h"
#include <Jolt/Compute/CPU/WrapShaderEnd.h>
#define JPH_SHADER_NAME TestCompute2
#include <Jolt/Compute/CPU/WrapShaderBegin.h>
#include "TestCompute2.hlsl"
#include <Jolt/Compute/CPU/WrapShaderBindings.h>
#include "TestCompute2Bindings.h"
#include <Jolt/Compute/CPU/WrapShaderEnd.h>
#endif // JPH_USE_CPU_COMPUTE