はじめに
- 2次元ディスタンスフィールドまとめページが肥大化したので別ページとして切り出し詳細にまとめ直しました。
- 直線・線分のディスタンスフィールドは特殊で円のディスタンスフィールドに比べると複雑になっています。
- 最大の特徴としては、円や正多角形のディスタンスフィールドは原点が”点”でしたが、原点が"線"や”線分”になっています。
- 一般的に使用する線とは2点間を結ぶ『線分』のことを指しますが、『線分』のディスタンスフィールドを理解するには、まずは、永久に続く線『直線』のディスタンスフィールドを理解する必要がある為、『線分』のディスタンスフィールドから解説します。
直線
ディスタンスフィールドに(永久に続く)直線はあるが、基本的に線分(2点で区切られた直線)はない。
ので、結論としては、直線をif文で条件分けして実現するのが一番てっとり早い。
他には、テッセレーションで直線を線分に分割したり、四角形の対向する2辺を極限まで小さくする方法があるが、if文で分岐するのが一番楽でしょう。
線分の仕方もいくつあるが、直線の描画方法にもいくつかの種類がある。
横軸直線
l=abs(p.y);
p.x=0.;
p*=vec2(0.,1.);
p*=mat2(0.,0.,0.,1.);
縦軸直線
l=abs(p.x);
p.y=0.;
p*=vec2(1.,0.);
p*=mat2(1.,0.,0.,0.);
任意角度直線
p=vec2(p.x*cos(t)+p.y*sin(t));
任意ベクトル方向直線
precision mediump float;
uniform vec2 m; // mouse
uniform float t; // time
uniform vec2 r; // resolution
uniform sampler2D smp; // prev scene
float cross(vec2 v1, vec2 v2) {return v1.x*v2.y - v1.y*v2.x;}
void main(void){
vec2 p = (gl_FragCoord.xy * 2.0 - r) / min(r.x, r.y);
float l;
l=abs(cross(p,normalize(vec2(3.,4.))));
gl_FragColor = vec4(vec3(l), 1.0);
}
片側直線
任意ベクトル
片方しか要らない場合はabsを外す
l=cross(p,normalize(vec2(3.,4.)));
dotにすると90度回転、このまま使う場合もある
l=dot(p,normalize(vec2(3.,4.)));
実際に使うときはマイナスの時は NaN,Infinite or 1として使う
- NaN
l=l<0.?sqrt(l):l;
- 1
l=l<0.?1.:l;
- 1(三項演算子ではなく、stepを使ったバージョン、どっちが早い?)
l=step(l,0.) + step(0.,l)*l;
- Infinite
l=l<0.?1./0.:l;
縦
l=p.y;
横
l=p.x;
任意方向
l=p.x*cos(t)+p.y*sin(t);
線分
ディスタンスフィールドは、極座標系なので、基本的に線分は存在しない。
ので、以下の3つの方法のどれかを選択します。
イラストレーターの線の先端に相当
実装が簡単なので、glslでは丸形先端が使われることが多いですが、線分と線分の接合部分がおかしくなりがちなので、止めた方がいいです。
- 90度反転した直線のディスタンスフィールドでマスク(バット先端)
- 細長いカプセルで、太さを最小としたもの(丸形先端)
- 細長い長方形で、太さを最小としたもの(突出先端)
バット先端
- 90度回転させた直線のディタンスフィールドをもう1個使ってマスクする方法
90度回転させた直線のディタンスフィールドは外積の代わりに内積を使えばいい
precision mediump float;
uniform vec2 m; // mouse
uniform float t; // time
uniform vec2 r; // resolution
uniform sampler2D smp; // prev scene
float cross(vec2 v1, vec2 v2) {return dot(v1,vec2(v2.y,-v2.x));}
float lLine(vec2 p,vec2 v1,vec2 v2){
vec2 v = v2 - v1;
vec2 d = normalize(v); //direction
p-=v1;
return 1.-(1.-abs(cross(p,d)))*step(0.,length(v)*.5-abs(dot(p-v*.5,d)));
}
void main(void){
vec2 p = (gl_FragCoord.xy * 2.0 - r) / min(r.x, r.y);
float l;
l=lLine(p,vec2(-.8,-0.8),vec2(.8,.8));
gl_FragColor = vec4(vec3(l), 1.0);
}
- dotだけで作りif文で分岐するバージョン
precision mediump float;
uniform vec2 m; // mouse
uniform float t; // time
uniform vec2 r; // resolution
uniform sampler2D smp; // prev scene
float cross(vec2 v1, vec2 v2) {return dot(v1,vec2(v2.y,-v2.x));}
float lLine( vec2 p,vec2 v1, vec2 v2){
vec2 v = v1 - v2;
vec2 p2p = p - v2;
vec2 p1p = p - v1;
vec2 pd = normalize(vec2(v.y, -v.x));
float proj = dot(pd, -p1p);
float pr1 = dot( v, p2p);
float pr2 = dot(-v, p1p);
return (pr1 > 0.0 && pr2 > 0.0) ? abs(proj): 0.;
}
void main(void){
vec2 p = (gl_FragCoord.xy * 2.0 - r) / min(r.x, r.y);
float l;
l=lLine(p,vec2(-.8),vec2(.8));
gl_FragColor = vec4(vec3(l), 1.0);
}
丸形先端
precision mediump float;
uniform vec2 m; // mouse
uniform float t; // time
uniform vec2 r; // resolution
uniform sampler2D smp; // prev scene
const float PI = acos(-1.);
const float TAU = PI * 2.;
float cross(vec2 v1, vec2 v2) {return dot(v1,vec2(v2.y,-v2.x));}
float sumabs(vec2 p){return abs(p.x)+abs(p.y);}
float maxabs(vec2 p){return max(abs(p.x), abs(p.y));}
mat2 rot(float a){float c=cos(a),s=sin(a);return mat2(c,-s,s,c);}
float line(vec2 p, vec2 v1, vec2 v2,int cap) {
vec2 p1 = p - v1;
vec2 v = v2 - v1;
float t = dot(p1, normalize(v));
vec2 pt = (p-((t<0.)?v1:v2))*rot(atan(v.x,v.y));
return (t<0.||t>length(v))
? ((cap==1)?length(pt):(cap==2)?maxabs(pt):(cap==3)?sumabs(pt):1e10)
: abs(cross(p1, normalize(v)));
}
float line(vec2 p, vec2 v1, vec2 v2) {return line(p,v1,v2,0);}
void main(void){
vec2 p = (gl_FragCoord.xy * 2.0 - r) / min(r.x, r.y);
float l;
l=line(p,vec2(-.1,-.4),vec2(.4),1);
gl_FragColor = vec4(vec3(l), 1.0);
}
先端の形状
- cap=0 バット先端
- cap=1 丸型先端
- cap=2 突出先端
- cap=3 矢印