CultLib/packages/cultmath/shaders/CultMath.Phacelle.hlsl
Meta Vens d605c70bba CultPhasor.cos/.sin become .cosine/.sine in C# and HLSL (GLSL regenerated next)
SPIRV-Cross renames members matching GLSL builtins, so the source names change. Resolver and JSON writer accessors follow the members; the wire (positional MessagePack, JSON keys) is untouched in this commit. The committed GLSL is stale until its regeneration commit, so CommittedGlslEqualsLowering fails here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 19:25:39 +02:00

99 lines
4 KiB
HLSL

#ifndef CULTMATH_PHACELLE_HLSL
#define CULTMATH_PHACELLE_HLSL
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of
// the MPL was not distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/.
//
// Phacelle noise. Copyright (c) 2025 Rune Skovbo Johansen (Shadertoy t3dyWl; MPL-2.0), read from the
// C# port PhacelleNoise in lpmitchell/AdvancedTerrainErosion, converted to burstable C# by Luke
// Mitchell (2026). Modified by GameCult: 3D cells, a caller-supplied stripe wave vector, an exact
// gradient and exact pruning. This file is an MPL-2.0 file; CultMath.hlsl, which includes it after
// its own definitions of CULTMATH_TAU and cultmath_pcg3d, is MIT (see THIRD-PARTY-NOTICES.md).
// Invariant 8's struct return shape for cultmath_phacelle, compared field by field and bit for bit
// against CultMath.CultPhasor, same field order.
struct CultPhasor
{
float4 cosine;
float4 sine;
};
// Johansen's Phacelle noise on 3D cells. See math.phacelle's comment (math.Phacelle.cs) for the stripe wave
// vector, the weight, the normalization, the exact gradient and why the prune is exact. Mirrors
// the C# body operation for operation; the C#-only internal overload that switches the prune off
// has no shader twin.
CultPhasor cultmath_phacelle(float3 p, float3 side, float offset, float normalization)
{
float3 cell = floor(p);
float3 local = p - cell;
float phaseOffset = offset * CULTMATH_TAU;
float sumW = 0.0;
float sumCos = 0.0;
float sumSin = 0.0;
float3 gradW = float3(0.0, 0.0, 0.0);
float3 gradCos = float3(0.0, 0.0, 0.0);
float3 gradSin = float3(0.0, 0.0, 0.0);
for (int dz = -1; dz <= 2; dz++)
{
float gz = max(0.0, abs(local.z - (float)dz) - 0.5);
for (int dy = -1; dy <= 2; dy++)
{
float gy = max(0.0, abs(local.y - (float)dy) - 0.5);
for (int dx = -1; dx <= 2; dx++)
{
float gx = max(0.0, abs(local.x - (float)dx) - 0.5);
if (gx * gx + gy * gy + gz * gz >= 2.25)
continue;
float3 gridStep = float3(dx, dy, dz);
int3 hash = cultmath_pcg3d(int3(cell + gridStep));
float3 jitter = float3(
(float)((uint)hash.x >> 8) * (1.0 / 16777216.0) - 0.5,
(float)((uint)hash.y >> 8) * (1.0 / 16777216.0) - 0.5,
(float)((uint)hash.z >> 8) * (1.0 / 16777216.0) - 0.5);
float3 v = local - gridStep - jitter;
float falloff = exp(-2.0 * dot(v, v));
float w = max(0.0, falloff - 0.01111);
float3 dw = w > 0.0 ? (-4.0 * falloff) * v : float3(0.0, 0.0, 0.0);
float phase = dot(v, side) + phaseOffset;
float c = cos(phase);
float s = sin(phase);
sumW += w;
sumCos += w * c;
sumSin += w * s;
gradW += dw;
gradCos += dw * c - (w * s) * side;
gradSin += dw * s + (w * c) * side;
}
}
}
float invW = 1.0 / sumW;
float rawCos = sumCos * invW;
float rawSin = sumSin * invW;
float3 gradRawCos = (gradCos - rawCos * gradW) * invW;
float3 gradRawSin = (gradSin - rawSin * gradW) * invW;
float rawLength = sqrt(rawCos * rawCos + rawSin * rawSin);
float floorLength = 1.0 - normalization;
CultPhasor result;
if (rawLength > floorLength)
{
float outCos = rawCos / rawLength;
float outSin = rawSin / rawLength;
float3 gradLength = (rawCos * gradRawCos + rawSin * gradRawSin) / rawLength;
result.cosine = float4((gradRawCos - outCos * gradLength) / rawLength, outCos);
result.sine = float4((gradRawSin - outSin * gradLength) / rawLength, outSin);
}
else
{
result.cosine = float4(gradRawCos / floorLength, rawCos / floorLength);
result.sine = float4(gradRawSin / floorLength, rawSin / floorLength);
}
return result;
}
#endif