/******************************************************************************/ /* 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; }