You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

405 lines
14 KiB
HLSL

#define INITIAL_DISTANCE 1e10f
#define MARGIN g_CellSize
#define GRID_MARGIN int3(1, 1, 1)
#include "FurMotionInclude.cginc"
#include "Thread.hlsl"
uniform float4 g_Origin;
uniform float g_CellSize;
uniform int g_NumCellsX;
uniform int g_NumCellsY;
uniform int g_NumCellsZ;
// bindsets: 0 -> GenerateSDFLayout
// 1 -> ApplySDFLayout
// 2 ->
//Actually contains floats; make sure to use asfloat() when accessing. uint is used to allow atomics.
RWStructuredBuffer<uint> g_SignedDistanceField;
StructuredBuffer<uint> g_SignedDistanceField_read_only;
RWByteAddressBuffer g_TrimeshVertexIndices;
uniform RWStructuredBuffer<MeshProperties> collMeshVertexPositions;
//When building the SDF we want to find the lowest distance at each SDF cell. In order to allow multiple threads to write to the same
//cells, it is necessary to use atomics. However, there is no support for atomics with 32-bit floats so we convert the float into unsigned int
//and use atomic_min() / InterlockedMin() as a workaround.
//
//When used with atomic_min, both FloatFlip2() and FloatFlip3() will store the float with the lowest magnitude.
//The difference is that FloatFlip2() will preper negative values ( InterlockedMin( FloatFlip2(1.0), FloatFlip2(-1.0) ) == -1.0 ),
//while FloatFlip3() prefers positive values ( InterlockedMin( FloatFlip3(1.0), FloatFlip3(-1.0) ) == 1.0 ).
//Using FloatFlip3() seems to result in a SDF with higher quality compared to FloatFlip2().
uint FloatFlip3(float fl)
{
uint f = asuint(fl);
return (f << 1) | (f >> 31); //Rotate sign bit to least significant
}
uint IFloatFlip3(uint f2)
{
return (f2 >> 1) | (f2 << 31);
}
// Get SDF cell index coordinates (x, y and z) from a point position in world space
int3 GetSdfCoordinates(float3 positionInWorld)
{
float3 sdfPosition = (positionInWorld - g_Origin.xyz) / g_CellSize;
int3 result;
result.x = (int)sdfPosition.x;
result.y = (int)sdfPosition.y;
result.z = (int)sdfPosition.z;
return result;
}
float3 GetSdfCellPosition(int3 gridPosition)
{
float3 cellCenter = float3(gridPosition.x, gridPosition.y, gridPosition.z) * g_CellSize;
cellCenter += g_Origin.xyz;
return cellCenter;
}
int GetSdfCellIndex(int3 gridPosition)
{
int cellsPerLine = g_NumCellsX;
int cellsPerPlane = g_NumCellsX * g_NumCellsY;
return cellsPerPlane * gridPosition.z + cellsPerLine * gridPosition.y + gridPosition.x;
}
float DistancePointToEdge(float3 p, float3 x0, float3 x1, out float3 n)
{
float3 x10 = x1 - x0;
float t = dot(x1 - p, x10) / dot(x10, x10);
t = max(0.0f, min(t, 1.0f));
float3 a = p - (t * x0 + (1.0f - t) * x1);
float d = length(a);
n = a / (d + 1e-30f);
return d;
}
// Check if p is in the positive or negative side of triangle (x0, x1, x2)
// Positive side is where the normal vector of triangle ( (x1-x0) x (x2-x0) ) is pointing to.
float SignedDistancePointToTriangle(float3 p, float3 x0, float3 x1, float3 x2)
{
float d = 0;
float3 x02 = x0 - x2;
float l0 = length(x02) + 1e-30f;
x02 = x02 / l0;
float3 x12 = x1 - x2;
float l1 = dot(x12, x02);
x12 = x12 - l1 * x02;
float l2 = length(x12) + 1e-30f;
x12 = x12 / l2;
float3 px2 = p - x2;
float b = dot(x12, px2) / l2;
float a = (dot(x02, px2) - l1 * b) / l0;
float c = 1 - a - b;
// normal vector of triangle. Don't need to normalize this yet.
float3 nTri = cross((x1 - x0), (x2 - x0));
float3 n;
float tol = 1e-8f;
if (a >= -tol && b >= -tol && c >= -tol)
{
n = p - (a * x0 + b * x1 + c * x2);
d = length(n);
float3 n1 = n / d;
float3 n2 = nTri / (length(nTri) + 1e-30f); // if d == 0
n = (d > 0) ? n1 : n2;
}
else
{
float3 n_12;
float3 n_02;
d = DistancePointToEdge(p, x0, x1, n);
float d12 = DistancePointToEdge(p, x1, x2, n_12);
float d02 = DistancePointToEdge(p, x0, x2, n_02);
d = min(d, d12);
d = min(d, d02);
n = (d == d12) ? n_12 : n;
n = (d == d02) ? n_02 : n;
}
d = (dot(p - x0, nTri) < 0.f) ? -d : d;
return d;
}
float LinearInterpolate(float a, float b, float t)
{
return a * (1.0f - t) + b * t;
}
// bilinear interpolation
//
// p : 1-p
// c------------+----d
// | | |
// | | |
// | 1-q | |
// | | |
// | x |
// | | |
// | q | |
// a------------+----b
// p : 1-p
//
// x = BilinearInterpolate(a, b, c, d, p, q)
// = LinearInterpolate(LinearInterpolate(a, b, p), LinearInterpolate(c, d, p), q)
float BilinearInterpolate(float a, float b, float c, float d, float p, float q)
{
return LinearInterpolate(LinearInterpolate(a, b, p), LinearInterpolate(c, d, p), q);
}
float TrilinearInterpolate(float a, float b, float c, float d, float e, float f, float g, float h, float p, float q, float r)
{
return LinearInterpolate(BilinearInterpolate(a, b, c, d, p, q), BilinearInterpolate(e, f, g, h, p, q), r);
}
// Get signed distance at the position in world space
float GetSignedDistance(float3 positionInWorld)
{
int3 gridCoords = GetSdfCoordinates(positionInWorld);
if (!(0 <= gridCoords.x && gridCoords.x < g_NumCellsX - 2)
|| !(0 <= gridCoords.y && gridCoords.y < g_NumCellsY - 2)
|| !(0 <= gridCoords.z && gridCoords.z < g_NumCellsZ - 2))
return INITIAL_DISTANCE;
int sdfIndices[8];
{
int index = GetSdfCellIndex(gridCoords);
for (int i = 0; i < 8; ++i) sdfIndices[i] = index;
int x = 1;
int y = g_NumCellsX;
int z = g_NumCellsY * g_NumCellsX;
sdfIndices[1] += x;
sdfIndices[2] += y;
sdfIndices[3] += y + x;
sdfIndices[4] += z;
sdfIndices[5] += z + x;
sdfIndices[6] += z + y;
sdfIndices[7] += z + y + x;
}
float distances[8];
for (int j = 0; j < 8; ++j)
{
int sdfCellIndex = sdfIndices[j];
float dist = asfloat(g_SignedDistanceField_read_only[sdfCellIndex]);
if (dist == INITIAL_DISTANCE)
return INITIAL_DISTANCE;
distances[j] = dist;
}
float distance_000 = distances[0]; // X, Y, Z
float distance_100 = distances[1]; //+X, Y, Z
float distance_010 = distances[2]; // X, +Y, Z
float distance_110 = distances[3]; //+X, +Y, Z
float distance_001 = distances[4]; // X, Y, +Z
float distance_101 = distances[5]; //+X, Y, +Z
float distance_011 = distances[6]; // X, +Y, +Z
float distance_111 = distances[7]; //+X, +Y, +Z
float3 cellPosition = GetSdfCellPosition(gridCoords);
float3 interp = (positionInWorld - cellPosition) / g_CellSize;
return TrilinearInterpolate(distance_000, distance_100, distance_010, distance_110,
distance_001, distance_101, distance_011, distance_111,
interp.x, interp.y, interp.z);
}
float collisionMargin;
void collideWithSDF(inout int didCollide, inout float3 hairVertex, float vertexMoveAbility)
{
//When using forward differences to compute the SDF gradient,
//we need to use trilinear interpolation to look up 4 points in the SDF.
//In the worst case, this involves reading the distances in 4 different cubes (reading 32 floats from the SDF).
//In the ideal case, only 1 cube needs to be read (reading 8 floats from the SDF).
//The number of distance lookups varies depending on whether the 4 points cross cell boundaries.
//
//If we assume that (h < g_CellSize), where h is the distance between the points used for finite difference,
//we can mix forwards and backwards differences to ensure that the points never cross cell boundaries (always read 8 floats).
//By default we use forward differences, and switch to backwards differences for each dimension that crosses a cell boundary.
//
//This is much faster than using forward differences only, but it could also be less stable.
//In particular, it has the effect of making the gradient less accurate. The amount of inaccuracy is
//proportional to the size of h, so it is recommended keep h as low as possible.
float3 vertexInSdfLocalSpace = hairVertex;
didCollide = 0;
int3 gridCoords = GetSdfCoordinates(vertexInSdfLocalSpace);
if (!(0 <= gridCoords.x && gridCoords.x < g_NumCellsX - 2)
|| !(0 <= gridCoords.y && gridCoords.y < g_NumCellsY - 2)
|| !(0 <= gridCoords.z && gridCoords.z < g_NumCellsZ - 2))
return;
float distances[8];
{
int sdfIndices[8];
{
int index = GetSdfCellIndex(gridCoords);
for (int i = 0; i < 8; ++i) sdfIndices[i] = index;
int x = 1;
int y = g_NumCellsX;
int z = g_NumCellsY * g_NumCellsX;
sdfIndices[1] += x;
sdfIndices[2] += y;
sdfIndices[3] += y + x;
sdfIndices[4] += z;
sdfIndices[5] += z + x;
sdfIndices[6] += z + y;
sdfIndices[7] += z + y + x;
}
for (int j = 0; j < 8; ++j)
{
int sdfCellIndex = sdfIndices[j];
float dist = asfloat(g_SignedDistanceField[sdfCellIndex]);
if (dist == INITIAL_DISTANCE) return;
distances[j] = dist;
}
}
float distance_000 = distances[0]; // X, Y, Z
float distance_100 = distances[1]; //+X, Y, Z
float distance_010 = distances[2]; // X, +Y, Z
float distance_110 = distances[3]; //+X, +Y, Z
float distance_001 = distances[4]; // X, Y, +Z
float distance_101 = distances[5]; //+X, Y, +Z
float distance_011 = distances[6]; // X, +Y, +Z
float distance_111 = distances[7]; //+X, +Y, +Z
float3 cellPosition = GetSdfCellPosition(gridCoords);
float3 interp = (vertexInSdfLocalSpace - cellPosition) / g_CellSize;
float distanceAtVertex = TrilinearInterpolate(distance_000, distance_100, distance_010, distance_110,
distance_001, distance_101, distance_011, distance_111,
interp.x, interp.y, interp.z);
//Compute gradient with finite differences
float3 sdfGradient;
{
float h = 0.1f * g_CellSize;
float3 direction;
direction.x = (interp.x + h < 1.0f) ? 1.0f : -1.0f;
direction.y = (interp.y + h < 1.0f) ? 1.0f : -1.0f;
direction.z = (interp.z + h < 1.0f) ? 1.0f : -1.0f;
float3 neighborDistances;
neighborDistances.x = TrilinearInterpolate(distance_000, distance_100, distance_010, distance_110,
distance_001, distance_101, distance_011, distance_111,
interp.x + h * direction.x, interp.y, interp.z);
neighborDistances.y = TrilinearInterpolate(distance_000, distance_100, distance_010, distance_110,
distance_001, distance_101, distance_011, distance_111,
interp.x, interp.y + h * direction.y, interp.z);
neighborDistances.z = TrilinearInterpolate(distance_000, distance_100, distance_010, distance_110,
distance_001, distance_101, distance_011, distance_111,
interp.x, interp.y, interp.z + h * direction.z);
sdfGradient = (direction * (neighborDistances - float3(distanceAtVertex, distanceAtVertex, distanceAtVertex))) / h;
}
//Project hair vertex out of SDF
float3 normal = normalize(sdfGradient);
float collMargin = collisionMargin;
if (!isnan(normal.x) && !isinf(normal.x) && distanceAtVertex < collMargin)
{
#if defined(SHADER_API_METAL)
distanceAtVertex = clamp(distanceAtVertex, 0.0001, 0.03);
#endif
hairVertex = hairVertex.xyz + normal * (collMargin - distanceAtVertex);
didCollide = 1;
}
}
//SDF-Hair collision using forward differences only
// One thread per one hair vertex
void CollideHairVerticesWithSdf_forward(inout int didCollide, inout float3 hairVertex, float vertexMoveAbility)
{
didCollide = 0;
const float3 vertexInSdfLocalSpace = hairVertex;
float distance = GetSignedDistance(vertexInSdfLocalSpace);
// early exit if the distance is larger than collision margin
if (distance > collisionMargin)
return;
// small displacement.
float h = 0.1f * g_CellSize;
float3 sdfGradient;
{
//Compute gradient using forward difference
float3 offset[3];
offset[0] = float3(1, 0, 0);
offset[1] = float3(0, 1, 0);
offset[2] = float3(0, 0, 1);
float3 neighborCellPositions[3];
for (int i = 0; i < 3; ++i)
neighborCellPositions[i] = vertexInSdfLocalSpace + offset[i] * h;
//Use trilinear interpolation to get distances
float neighborCellDistances[3];
for (int j = 0; j < 3; ++j)
neighborCellDistances[j] = GetSignedDistance(neighborCellPositions[j]);
float3 forwardDistances;
forwardDistances.x = neighborCellDistances[0];
forwardDistances.y = neighborCellDistances[1];
forwardDistances.z = neighborCellDistances[2];
sdfGradient = (forwardDistances - float3(distance, distance, distance)) / h;
}
//Project hair vertex out of SDF
const float3 normal = normalize(sdfGradient);
float collMargin = collisionMargin;
if (!isnan(normal.x) && !isinf(normal.x) && distance < collMargin)
{
#if defined(SHADER_API_METAL)
distance = clamp(distance, 0.0001, 0.03);
#endif
hairVertex = hairVertex + normal * (collMargin - distance);
didCollide = 1;
}
}