389 lines
13 KiB
HLSL
389 lines
13 KiB
HLSL
static const int SDF_INDEX = 7;
|
|
|
|
#include "D3D12SdfCommon.hlsli"
|
|
#include "D3D12SdfMath.hlsli"
|
|
|
|
static const float LIFE_B = 0.125;
|
|
static const float LIFE_PROFILE_B = 0.175;
|
|
static const float LIFE_PROFILE_START_RADIUS = 0.030;
|
|
static const float LIFE_PROFILE_END_RADIUS = 0.0;
|
|
static const float LIFE_TURNS = 3.10;
|
|
static const float LIFE_WALL = 0.020;
|
|
static const float LIFE_C_SPAN = 4.25;
|
|
static const float LIFE_OPEN_CENTER = PI;
|
|
static const float LIFE_APERTURE_FLARE = 0.0;
|
|
static const float LIFE_CONTACT_PULL = 1.0;
|
|
static const float LIFE_CONTACT_SPAN_PULL = 1.0;
|
|
static const float LIFE_TERMINAL_U = 0.34;
|
|
|
|
static const float LIFE_MAT_OUTER = 1.0;
|
|
static const float LIFE_MAT_INNER = 2.0;
|
|
static const float LIFE_MAT_SEAM = 3.0;
|
|
static const float LIFE_MAT_PEARL = 4.0;
|
|
static const float LIFE_MAT_EMBER = 5.0;
|
|
|
|
struct LifeDomain
|
|
{
|
|
float body;
|
|
float shell;
|
|
float ember;
|
|
float seam;
|
|
float pearl;
|
|
float lip;
|
|
float mat;
|
|
float u;
|
|
float v;
|
|
float w;
|
|
float edge;
|
|
float endpoint;
|
|
float pressure;
|
|
};
|
|
|
|
float2 lifeRotate2(float2 p, float angle)
|
|
{
|
|
float c = cos(angle);
|
|
float s = sin(angle);
|
|
return float2(c * p.x - s * p.y, s * p.x + c * p.y);
|
|
}
|
|
|
|
float lifeTri(float x)
|
|
{
|
|
return abs(frac(x) - 0.5) * 2.0;
|
|
}
|
|
|
|
float lifeHash21(float2 p)
|
|
{
|
|
return frac(sin(dot(p, float2(127.1, 311.7))) * 43758.5453);
|
|
}
|
|
|
|
float2 lifeHash22(float2 p)
|
|
{
|
|
float2 q = float2(dot(p, float2(127.1, 311.7)), dot(p, float2(269.5, 183.3)));
|
|
return frac(sin(q) * 43758.5453);
|
|
}
|
|
|
|
float lifeVoronoiEdge(float2 p)
|
|
{
|
|
float2 cell = floor(p);
|
|
float2 local = frac(p);
|
|
float nearest = 8.0;
|
|
float nextNearest = 8.0;
|
|
|
|
[unroll]
|
|
for (int y = -1; y <= 1; y++)
|
|
{
|
|
[unroll]
|
|
for (int x = -1; x <= 1; x++)
|
|
{
|
|
float2 offset = float2((float)x, (float)y);
|
|
float2 jitter = lifeHash22(cell + offset);
|
|
float2 d = offset + jitter - local;
|
|
float distanceSquared = dot(d, d);
|
|
if (distanceSquared < nearest)
|
|
{
|
|
nextNearest = nearest;
|
|
nearest = distanceSquared;
|
|
}
|
|
else if (distanceSquared < nextNearest)
|
|
{
|
|
nextNearest = distanceSquared;
|
|
}
|
|
}
|
|
}
|
|
|
|
return sqrt(nextNearest) - sqrt(nearest);
|
|
}
|
|
|
|
float lifeAngleDelta(float a, float b)
|
|
{
|
|
return atan2(sin(a - b), cos(a - b));
|
|
}
|
|
|
|
float lifeSpiralRadius(float u)
|
|
{
|
|
return exp(LIFE_B * u);
|
|
}
|
|
|
|
float lifeProfileGrowthRadius(float u)
|
|
{
|
|
float age = clamp(u + LIFE_TURNS * 2.0 * PI, 0.0, LIFE_TURNS * 2.0 * PI);
|
|
return LIFE_PROFILE_START_RADIUS * exp(LIFE_PROFILE_B * age);
|
|
}
|
|
|
|
float lifeProfileTerminalFlare(float u)
|
|
{
|
|
return 0.0;
|
|
}
|
|
|
|
float lifeProfileBaseRadius(float u)
|
|
{
|
|
return lifeProfileGrowthRadius(u) + lifeProfileTerminalFlare(u);
|
|
}
|
|
|
|
float lifeShellThickness(float profileRadius)
|
|
{
|
|
return LIFE_WALL * max(pow(profileRadius, 0.35), 0.76);
|
|
}
|
|
|
|
float lifeApertureHalfSpan(float u)
|
|
{
|
|
return LIFE_C_SPAN * 0.5 - LIFE_APERTURE_FLARE * smoothstep(-1.05, 0.25, u);
|
|
}
|
|
|
|
float lifeArcCenter(float u)
|
|
{
|
|
return LIFE_OPEN_CENTER + PI;
|
|
}
|
|
|
|
float lifeContactHalfSpan(float halfSpan, float u)
|
|
{
|
|
float whorlRadius = lifeSpiralRadius(u);
|
|
float previousProfile = lifeProfileBaseRadius(u - 2.0 * PI);
|
|
float d = (1.0 - exp(-LIFE_B * 2.0 * PI)) * whorlRadius;
|
|
float ratio = previousProfile / max(d, 0.0001);
|
|
float feasible = step(0.0001, ratio);
|
|
float kp = clamp(ratio, 0.0, 1.0);
|
|
float minimum = lerp(halfSpan, acos(-sqrt(max(1.0 - kp * kp, 0.0))) + 0.035, feasible);
|
|
return lerp(halfSpan, max(halfSpan, minimum), LIFE_CONTACT_SPAN_PULL);
|
|
}
|
|
|
|
float lifeContactArcRadius(float whorlRadius, float u, float center, float halfSpan)
|
|
{
|
|
float k = exp(-LIFE_B * 2.0 * PI);
|
|
float d = (1.0 - k) * whorlRadius;
|
|
float previousProfile = lifeProfileBaseRadius(u - 2.0 * PI);
|
|
float base = lifeProfileBaseRadius(u);
|
|
float endpoint = center + halfSpan;
|
|
float c = cos(endpoint);
|
|
float s = sin(endpoint);
|
|
float root = previousProfile * previousProfile - d * d * s * s;
|
|
float solved = -d * c + sqrt(max(root, 0.0));
|
|
float feasible = step(0.0, root) * step(0.0, solved);
|
|
float contactRadius = lerp(base, max(solved, 0.012), feasible);
|
|
return lerp(base, contactRadius, LIFE_CONTACT_PULL);
|
|
}
|
|
|
|
float lifeArcDistance(float2 q, float radius, float center, float halfSpan, out float v, out float w, out float edge, out float endpointMask)
|
|
{
|
|
float lenQ = max(length(q), 0.0001);
|
|
v = atan2(q.y, q.x);
|
|
w = lenQ / max(radius, 0.0001);
|
|
float da = lifeAngleDelta(v, center);
|
|
float absDa = abs(da);
|
|
|
|
float2 e0 = radius * float2(cos(center - halfSpan), sin(center - halfSpan));
|
|
float2 e1 = radius * float2(cos(center + halfSpan), sin(center + halfSpan));
|
|
float circleDistance = abs(lenQ - radius);
|
|
float endpointDistance = min(length(q - e0), length(q - e1));
|
|
|
|
edge = (absDa - halfSpan) * radius;
|
|
endpointMask = smoothstep(0.035, -0.010, abs(edge));
|
|
return absDa <= halfSpan ? circleDistance : endpointDistance;
|
|
}
|
|
|
|
float lifeBranchSolidDistance(float3 p, float t, float n, out float u, out float v, out float w, out float edge, out float endpoint)
|
|
{
|
|
u = 2.0 * PI * n + t;
|
|
float whorlRadius = lifeSpiralRadius(u);
|
|
float r = max(length(p.xy), 0.0001);
|
|
float center = lifeArcCenter(u);
|
|
float halfSpan = lifeContactHalfSpan(lifeApertureHalfSpan(u), u);
|
|
float profileRadius = lifeContactArcRadius(whorlRadius, u, center, halfSpan);
|
|
float centerDistance = lifeArcDistance(float2(r - whorlRadius, p.z), profileRadius, center, halfSpan, v, w, edge, endpoint);
|
|
|
|
float d = centerDistance - lifeShellThickness(profileRadius);
|
|
d = max(d, (-LIFE_TURNS * 2.0 * PI - 0.16) - u);
|
|
d = max(d, u - LIFE_TERMINAL_U);
|
|
return d;
|
|
}
|
|
|
|
LifeDomain lifeMakeDomain(float d, float shell, float mat, float u, float v, float w, float edge, float endpoint, float pressure)
|
|
{
|
|
LifeDomain domain;
|
|
domain.body = d;
|
|
domain.shell = shell;
|
|
domain.ember = 999.0;
|
|
domain.seam = 999.0;
|
|
domain.pearl = 999.0;
|
|
domain.lip = 999.0;
|
|
domain.mat = mat;
|
|
domain.u = u;
|
|
domain.v = v;
|
|
domain.w = w;
|
|
domain.edge = edge;
|
|
domain.endpoint = endpoint;
|
|
domain.pressure = pressure;
|
|
return domain;
|
|
}
|
|
|
|
LifeDomain lifeChoose(LifeDomain a, LifeDomain b)
|
|
{
|
|
if (b.body < a.body)
|
|
{
|
|
return b;
|
|
}
|
|
|
|
return a;
|
|
}
|
|
|
|
LifeDomain lifeBranchDomain(float3 p, float t, float n, float pressure)
|
|
{
|
|
float u;
|
|
float v;
|
|
float w;
|
|
float edge;
|
|
float endpoint;
|
|
float shell = lifeBranchSolidDistance(p, t, n, u, v, w, edge, endpoint);
|
|
|
|
float prevU;
|
|
float prevV;
|
|
float prevW;
|
|
float prevEdge;
|
|
float prevEndpoint;
|
|
float previous = lifeBranchSolidDistance(p, t, n - 1.0, prevU, prevV, prevW, prevEdge, prevEndpoint);
|
|
float hasPrevious = smoothstep(-LIFE_TURNS * 2.0 * PI + 0.12, -LIFE_TURNS * 2.0 * PI + 0.42, u);
|
|
float seamLocality = smoothstep(0.030, 0.003, abs(edge));
|
|
float trimmedShell = shell;
|
|
|
|
float innerSide = (1.0 - smoothstep(0.990, 1.015, w)) * (1.0 - endpoint);
|
|
float mat = (innerSide > 0.50 && u > -2.65) ? LIFE_MAT_INNER : LIFE_MAT_OUTER;
|
|
LifeDomain domain = lifeMakeDomain(trimmedShell, shell, mat, u, v, w, edge, endpoint, pressure);
|
|
|
|
float seamGate = smoothstep(-LIFE_TURNS * 2.0 * PI + 0.55, -LIFE_TURNS * 2.0 * PI + 1.20, u);
|
|
float seam = max(abs(edge) - 0.0035, shell - 0.006);
|
|
seam = lerp(999.0, seam, hasPrevious * seamLocality);
|
|
domain.seam = seam;
|
|
|
|
float pearlWave = lifeTri(u * 1.72 + 0.040 * sin(v * 1.7));
|
|
float pearlLine = pearlWave * 0.052;
|
|
float pearlBand = length(float2(pearlLine, shell)) - 0.009;
|
|
float pearlGate = seamGate * (1.0 - endpoint * 0.50) * smoothstep(0.20, 0.92, w);
|
|
domain.pearl = lerp(999.0, pearlBand, pearlGate);
|
|
|
|
domain.lip = lerp(999.0, length(float2(abs(edge), shell)) - 0.024, endpoint);
|
|
|
|
float pearlDetail = domain.pearl;
|
|
float lipDetail = domain.lip;
|
|
float detail = min(pearlDetail, lipDetail);
|
|
domain.body = min(trimmedShell, detail);
|
|
if (detail < trimmedShell)
|
|
{
|
|
domain.mat = LIFE_MAT_PEARL;
|
|
}
|
|
|
|
return domain;
|
|
}
|
|
|
|
LifeDomain lifeDomain(float3 local, SdfObject sdfObject)
|
|
{
|
|
float pressure = saturate(sdfObject.state.z + (1.0 - saturate(sdfObject.state.y)) * 0.10);
|
|
float3 p = local;
|
|
p *= 1.08;
|
|
p.yz = lifeRotate2(p.yz, -0.10);
|
|
p.xy = lifeRotate2(p.xy, -0.22);
|
|
|
|
// Shell grammar lives in xy/z: xy is the logarithmic spiral plane, z is
|
|
// the C-profile thickness axis. Map world yz into that domain so the
|
|
// right-side view sees the growth spiral instead of a sliced rim.
|
|
p = float3(p.y, p.z, p.x);
|
|
|
|
float r = max(length(p.xy), 0.0001);
|
|
float t = atan2(p.y, p.x);
|
|
float n = (log(max((r * r + p.z * p.z) / (2.0 * r), 0.0001)) / LIFE_B - t) / (2.0 * PI);
|
|
n = clamp(n, -LIFE_TURNS, 0.0);
|
|
|
|
LifeDomain domain = lifeMakeDomain(999.0, 999.0, LIFE_MAT_OUTER, 0.0, 0.0, 0.0, 999.0, 0.0, pressure);
|
|
float n0 = floor(n);
|
|
float n1 = ceil(n);
|
|
domain = lifeChoose(domain, lifeBranchDomain(p, t, n0, pressure));
|
|
domain = lifeChoose(domain, lifeBranchDomain(p, t, n1, pressure));
|
|
|
|
float ember = sdSphere(p - float3(0.0, 0.0, 0.0), 0.060 + 0.004 * sin(timeSeconds * 2.1));
|
|
if (ember < domain.body)
|
|
{
|
|
LifeDomain emberDomain = lifeMakeDomain(ember, domain.shell, LIFE_MAT_EMBER, domain.u, domain.v, domain.w, domain.edge, domain.endpoint, pressure);
|
|
emberDomain.ember = ember;
|
|
return emberDomain;
|
|
}
|
|
|
|
domain.ember = ember;
|
|
return domain;
|
|
}
|
|
|
|
float sdfDistance(float3 p, int sdfIndex)
|
|
{
|
|
SdfObject sdfObject = sdfObjects[sdfIndex];
|
|
float radius = max(sdfObject.centerRadius.w, 0.001);
|
|
float3 local = (p - sdfObject.centerRadius.xyz) / radius;
|
|
return lifeDomain(local, sdfObject).body * radius;
|
|
}
|
|
|
|
SdfSurface sdfSurface(float3 p, int sdfIndex)
|
|
{
|
|
SdfObject sdfObject = sdfObjects[sdfIndex];
|
|
float radius = max(sdfObject.centerRadius.w, 0.001);
|
|
float3 local = (p - sdfObject.centerRadius.xyz) / radius;
|
|
LifeDomain domain = lifeDomain(local, sdfObject);
|
|
float heartbeat = saturate(sdfObject.state.y);
|
|
|
|
float emberMask = domain.mat == LIFE_MAT_EMBER ? 1.0 : 0.0;
|
|
float pearlMask = domain.mat == LIFE_MAT_PEARL ? 1.0 : 0.0;
|
|
float seamMask = (1.0 - emberMask) * (1.0 - pearlMask) * (1.0 - smoothstep(0.000, 0.010, domain.seam));
|
|
float innerMask = domain.mat == LIFE_MAT_INNER ? 1.0 : 0.0;
|
|
|
|
float nacre = 0.5 + 0.5 * sin(domain.u * 0.78 + domain.v * 1.35 + timeSeconds * 0.06);
|
|
float3 fractureP = local * 3.75;
|
|
float warp = sin(dot(fractureP, float3(1.7, -0.9, 1.2))) * 0.28
|
|
+ sin(dot(fractureP, float3(-0.6, 1.9, 1.1))) * 0.18;
|
|
float fractureEdgeA = lifeVoronoiEdge(fractureP.xy + warp);
|
|
float fractureEdgeB = lifeVoronoiEdge(fractureP.yz * 1.13 - warp * 0.7);
|
|
float fractureEdgeC = lifeVoronoiEdge(fractureP.zx * 0.91 + warp * 0.5);
|
|
float fractureEdge = min(fractureEdgeA, min(fractureEdgeB, fractureEdgeC));
|
|
float fractureMask = (1.0 - emberMask) * smoothstep(0.080, 0.010, fractureEdge);
|
|
float3 teal = lerp(float3(0.006, 0.185, 0.250), float3(0.030, 0.470, 0.610), nacre);
|
|
float3 innerPearl = lerp(float3(0.840, 0.805, 0.700), float3(0.985, 0.940, 0.800), nacre);
|
|
float3 gold = lerp(float3(0.82, 0.420, 0.080), float3(1.0, 0.735, 0.205), nacre);
|
|
float3 pearlColor = float3(0.965, 0.905, 0.735);
|
|
float3 seamColor = float3(0.075, 0.050, 0.026);
|
|
float3 emberColor = float3(1.0, 0.390, 0.050);
|
|
|
|
SdfSurface surface;
|
|
surface.baseColor = lerp(teal, innerPearl, innerMask);
|
|
surface.baseColor = lerp(surface.baseColor, gold, fractureMask * lerp(0.42, 0.76, innerMask));
|
|
surface.baseColor = lerp(surface.baseColor, pearlColor, pearlMask);
|
|
surface.baseColor = lerp(surface.baseColor, seamColor, seamMask);
|
|
surface.baseColor = lerp(surface.baseColor, emberColor, emberMask);
|
|
surface.metallic = lerp(0.04, 0.10, saturate(fractureMask + seamMask * 0.35));
|
|
surface.metallic = lerp(surface.metallic, 0.03, pearlMask);
|
|
surface.roughness = lerp(0.28, 0.16, saturate(innerMask + pearlMask + seamMask * 0.6));
|
|
surface.roughness = lerp(surface.roughness, 0.08, emberMask);
|
|
surface.emission = primitiveEmissionRadiance(sdfFieldId(sdfIndex)) * 0.012
|
|
+ gold * innerMask * 0.050
|
|
+ gold * fractureMask * 0.095
|
|
+ pearlColor * pearlMask * 0.040
|
|
+ gold * seamMask * 0.090
|
|
+ emberColor * emberMask * (3.2 + heartbeat * 1.1 + domain.pressure * 0.8);
|
|
surface.temporalDetail = 0.0;
|
|
surface.reservoirConfidence = 1.0;
|
|
return surface;
|
|
}
|
|
|
|
float3 shadeSdf(float2 uv, float travel, float3 p, float3 normal, int sdfIndex, SdfSurface surface)
|
|
{
|
|
SdfObject sdfObject = sdfObjects[sdfIndex];
|
|
float radius = max(sdfObject.centerRadius.w, 0.001);
|
|
float3 local = (p - sdfObject.centerRadius.xyz) / radius;
|
|
LifeDomain domain = lifeDomain(local, sdfObject);
|
|
float3 shaded = shadeSdfPbr(p, normal, surface);
|
|
float3 viewDirection = normalize(cameraPosition - p);
|
|
float fresnel = pow(1.0 - saturate(dot(normal, viewDirection)), 3.2);
|
|
float innerGlow = domain.mat == LIFE_MAT_INNER ? 1.0 : 0.0;
|
|
return shaded
|
|
+ float3(0.020, 0.360, 0.330) * fresnel * 0.10
|
|
+ float3(1.0, 0.470, 0.080) * innerGlow * 0.050;
|
|
}
|
|
|
|
#define SDF_TRACE_STEP_SCALE 0.12
|
|
#define SDF_TRACE_MAX_STEP_RADIUS_SCALE 0.006
|
|
|
|
#include "D3D12SdfProxy.hlsli"
|