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

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