WebGL
GLSL

2次元ディスタンスフィールドまとめ

ディスタンスフィールド(距離関数)とは

相手との距離を返す関数です。
glslのフラグメントシェーダーでシェイプを描くのに利用されます。
この関数単体では図形になりませんが、相手との距離を設定することで図形になります。

まずは基本のlength3兄弟

円(length)

length.png
https://goo.gl/KxQ9Rq

float l=length(p);

シンプルに、相手との距離を測る関数lengthを使うだけです。もちろん、それは円状になりますね。これがすべての基本になります。

後述のlengthN関数の中ではlength2に相当します。

length(p)と同じように円を描く距離関数にdot(p,p)がありますが、

dot.png
https://goo.gl/ZBLHrm

float l=dot(p,p);

こちらは、半径1の円を描くときには同じですが、等間隔ではないので注意が必要です。
球状の塗りをする場合にはこちらを使います。length(p)の場合は円錐状の塗りになります。
length(p)よりも軽いという特徴があります。ベタ塗りだったらdotの方が良いです。

正方形

45度回転バージョンと通常バージョンの2パターンあります。

lengthを使っていませんが、ようは、X軸、Y軸に沿って距離を図るということですね。

  • まずは通常バージョン

square.png

https://goo.gl/VMcZwS

precision mediump float;
uniform vec2  m;       // mouse
uniform float t;       // time
uniform vec2  r;       // resolution
uniform sampler2D smp; // prev scene

void main(void){
    vec2 p = (gl_FragCoord.xy * 2.0 - r) / min(r.x, r.y);
    float l=max(abs(p.x),abs(p.y));
    gl_FragColor = vec4(vec3(l), 1.0);
}

これは後述のlengthN関数の中ではlengthInfに相当します。
Nを無限にした場合ですね。

  • 45度バージョン

square2.png
https://goo.gl/sKx4FS

precision mediump float;
uniform vec2  m;       // mouse
uniform float t;       // time
uniform vec2  r;       // resolution
uniform sampler2D smp; // prev scene

void main(void){
    vec2 p = (gl_FragCoord.xy * 2.0 - r) / min(r.x, r.y);
    float l=abs(p.x)+abs(p.y);
    gl_FragColor = vec4(vec3(l), 1.0);
}

これは後述のlengthN関数の中ではlength1に相当します。

円と正方形の間 lengthN

乗数Nが増えると円の形状から段々正方形に近くなります。

尚、角丸正方形とは異なり、全体的に丸くなります。

  • N<1 星型
  • N=1 45度の正方形
  • N=2 円
  • N>2 円から正方形に近づく

イラストレーター的には、パンクと膨張です。

N=1で45度の正方形

square2.png
https://goo.gl/2rgudQ

precision mediump float;
uniform vec2  m;       // mouse
uniform float t;       // time
uniform vec2  r;       // resolution
uniform sampler2D smp; // prev scene

float lengthN(vec2 v, float n){
  vec2 tmp = pow(abs(v), vec2(n));
  return pow(tmp.x+tmp.y, 1.0/n);
}

void main(void){
    vec2 p = (gl_FragCoord.xy * 2.0 - r) / min(r.x, r.y);
    float l=lengthN(p,1.);
    gl_FragColor = vec4(vec3(l), 1.0);
}

N=2で円

length2.png
https://goo.gl/xKEh3B

つまりはlength関数と一緒です。length2 = length

Nが2より大きい(6)とNが大きいほど正方形に近づく

length6.png
https://goo.gl/tzkopj

    float l=lengthN(p,6.);

Nが1より小さい(.5)とアステロイド

length05.png
https://goo.gl/NpGq49

    float l=lengthN(p,.5);

Nが無限(Infinite)だとと正方形

square.png

といっても

    float l=lengthN(p,1./0.);

とかして、無限を入れるとシェーダーがエラーも吐かず動作しない可能性が高いので辞めましょうね。※ 環境によっては動作するかもしれません。

    float l=lengthN(p,1e20);

