Signed Distance Field — Mathématique
Structure du projet
MSLearn/
├── Renderer/ …
│ ├── SpriteRenderer.cpp
│ ├── SpriteRenderer.hpp
│ ├── Renderer.cpp
│ └── Renderer.hpp
├── Shaders/ …
│ └── Sprite.metal
├── includes/ …
│ ├── SharedGPU/ …
│ │ └── SpriteTypes.hpp
│ └── extern/
│ └── stb_image.h #
└── Makefile
/// MSL — Shiba.metal — update:30/09/26
#include <metal_stdlib>
using namespace metal;
#define SUN float3(1.00, 0.92, 0.10)
#define CREAM float3(0.99, 0.94, 0.84)
#define OUTLINE float3(0.15, 0.025, 0.01)
#define PANDA_OUTLINE float3(0.15, 0.025, 0.01)
constant float3 SHIBA_RED = float3(0.847, 0.404, 0.157);
#define OUTLINE_WIDTH 0.009
#define OUTLINE_WIDTH 0.009
constant float3 BRAND = float3(1.0, 0.37254901960784315, 0.12156862745098039);
float sdEllipse(float2 p, float2 r)
{
p /= r;
return (length(p) - 1.0) * min(r.x, r.y);
}
float sdCircle(float2 p, float r)
{
return length(p) - r;
}
float sdRoundedBox(float2 p, float2 b, float r)
{
float2 q = abs(p) - b + r;
return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - r;
}
float sdTriangle(float2 p, float2 p0, float2 p1, float2 p2)
{
float2 e0 = p1 - p0, e1 = p2 - p1, e2 = p0 - p2;
float2 v0 = p - p0, v1 = p - p1, v2 = p - p2;
float2 pq0 = v0 - e0 * clamp(dot(v0, e0) / dot(e0, e0), 0.0, 1.0);
float2 pq1 = v1 - e1 * clamp(dot(v1, e1) / dot(e1, e1), 0.0, 1.0);
float2 pq2 = v2 - e2 * clamp(dot(v2, e2) / dot(e2, e2), 0.0, 1.0);
float s = sign(e0.x * e2.y - e0.y * e2.x);
float2 d = min(min(float2(dot(pq0, pq0), s * (v0.x * e0.y - v0.y * e0.x)),
float2(dot(pq1, pq1), s * (v1.x * e1.y - v1.y * e1.x))),
float2(dot(pq2, pq2), s * (v2.x * e2.y - v2.y * e2.x)));
return -sqrt(d.x) * sign(d.y);
}
float sdEar(float2 p, float w, float h)
{
return sdTriangle(p,
float2(-w * 0.5, 0.0),
float2( w * 0.5, 0.0),
float2( 0.0, h));
}
float sdSmile(float2 p, float r, float thickness)
{
float outer = abs(length(p) - r) - thickness;
// On coupe la moitié supérieure pour ne garder que le sourire
float cut = p.y;
return max(outer, cut);
}
float2 rotated(float2 p, float angle)
{
float c = cos(angle);
float sn = sin(angle);
return float2(c * p.x - sn * p.y, sn * p.x + c * p.y);
}
float sdSegment(float2 p, float2 a, float2 b, float r)
{
float2 pa = p - a;
float2 ba = b - a;
float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);
return length(pa - ba * h) - r;
}
float smin(float a, float b, float k)
{
float h = clamp(0.5 + 0.5 * (b - a) / k, 0.0, 1.0);
return mix(b, a, h) - k * h * (1.0 - h);
}
float fillMask(float d, float aa)
{
return 1.0 - smoothstep(-aa, aa, d);
}
float4 paint(float4 dst, float d, float3 color, float aa)
{
float m = fillMask(d, aa);
return float4(mix(dst.rgb, color, m), max(dst.a, m));
}
float4 paintOutlined(float4 dst, float d, float3 color, float aa)
{
dst = paint(dst, d, color, aa);
return paint(dst, abs(d) - OUTLINE_WIDTH, OUTLINE, aa);
}
float2 tailCurve(float t)
{
float2 a = float2(0.22, -0.58);
float2 b = float2(0.92, -1.02);
float2 c = float2(0.74, -0.14);
float u = 1.0 - t;
return u * u * a + 2.0 * u * t * b + t * t * c;
}
float2 tailTangent(float t)
{
return normalize(tailCurve(t + 0.01) - tailCurve(t - 0.01));
}
float2 tailCenter(float t, float time)
{
float2 tangent = tailTangent(t);
float2 normal = float2(-tangent.y, tangent.x);
return tailCurve(t) + normal * sin(time * 2.0 - t * 3.0) * 0.05 * t;
}
float tailRadius(float t)
{
return mix(0.15, 0.11, t) + 0.03 * sin(M_PI_F * t);
}
struct ShibaVertexOut
{
float4 position [[position]];
float2 uv;
};
struct ShibaUniforms
{
float time;
float2 resolution;
int level;
};
float4 mameShibaInu(float2 point, float time, int anyme)
{
float4 shibaInu = float4(0.0);
float bob = sin(time * 1.5) * 0.03; // flottement vertical
float breath = 1.0 + sin(time * 2.2) * 0.018;
float2 fixPoint = point;
point.y -= bob;
float sway = sin(time * 1.98 - 0.9) * 0.089; // balancement latéral
float tailTipSway = sin(time * 5.0) * 0.12;
float aa = fwidth(point.x) * 1.5;
float box = sdRoundedBox(float2(0.0, 0.0), float2(0.99, 0.99), 0.89);
float2 headPoint = point - float2(sway, 0.023); // , hauteur);
float sun = sdCircle(fixPoint, 0.89);
float halo = sun - 0.06 - sin(time * 0.8) * 0.01;
shibaInu = paint(shibaInu, halo, mix(CREAM, SUN, 0.35), aa);
shibaInu = paint(shibaInu, sun, SUN, aa);
/* queue */
int TAIL_SEGMENTS = 2 + int(fmod(time, 9.0));
float tail = sdCircle(point - tailCenter(0.0, time), tailRadius(0.0));
for (int i = 1; i < TAIL_SEGMENTS; ++i)
{
float t = float(i) / float(TAIL_SEGMENTS - 1);
tail = smin(tail, sdCircle(point - tailCenter(t, time), tailRadius(t)), 0.04);
}
shibaInu = paint(shibaInu, tail, BLACK, aa);
for (int i = 0; i < TAIL_SEGMENTS; ++i)
{
float t = float(i) / float(TAIL_SEGMENTS - 1);
float2 tangent = tailTangent(t);
float2 normal = float2(-tangent.y, tangent.x);
float radius = tailRadius(t);
float2 q = point - tailCenter(t, time);
q = float2(dot(q, tangent), dot(q, normal));
float segment = sdEllipse(q, float2(radius, radius * 1.4));
float3 fur = mix(ORANGE_DK, ORANGE, t);
if (i % 2 == 1)
{
fur = BLACK;
}
shibaInu = paint(shibaInu, max(segment, tail), fur, aa);
shibaInu = paint(shibaInu, max(abs(segment) - OUTLINE_WIDTH, tail), PANDA_OUTLINE, aa);
}
shibaInu = paint(shibaInu, abs(tail) - OUTLINE_WIDTH, PANDA_OUTLINE, aa);
/* fin queue */
float2 brandNoRot = point - float2(0.68 + sway, 0.03);
brandNoRot = rotated(brandNoRot, 165.0 * M_PI_F / 180.0);
float brand = sdEllipse(brandNoRot, float2(0.23, 0.2));
float brandMask = 1.0 - smoothstep(-aa, aa, brand); // fillMask()
float outline = 1.0 - smoothstep(0.0, OUTLINE_WIDTH, abs(brand));
shibaInu = paintOutlined(shibaInu, brand - OUTLINE_WIDTH, PANDA_OUTLINE, aa);
shibaInu = paintOutlined(shibaInu, brand - OUTLINE_WIDTH - OUTLINE_WIDTH, BLACK, aa);
shibaInu = paint(shibaInu, brand, float3(BRAND), aa);
float2 urajiroNoRot = point - float2(0.68 + sway, 0.1);
urajiroNoRot = rotated(urajiroNoRot, 165.0 * M_PI_F / 180.0);
float urajiro = sdEllipse(urajiroNoRot, float2(0.16, 0.13));
float bellyMask = 1.0 - smoothstep(-aa, aa, urajiro);
shibaInu = paint(shibaInu, urajiro, float3(CREAM), aa);
return shibaInu;
}
fragment float4 shiba_fragment(ShibaUniforms in [[stage_in]],
constant ShibaUniforms& uniforms [[buffer(0)]])
{
float2 p = in.uv * 2.0 - 1.0;
float aspect = uniforms.resolution.x / uniforms.resolution.y;
p.x *= aspect;
float t = uniforms.time;
float4 shibaInu = mameShibaInu(p, t, uniforms.level);
float4 background = float4(0.0, 0.0, 0.0, 0.0);
float3 color = mix(background.rgb, shibaInu.rgb, shibaInu.a);
float alpha = mix(background.a, 1.0, shibaInu.a);
return float4(color * alpha, alpha);
}
CheckPoint Compilation — mieux vaut tester cent fois