Chapitre -115 · SDF mathématique

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