using System.Globalization; using System.Linq; /// /// The single author of Assets/Shaders/Gravity.cginc, the GPU lowering of the zone's potential. The law is /// GravityField's (its constants and smoothstep coefficients are read here, not respelled) and the height is /// Zone.GetHeight: each term's potential minus its reference, summed over the terms whose edge the point is inside, /// divided by the exaggeration. Unity does not run C# when it imports a shader, so the committed cginc is this output; /// GravityShaderTests fails unless it equals Source() exactly. /// To change the shader, change this file and regenerate: AETHERIA_REGENERATE_GRAVITY_CGINC=1 dotnet test --filter GravityShaderTests. /// public static class GravityShader { public const string Path = "Assets/Shaders/Gravity.cginc"; /// The whole file, with LF line ends. public static string Source() => Template .Replace("@ORDER@", GravityField.Order.ToString(CultureInfo.InvariantCulture)) .Replace("@N@", GravityField.S.Length.ToString(CultureInfo.InvariantCulture)) .Replace("@Q@", (GravityField.S.Length - 1).ToString(CultureInfo.InvariantCulture)) .Replace("@QLAST@", (GravityField.S.Length - 2).ToString(CultureInfo.InvariantCulture)) .Replace("@SERIESFROM@", GravityField.SeriesFrom.ToString("R", CultureInfo.InvariantCulture)) .Replace("@SERIES@", GravityField.SeriesTerms.ToString(CultureInfo.InvariantCulture)) .Replace("@ONEMINUSS@", Coefficients(GravityField.OneMinusS)) .Replace("@S@", Coefficients(GravityField.S)) .Replace("\r\n", "\n"); private static string Coefficients(float[] c) => string.Join(", ", c.Select(x => x.ToString("R", CultureInfo.InvariantCulture))); private const string Template = @"#ifndef AETHERIA_GRAVITY_INCLUDED #define AETHERIA_GRAVITY_INCLUDED // GENERATED by ServerShared/GravityShader.cs from GravityField; do not edit (GravityShaderTests fails on any difference). // The lowering of the zone's potential (aetheria-gravity, cut grid-lowering). The law is ServerShared's GravityField // and the height is Zone.GetHeight; this file decides nothing. GravityLowering.Lower fills the buffer and the globals // from the zone's WellTree (positions from the nodes' rails at the drawn time) each frame. // GravityFieldParityVerify renders it and compares every texel with -Zone.GetHeight. struct GravityTerm { float2 position; float strength; float softening; float inner; float outer; float reference; }; StructuredBuffer _GravityWells; int _GravityWellCount; float _HeightExaggeration; // GravityField.OneMinusS and GravityField.S: the band profile B(t) = 1 - S(t) and the order-@ORDER@ smoothstep, lowest first. static const float GravityOneMinusS[@N@] = { @ONEMINUSS@ }; static const float GravityS[@N@] = { @S@ }; // GravityField.BandClosed: the band's potential at t for tau <= @SERIESFROM@, per unit strength. float gravityBandClosed(float tau, float t) { float q[@Q@]; q[@QLAST@] = GravityOneMinusS[@Q@]; [unroll] for (int i = @QLAST@; i > 0; i--) q[i - 1] = GravityOneMinusS[i] - tau * q[i]; float remainder = GravityOneMinusS[0] - tau * q[0]; float whole = 0; float part = 0; [unroll] for (int j = @QLAST@; j >= 0; j--) { whole = whole + q[j] / (j + 1); part = (part + q[j] / (j + 1)) * t; } return 0.5 * (remainder * log((t + tau) / (1 + tau)) - (whole - part)); } // GravityField.BandSeries: the band's potential at t for tau > @SERIESFROM@, a series in (1 - t) / (1 + tau). float gravityBandSeries(float tau, float t) { float y = 1 - t; float yj[@N@]; yj[0] = 1; [unroll] for (int j = 1; j < @N@; j++) yj[j] = yj[j - 1] * y; float w = 1 / (1 + tau); float wk = w; float yk = y; float acc = 0; for (int k = 0; k < @SERIES@; k++) { float inner = 0; [unroll] for (int m = @ORDER@; m < @N@; m++) inner += GravityS[m] * yj[m] / (m + k + 1); acc += wk * yk * inner; wk *= w; yk *= y; } return -0.5 * acc; } // GravityField.Potential: one band term, per unit strength; the global term when b is infinite. float gravityTermPotential(float softening, float a, float b, float r) { float s = r * r; float e2 = softening * softening; if (isinf(b)) return 0.5 * log(s + e2); float a2 = a * a; float b2 = b * b; if (s >= b2) return 0; float tau = (a2 + e2) / (b2 - a2); if (s <= a2) return 0.5 * log((s + e2) / (a2 + e2)) + (tau <= @SERIESFROM@ ? gravityBandClosed(tau, 0) : gravityBandSeries(tau, 0)); float t = (s - a2) / (b2 - a2); return tau <= @SERIESFROM@ ? gravityBandClosed(tau, t) : gravityBandSeries(tau, t); } // Zone.GetHeight at world xz: each term's potential minus its own reference, summed, divided by the exaggeration. float gravityHeight(float2 p) { float result = 0; for (int i = 0; i < _GravityWellCount; i++) { GravityTerm term = _GravityWells[i]; float r = length(p - term.position); if (r < term.outer) result += term.strength * gravityTermPotential(term.softening, term.inner, term.outer, r) - term.reference; } return result / _HeightExaggeration; } #endif "; }