文章

SDF 几何

SDF 几何

SDF 的全称是 Signed Distance Field(有符号距离场),用于表示空间中各点到物体表面的距离。

  • 有符号:指的是正数和负数,正数代表在物体外,负数代表在物体内。

  • 距离场:其中的 数值正是代表到物体表面的距离,0就代表物体表面。

    例如数值5就代表当前点在物体外,距离表面还有5的距离,负数则相反。

SDF 常被应用于字体渲染、Ray Marching、物理引擎等领域。

本文用到的 GLSL 内置函数说明:

  • clamp(x, y, z):x < y 返回 y,x > z 返回 z,否则返回 x
  • mix(x, y, z):x, y 的线性混叠, x(1 - z) + y * z
  • length(x):返回一个向量的模(长度),即 sqrt(dot(x,x))
  • sign(x):x < 0 时返回 -1,x == 0 返回 0,x > 0 返回 1

2D 几何

Circle
  • exact (https://www.shadertoy.com/view/3ltSW2)
1
2
3
4
float sdCircle( vec2 p, float r )
{
    return length(p) - r;
}
Rounded Box
  • exact (https://www.shadertoy.com/view/4llXD7 and https://www.youtube.com/watch?v=s5NGeUV2EyU)
1
2
3
4
5
6
7
float sdRoundedBox( in vec2 p, in vec2 b, in vec4 r )
{
    r.xy = (p.x>0.0)?r.xy : r.zw;
    r.x  = (p.y>0.0)?r.x  : r.y;
    vec2 q = abs(p)-b+r.x;
    return min(max(q.x,q.y),0.0) + length(max(q,0.0)) - r.x;
}
Box
  • exact (https://www.youtube.com/watch?v=62-pRVZuS5c)
1
2
3
4
5
float sdBox( in vec2 p, in vec2 b )
{
    vec2 d = abs(p)-b;
    return length(max(d,0.0)) + min(max(d.x,d.y),0.0);
}
Oriented Box
  • exact
1
2
3
4
5
6
7
8
9
float sdOrientedBox( in vec2 p, in vec2 a, in vec2 b, float th )
{
    float l = length(b-a);
    vec2  d = (b-a)/l;
    vec2  q = (p-(a+b)*0.5);
          q = mat2(d.x,-d.y,d.y,d.x)*q;
          q = abs(q)-vec2(l,th)*0.5;
    return length(max(q,0.0)) + min(max(q.x,q.y),0.0);    
}
Segment
  • exact (https://www.shadertoy.com/view/3tdSDj and https://www.youtube.com/watch?v=PMltMdi1Wzg)
1
2
3
4
5
6
float sdSegment( in vec2 p, in vec2 a, in vec2 b )
{
    vec2 pa = p-a, ba = b-a;
    float h = clamp( dot(pa,ba)/dot(ba,ba), 0.0, 1.0 );
    return length( pa - ba*h );
}
Rhombus
  • exact (https://www.shadertoy.com/view/XdXcRB)
1
2
3
4
5
6
7
8
float ndot(vec2 a, vec2 b ) { return a.x*b.x - a.y*b.y; }
float sdRhombus( in vec2 p, in vec2 b ) 
{
    p = abs(p);
    float h = clamp( ndot(b-2.0*p,b)/dot(b,b), -1.0, 1.0 );
    float d = length( p-0.5*b*vec2(1.0-h,1.0+h) );
    return d * sign( p.x*b.y + p.y*b.x - b.x*b.y );
}
Isosceles Trapezoid
  • exact (https://www.shadertoy.com/view/MlycD3)
1
2
3
4
5
6
7
8
9
10
float sdTrapezoid( in vec2 p, in float r1, float r2, float he )
{
    vec2 k1 = vec2(r2,he);
    vec2 k2 = vec2(r2-r1,2.0*he);
    p.x = abs(p.x);
    vec2 ca = vec2(p.x-min(p.x,(p.y<0.0)?r1:r2), abs(p.y)-he);
    vec2 cb = p - k1 + k2*clamp( dot(k1-p,k2)/dot2(k2), 0.0, 1.0 );
    float s = (cb.x<0.0 && ca.y<0.0) ? -1.0 : 1.0;
    return s*sqrt( min(dot2(ca),dot2(cb)) );
}
Parallelogram
  • exact (https://www.shadertoy.com/view/7dlGRf)
1
2
3
4
5
6
7
8
9
10
11
12
float sdParallelogram( in vec2 p, float wi, float he, float sk )
{
    vec2 e = vec2(sk,he);
    p = (p.y<0.0)?-p:p;
    vec2  w = p - e; w.x -= clamp(w.x,-wi,wi);
    vec2  d = vec2(dot(w,w), -w.y);
    float s = p.x*e.y - p.y*e.x;
    p = (s<0.0)?-p:p;
    vec2  v = p - vec2(wi,0); v -= e*clamp(dot(v,e)/dot(e,e),-1.0,1.0);
    d = min( d, vec2(dot(v,v), wi*he-abs(s)));
    return sqrt(d.x)*sign(-d.y);
}
Equilateral Triangle
  • exact (https://www.shadertoy.com/view/Xl2yDW)
1
2
3
4
5
6
7
8
9
float sdEquilateralTriangle( in vec2 p )
{
    const float k = sqrt(3.0);
    p.x = abs(p.x) - 1.0;
    p.y = p.y + 1.0/k;
    if( p.x+k*p.y>0.0 ) p = vec2(p.x-k*p.y,-k*p.x-p.y)/2.0;
    p.x -= clamp( p.x, -2.0, 0.0 );
    return -length(p)*sign(p.y);
}
Isosceles Triangle
  • exact (https://www.shadertoy.com/view/MldcD7)
1
2
3
4
5
6
7
8
9
10
float sdTriangleIsosceles( in vec2 p, in vec2 q )
{
    p.x = abs(p.x);
    vec2 a = p - q*clamp( dot(p,q)/dot(q,q), 0.0, 1.0 );
    vec2 b = p - q*vec2( clamp( p.x/q.x, 0.0, 1.0 ), 1.0 );
    float s = -sign( q.y );
    vec2 d = min( vec2( dot(a,a), s*(p.x*q.y-p.y*q.x) ),
                  vec2( dot(b,b), s*(p.y-q.y)  ));
    return -sqrt(d.x)*sign(d.y);
}
Triangle
  • exact (https://www.shadertoy.com/view/XsXSz4)
1
2
3
4
5
6
7
8
9
10
11
12
13
float sdTriangle( in vec2 p, in vec2 p0, in vec2 p1, in vec2 p2 )
{
    vec2 e0 = p1-p0, e1 = p2-p1, e2 = p0-p2;
    vec2 v0 = p -p0, v1 = p -p1, v2 = p -p2;
    vec2 pq0 = v0 - e0*clamp( dot(v0,e0)/dot(e0,e0), 0.0, 1.0 );
    vec2 pq1 = v1 - e1*clamp( dot(v1,e1)/dot(e1,e1), 0.0, 1.0 );
    vec2 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 );
    vec2 d = min(min(vec2(dot(pq0,pq0), s*(v0.x*e0.y-v0.y*e0.x)),
                     vec2(dot(pq1,pq1), s*(v1.x*e1.y-v1.y*e1.x))),
                     vec2(dot(pq2,pq2), s*(v2.x*e2.y-v2.y*e2.x)));
    return -sqrt(d.x)*sign(d.y);
}
Uneven Capsule
  • exact (https://www.shadertoy.com/view/4lcBWn)
1
2
3
4
5
6
7
8
9
10
float sdUnevenCapsule( vec2 p, float r1, float r2, float h )
{
    p.x = abs(p.x);
    float b = (r1-r2)/h;
    float a = sqrt(1.0-b*b);
    float k = dot(p,vec2(-b,a));
    if( k < 0.0 ) return length(p) - r1;
    if( k > a*h ) return length(p-vec2(0.0,h)) - r2;
    return dot(p, vec2(a,b) ) - r1;
}
Regular Pentagon
  • exact (https://www.shadertoy.com/view/llVyWW)
1
2
3
4
5
6
7
8
9
float sdPentagon( in vec2 p, in float r )
{
    const vec3 k = vec3(0.809016994,0.587785252,0.726542528);
    p.x = abs(p.x);
    p -= 2.0*min(dot(vec2(-k.x,k.y),p),0.0)*vec2(-k.x,k.y);
    p -= 2.0*min(dot(vec2( k.x,k.y),p),0.0)*vec2( k.x,k.y);
    p -= vec2(clamp(p.x,-r*k.z,r*k.z),r);    
    return length(p)*sign(p.y);
}
Regular Hexagon
  • exact
1
2
3
4
5
6
7
8
float sdHexagon( in vec2 p, in float r )
{
    const vec3 k = vec3(-0.866025404,0.5,0.577350269);
    p = abs(p);
    p -= 2.0*min(dot(k.xy,p),0.0)*k.xy;
    p -= vec2(clamp(p.x, -k.z*r, k.z*r), r);
    return length(p)*sign(p.y);
}
Regular Octogon
  • exact (https://www.shadertoy.com/view/llGfDG)
1
2
3
4
5
6
7
8
9
float sdOctogon( in vec2 p, in float r )
{
    const vec3 k = vec3(-0.9238795325, 0.3826834323, 0.4142135623 );
    p = abs(p);
    p -= 2.0*min(dot(vec2( k.x,k.y),p),0.0)*vec2( k.x,k.y);
    p -= 2.0*min(dot(vec2(-k.x,k.y),p),0.0)*vec2(-k.x,k.y);
    p -= vec2(clamp(p.x, -k.z*r, k.z*r), r);
    return length(p)*sign(p.y);
}
Hexagram
  • exact (https://www.shadertoy.com/view/tt23RR)
1
2
3
4
5
6
7
8
9
float sdHexagram( in vec2 p, in float r )
{
    const vec4 k = vec4(-0.5,0.8660254038,0.5773502692,1.7320508076);
    p = abs(p);
    p -= 2.0*min(dot(k.xy,p),0.0)*k.xy;
    p -= 2.0*min(dot(k.yx,p),0.0)*k.yx;
    p -= vec2(clamp(p.x,r*k.z,r*k.w),r);
    return length(p)*sign(p.y);
}
Star 5
  • exact (https://www.shadertoy.com/view/3tSGDy)
1
2
3
4
5
6
7
8
9
10
11
12
13
float sdStar5(in vec2 p, in float r, in float rf)
{
    const vec2 k1 = vec2(0.809016994375, -0.587785252292);
    const vec2 k2 = vec2(-k1.x,k1.y);
    p.x = abs(p.x);
    p -= 2.0*max(dot(k1,p),0.0)*k1;
    p -= 2.0*max(dot(k2,p),0.0)*k2;
    p.x = abs(p.x);
    p.y -= r;
    vec2 ba = rf*vec2(-k1.y,k1.x) - vec2(0,1);
    float h = clamp( dot(p,ba)/dot(ba,ba), 0.0, r );
    return length(p-ba*h) * sign(p.y*ba.x-p.x*ba.y);
}
Regular Star
  • exact (https://www.shadertoy.com/view/3tSGDy)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
float sdStar(in vec2 p, in float r, in int n, in float m)
{
    // next 4 lines can be precomputed for a given shape
    float an = 3.141593/float(n);
    float en = 3.141593/m;  // m is between 2 and n
    vec2  acs = vec2(cos(an),sin(an));
    vec2  ecs = vec2(cos(en),sin(en)); // ecs=vec2(0,1) for regular polygon

    float bn = mod(atan(p.x,p.y),2.0*an) - an;
    p = length(p)*vec2(cos(bn),abs(sin(bn)));
    p -= r*acs;
    p += ecs*clamp( -dot(p,ecs), 0.0, r*acs.y/ecs.y);
    return length(p)*sign(p.x);
}
Pie
  • exact (https://www.shadertoy.com/view/3l23RK)
1
2
3
4
5
6
7
float sdPie( in vec2 p, in vec2 c, in float r )
{
    p.x = abs(p.x);
    float l = length(p) - r;
    float m = length(p-c*clamp(dot(p,c),0.0,r)); // c=sin/cos of aperture
    return max(l,m*sign(c.y*p.x-c.x*p.y));
}
Cut Disk
  • exact (https://www.shadertoy.com/view/ftVXRc)
1
2
3
4
5
6
7
8
9
float sdCutDisk( in vec2 p, in float r, in float h )
{
    float w = sqrt(r*r-h*h); // constant for any given shape
    p.x = abs(p.x);
    float s = max( (h-r)*p.x*p.x+w*w*(h+r-2.0*p.y), h*p.x-w*p.y );
    return (s<0.0) ? length(p)-r :
           (p.x<w) ? h - p.y     :
                     length(p-vec2(w,h));
}
Arc
  • exact (https://www.shadertoy.com/view/wl23RK)
1
2
3
4
5
6
7
float sdArc( in vec2 p, in vec2 sc, in float ra, float rb )
{
    // sc is the sin/cos of the arc's aperture
    p.x = abs(p.x);
    return ((sc.y*p.x>sc.x*p.y) ? length(p-sc*ra) : 
                                  abs(length(p)-ra)) - rb;
}
Horseshoe
  • exact (https://www.shadertoy.com/view/WlSGW1)
1
2
3
4
5
6
7
8
9
10
float sdHorseshoe( in vec2 p, in vec2 c, in float r, in vec2 w )
{
    p.x = abs(p.x);
    float l = length(p);
    p = mat2(-c.x, c.y, c.y, c.x)*p;
    p = vec2((p.y>0.0 || p.x>0.0)?p.x:l*sign(-c.x),
             (p.x>0.0)?p.y:l );
    p = vec2(p.x,abs(p.y-r))-w;
    return length(max(p,0.0)) + min(0.0,max(p.x,p.y));
}
Vesica
  • exact (https://www.shadertoy.com/view/XtVfRW)
1
2
3
4
5
6
7
float sdVesica(vec2 p, float r, float d)
{
    p = abs(p);
    float b = sqrt(r*r-d*d);
    return ((p.y-b)*d>p.x*b) ? length(p-vec2(0.0,b))
                             : length(p-vec2(-d,0.0))-r;
}
Moon
  • exact (https://www.shadertoy.com/view/WtdBRS)
1
2
3
4
5
6
7
8
9
10
float sdMoon(vec2 p, float d, float ra, float rb )
{
    p.y = abs(p.y);
    float a = (ra*ra - rb*rb + d*d)/(2.0*d);
    float b = sqrt(max(ra*ra-a*a,0.0));
    if( d*(p.x*b-p.y*a) > d*d*max(b-p.y,0.0) )
          return length(p-vec2(a,b));
    return max( (length(p          )-ra),
               -(length(p-vec2(d,0))-rb));
}
Circle Cross
  • exact (https://www.shadertoy.com/view/NslXDM)
1
2
3
4
5
6
7
8
9
float sdRoundedCross( in vec2 p, in float h )
{
    float k = 0.5*(h+1.0/h); // k should be const at modeling time
    p = abs(p);
    return ( p.x<1.0 && p.y<p.x*(k-h)+h ) ? 
             k-sqrt(dot2(p-vec2(1,k)))  :
           sqrt(min(dot2(p-vec2(0,h)),
                    dot2(p-vec2(1,0))));
}
Simple Egg
  • exact (https://www.shadertoy.com/view/XtVfRW)
1
2
3
4
5
6
7
8
9
float sdEgg( in vec2 p, in float ra, in float rb )
{
    const float k = sqrt(3.0);
    p.x = abs(p.x);
    float r = ra - rb;
    return ((p.y<0.0)       ? length(vec2(p.x,  p.y    )) - r :
            (k*(p.x+r)<p.y) ? length(vec2(p.x,  p.y-k*r)) :
                              length(vec2(p.x+r,p.y    )) - 2.0*r) - rb;
}
Heart
  • exact (https://www.shadertoy.com/view/3tyBzV)
1
2
3
4
5
6
7
8
9
float sdHeart( in vec2 p )
{
    p.x = abs(p.x);

    if( p.y+p.x>1.0 )
        return sqrt(dot2(p-vec2(0.25,0.75))) - sqrt(2.0)/4.0;
    return sqrt(min(dot2(p-vec2(0.00,1.00)),
                    dot2(p-0.5*max(p.x+p.y,0.0)))) * sign(p.x-p.y);
}
Cross
  • exact exterior, bound interior (https://www.shadertoy.com/view/XtGfzw)
1
2
3
4
5
6
7
8
float sdCross( in vec2 p, in vec2 b, float r ) 
{
    p = abs(p); p = (p.y>p.x) ? p.yx : p.xy;
    vec2  q = p - b;
    float k = max(q.y,q.x);
    vec2  w = (k>0.0) ? q : vec2(b.y-p.x,-k);
    return sign(k)*length(max(w,0.0)) + r;
}
Rounded X
  • exact (https://www.shadertoy.com/view/3dKSDc)
1
2
3
4
5
float sdRoundedX( in vec2 p, in float w, in float r )
{
    p = abs(p);
    return length(p-min(p.x+p.y,w)*0.5) - r;
}
Polygon
  • exact (https://www.shadertoy.com/view/wdBXRW)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
float sdPolygon( in vec2[N] v, in vec2 p )
{
    float d = dot(p-v[0],p-v[0]);
    float s = 1.0;
    for( int i=0, j=N-1; i<N; j=i, i++ )
    {
        vec2 e = v[j] - v[i];
        vec2 w =    p - v[i];
        vec2 b = w - e*clamp( dot(w,e)/dot(e,e), 0.0, 1.0 );
        d = min( d, dot(b,b) );
        bvec3 c = bvec3(p.y>=v[i].y,p.y<v[j].y,e.x*w.y>e.y*w.x);
        if( all(c) || all(not(c)) ) s*=-1.0;  
    }
    return s*sqrt(d);
}
Ellipse
  • exact (https://www.shadertoy.com/view/4sS3zz)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
float sdEllipse( in vec2 p, in vec2 ab )
{
    p = abs(p); if( p.x > p.y ) {p=p.yx;ab=ab.yx;}
    float l = ab.y*ab.y - ab.x*ab.x;
    float m = ab.x*p.x/l;      float m2 = m*m; 
    float n = ab.y*p.y/l;      float n2 = n*n; 
    float c = (m2+n2-1.0)/3.0; float c3 = c*c*c;
    float q = c3 + m2*n2*2.0;
    float d = c3 + m2*n2;
    float g = m + m*n2;
    float co;
    if( d<0.0 )
    {
        float h = acos(q/c3)/3.0;
        float s = cos(h);
        float t = sin(h)*sqrt(3.0);
        float rx = sqrt( -c*(s + t + 2.0) + m2 );
        float ry = sqrt( -c*(s - t + 2.0) + m2 );
        co = (ry+sign(l)*rx+abs(g)/(rx*ry)- m)/2.0;
    }
    else
    {
        float h = 2.0*m*n*sqrt( d );
        float s = sign(q+h)*pow(abs(q+h), 1.0/3.0);
        float u = sign(q-h)*pow(abs(q-h), 1.0/3.0);
        float rx = -s - u - c*4.0 + 2.0*m2;
        float ry = (s - u)*sqrt(3.0);
        float rm = sqrt( rx*rx + ry*ry );
        co = (ry/sqrt(rm-rx)+2.0*g/rm-m)/2.0;
    }
    vec2 r = ab * vec2(co, sqrt(1.0-co*co));
    return length(r-p) * sign(p.y-r.y);
}
Parabola
  • exact (https://www.shadertoy.com/view/ws3GD7)
1
2
3
4
5
6
7
8
9
10
11
12
13
float sdParabola( in vec2 pos, in float k )
{
    pos.x = abs(pos.x);
    float ik = 1.0/k;
    float p = ik*(pos.y - 0.5*ik)/3.0;
    float q = 0.25*ik*ik*pos.x;
    float h = q*q - p*p*p;
    float r = sqrt(abs(h));
    float x = (h>0.0) ? 
        pow(q+r,1.0/3.0) - pow(abs(q-r),1.0/3.0)*sign(r-q) :
        2.0*cos(atan(r,q)/3.0)*sqrt(p);
    return length(pos-vec2(x,k*x*x)) * sign(pos.x-x);
}
Parabola Segment
  • exact (https://www.shadertoy.com/view/3lSczz)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
float sdParabola( in vec2 pos, in float wi, in float he )
{
    pos.x = abs(pos.x);
    float ik = wi*wi/he;
    float p = ik*(he-pos.y-0.5*ik)/3.0;
    float q = pos.x*ik*ik*0.25;
    float h = q*q - p*p*p;
    float r = sqrt(abs(h));
    float x = (h>0.0) ? 
        pow(q+r,1.0/3.0) - pow(abs(q-r),1.0/3.0)*sign(r-q) :
        2.0*cos(atan(r/q)/3.0)*sqrt(p);
    x = min(x,wi);
    return length(pos-vec2(x,he-x*x/ik)) * 
           sign(ik*(pos.y-he)+pos.x*pos.x);
}
Quadratic Bezier
  • exact (https://www.shadertoy.com/view/MlKcDD)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
float sdBezier( in vec2 pos, in vec2 A, in vec2 B, in vec2 C )
{    
    vec2 a = B - A;
    vec2 b = A - 2.0*B + C;
    vec2 c = a * 2.0;
    vec2 d = A - pos;
    float kk = 1.0/dot(b,b);
    float kx = kk * dot(a,b);
    float ky = kk * (2.0*dot(a,a)+dot(d,b)) / 3.0;
    float kz = kk * dot(d,a);      
    float res = 0.0;
    float p = ky - kx*kx;
    float p3 = p*p*p;
    float q = kx*(2.0*kx*kx-3.0*ky) + kz;
    float h = q*q + 4.0*p3;
    if( h >= 0.0) 
    { 
        h = sqrt(h);
        vec2 x = (vec2(h,-h)-q)/2.0;
        vec2 uv = sign(x)*pow(abs(x), vec2(1.0/3.0));
        float t = clamp( uv.x+uv.y-kx, 0.0, 1.0 );
        res = dot2(d + (c + b*t)*t);
    }
    else
    {
        float z = sqrt(-p);
        float v = acos( q/(p*z*2.0) ) / 3.0;
        float m = cos(v);
        float n = sin(v)*1.732050808;
        vec3  t = clamp(vec3(m+m,-n-m,n-m)*z-kx,0.0,1.0);
        res = min( dot2(d+(c+b*t.x)*t.x),
                   dot2(d+(c+b*t.y)*t.y) );
        // the third root cannot be the closest
        // res = min(res,dot2(d+(c+b*t.z)*t.z));
    }
    return sqrt( res );
}
Bobbly Cross
  • exact (https://www.shadertoy.com/view/NssXWM)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
float sdBlobbyCross( in vec2 pos, float he )
{
    pos = abs(pos);
    pos = vec2(abs(pos.x-pos.y),1.0-pos.x-pos.y)/sqrt(2.0);

    float p = (he-pos.y-0.25/he)/(6.0*he);
    float q = pos.x/(he*he*16.0);
    float h = q*q - p*p*p;
    
    float x;
    if( h>0.0 ) { float r = sqrt(h); x = pow(q+r,1.0/3.0)-pow(abs(q-r),1.0/3.0)*sign(r-q); }
    else        { float r = sqrt(p); x = 2.0*r*cos(acos(q/(p*r))/3.0); }
    x = min(x,sqrt(2.0)/2.0);
    
    vec2 z = vec2(x,he*(1.0-2.0*x*x)) - pos;
    return length(z) * sign(z.y);
}
Tunnel
  • exact (https://www.shadertoy.com/view/flSSDy)
1
2
3
4
5
6
7
8
9
10
11
12
float sdTunnel( in vec2 p, in vec2 wh )
{
    p.x = abs(p.x); p.y = -p.y;
    vec2 q = p - wh;

    float d1 = dot2(vec2(max(q.x,0.0),q.y));
    q.x = (p.y>0.0) ? q.x : length(p)-wh.x;
    float d2 = dot2(vec2(q.x,max(q.y,0.0)));
    float d = sqrt( min(d1,d2) );
    
    return (max(q.x,q.y)<0.0) ? -d : d;
}
Stairs
  • exact (https://www.shadertoy.com/view/7tKSWt)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
float sdStairs( in vec2 p, in vec2 wh, in float n )
{
    vec2 ba = wh*n;
    float d = min(dot2(p-vec2(clamp(p.x,0.0,ba.x),0.0)), 
                  dot2(p-vec2(ba.x,clamp(p.y,0.0,ba.y))) );
    float s = sign(max(-p.y,p.x-ba.x) );

    float dia = length(wh);
    p = mat2(wh.x,-wh.y, wh.y,wh.x)*p/dia;
    float id = clamp(round(p.x/dia),0.0,n-1.0);
    p.x = p.x - id*dia;
    p = mat2(wh.x, wh.y,-wh.y,wh.x)*p/dia;

    float hh = wh.y/2.0;
    p.y -= hh;
    if( p.y>hh*sign(p.x) ) s=1.0;
    p = (id<0.5 || p.x>0.0) ? p : -p;
    d = min( d, dot2(p-vec2(0.0,clamp(p.y,-hh,hh))) );
    d = min( d, dot2(p-vec2(clamp(p.x,0.0,wh.x),hh)) );
    
    return sqrt(d)*s;
}

3D 几何

Sphere
  • exact (https://www.shadertoy.com/view/Xds3zN)
1
2
3
4
5
6
float sdSphere( vec3 p, float s )
{
  return length(p)-s;
}


Box
  • exact (Youtube Tutorial with derivation: https://www.youtube.com/watch?v=62-pRVZuS5c)
1
2
3
4
5
6
7
float sdBox( vec3 p, vec3 b )
{
  vec3 q = abs(p) - b;
  return length(max(q,0.0)) + min(max(q.x,max(q.y,q.z)),0.0);
}


Round Box
  • exact
1
2
3
4
5
6
7
float sdRoundBox( vec3 p, vec3 b, float r )
{
  vec3 q = abs(p) - b;
  return length(max(q,0.0)) + min(max(q.x,max(q.y,q.z)),0.0) - r;
}


Box Frame
  • exact (https://www.shadertoy.com/view/3ljcRh)
1
2
3
4
5
6
7
8
9
10
11
float sdBoxFrame( vec3 p, vec3 b, float e )
{
       p = abs(p  )-b;
  vec3 q = abs(p+e)-e;
  return min(min(
      length(max(vec3(p.x,q.y,q.z),0.0))+min(max(p.x,max(q.y,q.z)),0.0),
      length(max(vec3(q.x,p.y,q.z),0.0))+min(max(q.x,max(p.y,q.z)),0.0)),
      length(max(vec3(q.x,q.y,p.z),0.0))+min(max(q.x,max(q.y,p.z)),0.0));
}


Torus
  • exact
1
2
3
4
5
6
7
float sdTorus( vec3 p, vec2 t )
{
  vec2 q = vec2(length(p.xz)-t.x,p.y);
  return length(q)-t.y;
}


Capped Torus
  • exact (https://www.shadertoy.com/view/tl23RK)
1
2
3
4
5
6
7
8
float sdCappedTorus(in vec3 p, in vec2 sc, in float ra, in float rb)
{
  p.x = abs(p.x);
  float k = (sc.y*p.x>sc.x*p.y) ? dot(p.xy,sc) : length(p.xy);
  return sqrt( dot(p,p) + ra*ra - 2.0*ra*k ) - rb;
}


  • exact (https://www.shadertoy.com/view/wlXSD7)
1
2
3
4
5
6
7
float sdLink( vec3 p, float le, float r1, float r2 )
{
  vec3 q = vec3( p.x, max(abs(p.y)-le,0.0), p.z );
  return length(vec2(length(q.xy)-r1,q.z)) - r2;
}


Infinite Cylinder
  • exact
1
2
3
4
5
6
float sdCylinder( vec3 p, vec3 c )
{
  return length(p.xz-c.xy)-c.z;
}


Cone
  • exact
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
float sdCone( in vec3 p, in vec2 c, float h )
{
  // c is the sin/cos of the angle, h is height
  // Alternatively pass q instead of (c,h),
  // which is the point at the base in 2D
  vec2 q = h*vec2(c.x/c.y,-1.0);
    
  vec2 w = vec2( length(p.xz), p.y );
  vec2 a = w - q*clamp( dot(w,q)/dot(q,q), 0.0, 1.0 );
  vec2 b = w - q*vec2( clamp( w.x/q.x, 0.0, 1.0 ), 1.0 );
  float k = sign( q.y );
  float d = min(dot( a, a ),dot(b, b));
  float s = max( k*(w.x*q.y-w.y*q.x),k*(w.y-q.y)  );
  return sqrt(d)*sign(s);
}
Cone - bound (not exact!)
1
2
3
4
5
float sdCone( vec3 p, vec2 c, float h )
{
  float q = length(p.xz);
  return max(dot(c.xy,vec2(q,p.y)),-h-p.y);
}
Infinite Cone
  • exact
1
2
3
4
5
6
7
float sdCone( vec3 p, vec2 c )
{
    // c is the sin/cos of the angle
    vec2 q = vec2( length(p.xz), -p.y );
    float d = length(q-c*max(dot(q,c), 0.0));
    return d * ((q.x*c.y-q.y*c.x<0.0)?-1.0:1.0);
}
Plane
  • exact
1
2
3
4
5
float sdPlane( vec3 p, vec3 n, float h )
{
  // n must be normalized
  return dot(p,n) + h;
}
Hexagonal Prism
  • exact
1
2
3
4
5
6
7
8
9
10
float sdHexPrism( vec3 p, vec2 h )
{
  const vec3 k = vec3(-0.8660254, 0.5, 0.57735);
  p = abs(p);
  p.xy -= 2.0*min(dot(k.xy, p.xy), 0.0)*k.xy;
  vec2 d = vec2(
       length(p.xy-vec2(clamp(p.x,-k.z*h.x,k.z*h.x), h.x))*sign(p.y-h.x),
       p.z-h.y );
  return min(max(d.x,d.y),0.0) + length(max(d,0.0));
}
Triangular Prism - bound
1
2
3
4
5
float sdTriPrism( vec3 p, vec2 h )
{
  vec3 q = abs(p);
  return max(q.z-h.y,max(q.x*0.866025+p.y*0.5,-p.y)-h.x*0.5);
}
Capsule / Line
  • exact
1
2
3
4
5
6
float sdCapsule( vec3 p, vec3 a, vec3 b, float r )
{
  vec3 pa = p - a, ba = b - a;
  float h = clamp( dot(pa,ba)/dot(ba,ba), 0.0, 1.0 );
  return length( pa - ba*h ) - r;
}
Capsule / Line
  • exact
1
2
3
4
5
float sdVerticalCapsule( vec3 p, float h, float r )
{
  p.y -= clamp( p.y, 0.0, h );
  return length( p ) - r;
}
Capped Cylinder
  • exact
1
2
3
4
5
float sdCappedCylinder( vec3 p, float h, float r )
{
  vec2 d = abs(vec2(length(p.xz),p.y)) - vec2(h,r);
  return min(max(d.x,d.y),0.0) + length(max(d,0.0));
}
Capped Cylinder
  • exact (https://www.shadertoy.com/view/wdXGDr)
1
2
3
4
5
6
7
8
9
10
11
12
13
float sdCappedCylinder(vec3 p, vec3 a, vec3 b, float r)
{
  vec3  ba = b - a;
  vec3  pa = p - a;
  float baba = dot(ba,ba);
  float paba = dot(pa,ba);
  float x = length(pa*baba-ba*paba) - r*baba;
  float y = abs(paba-baba*0.5)-baba*0.5;
  float x2 = x*x;
  float y2 = y*y*baba;
  float d = (max(x,y)<0.0)?-min(x2,y2):(((x>0.0)?x2:0.0)+((y>0.0)?y2:0.0));
  return sign(d)*sqrt(abs(d))/baba;
}
Rounded Cylinder
  • exact
1
2
3
4
5
float sdRoundedCylinder( vec3 p, float ra, float rb, float h )
{
  vec2 d = vec2( length(p.xz)-2.0*ra+rb, abs(p.y) - h );
  return min(max(d.x,d.y),0.0) + length(max(d,0.0)) - rb;
}
Capped Cone
  • exact
1
2
3
4
5
6
7
8
9
10
float sdCappedCone( vec3 p, float h, float r1, float r2 )
{
  vec2 q = vec2( length(p.xz), p.y );
  vec2 k1 = vec2(r2,h);
  vec2 k2 = vec2(r2-r1,2.0*h);
  vec2 ca = vec2(q.x-min(q.x,(q.y<0.0)?r1:r2), abs(q.y)-h);
  vec2 cb = q - k1 + k2*clamp( dot(k1-q,k2)/dot2(k2), 0.0, 1.0 );
  float s = (cb.x<0.0 && ca.y<0.0) ? -1.0 : 1.0;
  return s*sqrt( min(dot2(ca),dot2(cb)) );
}
Capped Cone
  • exact (https://www.shadertoy.com/view/tsSXzK)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
float sdCappedCone(vec3 p, vec3 a, vec3 b, float ra, float rb)
{
  float rba  = rb-ra;
  float baba = dot(b-a,b-a);
  float papa = dot(p-a,p-a);
  float paba = dot(p-a,b-a)/baba;
  float x = sqrt( papa - paba*paba*baba );
  float cax = max(0.0,x-((paba<0.5)?ra:rb));
  float cay = abs(paba-0.5)-0.5;
  float k = rba*rba + baba;
  float f = clamp( (rba*(x-ra)+paba*baba)/k, 0.0, 1.0 );
  float cbx = x-ra - f*rba;
  float cby = paba - f;
  float s = (cbx<0.0 && cay<0.0) ? -1.0 : 1.0;
  return s*sqrt( min(cax*cax + cay*cay*baba,
                     cbx*cbx + cby*cby*baba) );
}
Solid Angle
  • exact (https://www.shadertoy.com/view/wtjSDW)
1
2
3
4
5
6
7
8
float sdSolidAngle(vec3 p, vec2 c, float ra)
{
  // c is the sin/cos of the angle
  vec2 q = vec2( length(p.xz), p.y );
  float l = length(q) - ra;
  float m = length(q - c*clamp(dot(q,c),0.0,ra) );
  return max(l,m*sign(c.y*q.x-c.x*q.y));
}
Cut Sphere
  • exact (https://www.shadertoy.com/view/stKSzc)
1
2
3
4
5
6
7
8
9
10
11
12
float sdCutSphere( vec3 p, float r, float h )
{
  // sampling independent computations (only depend on shape)
  float w = sqrt(r*r-h*h);

  // sampling dependant computations
  vec2 q = vec2( length(p.xz), p.y );
  float s = max( (h-r)*q.x*q.x+w*w*(h+r-2.0*q.y), h*q.x-w*q.y );
  return (s<0.0) ? length(q)-r :
         (q.x<w) ? h - q.y     :
                   length(q-vec2(w,h));
}
Cut Hollow Sphere
  • exact (https://www.shadertoy.com/view/7tVXRt)
1
2
3
4
5
6
7
8
9
10
float sdCutHollowSphere( vec3 p, float r, float h, float t )
{
  // sampling independent computations (only depend on shape)
  float w = sqrt(r*r-h*h);
  
  // sampling dependant computations
  vec2 q = vec2( length(p.xz), p.y );
  return ((h*q.x<w*q.y) ? length(q-vec2(w,h)) : 
                          abs(length(q)-r) ) - t;
}
Death Star
  • exact (https://www.shadertoy.com/view/7lVXRt)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
float sdDeathStar( in vec3 p2, in float ra, float rb, in float d )
{
  // sampling independent computations (only depend on shape)
  float a = (ra*ra - rb*rb + d*d)/(2.0*d);
  float b = sqrt(max(ra*ra-a*a,0.0));
    
  // sampling dependant computations
  vec2 p = vec2( p2.x, length(p2.yz) );
  if( p.x*b-p.y*a > d*max(b-p.y,0.0) )
    return length(p-vec2(a,b));
  else
    return max( (length(p          )-ra),
               -(length(p-vec2(d,0))-rb));
}
Round cone
  • exact
1
2
3
4
5
6
7
8
9
10
11
12
13
float sdRoundCone( vec3 p, float r1, float r2, float h )
{
  // sampling independent computations (only depend on shape)
  float b = (r1-r2)/h;
  float a = sqrt(1.0-b*b);

  // sampling dependant computations
  vec2 q = vec2( length(p.xz), p.y );
  float k = dot(q,vec2(-b,a));
  if( k<0.0 ) return length(q) - r1;
  if( k>a*h ) return length(q-vec2(0.0,h)) - r2;
  return dot(q, vec2(a,b) ) - r1;
}
Round Cone
  • exact (https://www.shadertoy.com/view/tdXGWr)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
float sdRoundCone(vec3 p, vec3 a, vec3 b, float r1, float r2)
{
  // sampling independent computations (only depend on shape)
  vec3  ba = b - a;
  float l2 = dot(ba,ba);
  float rr = r1 - r2;
  float a2 = l2 - rr*rr;
  float il2 = 1.0/l2;
    
  // sampling dependant computations
  vec3 pa = p - a;
  float y = dot(pa,ba);
  float z = y - l2;
  float x2 = dot2( pa*l2 - ba*y );
  float y2 = y*y*l2;
  float z2 = z*z*l2;

  // single square root!
  float k = sign(rr)*rr*rr*x2;
  if( sign(z)*a2*z2>k ) return  sqrt(x2 + z2)        *il2 - r2;
  if( sign(y)*a2*y2<k ) return  sqrt(x2 + y2)        *il2 - r1;
                        return (sqrt(x2*a2*il2)+y*rr)*il2 - r1;
}
Ellipsoid - bound (not exact!) (https://www.shadertoy.com/view/tdS3DG)
1
2
3
4
5
6
float sdEllipsoid( vec3 p, vec3 r )
{
  float k0 = length(p/r);
  float k1 = length(p/(r*r));
  return k0*(k0-1.0)/k1;
}
Rhombus
  • exact (https://www.shadertoy.com/view/tlVGDc)
1
2
3
4
5
6
7
8
float sdRhombus(vec3 p, float la, float lb, float h, float ra)
{
  p = abs(p);
  vec2 b = vec2(la,lb);
  float f = clamp( (ndot(b,b-2.0*p.xz))/dot(b,b), -1.0, 1.0 );
  vec2 q = vec2(length(p.xz-0.5*b*vec2(1.0-f,1.0+f))*sign(p.x*b.y+p.z*b.x-b.x*b.y)-ra, p.y-h);
  return min(max(q.x,q.y),0.0) + length(max(q,0.0));
}
Octahedron
  • exact (https://www.shadertoy.com/view/wsSGDG)
1
2
3
4
5
6
7
8
9
10
11
12
13
float sdOctahedron( vec3 p, float s)
{
  p = abs(p);
  float m = p.x+p.y+p.z-s;
  vec3 q;
       if( 3.0*p.x < m ) q = p.xyz;
  else if( 3.0*p.y < m ) q = p.yzx;
  else if( 3.0*p.z < m ) q = p.zxy;
  else return m*0.57735027;
    
  float k = clamp(0.5*(q.z-q.y+s),0.0,s); 
  return length(vec3(q.x,q.y-s+k,q.z-k)); 
}
Octahedron - bound (not exact)
1
2
3
4
5
float sdOctahedron( vec3 p, float s)
{
  p = abs(p);
  return (p.x+p.y+p.z-s)*0.57735027;
}
Pyramid
  • exact (https://www.shadertoy.com/view/Ws3SDl)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
float sdPyramid( vec3 p, float h)
{
  float m2 = h*h + 0.25;
    
  p.xz = abs(p.xz);
  p.xz = (p.z>p.x) ? p.zx : p.xz;
  p.xz -= 0.5;

  vec3 q = vec3( p.z, h*p.y - 0.5*p.x, h*p.x + 0.5*p.y);
   
  float s = max(-q.x,0.0);
  float t = clamp( (q.y-0.5*p.z)/(m2+0.25), 0.0, 1.0 );
    
  float a = m2*(q.x+s)*(q.x+s) + q.y*q.y;
  float b = m2*(q.x+0.5*t)*(q.x+0.5*t) + (q.y-m2*t)*(q.y-m2*t);
    
  float d2 = min(q.y,-q.x*m2-q.y*0.5) > 0.0 ? 0.0 : min(a,b);
    
  return sqrt( (d2+q.z*q.z)/m2 ) * sign(max(q.z,-p.y));
}
Triangle
  • exact (https://www.shadertoy.com/view/4sXXRN)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
float udTriangle( vec3 p, vec3 a, vec3 b, vec3 c )
{
  vec3 ba = b - a; vec3 pa = p - a;
  vec3 cb = c - b; vec3 pb = p - b;
  vec3 ac = a - c; vec3 pc = p - c;
  vec3 nor = cross( ba, ac );

  return sqrt(
    (sign(dot(cross(ba,nor),pa)) +
     sign(dot(cross(cb,nor),pb)) +
     sign(dot(cross(ac,nor),pc))<2.0)
     ?
     min( min(
     dot2(ba*clamp(dot(ba,pa)/dot2(ba),0.0,1.0)-pa),
     dot2(cb*clamp(dot(cb,pb)/dot2(cb),0.0,1.0)-pb) ),
     dot2(ac*clamp(dot(ac,pc)/dot2(ac),0.0,1.0)-pc) )
     :
     dot(nor,pa)*dot(nor,pa)/dot2(nor) );
}
Quad
  • exact (https://www.shadertoy.com/view/Md2BWW)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
float udQuad( vec3 p, vec3 a, vec3 b, vec3 c, vec3 d )
{
  vec3 ba = b - a; vec3 pa = p - a;
  vec3 cb = c - b; vec3 pb = p - b;
  vec3 dc = d - c; vec3 pc = p - c;
  vec3 ad = a - d; vec3 pd = p - d;
  vec3 nor = cross( ba, ad );

  return sqrt(
    (sign(dot(cross(ba,nor),pa)) +
     sign(dot(cross(cb,nor),pb)) +
     sign(dot(cross(dc,nor),pc)) +
     sign(dot(cross(ad,nor),pd))<3.0)
     ?
     min( min( min(
     dot2(ba*clamp(dot(ba,pa)/dot2(ba),0.0,1.0)-pa),
     dot2(cb*clamp(dot(cb,pb)/dot2(cb),0.0,1.0)-pb) ),
     dot2(dc*clamp(dot(dc,pc)/dot2(dc),0.0,1.0)-pc) ),
     dot2(ad*clamp(dot(ad,pd)/dot2(ad),0.0,1.0)-pd) )
     :
     dot(nor,pa)*dot(nor,pa)/dot2(nor) );
}

特效

平移

将渲染点减去移动的坐标,在调用 SDF 函数求距离场,就得到了移动过后的距离场

1
float dist_f = sdf_circle(translate(render_v2_, vec2(100.0, 100.0)), 10.0);
1
2
3
4
vec2 translate(vec2 render_v2_, vec2 move_v2_) 
{
    return render_v2_ - move_v2_;
}
旋转

向量 * 二维旋转矩阵

1
2
3
4
5
6
7
8
9
10
11
12
13
// 逆时针旋转
vec2 rotate_ccw(vec2 render_v2_, float radian_f_) 
{
    mat2 m = mat2(cos(radian_f_), sin(radian_f_), -sin(radian_f_), cos(radian_f_));
    return render_v2_ * m; 
}

// 顺时针旋转
vec2 rotate_cw(vec2 render_v2_, float radian_f_) 
{
    mat2 m = mat2(cos(radian_f_), -sin(radian_f_), sin(radian_f_), cos(radian_f_));
    return render_v2_ * m;
}
展示多个物体

?? 不理解

1
2
3
4
float merge(float dist_f_, float dist2_f_)
{
    return min(dist_f_, dist2_f_);
}
相交

在两个物体的距离场同时 < 0 时才会返回 < 0

原理就是只有两个数同时 < 0 时,max 才会返回负数

1
2
3
4
5
6
7
8
9
float intersect(float dist_f_, float dist2_f_) 
{
 // dist_f_ < 0, dist2_f_ > 0  例 dist_f_ = -2, dist2_f_ = 3,r = 3, 例 dist_f_ = -2, dist2_f_ = 1,r = 1, 则值 > 0
 // dist_f_ > 0, dist2_f_ < 0  例 dist_f_ = 2, dist2_f_ = -1,r = 2, 例 dist_f_ = 2, dist2_f_ = -5,r = 2, 则值 > 0 
 // dist_f_ > 0, dist2_f_ > 0  例 dist_f_ = 1, dist2_f_ = 2,r = 2, 例 dist_f_ = 2, dist2_f_ = 1,r = 2, 则值 > 0 
 // dist_f_ < 0, dist2_f_ < 0  例 dist_f_ = -2, dist2_f_ = -3,r = -2, 例 dist_f_ = -2, dist2_f_ = -1,r = -1, 则值 < 0
 // 所以最终结果只会在 dist_f_ 和 dist2_f_ 重合时展示
 return max(dist_f_, dist2_f_);
}
融合

有点像 metaball 的效果

只有 k_f_ > abs(dist_f_ - dist2f) 时才会对结果进行操作,如果传入的 dist_f_ 和 dist2f 结果相差不大,那么就会小于 k_f_ ,从而让两个物体的中间位置返回的值更大

1
2
3
4
5
6
7
8
9
float smooth_merge(float dist_f_, float dist2_f_, float k_f_) {
 // k_f_ 如果不超过 abs(dist_f_ - dist2_f_),那么都是无效值(0 或 1)
    float h_f = clamp(0.5 + 0.5 * (dist2_f_ - dist_f_) / k_f_, 0.0, 1.0);
 // 假设 k_f_ = 0, dist_f_ = 2, dist2_f_ = 1,则 h_f = 0, mix(...) = dist2_f_, k_f_ * h_f * (1.0 - h_f) = 0,结果为 dist2_f_
 // 假设 k_f_ = 0, dist_f_ = 1, dist2_f_ = 2,则 h_f = 1, mix(...) = dist_f_, k_f_ * h_f * (1.0 - h_f) = 0,结果为 dist_f_
 // 如果 k_f_  为无效值,那么返回结果将 = min(dist_f_, dist2_f_),和 merge 结果相同
 // 如果 k_f_ 为有效值,那么将返回比 min(dist_f_, dist2_f_) 还要小的值,k_f_  越大,结果越小
    return mix(dist2_f_, dist_f_, h_f) - k_f_ * h_f * (1.0 - h_f);
}
抵消

将 dist_f_ < 0 && dist2f < 0 的值变成 > 0 的值,这样就会得到在物体外部,也就是一个正数,从而实现抵消效果

1
2
3
4
5
6
7
8
float merge_exclude(float dist_f_, float dist2_f_) {
 // 如果 dist_f_ < 0,dist2_f_ > 0  例 dist_f_ = -2  dist2_f_ = 6, r = -2, 例 dist_f_ = -2  dist2_f_ = 3, r = -2
 // 如果 dist_f_ > 0,dist2_f_ < 0  例 dist_f_ = 2  dist2_f_ = -6, r = -6, 例 dist_f_ = -2  dist2_f_ = 3, r = -2
 // 如果 dist_f_ > 0,dist2_f_ > 0  例 dist_f_ = 2  dist2_f_ = 6, r = 2, 例 dist_f_ = 5  dist2_f_ = 3, r = 3
 // 如果 dist_f_ < 0,dist2_f_ < 0  例 dist_f_ = -2  dist2_f_ = -3, r = 4, 例 dist_f_ = -3  dist2_f_ = -2, r = 4
 // 所以最终结果只会将 dist_f_ < 0 && dist2_f_ < 0 的值变成 > 0 的值
 return min(max(-dist_f_, dist2_f_), max(-dist2_f_, dist_f_));
}
相减

只有 dist_f_ > 0 && dist2f < 0 返回的值 < 0,而其他条件结果都 > 0

  • dist_f_ > 0,dist2f < 0 返回 < 0 代表了渲染点不在第一个物体内且在第二个物体内才展示
  • 而 dist_f_ > 0, dist2f < 0 返回 > 0 就代表了渲染点同时在两个物体内,也就是抵消效果
1
2
3
4
5
6
7
8
float substract(float dist_f_, float dist2_f_) {
 // dist_f_ < 0, dist2_f_ > 0  例 dist_f_ = -2, dist2_f_ = 3,r = 3, 例 dist_f_ = -2, dist2_f_ = 1,r = 2, 则值 > 0
 // dist_f_ > 0, dist2_f_ < 0  例 dist_f_ = 2, dist2_f_ = -1,r = -1, 例 dist_f_ = 2, dist2_f_ = -5,r = -2, 则值 < 0 
 // dist_f_ > 0, dist2_f_ > 0  例 dist_f_ = 1, dist2_f_ = 2,r = 2, 例 dist_f_ = 2, dist2_f_ = 1,r = 1, 则值 > 0 
 // dist_f_ < 0, dist2_f_ < 0  例 dist_f_ = -2, dist2_f_ = -3,r = 4, 例 dist_f_ = -2, dist2_f_ = -1,r = 4, 则值 > 0
 // 所以最终结果只会展示 dist2_f_, 且 dist_f_ 和 dist2_f_ 重合时不会展示
 return max(-dist_f_, dist2_f_);
}
描边

dist_f 的有效值是 (0~ 1.0 + width_f),所以会在此范围内通过 clamp - clamp 返回一个负数,abs 将其转换为正数,再通过 mix 混合,就得到了物体边缘的混合颜色。

  • output_v4:片段着色器输出的颜色
  • float dist_f:距离场
  • vec4 color_v4:描边颜色
  • float width_f:描边宽度
1
output_v4 = mix(output_v4, color_v4, abs(clamp(dist_f - width_f, 0.0, 1.0) - clamp(dist_f, 0.0, 1.0)));
外发光

dist_f_ 的有效值范围是( 0 ~ radius )。

  • 如果 dist_f_ > radius_f_ :

a_f = 0;

b_f = min(max(0.0, dist_f_), 0) = 0;

返回值就为 color_v4_,此时为无效值。

  • 如果 dist_f_ < 0 :

a_f = 1;

b_f = min(max(0.0, dist_f_), 1) = 0;

返回值就为 color_v4_,此时为无效值。

  • float dist_f:距离场
  • vec4 color_v4_:渲染点的颜色
  • vec4 input_color_v4_:外发光颜色
  • float radius_f_:外发光半径
1
2
3
4
5
6
7
8
9
10
vec4 outer_glow(float dist_f_, vec4 color_v4_, vec4 input_color_v4_, float radius_f_) {
    // dist_f_ > radius_f_ 结果为 0
    // dist_f_ < 0 结果为 1
    // dist_f_ > 0 && dist_f_ < radius_f_ 则 dist_f_ 越大 a_f 越小,范围 0 ~ 1
    float a_f = abs(clamp(dist_f_ / radius_f_, 0.0, 1.0) - 1.0);
    // pow:平滑 a_f
    // max and min:防止在物体内部渲染
    float b_f = min(max(0.0, dist_f_), pow(a_f, 5.0));
    return color_v4_ + input_color_v4_ * b_f;
}
内发光
  • 如果 (dist_f_ + radius_f_) > radius_f_ :

a_f = 1.0;

b_f = 1.0 - max(1.0, -2.0) = 0;

返回值就为 color_v4_,此时为无效值。

  • 如果 (dist_f_ + radius_f_) < 0 :

a_f = 0.0;

b_f = 1.0 - max(1.0, 1.0) = 0;

返回值就为 color_v4_,此时为无效值。

由于 dist_f 越往物体内部越小,所以也会导致 a_f 也是也是如此,所以最后 1.0 - max。

1
2
3
4
5
6
7
8
9
10
11
vec4 inner_glow(float dist_f_, vec4 color_v4_, vec4 input_color_v4_, float radius_f_) {
    // (dist_f_ + radius_f_) > radius_f_ 结果为1
    // (dist_f_ + radius_f_) < 0 结果为0
    // (dist_f_ + radius_f_) > 0 && (dist_f_ + radius_f_) < radius_f_ 则 dist_f_ 越大 a_f 越大,范围 0 ~ 1
    float a_f = clamp((dist_f_ + radius_f_) / radius_f_, 0.0, 1.0);
    // pow:平滑 a_f
    // 1.0+:在物体内渲染
    // max(1.0, sign(dist_f_) * -:dist_f_ < 0 时返回 -1,dist_f_ == 0 返回 0,dist_f_ > 0 返回 1,所以有效值只在物体内部
    float b_f = 1.0 - max(1.0, sign(dist_f_) * -(1.0 + pow(a_f, 5.0)));
  return color_v4_ + input_color_v4_ * b_f;
}

阴影

硬阴影

从渲染点出发到光源点,依次步进安全距离(SDF 距离场,代表这个范围不会触碰到物体),如果距离场 < 0,则代表碰到了物体,返回 0,再把我们的光源的 color *= 返回值,就得到了阴影。

  • vec2 render_v2_ 渲染点
  • vec2 light_v2_ 光源点

图1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
float shadow(vec2 render_v2_, vec2 light_v2_) {
  // 当前渲染位置到光源位置的方向向量
    vec2 render_to_light_dir_v2 = normalize(light_v2_ - render_v2_);
  // 渲染位置至光源位置距离
    float render_to_light_dist_f = length(render_v2_ - light_v2_);
  // 行走距离
    float travel_dist_f = 0.01;

    for (int k_i = 0; k_i < max_shadow_step; ++k_i) {    
      // 渲染点到场景的距离
      float dist_f = scene_dist(render_v2_ + render_to_light_dir_v2 * travel_dist_f);
      // 小于0表示在物体内部
      if (dist_f < 0.0) {
        return 0.0;
      }
      // abs:避免往回走
      // max 避免渲染点距离物理表面过近导致极小耗尽遍历次数,所以有可能会跳过物体距离小于1.0的阴影绘制
      travel_dist_f += max(1.0, abs(dist_f));
      // travel_dist_f += abs(dist_f); 精确的阴影

      // 渲染点的距离超过光源点
      if (travel_dist_f > render_to_light_dist_f) {
        return 1.0;
      }
    }
    return 0.0;
  }

软阴影

原理是:从渲染点出发到光源点,依次步进安全距离(SDF距离场,代表这个范围不会触碰到物体),如果距离场 < -hard_f_ 则返回 0,为什么是 -hard_f_ ,因为我们要用物体表面往内 hard_f_ 的距离来绘制阴影,这样软阴影就可以过渡到硬阴影的范围内,看起来更真实

相较硬阴影,软阴影更加有真实感。目前我了解的 SDF 实现软阴影目前大概是两种,一种是 iq 大神和 games202 里面提到的公式,但是效果并不好,在靠近物体时会产生弯曲的软阴影(上图1);而本文将参考 Shadertoy 上另一位大神的代码去实现,效果非常好(上图2)。

图2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
float shadow(vec2 render_v2_, vec2 light_v2_, float hard_f_) {
  // 当前渲染位置到光源位置的方向向量
  vec2 render_to_light_dir_v2 = normalize(light_v2_ - render_v2_);
  // 渲染位置至光源位置距离
  float render_to_light_dist_f = length(render_v2_ - light_v2_);
  // 可见光的一部分,从一个半径开始(最后添加下半部分);
  float brightness_f = hard_f_ * render_to_light_dist_f;
  // 行走距离
  float travel_dist_f = 0.01;

  for (int k_i = 0; k_i < max_shadow_step; ++k_i) {    
  // 当前位置到场景的距离
  float dist_f = scene_dist(render_v2_ + render_to_light_dir_v2 * travel_dist_f);

  // 渲染点在物体内部
  if (dist_f < -hard_f_) {
   return 0.0;
  }
    
  // dist_f 不变,brightness_f 越小,在越靠近光源和物体时 brightness_f 越小
  brightness_f = min(brightness_f, dist_f / travel_dist_f);

  // max 避免渲染点距离物理表面过近导致极小耗尽遍历次数,所以有可能会跳过物体距离小于1.0的阴影绘制
  // abs 避免朝回走
  travel_dist_f += max(1.0, abs(dist_f));

  // 渲染点的距离超过光源点
  if (travel_dist_f > render_to_light_dist_f) {
   break;
  }
 }

 // brightness_f * render_to_light_dist_f 根据距离平滑, 离光源越近越小,消除波纹线
 // 放大阴影,hard_f 越大结果越小则阴影越大, hard_f_ / (2.0 * hard_f_) 使结果趋近于0.5,用于平滑过渡
 brightness_f = clamp((brightness_f * render_to_light_dist_f + hard_f_) / (2.0 * hard_f_), 0.0, 1.0);
 brightness_f = smoothstep(0.0, 1.0, brightness_f);
 return brightness_f;
}

Ref

  • 阴影: https://mp.weixin.qq.com/s/hSEs3k8PRYyQrfEVhd5KHA
  • 2D几何: https://iquilezles.org/articles/distfunctions2d/
  • 3D几何:https://iquilezles.org/articles/distfunctions/
  • 软硬阴影:https://www.ronja-tutorials.com/post/037-2d-shadows/
本文由作者按照 CC BY 4.0 进行授权