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.
504 lines
14 KiB
Plaintext
504 lines
14 KiB
Plaintext
/******************************************************************************/
|
|
/*
|
|
Project - MudBun
|
|
Publisher - Long Bunny Labs
|
|
http://LongBunnyLabs.com
|
|
Author - Ming-Lun "Allen" Chou
|
|
http://AllenChou.net
|
|
*/
|
|
/******************************************************************************/
|
|
|
|
#pragma kernel dual_contouring_move_point
|
|
#pragma kernel dual_contouring_move_point_2d
|
|
|
|
#include "../../Shader/ComputeCommon.cginc"
|
|
|
|
#include "../../Shader/BrushFuncs.cginc"
|
|
#include "../../Shader/DualMeshingFuncs.cginc"
|
|
#include "../../Shader/GenPointDefs.cginc"
|
|
#include "../../Shader/IndirectArgsDefs.cginc"
|
|
#include "../../Shader/Math/MathConst.cginc"
|
|
#include "../../Shader/MeshingModeDefs.cginc"
|
|
#include "../../Shader/NormalFuncs.cginc"
|
|
#include "../../Shader/RenderModeDefs.cginc"
|
|
|
|
// https://www.boristhebrave.com/2018/04/15/dual-contouring-tutorial/
|
|
// https://www.mattkeeter.com/projects/contours/
|
|
|
|
float dualContouringDualQuadsBlend;
|
|
float dualContouringRelaxation;
|
|
int dualContouringSolverIterations;
|
|
int dualContouringBinarySearchIterations;
|
|
int dualContouringGradientDescentIterations;
|
|
float dualContouringGradientDescentFactor;
|
|
|
|
static int aEdgeVertIndex3d[12][2] =
|
|
{
|
|
{ 0, 1 },
|
|
{ 1, 5 },
|
|
{ 5, 4 },
|
|
{ 4, 0 },
|
|
{ 2, 3 },
|
|
{ 3, 7 },
|
|
{ 7, 6 },
|
|
{ 6, 2 },
|
|
{ 0, 2 },
|
|
{ 1, 3 },
|
|
{ 5, 7 },
|
|
{ 4, 6 },
|
|
};
|
|
|
|
static int aEdgeVertIndex2d[4][2] =
|
|
{
|
|
{ 0, 1 },
|
|
{ 1, 2 },
|
|
{ 2, 3 },
|
|
{ 3, 0 },
|
|
};
|
|
|
|
[numthreads(kThreadGroupSize, 1, 1)]
|
|
void dual_contouring_move_point(int3 id : SV_DispatchThreadID)
|
|
{
|
|
#if defined(MUDBUN_DISABLE_DUAL_CONTOURING) || defined(MUDBUN_FAST_ITERATION)
|
|
return;
|
|
#endif
|
|
|
|
if (id.x >= indirectDrawArgs[0])
|
|
return;
|
|
|
|
int iGenPoint = id.x;
|
|
|
|
if (dualContouringDualQuadsBlend >= 1.0f)
|
|
{
|
|
if (renderMode == kRenderModeQuadSplats)
|
|
aGenPoint[iGenPoint].material.size *= 0.70711f;
|
|
return;
|
|
}
|
|
|
|
float h = 0.5f * voxelSize;
|
|
float3 center = aGenPoint[iGenPoint].posNorm.xyz;
|
|
float3 minCornerOffset = -h;
|
|
int iBrushMask = aGenPoint[iGenPoint].iBrushMask;
|
|
|
|
float3 aCornerOffset[8] =
|
|
{
|
|
float3(-h, -h, -h),
|
|
float3( h, -h, -h),
|
|
float3(-h, h, -h),
|
|
float3( h, h, -h),
|
|
float3(-h, -h, h),
|
|
float3( h, -h, h),
|
|
float3(-h, h, h),
|
|
float3( h, h, h),
|
|
};
|
|
|
|
float aCornerRes[8];
|
|
SdfBrushMaterial mat;
|
|
[loop] for (int iCorner = 0; iCorner < 8; ++iCorner)
|
|
{
|
|
aCornerRes[iCorner] = sdf_masked_brushes(center + aCornerOffset[iCorner], iBrushMask, mat);
|
|
}
|
|
|
|
float3 avgEdgeOffset = 0.0f;
|
|
int numEdges = 0;
|
|
float3 aEdgeOffset[12];
|
|
[loop] for (int iEdge = 0; iEdge < 12; ++iEdge)
|
|
{
|
|
int iVert0 = aEdgeVertIndex3d[iEdge][0];
|
|
int iVert1 = aEdgeVertIndex3d[iEdge][1];
|
|
float res0 = aCornerRes[iVert0];
|
|
float res1 = aCornerRes[iVert1];
|
|
if (res0 * res1 > 0)
|
|
continue;
|
|
|
|
float3 offset0 = aCornerOffset[iVert0];
|
|
float3 offset1 = aCornerOffset[iVert1];
|
|
|
|
int iEdgeOutput = numEdges++;
|
|
|
|
float3 edgeOffset;
|
|
if (res0 == 0.0f && res1 == 0.0f)
|
|
{
|
|
edgeOffset = 0.5f * (offset0 + offset1);
|
|
}
|
|
else if (res0 == 0.0f)
|
|
{
|
|
edgeOffset = offset0;
|
|
}
|
|
else if (res1 == 0.0f)
|
|
{
|
|
edgeOffset = offset1;
|
|
}
|
|
else if (dualContouringBinarySearchIterations <= 0)
|
|
{
|
|
// lerp approximation
|
|
float t = -res0 / (res1 - res0);
|
|
edgeOffset = lerp(offset0, offset1, t);
|
|
}
|
|
else
|
|
{
|
|
// binary search
|
|
edgeOffset = 0.5f * (offset0 + offset1);
|
|
[loop] for (int iSearch = 0; iSearch < dualContouringBinarySearchIterations; ++iSearch)
|
|
{
|
|
float resT = sdf_masked_brushes(center + edgeOffset, iBrushMask, mat);
|
|
if (res0 * resT < 0.0f)
|
|
{
|
|
res1 = resT;
|
|
offset1 = edgeOffset;
|
|
}
|
|
else if (resT * res1 < 0.0f)
|
|
{
|
|
res0 = resT;
|
|
offset0 = edgeOffset;
|
|
}
|
|
edgeOffset = 0.5f * (offset0 + offset1);
|
|
}
|
|
}
|
|
|
|
avgEdgeOffset += edgeOffset;
|
|
aEdgeOffset[iEdgeOutput] = edgeOffset;
|
|
}
|
|
|
|
if (numEdges <= 0)
|
|
return;
|
|
|
|
avgEdgeOffset /= numEdges;
|
|
|
|
float3 aEdgeNorm[12];
|
|
[loop] for (int iEdgeOffset = 0; iEdgeOffset < numEdges; ++iEdgeOffset)
|
|
{
|
|
SDF_NORMAL_FULL(aEdgeNorm[iEdgeOffset], center + aEdgeOffset[iEdgeOffset], sdf_masked_brushes, iBrushMask, 1e-2f * voxelSize);
|
|
}
|
|
|
|
float3 bestOffset = 0.0f;
|
|
|
|
if (dualContouringSolverIterations > 0)
|
|
{
|
|
float aD[12];
|
|
for (int iEdgeOffset = 0; iEdgeOffset < numEdges; ++iEdgeOffset)
|
|
{
|
|
aD[iEdgeOffset] = dot(aEdgeNorm[iEdgeOffset], aEdgeOffset[iEdgeOffset]);
|
|
}
|
|
for (int iSolve = 0; iSolve < dualContouringSolverIterations; ++iSolve)
|
|
{
|
|
for (int iEdgeOffset = 0; iEdgeOffset < numEdges; ++iEdgeOffset)
|
|
{
|
|
bestOffset -= 0.75f * aEdgeNorm[iEdgeOffset] * dot(aEdgeNorm[iEdgeOffset], bestOffset - aEdgeOffset[iEdgeOffset]);
|
|
}
|
|
}
|
|
bestOffset = clamp(bestOffset, -h, h);
|
|
}
|
|
else
|
|
{
|
|
// minimize ||A * x - b||^2
|
|
float3x3 A[5];
|
|
float3 b[5];
|
|
for (int i = 0; i < 5; ++i)
|
|
{
|
|
A[i] = 0.0f;
|
|
b[i] = 0.0f;
|
|
}
|
|
{
|
|
int r = 0;
|
|
float dualContouringRelaxationComp = 1.0f - dualContouringRelaxation;
|
|
while (r < numEdges)
|
|
{
|
|
uint i = uint(r) / 3;
|
|
switch (uint(r) % 3)
|
|
{
|
|
case 0:
|
|
A[i]._m00_m01_m02 = dualContouringRelaxationComp * aEdgeNorm[r];
|
|
b[i].x = dualContouringRelaxationComp * dot(aEdgeNorm[r], aEdgeOffset[r]);
|
|
break;
|
|
case 1:
|
|
A[i]._m10_m11_m12 = dualContouringRelaxationComp * aEdgeNorm[r];
|
|
b[i].y = dualContouringRelaxationComp * dot(aEdgeNorm[r], aEdgeOffset[r]);
|
|
break;
|
|
case 2:
|
|
A[i]._m20_m21_m22 = dualContouringRelaxationComp * aEdgeNorm[r];
|
|
b[i].z = dualContouringRelaxationComp * dot(aEdgeNorm[r], aEdgeOffset[r]);
|
|
break;
|
|
}
|
|
++r;
|
|
}
|
|
A[4]._m00_m01_m02 = float3(dualContouringRelaxation, 0.0f, 0.0f);
|
|
A[4]._m10_m11_m12 = float3(0.0f, dualContouringRelaxation, 0.0f);
|
|
A[4]._m20_m21_m22 = float3(0.0f, 0.0f, dualContouringRelaxation);
|
|
b[4] = dualContouringRelaxation * avgEdgeOffset;
|
|
}
|
|
|
|
// pseudoinverse
|
|
{
|
|
float3x3 pInvA[5];
|
|
{
|
|
// tempA = (A' * A)^-1
|
|
float3x3 tempA = 0.0f;
|
|
for (int iA = 0; iA < 5; ++iA)
|
|
{
|
|
tempA += mul(transpose(A[iA]), A[iA]);
|
|
}
|
|
float3x3 tempACopy = tempA;
|
|
float det =
|
|
tempACopy._m00 * (tempACopy._m11 * tempACopy._m22 - tempACopy._m21 * tempACopy._m12) -
|
|
tempACopy._m01 * (tempACopy._m10 * tempACopy._m22 - tempACopy._m12 * tempACopy._m20) +
|
|
tempACopy._m02 * (tempACopy._m10 * tempACopy._m21 - tempACopy._m11 * tempACopy._m20);
|
|
float detInv = 1.0f / det;
|
|
tempA._m00 = (tempACopy._m11 * tempACopy._m22 - tempACopy._m21 * tempACopy._m12) * detInv;
|
|
tempA._m01 = (tempACopy._m02 * tempACopy._m21 - tempACopy._m01 * tempACopy._m22) * detInv;
|
|
tempA._m02 = (tempACopy._m01 * tempACopy._m12 - tempACopy._m02 * tempACopy._m11) * detInv;
|
|
tempA._m10 = (tempACopy._m12 * tempACopy._m20 - tempACopy._m10 * tempACopy._m22) * detInv;
|
|
tempA._m11 = (tempACopy._m00 * tempACopy._m22 - tempACopy._m02 * tempACopy._m20) * detInv;
|
|
tempA._m12 = (tempACopy._m10 * tempACopy._m02 - tempACopy._m00 * tempACopy._m12) * detInv;
|
|
tempA._m20 = (tempACopy._m10 * tempACopy._m21 - tempACopy._m20 * tempACopy._m11) * detInv;
|
|
tempA._m21 = (tempACopy._m20 * tempACopy._m01 - tempACopy._m00 * tempACopy._m21) * detInv;
|
|
tempA._m22 = (tempACopy._m00 * tempACopy._m11 - tempACopy._m10 * tempACopy._m01) * detInv;
|
|
|
|
// pInvA = (A' * A)^-1 * A' = tempA * A'
|
|
for (i = 0; i < 5; ++i)
|
|
{
|
|
pInvA[i] = mul(tempA, transpose(A[i]));
|
|
}
|
|
}
|
|
|
|
// bestOffset = pInvA * b
|
|
for (int iA = 0; iA < 5; ++iA)
|
|
{
|
|
bestOffset += mul(pInvA[iA], b[iA]);
|
|
}
|
|
} // end: pseudoinverse
|
|
}
|
|
|
|
float3 bestPos = center + bestOffset;
|
|
|
|
// doesn't buy us much and bloats compile time
|
|
// gradient descent
|
|
if (dualContouringGradientDescentIterations > 0)
|
|
{
|
|
float3 n;
|
|
SDF_NORMAL_FULL(n, bestPos, sdf_masked_brushes, iBrushMask, 1e-2f * voxelSize);
|
|
SdfBrushMaterial mat;
|
|
[loop] for (int iDescent = 0; iDescent < dualContouringGradientDescentIterations; ++iDescent)
|
|
{
|
|
float d = sdf_masked_brushes(bestPos, iBrushMask, mat);
|
|
bestOffset -= dualContouringGradientDescentFactor * n * d;
|
|
bestPos = center + bestOffset;
|
|
}
|
|
}
|
|
|
|
aGenPoint[iGenPoint].posNorm.xyz = lerp(bestPos, aGenPoint[iGenPoint].posNorm.xyz, dualContouringDualQuadsBlend);
|
|
|
|
if (renderMode == kRenderModeQuadSplats)
|
|
aGenPoint[iGenPoint].material.size *= 1.0f - (1.f - 0.70711f) * dualContouringDualQuadsBlend;
|
|
}
|
|
|
|
|
|
[numthreads(kThreadGroupSize, 1, 1)]
|
|
void dual_contouring_move_point_2d(int3 id : SV_DispatchThreadID)
|
|
{
|
|
#if defined(MUDBUN_DISABLE_DUAL_CONTOURING) || defined(MUDBUN_FAST_ITERATION)
|
|
return;
|
|
#endif
|
|
|
|
if (id.x >= indirectDrawArgs[0])
|
|
return;
|
|
|
|
int iGenPoint = id.x;
|
|
|
|
if (dualContouringDualQuadsBlend >= 1.0f)
|
|
{
|
|
if (renderMode == kRenderModeQuadSplats)
|
|
aGenPoint[iGenPoint].material.size *= 0.70711f;
|
|
return;
|
|
}
|
|
|
|
float h = 0.5f * voxelSize;
|
|
float3 center = aGenPoint[iGenPoint].posNorm.xyz;
|
|
float3 minCornerOffset = -h;
|
|
int iBrushMask = aGenPoint[iGenPoint].iBrushMask;
|
|
|
|
float3 aCornerOffset[4] =
|
|
{
|
|
float3(-h, -h, 0.0f),
|
|
float3(-h, h, 0.0f),
|
|
float3( h, h, 0.0f),
|
|
float3( h, -h, 0.0f),
|
|
};
|
|
|
|
bool anyOutside = false;
|
|
float aCornerRes[8];
|
|
SdfBrushMaterial mat;
|
|
[loop] for (int iCorner = 0; iCorner < 4; ++iCorner)
|
|
{
|
|
float cornerRes = sdf_masked_brushes(center + aCornerOffset[iCorner], iBrushMask, mat);
|
|
aCornerRes[iCorner] = cornerRes;
|
|
if (cornerRes >= 0.0f)
|
|
anyOutside = true;
|
|
}
|
|
|
|
if (!anyOutside)
|
|
return;
|
|
|
|
float3 avgEdgeOffset = 0.0f;
|
|
int numEdges = 0;
|
|
float3 aEdgeOffset[4];
|
|
[loop] for (int iEdge = 0; iEdge < 4; ++iEdge)
|
|
{
|
|
int iVert0 = aEdgeVertIndex2d[iEdge][0];
|
|
int iVert1 = aEdgeVertIndex2d[iEdge][1];
|
|
float res0 = aCornerRes[iVert0];
|
|
float res1 = aCornerRes[iVert1];
|
|
if (res0 * res1 > 0)
|
|
continue;
|
|
|
|
float3 offset0 = aCornerOffset[iVert0];
|
|
float3 offset1 = aCornerOffset[iVert1];
|
|
|
|
int iEdgeOutput = numEdges++;
|
|
|
|
float3 edgeOffset;
|
|
if (res0 == 0.0f && res1 == 0.0f)
|
|
{
|
|
edgeOffset = 0.5f * (offset0 + offset1);
|
|
}
|
|
else if (res0 == 0.0f)
|
|
{
|
|
edgeOffset = offset0;
|
|
}
|
|
else if (res1 == 0.0f)
|
|
{
|
|
edgeOffset = offset1;
|
|
}
|
|
else if (dualContouringBinarySearchIterations <= 0)
|
|
{
|
|
// lerp approximation
|
|
float t = -res0 / (res1 - res0);
|
|
edgeOffset = lerp(offset0, offset1, t);
|
|
}
|
|
else
|
|
{
|
|
// binary search
|
|
edgeOffset = 0.5f * (offset0 + offset1);
|
|
[loop] for (int iSearch = 0; iSearch < dualContouringBinarySearchIterations; ++iSearch)
|
|
{
|
|
float resT = sdf_masked_brushes(center + edgeOffset, iBrushMask, mat);
|
|
if (res0 * resT < 0.0f)
|
|
{
|
|
res1 = resT;
|
|
offset1 = edgeOffset;
|
|
}
|
|
else if (resT * res1 < 0.0f)
|
|
{
|
|
res0 = resT;
|
|
offset0 = edgeOffset;
|
|
}
|
|
edgeOffset = 0.5f * (offset0 + offset1);
|
|
}
|
|
}
|
|
|
|
avgEdgeOffset += edgeOffset;
|
|
aEdgeOffset[iEdgeOutput] = edgeOffset;
|
|
}
|
|
|
|
if (numEdges <= 0)
|
|
return;
|
|
|
|
avgEdgeOffset /= numEdges;
|
|
|
|
float3 aEdgeNorm[4];
|
|
[loop] for (int iEdgeOffset = 0; iEdgeOffset < numEdges; ++iEdgeOffset)
|
|
{
|
|
SDF_NORMAL_2D(aEdgeNorm[iEdgeOffset], center + aEdgeOffset[iEdgeOffset], sdf_masked_brushes, iBrushMask, 1e-2f * voxelSize);
|
|
}
|
|
|
|
// minimize ||A * x - b||^2
|
|
float2x2 A[3];
|
|
float2 b[3];
|
|
for (int i = 0; i < 3; ++i)
|
|
{
|
|
A[i] = 0.0f;
|
|
b[i] = 0.0f;
|
|
}
|
|
{
|
|
int r = 0;
|
|
float dualContouringRelaxationComp = 1.0f - dualContouringRelaxation;
|
|
while (r < numEdges)
|
|
{
|
|
uint i = uint(r) / 2;
|
|
switch (uint(r) % 2)
|
|
{
|
|
case 0:
|
|
A[i]._m00_m01 = dualContouringRelaxationComp * aEdgeNorm[r].xy;
|
|
b[i].x = dualContouringRelaxationComp * dot(aEdgeNorm[r], aEdgeOffset[r]);
|
|
break;
|
|
case 1:
|
|
A[i]._m10_m11 = dualContouringRelaxationComp * aEdgeNorm[r].xy;
|
|
b[i].y = dualContouringRelaxationComp * dot(aEdgeNorm[r], aEdgeOffset[r]);
|
|
break;
|
|
}
|
|
++r;
|
|
}
|
|
A[2]._m00_m01 = float2(dualContouringRelaxation, 0.0f);
|
|
A[2]._m10_m11 = float2(0.0f, dualContouringRelaxation);
|
|
b[2] = dualContouringRelaxation * avgEdgeOffset.xy;
|
|
}
|
|
|
|
float2 bestOffset = 0.0f;
|
|
|
|
// pseudoinverse
|
|
{
|
|
float2x2 pInvA[3];
|
|
{
|
|
// tempA = (A' * A)^-1
|
|
float2x2 tempA = 0.0f;
|
|
for (int iA = 0; iA < 3; ++iA)
|
|
{
|
|
tempA += mul(transpose(A[iA]), A[iA]);
|
|
}
|
|
float2x2 tempACopy = tempA;
|
|
float det = tempACopy._m00 * tempACopy._m11 - tempACopy._m01 * tempACopy._m10;
|
|
float detInv = 1.0f / det;
|
|
tempA._m00 = tempACopy._m11 * detInv;
|
|
tempA._m01 = -tempACopy._m01 * detInv;
|
|
tempA._m10 = -tempACopy._m10 * detInv;
|
|
tempA._m11 = tempACopy._m00 * detInv;
|
|
|
|
// pInvA = (A' * A)^-1 * A' = tempA * A'
|
|
for (i = 0; i < 3; ++i)
|
|
{
|
|
pInvA[i] = mul(tempA, transpose(A[i]));
|
|
}
|
|
}
|
|
|
|
// bestOffset = pInvA * b
|
|
for (int iA = 0; iA < 3; ++iA)
|
|
{
|
|
bestOffset += mul(pInvA[iA], b[iA]);
|
|
}
|
|
} // end: pseudoinverse
|
|
|
|
float3 bestPos = center + float3(bestOffset, 0.0f);
|
|
|
|
// doesn't buy us much and bloats compile time
|
|
// gradient descent
|
|
if (dualContouringGradientDescentIterations > 0)
|
|
{
|
|
float3 n;
|
|
SDF_NORMAL_2D(n, bestPos, sdf_masked_brushes, iBrushMask, 1e-2f * voxelSize);
|
|
SdfBrushMaterial mat;
|
|
[loop] for (int iDescent = 0; iDescent < dualContouringGradientDescentIterations; ++iDescent)
|
|
{
|
|
float d = sdf_masked_brushes(bestPos, iBrushMask, mat);
|
|
bestOffset -= 0.5f * dualContouringGradientDescentFactor * n.xy * d;
|
|
bestPos = center + float3(bestOffset, 0.0f);
|
|
}
|
|
}
|
|
|
|
aGenPoint[iGenPoint].posNorm.xyz = lerp(bestPos, aGenPoint[iGenPoint].posNorm.xyz, dualContouringDualQuadsBlend);
|
|
aGenPoint[iGenPoint].sdfValue = sdf_masked_brushes(bestPos, iBrushMask, mat) + surfaceShift;
|
|
|
|
if (renderMode == kRenderModeQuadSplats)
|
|
aGenPoint[iGenPoint].material.size *= 1.0f - (1.f - 0.70711f) * dualContouringDualQuadsBlend;
|
|
}
|
|
|