作者:iquilezles
引言
3D SDF函数的文章相当受欢迎,因此我决定为2D基本图形写一篇类似的文章,因为大多数3D基本图形都是通过这些2D形状的拉伸或旋转生成的。所以,正确处理这些2D图形非常重要。特别地,下面的函数使用了尽可能少的平方根和除法运算,并且相比通过其他基本图形构建(在可能的情况下),能够产生更好/更快的结果。
请注意,在这里列出的所有基本图形都可以实时预览与修改。它们引用该链接上的Shadertoy示例,所以不要错过。
最后,如同本网站上的所有内容一样,所有的公式和代码都是由我自己推导得出的(除非另有说明),因此可能存在错误或更好的实现方法。如果你认为有这种情况,请告诉我。
基本图形
所有基本图形都以原点为中心;你需要变换点来获得任意旋转、平移和缩放的目标图形对象(见下文)。dot2(v)函数返回一个向量与自身的点积(或其长度的平方)。
Circle:
float sdCircle( vec2 p, float r )
{
return length(p) - r;
}
float sdCircle( in vec2 p, in float r )
{
return length(p)-r;
}
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 p = (2.0*fragCoord-iResolution.xy)/iResolution.y;
vec2 m = (2.0*iMouse.xy-iResolution.xy)/iResolution.y;
float d = sdCircle(p,0.5);
// coloring
vec3 col = (d>0.0) ? vec3(0.9,0.6,0.3) : vec3(0.65,0.85,1.0);
col *= 1.0 - exp(-6.0*abs(d));
col *= 0.8 + 0.2*cos(150.0*d);
col = mix( col, vec3(1.0), 1.0-smoothstep(0.0,0.01,abs(d)) );
if( iMouse.z>0.001 )
{
d = sdCircle(m,0.5);
col = mix(col, vec3(1.0,1.0,0.0), 1.0-smoothstep(0.0, 0.005, abs(length(p-m)-abs(d))-0.0025));
col = mix(col, vec3(1.0,1.0,0.0), 1.0-smoothstep(0.0, 0.005, length(p-m)-0.015));
}
fragColor = vec4(col,1.0);
}
Rounded Box:
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;
}
// b.x = half width
// b.y = half height
// r.x = roundness top-right
// r.y = roundness boottom-right
// r.z = roundness top-left
// r.w = roundness bottom-left
float sdRoundBox( 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;
}
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 p = (2.0*fragCoord-iResolution.xy)/iResolution.y;
vec2 m = (2.0*iMouse.xy-iResolution.xy)/iResolution.y;
vec2 si = vec2(0.9,0.6) + 0.3*cos(iTime+vec2(0,2));
vec4 ra = 0.3 + 0.3*cos( 2.0*iTime + vec4(0,1,2,3) );
ra = min(ra,min(si.x,si.y));
float d = sdRoundBox( p, si, ra );
vec3 col = (d>0.0) ? vec3(0.9,0.6,0.3) : vec3(0.65,0.85,1.0);
col *= 1.0 - exp(-6.0*abs(d));
col *= 0.8 + 0.2*cos(150.0*d);
col = mix( col, vec3(1.0), 1.0-smoothstep(0.0,0.01,abs(d)) );
if( iMouse.z>0.001 )
{
d = sdRoundBox(m, si, ra );
col = mix(col, vec3(1.0,1.0,0.0), 1.0-smoothstep(0.0, 0.005, abs(length(p-m)-abs(d))-0.0025));
col = mix(col, vec3(1.0,1.0,0.0), 1.0-smoothstep(0.0, 0.005, length(p-m)-0.015));
}
fragColor = vec4(col,1.0);
}
Box:
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:
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);
}
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*0.5,th);
return length(max(q,0.0)) + min(max(q.x,q.y),0.0);
}
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
// normalized pixel coordinates
vec2 p = (2.0*fragCoord-iResolution.xy)/iResolution.y;
vec2 m = (2.0*iMouse.xy-iResolution.xy)/iResolution.y;
p *= 1.4;
m *= 1.4;
// animation
vec2 v1 = cos( iTime*0.5 + vec2(0.0,1.00) + 0.0 );
vec2 v2 = cos( iTime*0.5 + vec2(0.0,3.00) + 1.5 );
float th = 0.3*(0.5+0.5*cos(iTime*1.1+1.0));
float d = sdOrientedBox( p, v1, v2, th );
// distance
vec3 col = vec3(1.0) - sign(d)*vec3(0.1,0.4,0.7);
col *= 1.0 - exp(-6.0*abs(d));
col *= 0.8 + 0.2*cos(120.0*d);
col = mix( col, vec3(1.0), 1.0-smoothstep(0.0,0.015,abs(d)) );
if( iMouse.z>0.001 )
{
d = sdOrientedBox(m, v1, v2, th);
col = mix(col, vec3(1.0,1.0,0.0), 1.0-smoothstep(0.0, 0.005, abs(length(p-m)-abs(d))-0.0025));
col = mix(col, vec3(1.0,1.0,0.0), 1.0-smoothstep(0.0, 0.005, length(p-m)-0.015));
}
fragColor = vec4(col, 1.0);
}
Segment:
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 );
}
float udSegment( in vec2 p, in vec2 a, in vec2 b )
{
vec2 ba = b-a;
vec2 pa = p-a;
float h =clamp( dot(pa,ba)/dot(ba,ba), 0.0, 1.0 );
return length(pa-h*ba);
}
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 p = (2.0*fragCoord-iResolution.xy)/iResolution.y;
vec2 m = (2.0*iMouse.xy-iResolution.xy)/iResolution.y;
p *= 1.4;
m *= 1.4;
vec2 v1 = cos( iTime*0.5 + vec2(0.0,1.00) + 0.0 );
vec2 v2 = cos( iTime*0.5 + vec2(0.0,3.00) + 1.5 );
float th = 0.3*(0.5+0.5*cos(iTime*1.1+1.0));
float d = udSegment( p, v1, v2 ) - th;
vec3 col = vec3(1.0) - sign(d)*vec3(0.1,0.4,0.7);
col *= 1.0 - exp(-3.0*abs(d));
col *= 0.8 + 0.2*cos(120.0*d);
col = mix( col, vec3(1.0), 1.0-smoothstep(0.0,0.015,abs(d)) );
if( iMouse.z>0.001 )
{
d = udSegment(m, v1, v2) - th;
col = mix(col, vec3(1.0,1.0,0.0), 1.0-smoothstep(0.0, 0.005, abs(length(p-m)-abs(d))-0.0025));
col = mix(col, vec3(1.0,1.0,0.0), 1.0-smoothstep(0.0, 0.005, length(p-m)-0.015));
}
fragColor = vec4(col,1.0);
}
Rhombus:
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 );
}
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 );
}
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 p = (2.0*fragCoord-iResolution.xy)/iResolution.y;
vec2 m = (2.0*iMouse.xy-iResolution.xy)/iResolution.y;
vec2 ra = 0.4 + 0.3*cos( iTime + vec2(0.0,1.57) + 0.0 );
float d = sdRhombus( p, ra );
vec3 col = vec3(1.0) - sign(d)*vec3(0.1,0.4,0.7);
col *= 1.0 - exp(-2.0*abs(d));
col *= 0.8 + 0.2*cos(140.0*d);
col = mix( col, vec3(1.0), 1.0-smoothstep(0.0,0.02,abs(d)) );
if( iMouse.z>0.001 )
{
d = sdRhombus(m, ra);
col = mix(col, vec3(1.0,1.0,0.0), 1.0-smoothstep(0.0, 0.005, abs(length(p-m)-abs(d))-0.0025));
col = mix(col, vec3(1.0,1.0,0.0), 1.0-smoothstep(0.0, 0.005, length(p-m)-0.015));
}
fragColor = vec4(col,1.0);
}
Isosceles Trapezoid:
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)) );
}
float dot2(in vec2 v ) { return dot(v,v); }
// trapezoid / capped cone, specialized for Y alignment
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(max(0.0,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)) );
}
// trapezoid / capped cone
float sdTrapezoid( in vec2 p, in vec2 a, in vec2 b, in 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) );
}
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 p = (2.0*fragCoord-iResolution.xy)/iResolution.y;
vec2 m = (2.0*iMouse.xy-iResolution.xy)/iResolution.y;
float ra = 0.2+0.15*sin(iTime*1.3+0.0);
float rb = 0.2+0.15*sin(iTime*1.4+1.1);
vec2 pa = vec2(-0.6,0.0)+0.4*sin(iTime*1.1+vec2(0.0,2.0));
vec2 pb = vec2(-0.6,0.0)+0.4*sin(iTime*1.2+vec2(1.0,2.5));
vec2 pc = vec2(0.8,0.0);
// axis aligned trapezoid
float d = sdTrapezoid( p-pc, ra, rb, 0.5+0.2*sin(1.3*iTime) );
// aribitrary trapezoid
d = min( d, sdTrapezoid( p, pa, pb , ra, rb ) );
vec3 col = vec3(1.0) - sign(d)*vec3(0.1,0.4,0.7);
col *= 1.0 - exp(-4.0*abs(d));
col *= 0.8 + 0.2*cos(140.0*d);
col = mix( col, vec3(1.0), 1.0-smoothstep(0.0,0.015,abs(d)) );
col = mix( col, vec3(1.0), 1.0-smoothstep(0.0,0.005,abs(d)) );
if( iMouse.z>0.001 )
{
d = sdTrapezoid( m, pa, pb , ra, rb );
d = min( d, sdTrapezoid( m-pc, ra, rb, 0.5+0.2*sin(1.3*iTime) ) );
col = mix(col, vec3(1.0,1.0,0.0), 1.0-smoothstep(0.0, 0.005, abs(length(p-m)-abs(d))-0.0025));
col = mix(col, vec3(1.0,1.0,0.0), 1.0-smoothstep(0.0, 0.005, length(p-m)-0.015));
}
fragColor = vec4(col,1.0);
}
Parallelogram:
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);
}
// Distance to a parallelogram, I implemented three methods:
// Method 1: computed by two edges, by symmetry, single square root
// Method 2: computed by interior/exterior, optimization of Pentan's idea
// Method 3: computed by zones
#define METHOD 1
// List of some other 2D distances: https://www.shadertoy.com/playlist/MXdSRf
//
// and iquilezles.org/articles/distfunctions2d
// signed distance to a 2D parallelogram (width, height, skew)
float sdParallelogram_1( in vec2 p, float wi, float he, float sk )
{
vec2 e = vec2(sk,he);
float e2 = sk*sk + he*he;
p = (p.y<0.0)?-p:p;
// horizontal edge
vec2 w = p - e; w.x -= clamp(w.x,-wi,wi);
vec2 d = vec2(dot(w,w), -w.y);
// vertical edge
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)/e2,-1.0,1.0);
d = min( d, vec2(dot(v,v), wi*he-abs(s)));
return sqrt(d.x)*sign(-d.y);
}
float sdParallelogram_2( in vec2 p, float wi, float he, float sk )
{
vec2 e = vec2(sk,he);
float e2 = sk*sk + he*he;
float da = abs(p.x*e.y-p.y*e.x)-wi*he;
float db = abs(p.y)-e.y;
if( max(da,db)<0.0 ) // interior
{
return max( da*inversesqrt(e2), db );
}
else // exterior
{
float f = clamp(p.y/e.y,-1.0,1.0);
float g = clamp(p.x-e.x*f, -wi, wi);
float h = clamp(((p.x-g)*e.x+p.y*e.y)/e2,-1.0,1.0);
return length(p-vec2(g+e.x*h,e.y*h));
}
}
float sdParallelogram_3( in vec2 p, float wi, float he, float sk )
{
// above
float db = abs(p.y)-he;
if( db>0.0 && abs(p.x-sk*sign(p.y))<wi )
return db;
// inside
float e2 = sk*sk + he*he;
float h = p.x*he - p.y*sk;
float da = (abs(h)-wi*he)*inversesqrt(e2);
if( da<0.0 && db<0.0 )
return max( da, db );
// sides
vec2 q = (h<0.0)?-p:p; q.x -= wi;
float v = abs(q.x*sk+q.y*he);
if( v<e2 )
return da;
// exterior
return sqrt( dot(q,q)+e2-2.0*v );
}
float sdParallelogram( in vec2 p, float wi, float he, float sk )
{
#if METHOD==1
return sdParallelogram_1(p,wi,he,sk);
#endif
#if METHOD==2
return sdParallelogram_2(p,wi,he,sk);
#endif
#if METHOD==3
return sdParallelogram_3(p,wi,he,sk);
#endif
}
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 p = (2.0*fragCoord-iResolution.xy)/iResolution.y;
vec2 m = (2.0*iMouse.xy-iResolution.xy)/iResolution.y;
// animate
float sk = 0.5*sin(iTime);
float ra = 0.0;//0.1 + 0.1*sin(1.3*iTime);
//p.x -= sk; // enable to lock base in place
// distance
float d = sdParallelogram(p,0.4,0.6,sk)-ra;
// colorize
vec3 col = (d>0.0) ? vec3(0.9,0.6,0.3) : vec3(0.65,0.85,1.0);
col *= 1.0 - exp(-6.0*abs(d));
col *= 0.8 + 0.2*cos(120.0*d);
col = mix( col, vec3(1.0), 1.0-smoothstep(0.0,0.01,abs(d)) );
if( iMouse.z>0.001 )
{
d = sdParallelogram(m,0.4,0.6,sk)-ra;
col = mix(col, vec3(1.0,1.0,0.0), 1.0-smoothstep(0.0, 0.005, abs(length(p-m)-abs(d))-0.0025));
col = mix(col, vec3(1.0,1.0,0.0), 1.0-smoothstep(0.0, 0.005, length(p-m)-0.015));
}
fragColor = vec4(col,1.0);
}
Equilateral Triangle:
float sdEquilateralTriangle( in vec2 p, in float r )
{
const float k = sqrt(3.0);
p.x = abs(p.x) - r;
p.y = p.y + r/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*r, 0.0 );
return -length(p)*sign(p.y);
}
// signed distance to an equilateral triangle
float sdEquilateralTriangle( in vec2 p, in float r )
{
const float k = sqrt(3.0);
#if 0
// form 1
p.x = abs(p.x) - r;
p.y = p.y + r/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*r, 0.0 );
return -length(p)*sign(p.y);
#else
// form 2
p.x = abs(p.x);
p -= vec2(0.5,0.5*k)*max(p.x+k*p.y,0.0);
p.x = p.x - clamp(p.x,-r,r);
p.y = -p.y - r*(1.0/k);
return length(p)*sign(p.y);
#endif
}
// not an SDF
float fEquilateralTriangle( in vec2 p, in float r )
{
const float k = sqrt(3.0);
return -p.y + 0.5*k*max(abs(p.x)+k*p.y,0.0) - r*(1.0/k);
}
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 p = (2.0*fragCoord.xy-iResolution.xy)/iResolution.y;
vec2 m = (2.0*iMouse.xy-iResolution.xy)/iResolution.y;
p *= 2.0;
m *= 2.0;
float d = sdEquilateralTriangle( p, 1.0 );
vec3 col = (d>0.0) ? vec3(0.9,0.6,0.3) : vec3(0.65,0.85,1.0);
col *= 1.0 - exp(-4.0*abs(d));
col *= 0.8 + 0.2*cos(60.0*d);
col = mix( col, vec3(1.0), 1.0-smoothstep(0.0,0.02,abs(d)) );
if( iMouse.z>0.001 )
{
d = sdEquilateralTriangle( m, 1.0 );
col = mix(col, vec3(1.0,1.0,0.0), 1.0-smoothstep(0.0, 0.01, abs(length(p-m)-abs(d))-0.005));
col = mix(col, vec3(1.0,1.0,0.0), 1.0-smoothstep(0.0, 0.01, length(p-m)-0.03));
}
fragColor = vec4(col,1.0);
}
Isosceles Triangle:
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);
}
// signed distance to a 2D triangle
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 k = sign( q.y );
float d = min(dot( a, a ),dot(b, b));
float s = max( k*(p.x*q.y-p.y*q.x),k*(p.y-q.y) );
return sqrt(d)*sign(s);
}
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 p = (2.0*fragCoord-iResolution.xy)/iResolution.y;
vec2 m = (2.0*iMouse.xy-iResolution.xy)/iResolution.y;
p *= 1.1;
m *= 1.1;
vec2 tri = vec2(0.3,-1.1); // width, height
float d = sdTriangleIsosceles( p-vec2(0.0,0.5), tri );
vec3 col = vec3(1.0) - sign(d)*vec3(0.1,0.4,0.7);
col *= 1.0 - exp(-2.0*abs(d));
col *= 0.8 + 0.2*cos(140.0*d);
col = mix( col, vec3(1.0), 1.0-smoothstep(0.0,0.02,abs(d)) );
if( iMouse.z>0.001 )
{
d = sdTriangleIsosceles(m-vec2(0.0,0.5), tri );
col = mix(col, vec3(1.0,1.0,0.0), 1.0-smoothstep(0.0, 0.005, abs(length(p-m)-abs(d))-0.0025));
col = mix(col, vec3(1.0,1.0,0.0), 1.0-smoothstep(0.0, 0.005, length(p-m)-0.015));
}
fragColor = vec4(col*1.2,1.0);
}
Triangle:
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);
}
// Other triangle functions:
//
// Distance: https://www.shadertoy.com/view/XsXSz4
// Gradient: https://www.shadertoy.com/view/tlVyWh
// Boundaries: https://www.shadertoy.com/view/tlKcDz
// signed distance to a 2D triangle
float dot2( in vec2 v ) { return dot(v,v); }
float sdTriangle( in vec2 p, in vec2 p0, in vec2 p1, in vec2 p2 )
{
vec2 e0=p1-p0, v0=p-p0; float d0=dot2(v0-e0*clamp(dot(v0,e0)/dot(e0,e0),0.0,1.0));
vec2 e1=p2-p1, v1=p-p1; float d1=dot2(v1-e1*clamp(dot(v1,e1)/dot(e1,e1),0.0,1.0));
vec2 e2=p0-p2, v2=p-p2; float d2=dot2(v2-e2*clamp(dot(v2,e2)/dot(e2,e2),0.0,1.0));
float o = e0.x*e2.y-e0.y*e2.x;
vec2 d = min(min(vec2(d0,o*(v0.x*e0.y-v0.y*e0.x)),
vec2(d1,o*(v1.x*e1.y-v1.y*e1.x))),
vec2(d2,o*(v2.x*e2.y-v2.y*e2.x)));
return -sqrt(d.x)*sign(d.y);
}
//---- everything below this line is NOT the triangle formula, and it's there just to produce the picture ---
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 p = (2.0*fragCoord.xy-iResolution.xy)/iResolution.y;
vec2 m = (2.0*iMouse.xy-iResolution.xy)/iResolution.y;
p *= 1.5;
m *= 1.5;
// animate
vec2 v1 = vec2(1.4,1.0)*cos( iTime + vec2(0.0,2.00) + 0.0 );
vec2 v2 = vec2(1.4,1.0)*cos( iTime + vec2(0.0,1.50) + 1.5 );
vec2 v3 = vec2(1.4,1.0)*cos( iTime + vec2(0.0,3.00) + 4.0 );
// distance
float d = sdTriangle( p, v1, v2, v3 );
// color
#if 1
vec3 col = vec3(1.0) - sign(d)*vec3(0.1,0.4,0.7);
col *= 1.0 - exp(-2.0*abs(d));
col *= 0.8 + 0.2*cos(120.0*d);
col = mix( col, vec3(1.0), 1.0-smoothstep(0.0,0.02,abs(d)) );
#else
vec3 col = (d<0.0) ? vec3(0.2,0.8,0.8) : vec3(0.8,0.2,0.8);
col *= exp2(-2.0*abs(d));
col = mix(col,vec3(1.20),exp2(-22.0*abs(d)));
col = mix( col, vec3(0.0), 1.0-smoothstep(0.01,0.02,abs(d)) );
col *= 1.0 + 0.5*smoothstep(-0.4,0.4,cos(100.0*d));
#endif
// interactivity
if( iMouse.z>0.001 )
{
d = sdTriangle(m, v1, v2, v3 );
col = mix(col, vec3(1.0,1.0,0.0), 1.0-smoothstep(0.0, 0.005, abs(length(p-m)-abs(d))-0.0025));
col = mix(col, vec3(1.0,1.0,0.0), 1.0-smoothstep(0.0, 0.005, length(p-m)-0.015));
}
fragColor = vec4(col,1.0);
}