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.
1064 lines
33 KiB
HLSL
1064 lines
33 KiB
HLSL
/*****************************************************************************/
|
|
/*
|
|
Project - MudBun
|
|
Publisher - Long Bunny Labs
|
|
http://LongBunnyLabs.com
|
|
Author - Ming-Lun "Allen" Chou
|
|
http://AllenChou.net
|
|
*/
|
|
/******************************************************************************/
|
|
|
|
#ifndef MUDBUN_BRUSH_FUNCS
|
|
#define MUDBUN_BRUSH_FUNCS
|
|
|
|
#include "BrushDefs.cginc"
|
|
|
|
#include "AabbTreeFuncs.cginc"
|
|
#include "BrushMaskFuncs.cginc"
|
|
#include "Math/CatmullRom.cginc"
|
|
#include "Math/Codec.cginc"
|
|
#include "Math/MathConst.cginc"
|
|
#include "Math/Quaternion.cginc"
|
|
#include "Math/Vector.cginc"
|
|
#include "Noise/RandomNoise.cginc"
|
|
#include "SDF/SDF.cginc"
|
|
#include "VoxelDefs.cginc"
|
|
|
|
SdfBrushMaterial init_brush_material()
|
|
{
|
|
SdfBrushMaterial mat;
|
|
mat.color = float4(0.0f, 0.0f, 0.0f, 1.0f);
|
|
mat.emissionHash = float4(0.0f, 0.0f, 0.0f, 0.0f);
|
|
mat.metallicSmoothnessSizeTightness = float4(0.0f, 0.0f, 1.0f, 0.0f);
|
|
mat.textureWeight = float4(0.0f, 0.0f, 0.0f, 0.0f);
|
|
mat.iBrush = -1;
|
|
mat.padding0 = mat.padding1 = mat.padding2 = 0;
|
|
return mat;
|
|
}
|
|
|
|
SdfBrushMaterial lerp(SdfBrushMaterial a, SdfBrushMaterial b, float t)
|
|
{
|
|
SdfBrushMaterial o = a;
|
|
|
|
o.color = lerp(a.color, b.color, t);
|
|
o.emissionHash.rgb = lerp(a.emissionHash.rgb, b.emissionHash.rgb, t);
|
|
o.emissionHash.a = (t < 0.5f) ? a.emissionHash.a : b.emissionHash.a;
|
|
o.iBrush = (t < 0.5f) ? a.iBrush : b.iBrush;
|
|
o.metallicSmoothnessSizeTightness.xyz = lerp(a.metallicSmoothnessSizeTightness.xyz, b.metallicSmoothnessSizeTightness.xyz, t);
|
|
o.textureWeight = lerp(a.textureWeight, b.textureWeight, t);
|
|
|
|
return o;
|
|
}
|
|
|
|
SdfBrushMaterialCompressed lerp(SdfBrushMaterialCompressed a, SdfBrushMaterialCompressed b, float t)
|
|
{
|
|
return pack_material(lerp(unpack_material(a), unpack_material(b), t));
|
|
}
|
|
|
|
float sdf_boundary(float3 pRel, SdfBrush b, int shape, out float fadeDist)
|
|
{
|
|
float3 h = abs(0.5f * b.size);
|
|
|
|
fadeDist = 0.0f;
|
|
|
|
float res = kInfinity;
|
|
switch (shape)
|
|
{
|
|
case kSdfNoiseBoundaryBox:
|
|
{
|
|
res = sdf_box(pRel, h);
|
|
fadeDist = max_comp(h);
|
|
break;
|
|
}
|
|
|
|
case kSdfNoiseBoundarySphere:
|
|
{
|
|
res = sdf_ellipsoid(pRel, b.radius * b.size);
|
|
fadeDist = b.radius * max_comp(b.size);
|
|
break;
|
|
}
|
|
|
|
case kSdfNoiseBoundaryCylinder:
|
|
{
|
|
float2 elongation = max(0.0f, b.size.xz - 1.0f);
|
|
pRel.xz -= clamp(pRel.xz, -elongation, elongation);
|
|
res = sdf_cylinder(pRel, h.y, b.radius);
|
|
fadeDist = max(b.radius, h.y);
|
|
break;
|
|
}
|
|
|
|
case kSdfNoiseBoundaryTorus:
|
|
{
|
|
float3 hTorus = float3(h.x + 0.5f * b.radius, h.y, h.z + 0.5f * b.radius);
|
|
res = sdf_torus(pRel, hTorus.x - hTorus.z, hTorus.z - b.radius, b.radius);
|
|
fadeDist = max(max(h.x, h.z), b.radius);
|
|
break;
|
|
}
|
|
|
|
case kSdfNoiseBoundarySolidAngle:
|
|
{
|
|
res = sdf_solid_angle(pRel, float2(b.data3.x, b.data3.y), b.radius);
|
|
res = sdf_int(res, sdf_box(pRel, b.radius * b.size));
|
|
fadeDist = b.radius;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
#include "../Customization/CustomBrush.cginc"
|
|
|
|
float sdf_brush(float res, inout float3 p, SdfBrush b)
|
|
{
|
|
float preMirrorX = p.x;
|
|
|
|
bool doMirrorX = ((b.flags & kSdfBrushFlagsMirrorX) != 0);
|
|
if (doMirrorX)
|
|
p.x = abs(p.x);
|
|
|
|
bool flipX = ((b.flags & kSdfBrushFlagsFlipX) != 0);
|
|
if (flipX)
|
|
p.x = -p.x;
|
|
|
|
// extent
|
|
float3 h = abs(0.5f * b.size);
|
|
|
|
// relative to transform
|
|
float3 pRel = quat_rot(quat_inv(b.rotation), p - b.position);
|
|
|
|
switch (b.type)
|
|
{
|
|
case kSdfBox:
|
|
{
|
|
float pivotShift = b.data0.x;
|
|
pRel.y += pivotShift * h.y;
|
|
res = sdf_box(pRel, h, b.radius);
|
|
break;
|
|
}
|
|
|
|
case kSdfSphere:
|
|
{
|
|
float pivotShift = b.data0.x;
|
|
pRel.y += pivotShift * h.y;
|
|
res = sdf_ellipsoid(pRel, b.radius * b.size);
|
|
break;
|
|
}
|
|
|
|
#ifndef MUDBUN_FAST_ITERATION
|
|
case kSdfCylinder:
|
|
{
|
|
float pivotShift = b.data0.z;
|
|
pRel.y += pivotShift * h.y;
|
|
|
|
float2 elongation = max(0.0f, b.size.xz - 1.0f);
|
|
pRel.xz -= clamp(pRel.xz, -elongation, elongation);
|
|
|
|
res = sdf_capped_cone(pRel, h.y, b.radius, max(0.0f, b.radius + b.data0.y), b.data0.x);
|
|
break;
|
|
}
|
|
|
|
case kSdfTorus:
|
|
{
|
|
float elongation = b.data0.x;
|
|
pRel.y -= clamp(pRel.y, -elongation, elongation);
|
|
float3 hTorus = float3(h.x + 0.5f * b.radius, h.y, h.z + 0.5f * b.radius);
|
|
float r = abs(0.25f * b.size.y);
|
|
res = sdf_torus(pRel, hTorus.x - hTorus.z, hTorus.z - r, r);
|
|
break;
|
|
}
|
|
|
|
case kSdfSolidAngle:
|
|
{
|
|
res = sdf_solid_angle(pRel, float2(b.data0.x, b.data0.y), b.radius, b.data0.z);
|
|
break;
|
|
}
|
|
|
|
#ifndef MUDBUN_DISABLE_SDF_NOISE_VOLUME
|
|
case kSdfNoiseVolume:
|
|
case kSdfNoiseModifier:
|
|
{
|
|
float thresholdFadeDist = kEpsilon;
|
|
int boundaryShape = int(b.data2.z);
|
|
float noiseRes = sdf_boundary(pRel, b, boundaryShape, thresholdFadeDist);
|
|
float thresholdFadeT = sqrt(saturate(length(pRel) / thresholdFadeDist));
|
|
|
|
// because noise functions are not real SDFs
|
|
float distScale = 1.0f;
|
|
|
|
float3 aSample[2];
|
|
float3 aPeriod[2];
|
|
float aWeight[2];
|
|
int numSamples = 0;
|
|
|
|
bool lockPosition = ((b.flags & kSdfBrushFlagsLockNoisePosition) != 0);
|
|
float3 size = b.data0.xyz;
|
|
float3 offset = b.data1.xyz;
|
|
if ((b.flags & kSdfBrushFlagsSphericalNoiseCoordinates) == 0)
|
|
{
|
|
// uniform
|
|
aSample[0] = lockPosition ? pRel : p;
|
|
aPeriod[0] = kCartesianNoisePeriod;
|
|
aWeight[0] = 1.0f;
|
|
numSamples = 1;
|
|
}
|
|
else
|
|
{
|
|
// radial
|
|
aSample[0] = cartesian_to_spherical(pRel.xzy + kEpsilon) * float3(1.0f, kSphericalNoisePeriod / kTwoPi, 1.0f);
|
|
aSample[1] = cartesian_to_spherical(-pRel.xyz + kEpsilon) * float3(1.0f, kSphericalNoisePeriod / kTwoPi, 1.0f);
|
|
aPeriod[0] = float3(kCartesianNoisePeriod, kSphericalNoisePeriod, kCartesianNoisePeriod);
|
|
aPeriod[1] = float3(kCartesianNoisePeriod, kSphericalNoisePeriod, kCartesianNoisePeriod);
|
|
aWeight[0] = min(aSample[0].z, kPi - aSample[0].z) / kHalfPi;
|
|
aWeight[1] = 1.0f - aWeight[0];
|
|
numSamples = 2;
|
|
|
|
if (!lockPosition)
|
|
{
|
|
aSample[0] += b.position;
|
|
aSample[1] += b.position;
|
|
}
|
|
|
|
distScale = 0.25f; //clamp(s.x, 1.0f, 1.0f);
|
|
}
|
|
float threshold = b.data0.w;
|
|
float thresholdFade = b.data2.y;
|
|
threshold += (1.0f - threshold) * thresholdFade * thresholdFadeT;
|
|
int numOctaves = int(b.data1.w);
|
|
float octaveOffsetFactor = b.data2.x;
|
|
float boundaryBlend = b.data2.w;
|
|
boundaryBlend = max(0.1f * min(min(h.x, h.y), h.z), boundaryBlend);
|
|
int noiseType = int(b.data3.z);
|
|
float sRes = 0.0f;
|
|
[loop] for (int i = 0; i < numSamples; ++i)
|
|
{
|
|
float s = sdf_noise(noiseType, aSample[i], -h, h, offset, size, threshold, numOctaves, octaveOffsetFactor, aPeriod[i]);
|
|
sRes += aWeight[i] * s * distScale;
|
|
}
|
|
noiseRes = sdf_int_cubic(noiseRes, sRes, boundaryBlend);
|
|
|
|
switch (b.type)
|
|
{
|
|
case kSdfNoiseVolume:
|
|
res = noiseRes;
|
|
break;
|
|
case kSdfNoiseModifier:
|
|
res -= noiseRes * b.blend;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
#endif // MUDBUN_DISABLE_SDF_NOISE_VOLUME
|
|
|
|
#ifndef MUDBUN_DISABLE_SDF_SIMPLE_CURVE
|
|
case kSdfCurveSimple:
|
|
{
|
|
float3 pA = b.data0.xyz;
|
|
float3 pB = b.data1.xyz;
|
|
float3 pC = b.data2.xyz;
|
|
|
|
float3 pRelRaw = pRel;
|
|
float elongation = b.data3.x;
|
|
pRel.z -= clamp(pRel.z, -elongation, elongation);
|
|
|
|
float controlPointR = b.data3.y;
|
|
float smoothStepBlend = b.data3.z;
|
|
float r = 0.0f;
|
|
|
|
const bool colinear = b.data3.w > 0.0f;
|
|
float2 curRes =
|
|
b.data3.w > 0.0f // colinear?
|
|
? sdf_segment(pRel, pA, pB)
|
|
: sdf_bezier(pRel, pA, pC, pB);
|
|
|
|
if (controlPointR < 0.0f)
|
|
{
|
|
float t = curRes.y;
|
|
r = b.data0.w + (b.data1.w - b.data0.w) * lerp(t, smoothstep(0.0f, 1.0f, t), smoothStepBlend);
|
|
}
|
|
else
|
|
{
|
|
if (curRes.y < 0.5f)
|
|
{
|
|
float t = 2.0f * curRes.y;
|
|
r = b.data0.w + (controlPointR - b.data0.w) * lerp(t, smoothstep(0.0f, 1.0f, t), smoothStepBlend);
|
|
}
|
|
else
|
|
{
|
|
float t = 2.0f * (curRes.y - 0.5f);
|
|
r = controlPointR + (b.data1.w - controlPointR) * lerp(t, smoothstep(0.0f, 1.0f, t), smoothStepBlend);
|
|
}
|
|
}
|
|
res = curRes.x - r;
|
|
|
|
bool useNoise = (b.data2.w > 0.0f);
|
|
if (useNoise)
|
|
{
|
|
float curveLen = 0.0f;
|
|
int precision = 16;
|
|
float dt = 1.0f / precision;
|
|
float t = dt;
|
|
float3 prevPos = pA;
|
|
[loop] for (int i = 1; i < precision; ++i, t += dt)
|
|
{
|
|
float3 currPos = bezier_quad(pA, pC, pB, t);
|
|
curveLen += length(currPos - prevPos);
|
|
prevPos = currPos;
|
|
}
|
|
if (curRes.y < 0.0001f)
|
|
curRes.y = min(0.0f, -dot(normalize(pA - pC), pRel - pA) / curveLen);
|
|
else if (curRes.y > 0.9999f)
|
|
curRes.y = max(1.0f, 1.0f + dot(normalize(pB - pC), pRel - pB) / curveLen);
|
|
|
|
float3 up = normalize(kUnitY + 1e-3f * mbn_rand(pA));
|
|
float3 front = normalize(slerp(pA - pC, pC - pB, curRes.y));
|
|
float3 left = normalize(cross(up, front));
|
|
up = cross(front, left);
|
|
float3 closest = bezier_quad(pA, pC, pB, curRes.y);
|
|
float3 pDelta = pRelRaw - closest;
|
|
float3 s = float3(curRes.y * curveLen, dot(pDelta, up), dot(pDelta, left));
|
|
|
|
// advance to additional noise data
|
|
b = aBrush[b.index + 1];
|
|
|
|
float thresholdFade = b.data3.x;
|
|
float thresholdCoreBias = b.data3.y;
|
|
|
|
// twist
|
|
float twistA = b.data2.y;
|
|
float twistB = b.data2.z;
|
|
float twistT = lerp(twistA, twistB, curRes.y);
|
|
float twistCos = cos(twistT);
|
|
float twistSin = sin(twistT);
|
|
s.yz = mul(float2x2(twistCos, twistSin, -twistSin, twistCos), s.yz);
|
|
|
|
float3 offset = b.data1.xyz;
|
|
float3 size = b.data0.xyz;
|
|
float threshold = b.data0.w;
|
|
float rDelta = length(pDelta);
|
|
float coreBiasT = 1.0f - saturate(rDelta / max(kEpsilon, r));
|
|
threshold = saturate(threshold + sign(thresholdCoreBias) * abs(thresholdCoreBias) * coreBiasT);
|
|
threshold += (1.0f - threshold) * thresholdFade * saturate(curRes.y);
|
|
int numOctaves = int(b.data1.w);
|
|
float octaveOffsetFactor = b.data2.x;
|
|
|
|
float twistSdfMult = 1.0f / (1.0f + saturate(abs(twistA - twistB))); // hack: evlauate more surrounding voxels when twisted to avoid holes
|
|
float n = twistSdfMult * sdf_noise(kSdfNoiseTypeCachedPerlin, s, -kInfinity, kInfinity, offset, size, threshold, numOctaves, octaveOffsetFactor, kCartesianNoisePeriod);
|
|
res = sdf_int_cubic(res, n, 0.5f * r);
|
|
}
|
|
|
|
break;
|
|
}
|
|
#endif // MUDBUN_DISABLE_SDF_SIMPLE_CURVE
|
|
|
|
#ifndef MUDBUN_DISABLE_SDF_FULL_CURVE
|
|
case kSdfCurveFull:
|
|
{
|
|
int numPoints = int(b.data0.x);
|
|
if (numPoints > 1)
|
|
{
|
|
res = kInfinity;
|
|
|
|
int precision = int(b.data0.y);
|
|
float dt = 1.0f / precision;
|
|
|
|
bool useNoise = false;//(b.data0.z > 0.0f);
|
|
|
|
int iA = b.index + (useNoise ? 2 : 1);
|
|
float globalLen = 0.0f;
|
|
int iClosest = -1;
|
|
float tClosest = 0.0f;
|
|
float segResClosest = 0.0f;
|
|
float rClosest = 0.0f;
|
|
float3 pClosest = 0.0f;
|
|
float closestLen = 0.0f;
|
|
[loop] for (int i = 1, n = numPoints - 2; i < n; ++i, ++iA)
|
|
{
|
|
float3 pA = aBrush[iA + 0].data0.xyz;
|
|
float3 pB = aBrush[iA + 1].data0.xyz;
|
|
float3 pC = aBrush[iA + 2].data0.xyz;
|
|
float3 pD = aBrush[iA + 3].data0.xyz;
|
|
float3 prevPos = pB;
|
|
float r = aBrush[iA + 1].data0.w;
|
|
float dr = (aBrush[iA + 2].data0.w - r) * dt;
|
|
float localLen = 0.0f;
|
|
for (float t = dt; t < 1.0001f; t += dt)
|
|
{
|
|
float3 currPos = catmull_rom(pA, pB, pC, pD, min(1.0f, t));
|
|
float segLen = length(currPos - prevPos);
|
|
float d = sdf_round_cone(p, prevPos, currPos, r, r + dr);
|
|
if (d < res)
|
|
{
|
|
float2 segRes = sdf_segment(p, prevPos, currPos);
|
|
res = d;
|
|
iClosest = i;
|
|
tClosest = t;
|
|
rClosest = r + dr * segRes.y;
|
|
pClosest = lerp(prevPos, currPos, segRes.y);
|
|
closestLen = globalLen + localLen + segLen * segRes.y;
|
|
segResClosest = segRes.y;
|
|
}
|
|
prevPos = currPos;
|
|
r += dr;
|
|
localLen += segLen;
|
|
}
|
|
globalLen += localLen;
|
|
}
|
|
|
|
/*
|
|
if (iClosest > 0
|
|
&& globalLen > kEpsilon
|
|
&& useNoise)
|
|
{
|
|
int iA = b.index + (useNoise ? 2 : 1) + (iClosest - 1); // reset
|
|
float3 p0 = (iClosest > 1) ? aBrush[iA - 1].data0.xyz : aBrush[iA + 0].data0.xyz;
|
|
float3 pA = aBrush[iA + 0].data0.xyz;
|
|
float3 pB = aBrush[iA + 1].data0.xyz;
|
|
float3 pC = aBrush[iA + 2].data0.xyz;
|
|
float3 pD = aBrush[iA + 3].data0.xyz;
|
|
float3 pE = (iClosest < numPoints - 3) ? aBrush[iA + 4].data0.xyz : aBrush[iA + 3].data0.xyz;
|
|
float3 segPosB = catmull_rom(pA, pB, pC, pD, tClosest - dt);
|
|
float3 segPosC = catmull_rom(pA, pB, pC, pD, tClosest);
|
|
float3 front = normalize(segPosC - segPosB);
|
|
float3 dir0B = pB - p0;
|
|
float3 dirAC = pC - pA;
|
|
float3 dirBD = pD - pB;
|
|
float3 dirCE = pE - pC;
|
|
//float3 front = normalize(catmull_rom(dir0B, dirAC, dirBD, dirCE, tClosest + (-1.0f + segResClosest) * dt));
|
|
float3 up = normalize(kUnitY + 1e-3f * mbn_rand(aBrush[iA].data0.xyz));
|
|
float3 left = normalize(cross(up, front));
|
|
up = cross(front, left);
|
|
float3 s = float3(closestLen, dot(p - pClosest, up), dot(p - pClosest, left));
|
|
float3 offset = aBrush[b.index + 1].data1.xyz;
|
|
float3 size = aBrush[b.index + 1].data0.xyz;
|
|
float threshold = aBrush[b.index + 1].data0.w;
|
|
int numOctaves = int(aBrush[b.index + 1].data1.w);
|
|
float octaveOffsetFactor = aBrush[b.index + 1].data2.x;
|
|
// TODO: noise type (if we ever re-instate noise along full curves...)
|
|
float n = sdf_noise(kSdfNoiseTypeCachedPerlin, s, -kInfinity, kInfinity, offset, size, threshold, numOctaves, octaveOffsetFactor, kCartesianNoisePeriod);
|
|
res = sdf_int_cubic(res, n, 0.5f * rClosest);
|
|
}
|
|
*/
|
|
}
|
|
break;
|
|
}
|
|
#endif // MUDBUN_DISABLE_SDF_FULL_CURVE
|
|
|
|
case kSdfParticleSystem:
|
|
{
|
|
res = kInfinity;
|
|
int numParticles = int(b.data2.x);
|
|
for (int i = 0; i < numParticles; ++i)
|
|
{
|
|
float3 pos = aBrush[b.index + i].data0.xyz;
|
|
float r = aBrush[b.index + i].data0.w;
|
|
float selfBlend = aBrush[b.index + i].data1.x;
|
|
res = sdf_uni_cubic(res, sdf_sphere(p - pos, r), selfBlend);
|
|
}
|
|
break;
|
|
}
|
|
|
|
#ifndef MUDBUN_DISABLE_SDF_DISTORTION_BRUSHES
|
|
case kSdfFishEye:
|
|
{
|
|
float r = length(pRel);
|
|
if (r > b.radius)
|
|
break;
|
|
|
|
float t = r / b.radius;
|
|
float strength = b.data0.x;
|
|
float fade = 1.0f - pow(abs(t), strength);
|
|
p -= (b.radius * fade) * quat_rot(b.rotation, normalize_safe(pRel, kUnitY));
|
|
break;
|
|
}
|
|
|
|
case kSdfPinch:
|
|
{
|
|
float depth = b.data0.x;
|
|
float r = length(pRel.xz);
|
|
if (sdf_cylinder(pRel + float3(0.0f, 0.5f * depth, 0.0f), 0.5f * depth, b.radius) > 0.0f)
|
|
break;
|
|
|
|
float amount = b.data0.y;
|
|
float strength = b.data0.z;
|
|
float g = -pRel.y / depth;
|
|
float t = r / max(kEpsilon, b.radius);
|
|
float pinchRatio = pow(abs(1.0f - t), strength);
|
|
g = pow(abs(g), 0.5f);
|
|
pRel.y = -g * depth; // remap
|
|
float fade = (depth + pRel.y) / depth;
|
|
p += (amount * pinchRatio * fade) * quat_rot(b.rotation, float3(0.0f, pRel.y, 0.0f));
|
|
break;
|
|
}
|
|
|
|
case kSdfTwist:
|
|
{
|
|
if (sdf_cylinder(pRel, h.y, b.radius, 0.0f) > 0.0f)
|
|
break;
|
|
|
|
float angle = b.data0.x;
|
|
float strength = b.data0.y;
|
|
float r = length(pRel.xz);
|
|
float t = r / b.radius;
|
|
float a = angle * (1.0f - pow(abs(t), strength));
|
|
float s = sin(a);
|
|
float c = cos(a);
|
|
pRel.xz = mul(float2x2(c, -s, s, c), pRel.xz);
|
|
p = quat_rot(b.rotation, pRel) + b.position;
|
|
break;
|
|
}
|
|
|
|
case kSdfQuantize:
|
|
{
|
|
float cellSize = b.data0.x;
|
|
float fade = b.data0.z;
|
|
float d = sdf_box(pRel, h, fade * cellSize);
|
|
if (d > 0.0f)
|
|
break;
|
|
|
|
float strength = b.data0.y;
|
|
float3 r = p / cellSize;
|
|
float3 f = floor(r);
|
|
float3 t = r - f;
|
|
float3 q = (f + smoothstep(0.0f, 1.0f, max(1.0f, strength) * (t - 0.5f) + 0.5f)) * cellSize;
|
|
p = lerp(p, q, saturate(strength) * saturate(-d / max(kEpsilon, fade * cellSize)));
|
|
break;
|
|
}
|
|
#endif // MUDBUN_DISABLE_SDF_DISTORTION_BRUSHES
|
|
|
|
#ifndef MUDBUN_DISABLE_SDF_MODIFIER_BRUSHES
|
|
case kSdfOnion:
|
|
{
|
|
float d = sdf_box(pRel, h, b.blend);
|
|
if (d > 0.0f)
|
|
break;
|
|
|
|
float thickness = b.data0.x;
|
|
res = abs(res) - thickness;
|
|
break;
|
|
}
|
|
#endif // MUDBUN_DISABLE_SDF_MODIFIER_BRUSHES
|
|
#endif // MUDBUN_FAST_ITERATION
|
|
|
|
default:
|
|
{
|
|
res = sdf_custom_brush(res, p, pRel, b);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (flipX || doMirrorX)
|
|
p.x = preMirrorX;
|
|
|
|
return res;
|
|
}
|
|
|
|
float sdf_distortion_modifier_bounds_query(float3 p, SdfBrush b)
|
|
{
|
|
float res = kInfinity;
|
|
|
|
float3 pRel = quat_rot(quat_inv(b.rotation), p - b.position);
|
|
float3 h = 0.5f * b.size;
|
|
|
|
switch (b.type)
|
|
{
|
|
#ifndef MUDBUN_FAST_ITERATION
|
|
case kSdfPinch:
|
|
{
|
|
float depth = b.data0.x;
|
|
res = sdf_cylinder(pRel + float3(0.0f, 0.5f * depth, 0.0f), 0.5f * depth, b.radius);
|
|
break;
|
|
}
|
|
|
|
case kSdfTwist:
|
|
{
|
|
float angle = b.data0.x;
|
|
res = sdf_cylinder(pRel, h.y, b.radius);
|
|
break;
|
|
}
|
|
|
|
case kSdfQuantize:
|
|
{
|
|
float cellSize = b.data0.x;
|
|
float fade = b.data0.z;
|
|
res = sdf_box(pRel, h, fade * cellSize);
|
|
break;
|
|
}
|
|
|
|
case kSdfFishEye:
|
|
{
|
|
res = sdf_sphere(pRel, b.radius);
|
|
break;
|
|
}
|
|
|
|
case kSdfOnion:
|
|
{
|
|
float thickness = b.data0.x;
|
|
res = sdf_box(pRel, h + thickness, b.blend);
|
|
break;
|
|
}
|
|
#endif // MUDBUN_FAST_ITERATION
|
|
|
|
default:
|
|
{
|
|
res = sdf_custom_distortion_modifier_bounds_query(p, pRel, b);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
float dist_blend_weight(float distA, float distB, float strength)
|
|
{
|
|
float m = 1.0f / max(kEpsilon, distA);
|
|
float n = 1.0f / max(kEpsilon, distB);
|
|
m = pow(m, strength);
|
|
n = pow(n, strength);
|
|
return saturate(n / (m + n));
|
|
}
|
|
|
|
float sdf_brush_apply(float res, float groupRes, SdfBrushMaterial groupMat, inout float3 p, SdfBrush b, inout SdfBrushMaterial oMat, bool outputMat = true, float halfNodeDiag = -1.0f)
|
|
{
|
|
float d = sdf_brush(res, p, b);
|
|
|
|
if (b.type == kSdfEndGroup)
|
|
d = groupRes;
|
|
|
|
bool isGroupBrush = false;
|
|
switch (b.type)
|
|
{
|
|
case kSdfBeginGroup:
|
|
case kSdfEndGroup:
|
|
isGroupBrush = true;
|
|
break;
|
|
}
|
|
|
|
float tMat = 0.0f;
|
|
float blend = b.blend;
|
|
switch (b.op)
|
|
{
|
|
/*
|
|
case kSdfUnionQuad:
|
|
// compute tMat before res is updated!
|
|
if (!isGroupBrush) // don't perform surface shift on group brushes, or it might crash the GPU!
|
|
d -= surfaceShift;
|
|
tMat = dist_blend_weight(res, d, 1.5f);
|
|
if (enable2dMode && d < 0.25 * blend)
|
|
tMat = max(tMat, min(1.0f, 1.0f - d / max(kEpsilon, 0.25 * blend)));
|
|
res = sdf_uni_quad(res, d, blend);
|
|
break;
|
|
|
|
case kSdfSubtractQuad:
|
|
if (!isGroupBrush) // don't perform surface shift on group brushes, or it might crash the GPU!
|
|
d += surfaceShift;
|
|
res = sdf_sub_quad(res, d, blend);
|
|
if (enable2dMode)
|
|
tMat = 1.0f - saturate(d / max(kEpsilon, blend));
|
|
else
|
|
tMat = 1.0f - saturate(2.0f * (d - 1.5f * voxelSize) / max(kEpsilon, blend));
|
|
break;
|
|
|
|
case kSdfIntersectQuad:
|
|
if (!isGroupBrush) // don't perform surface shift on group brushes, or it might crash the GPU!
|
|
d -= surfaceShift;
|
|
res = sdf_int_quad(res, d, blend);
|
|
if (enable2dMode)
|
|
tMat = saturate((-d + voxelSize) / max(kEpsilon, blend));
|
|
else
|
|
tMat = 1.0f - saturate(-2.0f * (d + 1.0f * voxelSize) / max(kEpsilon, blend));
|
|
break;
|
|
*/
|
|
|
|
case kSdfUnionCubic:
|
|
// compute tMat before res is updated!
|
|
if (!isGroupBrush) // don't perform surface shift on group brushes, or it might crash the GPU!
|
|
d -= surfaceShift;
|
|
tMat = dist_blend_weight(res, d, 1.5f);
|
|
if (enable2dMode && d < 0.25 * blend)
|
|
tMat = max(tMat, min(1.0f, 1.0f - d / max(kEpsilon, 0.25 * blend)));
|
|
res = sdf_uni_cubic(res, d, blend);
|
|
break;
|
|
|
|
case kSdfSubtractCubic:
|
|
if (!isGroupBrush) // don't perform surface shift on group brushes, or it might crash the GPU!
|
|
d += surfaceShift;
|
|
res = sdf_sub_cubic(res, d, blend);
|
|
if (enable2dMode)
|
|
tMat = 1.0f - saturate(d / max(kEpsilon, blend));
|
|
else
|
|
tMat = 1.0f - saturate(2.0f * (d - 1.5f * voxelSize) / max(kEpsilon, blend));
|
|
break;
|
|
|
|
case kSdfIntersectCubic:
|
|
if (!isGroupBrush) // don't perform surface shift on group brushes, or it might crash the GPU!
|
|
d -= surfaceShift;
|
|
res = sdf_int_cubic(res, d, blend);
|
|
if (enable2dMode)
|
|
tMat = saturate((-d + voxelSize) / max(kEpsilon, blend));
|
|
else
|
|
tMat = 1.0f - saturate(-2.0f * (d + 1.0f * voxelSize) / max(kEpsilon, blend));
|
|
break;
|
|
|
|
/*
|
|
case kSdfUnionRound:
|
|
// compute tMat before res is updated!
|
|
if (!isGroupBrush) // don't perform surface shift on group brushes, or it might crash the GPU!
|
|
d -= surfaceShift;
|
|
tMat = dist_blend_weight(res, d, 1.5f);
|
|
if (enable2dMode && d < 0.25 * blend)
|
|
tMat = max(tMat, min(1.0f, 1.0f - d / max(kEpsilon, 0.25 * blend)));
|
|
res = sdf_uni_round(res, d, blend);
|
|
break;
|
|
|
|
case kSdfSubtractRound:
|
|
if (!isGroupBrush) // don't perform surface shift on group brushes, or it might crash the GPU!
|
|
d += surfaceShift;
|
|
res = sdf_sub_round(res, d, blend);
|
|
if (enable2dMode)
|
|
tMat = 1.0f - saturate(d / max(kEpsilon, blend));
|
|
else
|
|
tMat = 1.0f - saturate(2.0f * (d - 1.5f * voxelSize) / max(kEpsilon, blend));
|
|
break;
|
|
|
|
case kSdfIntersectRound:
|
|
if (!isGroupBrush) // don't perform surface shift on group brushes, or it might crash the GPU!
|
|
d -= surfaceShift;
|
|
res = sdf_int_round(res, d, blend);
|
|
if (enable2dMode)
|
|
tMat = saturate((-d + voxelSize) / max(kEpsilon, blend));
|
|
else
|
|
tMat = 1.0f - saturate(-2.0f * (d + 1.0f * voxelSize) / max(kEpsilon, blend));
|
|
break;
|
|
*/
|
|
|
|
case kSdfUnionChamfer:
|
|
// compute tMat before res is updated!
|
|
if (!isGroupBrush) // don't perform surface shift on group brushes, or it might crash the GPU!
|
|
d -= surfaceShift;
|
|
tMat = dist_blend_weight(res, d, 1.5f);
|
|
if (enable2dMode && d < 0.25 * blend)
|
|
tMat = max(tMat, min(1.0f, 1.0f - d / max(kEpsilon, 0.25 * blend)));
|
|
res = sdf_uni_chamfer(res, d, blend);
|
|
break;
|
|
|
|
case kSdfSubtractChamfer:
|
|
if (!isGroupBrush) // don't perform surface shift on group brushes, or it might crash the GPU!
|
|
d += surfaceShift;
|
|
res = sdf_sub_chamfer(res, d, blend);
|
|
if (enable2dMode)
|
|
tMat = 1.0f - saturate(d / max(kEpsilon, blend));
|
|
else
|
|
tMat = 1.0f - saturate(2.0f * (d - 1.5f * voxelSize) / max(kEpsilon, blend));
|
|
break;
|
|
|
|
case kSdfIntersectChamfer:
|
|
if (!isGroupBrush) // don't perform surface shift on group brushes, or it might crash the GPU!
|
|
d -= surfaceShift;
|
|
res = sdf_int_chamfer(res, d, blend);
|
|
if (enable2dMode)
|
|
tMat = saturate((-d + voxelSize) / max(kEpsilon, blend));
|
|
else
|
|
tMat = 1.0f - saturate(-2.0f * (d + 1.0f * voxelSize) / max(kEpsilon, blend));
|
|
break;
|
|
|
|
case kSdfPipe:
|
|
if (!isGroupBrush) // don't perform surface shift on group brushes, or it might crash the GPU!
|
|
d -= surfaceShift;
|
|
res = sdf_pipe(res, d, blend);
|
|
tMat = saturate((-d + voxelSize) / max(kEpsilon, blend));
|
|
break;
|
|
|
|
case kSdfEngrave:
|
|
res = sdf_engrave(res, d, blend);
|
|
tMat = 1.0f - saturate((abs(d) - voxelSize) / max(kEpsilon, blend));
|
|
break;
|
|
|
|
case kSdfCullInside:
|
|
if (halfNodeDiag < 0.0f)
|
|
break;
|
|
if (!isGroupBrush
|
|
|| b.type == kSdfEndGroup)
|
|
{
|
|
if (d < -halfNodeDiag)
|
|
res = kCull;
|
|
}
|
|
break;
|
|
|
|
case kSdfCullOutside:
|
|
if (halfNodeDiag < 0.0f)
|
|
break;
|
|
if (!isGroupBrush
|
|
|| b.type == kSdfEndGroup)
|
|
{
|
|
if (d > halfNodeDiag)
|
|
res = kCull;
|
|
}
|
|
break;
|
|
|
|
case kSdfDistort:
|
|
res = sdf_uni(res, d);
|
|
break;
|
|
|
|
case kSdfModify:
|
|
res = d;
|
|
break;
|
|
|
|
/*
|
|
case kSdfDye:
|
|
if (!isGroupBrush) // don't perform surface shift on group brushes, or it might crash the GPU!
|
|
d -= surfaceShift;
|
|
if (enable2dMode)
|
|
tMat = 1.0f - saturate((d - voxelSize) / max(kEpsilon, 0.25f * blend));
|
|
else
|
|
tMat = 1.0f - saturate(max(0.0f, d) / max(kEpsilon, blend));
|
|
break;
|
|
*/
|
|
|
|
default:
|
|
if (is_sdf_dye(b.op))
|
|
{
|
|
if (!isGroupBrush) // don't perform surface shift on group brushes, or it might crash the GPU!
|
|
d -= surfaceShift;
|
|
if (enable2dMode)
|
|
tMat = 1.0f - saturate((d - voxelSize) / max(kEpsilon, 0.25f * blend));
|
|
else
|
|
tMat = 1.0f - saturate(max(0.0f, d) / max(kEpsilon, blend));
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (b.materialIndex >= 0)
|
|
{
|
|
float blendTightness = aBrushMaterial[b.materialIndex].metallicSmoothnessSizeTightness.w;
|
|
if (blendTightness > 0.0f)
|
|
{
|
|
// remap to between [-1.0, 1.0]
|
|
// take 1.0 - x
|
|
// curve with tightness
|
|
// take 1.0 - x
|
|
// remap back to [0.0, 1.0]
|
|
tMat -= 0.5f;
|
|
tMat = 0.5f + 0.5f * sign(tMat) * (1.0f - pow(abs(1.0f - abs(2.0f * tMat)), pow(1.0f + blendTightness, 5.0f)));
|
|
}
|
|
|
|
SdfBrushMaterial iMat = aBrushMaterial[b.materialIndex];
|
|
if (b.type == kSdfEndGroup)
|
|
iMat = groupMat;
|
|
|
|
if ((b.flags & kSdfBrushFlagsContributeMaterial) == 0)
|
|
{
|
|
iMat = oMat;
|
|
}
|
|
|
|
iMat.emissionHash.a = b.hash;
|
|
iMat.iBrush = b.index;
|
|
|
|
// dye blend modes
|
|
// https://docs.unity3d.com/Packages/com.unity.shadergraph@14.0/manual/Blend-Node.html
|
|
if (is_sdf_dye(b.op))
|
|
{
|
|
float3 base = oMat.color.rgb;
|
|
float3 blend = iMat.color.rgb;
|
|
float3 opacity = iMat.color.a;
|
|
switch (b.op)
|
|
{
|
|
case kSdfDye:
|
|
// overwrite; do nothing
|
|
break;
|
|
/*
|
|
case kSdfDyeBlendBurn:
|
|
iMat.color.rgb = lerp(base, 1.0f - (1.0f - blend) / max(1e-6f, base), opacity);
|
|
break;
|
|
case kSdfDyeBlendDarken:
|
|
iMat.color.rgb = lerp(base, min(blend, base), opacity);
|
|
break;
|
|
case kSdfDyeBlendDifference:
|
|
iMat.color.rgb = lerp(base, abs(blend - base), opacity);
|
|
break;
|
|
case kSdfDyeBlendDodge:
|
|
iMat.color.rgb = lerp(base, base / max(1e-6f, 1.0f - blend), opacity);
|
|
break;
|
|
case kSdfDyeBlendDivide:
|
|
iMat.color.rgb = lerp(base, base / max(1e-6f, blend), opacity);
|
|
break;
|
|
case kSdfDyeBlendExclusion:
|
|
iMat.color.rgb = lerp(base, blend + base - 2.0f * blend * base, opacity);
|
|
break;
|
|
case kSdfDyeBlendHardLight:
|
|
{
|
|
float3 result1 = 1.0f - 2.0f * (1.0f - base) * (1.0f - blend);
|
|
float3 result2 = 2.0f * base * blend;
|
|
float3 zeroOrOne = step(blend, 0.5f);
|
|
blend = result2 * zeroOrOne + (1.0f - zeroOrOne) * result1;
|
|
iMat.color.rgb = lerp(base, blend, opacity);
|
|
}
|
|
break;
|
|
case kSdfDyeBlendHardMix:
|
|
iMat.color.rgb = lerp(base, step(1.0f - base, blend), opacity);
|
|
break;
|
|
case kSdfDyeBlendLighten:
|
|
iMat.color.rgb = lerp(base, max(blend, base), opacity);
|
|
break;
|
|
case kSdfDyeBlendLightBurn:
|
|
iMat.color.rgb = lerp(base, base + blend - 1.0f, opacity);
|
|
break;
|
|
case kSdfDyeBlendLinearDodge:
|
|
iMat.color.rgb = lerp(base, base + blend, opacity);
|
|
break;
|
|
case kSdfDyeBlendLinearLight:
|
|
iMat.color.rgb = lerp(base, blend < 0.5f ? max(base + 2.0f * blend - 1.0f, 0.0f) : min(base + 2.0f * (blend - 0.5f), 1.0f), opacity);
|
|
break;
|
|
*/
|
|
case kSdfDyeBlendMultiply:
|
|
iMat.color.rgb = lerp(base, base * blend, opacity);
|
|
break;
|
|
/*
|
|
case kSdfDyeBlendNegation:
|
|
iMat.color.rgb = lerp(base, 1.0f - abs(1.0f - blend - base), opacity);
|
|
break;
|
|
*/
|
|
case kSdfDyeBlendOverlay:
|
|
{
|
|
float3 result1 = 1.0f - 2.0f * (1.0f - base) * (1.0f - blend);
|
|
float3 result2 = 2.0f * base * blend;
|
|
float3 zeroOrOne = step(base, 0.5f);
|
|
blend = result2 * zeroOrOne + (1.0f - zeroOrOne) * result1;
|
|
iMat.color.rgb = lerp(base, blend, opacity);
|
|
}
|
|
break;
|
|
/*
|
|
case kSdfDyeBlendPinLight:
|
|
{
|
|
float3 check = step(0.5f, blend);
|
|
float3 result1 = check * max(2.0f * (base - 0.5f), blend);
|
|
blend = result1 + (1.0f - check) * min(2.0f * base, blend);
|
|
iMat.color.rgb = lerp(base, blend, opacity);
|
|
}
|
|
break;
|
|
*/
|
|
case kSdfDyeBlendScreen:
|
|
iMat.color.rgb = lerp(base, 1.0f - (1.0f - blend) * (1.0f - base), opacity);
|
|
break;
|
|
/*
|
|
case kSdfDyeBlendSoftLight:
|
|
{
|
|
float3 result1 = 2.0f * base * blend + base * base * (1.0f - 2.0f * blend);
|
|
float3 result2 = sqrt(base) * (2.0f * blend - 1.0f) + 2.0f * base * (1.0f - blend);
|
|
float3 zeroOrOne = step(0.5f, blend);
|
|
blend = result2 * zeroOrOne + (1.0f - zeroOrOne) * result1;
|
|
iMat.color.rgb = lerp(base, blend, opacity);
|
|
}
|
|
break;
|
|
case kSdfDyeBlendSubtract:
|
|
iMat.color.rgb = lerp(base, base - blend, opacity);
|
|
break;
|
|
case kSdfDyeBlendVividLight:
|
|
{
|
|
float3 result1 = 1.0f - (1.0f - blend) / (2.0f * base);
|
|
float3 result2 = blend / (2.0f * (1.0f - base));
|
|
float3 zeroOrOne = step(0.5f, base);
|
|
blend = result2 * zeroOrOne + (1.0f - zeroOrOne) * result1;
|
|
iMat.color.rgb = lerp(base, blend, opacity);
|
|
}
|
|
break;
|
|
*/
|
|
case kSdfDyeBlendModePaint:
|
|
iMat.color.rgb = lerp(base, 2 * base * blend, opacity);
|
|
break;
|
|
}
|
|
|
|
iMat.color.rgb = saturate(iMat.color.rgb);
|
|
|
|
if (b.op != kSdfDye)
|
|
{
|
|
// non-overwrite blend modes don't affect alpha
|
|
iMat.color.a = oMat.color.a;
|
|
}
|
|
} // end: dye blend modes
|
|
|
|
// selection highlight
|
|
if (b.hash < 0.0f)
|
|
{
|
|
iMat.color.rgb = saturate(iMat.color.rgb + 0.1f);
|
|
iMat.emissionHash.rgb = saturate(iMat.emissionHash.rgb + 0.1f);
|
|
}
|
|
|
|
oMat = lerp(oMat, iMat, tMat);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
float sdf_all_brushes(float3 p, int iBrushMask, out SdfBrushMaterial mat, bool outputMat = true)
|
|
{
|
|
mat = init_brush_material();
|
|
float res = kInfinity;
|
|
for (int iBrush = 0; iBrush < numBrushes; ++iBrush)
|
|
res = sdf_brush_apply(res, res, mat, p, aBrush[iBrush], mat);
|
|
|
|
res -= surfaceShift;
|
|
|
|
return res;
|
|
}
|
|
|
|
float sdf_masked_brushes(float3 p, int iBrushMask, out SdfBrushMaterial mat, bool outputMat = true, float halfNodeDiag = -1.0f)
|
|
{
|
|
int iStack = -1;
|
|
float3 pStack[kMaxBrushGroupDepth];
|
|
float resStack[kMaxBrushGroupDepth];
|
|
SdfBrushMaterial matStack[kMaxBrushGroupDepth];
|
|
|
|
float res = kInfinity;
|
|
mat = init_brush_material();
|
|
float groupRes = kInfinity;
|
|
SdfBrushMaterial groupMat = init_brush_material();
|
|
FOR_EACH_BRUSH(iBrushMask,
|
|
switch (aBrush[iBrush].type)
|
|
{
|
|
case kSdfBeginGroup:
|
|
{
|
|
iStack = min(kMaxBrushGroupDepth - 1, iStack + 1);
|
|
pStack[iStack] = p;
|
|
resStack[iStack] = res;
|
|
matStack[iStack] = mat;
|
|
res = kInfinity;
|
|
mat = init_brush_material();
|
|
|
|
bool doMirrorX = ((aBrush[iBrush].flags & kSdfBrushFlagsMirrorX) != 0);
|
|
if (doMirrorX)
|
|
p.x = abs(p.x);
|
|
|
|
bool flipX = ((aBrush[iBrush].flags & kSdfBrushFlagsFlipX) != 0);
|
|
if (flipX)
|
|
p.x = -p.x;
|
|
}
|
|
break;
|
|
case kSdfEndGroup:
|
|
{
|
|
groupRes = res;
|
|
groupMat = mat;
|
|
p = pStack[iStack];
|
|
res = resStack[iStack];
|
|
mat = matStack[iStack];
|
|
}
|
|
break;
|
|
}
|
|
res = sdf_brush_apply(res, groupRes, groupMat, p, aBrush[iBrush], mat, outputMat, halfNodeDiag);
|
|
if (res == kCull)
|
|
break; // early-out if we have decided to cull the node
|
|
switch (aBrush[iBrush].type)
|
|
{
|
|
case kSdfEndGroup:
|
|
iStack = max(-1, iStack - 1);
|
|
break;
|
|
}
|
|
);
|
|
|
|
return res;
|
|
}
|
|
|
|
#endif
|
|
|
|
|