代わりに無限に近い巨大な数1e20 (= 10^20 = 一垓)と入れるとほぼ正方形になります。
まぁ、計算量が無駄ですので、素直に、正方形の描き方の方を使いましょう。

尚powは重いのでNの数が決まっているのであれば専用のlength6やlength8を作って使っているパターンもあります。

float length6( vec2 p ){
    p = p*p*p; p = p*p;
    return pow( p.x + p.y, 1.0/6.0 );
}

float length8( vec2 p ){
    p = p*p; p = p*p; p = p*p;
    return pow( p.x + p.y, 1.0/8.0 );
}

多角形

多角形

l3poly.png

https://goo.gl/AdfFVm

precision mediump float;
uniform vec2  m;       // mouse
uniform float t;       // time
uniform vec2  r;       // resolution
uniform sampler2D smp; // prev scene

const float PI  = 3.141592653589793;
const float PI2 = PI * 2.;

float lPolygon(vec2 p,int n){
  float a = atan(p.x,p.y)+PI;
  float r = PI2/float(n);
  return cos(floor(.5+a/r)*r-a)*length(p);
}

void main(void){
    vec2 p = (gl_FragCoord.xy * 2.0 - r) / min(r.x, r.y);
    float l=lPolygon(p,3);
    gl_FragColor = vec4(vec3(l), 1.0);
}

多角形星

starpolygon.png

https://goo.gl/xHVUqP

precision mediump float;
uniform vec2  m;       // mouse
uniform float t;       // time
uniform vec2  r;       // resolution
uniform sampler2D smp; // prev scene

const float PI = 3.14159265359;
const float PI2 = PI *2.;

float lPolygon(vec2 p,int n){
 float a = atan(p.x,p.y)+PI;
 float r = PI2/float(n);
 return cos(floor(.5+a/r)*r-a)*length(p);
}

mat2 mRotate(float a){
 float c=cos(a);
 float s=sin(a);
 return mat2(c,-s,s,c);
}

float lStarPolygon(vec2 p,int n,float o){
 return (lPolygon(p,n)-lPolygon(p*mRotate(PI2/float(n)/2.),n)*o)/(1.-o);
}

void main(void){
    vec2 p = (gl_FragCoord.xy * 2.0 - r) / min(r.x, r.y);
    float l = lStarPolygon(p,5,.5);
    gl_FragColor = vec4(vec3(l), 1.0);
}

多角形カーブ

多角形をパンクさせたり、収縮させたような図形を描く-でパンク+で収縮

polycurve.png

https://goo.gl/NegXtW

precision mediump float;
uniform vec2  m;       // mouse
uniform float t;       // time
uniform vec2  r;       // resolution
uniform sampler2D smp; // prev scene

#define PI 3.141592653589793
#define PI2 PI * 2.

float lPolygonCurve(vec2 p,int n,float f){
 float a = atan(p.x,p.y)+PI;
 float r = PI2/float(n);
 return cos(floor(.5+a/r)*r-a)*(length(p)+f)/(1.+f);
}

void main(void){
    vec2 p = (gl_FragCoord.xy * 2.0 - r) / min(r.x, r.y);
    float l=lPolygonCurve(p,5,.5);
    gl_FragColor = vec4(vec3(l), 1.0);
}

直線

ディスタンスフィールドに(永久に続く)直線はあるが、基本的に線分(2点で区切られた直線)はない。
ので、結論としては、直線をif文で条件分けして実現するのが一番てっとり早い。
他には、テッセレーションで直線を線分に分割したり、四角形の対向する2辺を極限まで小さくする方法があるが、if文で分岐するのが一番楽でしょう。

線分の仕方もいくつあるが、直線の描画方法にもいくつかの種類がある。

横軸直線

xline.png
https://goo.gl/6qC4pN

    l=abs(p.y);
    p.x=0.;
    p*=vec2(0.,1.);
    p*=mat2(0.,0.,0.,1.);

縦軸直線

yline.png
https://goo.gl/C4y5To

    l=abs(p.x);
    p.y=0.;
    p*=vec2(1.,0.);
    p*=mat2(1.,0.,0.,0.);

任意角度直線

linerotate.png
https://goo.gl/2vbVh3

    p=vec2(p.x*cos(t)+p.y*sin(t));

任意ベクトル方向直線

vectline.png
https://goo.gl/zvTdVP

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);
}

片側直線

absline.png

任意ベクトル

片方しか要らない場合はabsを外す

https://goo.gl/zuHgCG

    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度反転した直線のディスタンスフィールドでマスク(バット先端)
  • 細長いカプセルで、太さを最小としたもの(丸形先端)
  • 細長い長方形で、太さを最小としたもの(突出先端)

バット先端

lineseg.png

https://goo.gl/oFpkgj

  • 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);
}

丸形先端

liner.png

https://goo.gl/QdMY9m

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;}

float lLineRound(vec2 p, vec2 v1, vec2 v2) {
    p  -= v1;
    vec2 v = v2-v1;
    float t = dot(p, normalize(v));
    if (t<0.0) {
        return length(p);
    } else if (t>length(v)) {
        return length(p-v);
    } else {
        return abs(cross(p, normalize(v)));
    }
}

void main(void){
    vec2 p = (gl_FragCoord.xy * 2.0 - r) / min(r.x, r.y);
    float l;
    l=lLineRound(p,vec2(-.8),vec2(.8));
    gl_FragColor = vec4(vec3(l), 1.0);
}

参考

楕円

ellipse.png
https://goo.gl/zy5mW6

precision mediump float;
uniform vec2  m;       // mouse
uniform float t;       // time
uniform vec2  r;       // resolution
uniform sampler2D smp; // prev scene

float lEllipse(vec2 p, vec2 r, float s) {
    return (length(p / r) - s);
}

void main(void){
    vec2 p = (gl_FragCoord.xy * 2.0 - r) / min(r.x, r.y);
    float l;
    l=lEllipse(p,vec2(.5,.25),1.);
    gl_FragColor = vec4(vec3(l), 1.0);
}

長方形

rect.png

https://goo.gl/fkTvUm

precision mediump float;
uniform vec2  m;       // mouse
uniform float t;       // time
uniform vec2  r;       // resolution
uniform sampler2D smp; // prev scene

float lRect(vec2 p, vec2 size) {  
  vec2 d = abs(p) - size;
  return min(max(d.x, d.y), 0.0) + length(max(d,0.0));
}

void main(void){
    vec2 p = (gl_FragCoord.xy * 2.0 - r) / min(r.x, r.y);
    float l;
    l=lRect(p,vec2(.5,.25));
    gl_FragColor = vec4(vec3(l), 1.0);
}

角丸長方形

rrect.png

https://goo.gl/Kgjpo5

precision mediump float;
uniform vec2  m;       // mouse
uniform float t;       // time
uniform vec2  r;       // resolution
uniform sampler2D smp; // prev scene

float lRoundRect(vec2 p, vec2 size, float radius) {
  vec2 d = abs(p) - size + radius;
  return min(max(d.x, d.y), 0.0) + length(max(d,0.0))- radius;
}

void main(void){
    vec2 p = (gl_FragCoord.xy * 2.0 - r) / min(r.x, r.y);
    float l;
    l=lRoundRect(p,vec2(.5,.25),.1);
    gl_FragColor = vec4(vec3(l), 1.0);
}

十字星

ltwincle.png

https://goo.gl/54RVfF

precision mediump float;
uniform vec2  m;       // mouse
uniform float t;       // time
uniform vec2  r;       // resolution
uniform sampler2D smp; // prev scene

float lTwincle(vec2 p){return abs(p.x) * abs(p.y);}

void main(void){
    vec2 p = (gl_FragCoord.xy * 2.0 - r) / min(r.x, r.y);
    float l;
    l=lTwincle(p);
    gl_FragColor = vec4(vec3(l), 1.0);
}

関連