Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

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

はじめに

iq先生の2Dディスタンスフィールド(SDF)解説 を見ましょう。3Dの方は有名なのですが、こっち見逃してました。。。もっと早く知りたかった

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

相手との距離を返す関数です。
glslのフラグメントシェーダーでシェイプを描くのに利用されます。

ディスタンスフィールド(距離関数)の種類

0以上か、マイマスがあるのかの、符号あり、なしの2種類あるのですが、全く用途が異なります。図形を描く場合はもっぱらSDFが使われるので、だいたいSDFを指しますが、USDFじゃないとダメな場合があるので注意が必要です。

  • 符号なしディスタンスフィールド(unsigned distance function 略してUSDF?)

原点からの距離を測るものなので絶対にマイナスになりません。ボロノイの距離関数として使えます。有名なのはマンハッタン距離を使った回路みたいな模様を作るボロノイですね。USDFはなんでも使えるので、多種多様なボロノイを作ることが出来ます。また、ディスプレイスメント、極座標変換にも利用できます。length関数になっているところは、すべて置き換え可能なので、実はかなり使えます。この関数単体では図形になりませんが、相手との距離を設定する(引く)ことで図形になります。これが後述するSDFです。

  • 符号ありディスタンスフィールド(signed distance function 略してSDF)

基本的には、USDFからある距離を引いたものになりますが、図形の辺を原点としている為、必ずしもそうではありません。(原点が1つの点にならない場合がある)なので、SDFをUSDFとして戻して使うことができない場合が多々あります。

左上 USDF 右上 SDF 相互変換できる場合(原点が1つの点)
image.png
左下 USDF 右下 SDF 相互変換できない場合(原点が縦軸の直線)

符号ありディスタンスフィールドは2種類あり、境界線から、内側外側のどちらがプラスかマイナスかの二種類ある。結論から言うと、境界線より外側をプラスとするiq先生式が使いやすい。境界線より内側をプラスとする方は、図形の形をはっきり認識できるので、理解するにはこちらの方がいいが、グローとかを掛けるときに不具合が生じる。

ベクトル 距離(スカラー)
ベクトルフィールド(p) 符号なしディスタンスフィールド(length(p))
符号ありベクトルフィールド(normalize(p)-p) 符号ありディスタンスフィールド(length(p)-1)
符号ありベクトルフィールド(p-normalize(p)) 符号ありディスタンスフィールド(1.-length(p))

符号ありベクトルフィールドは、符号ありディスタンスフィールドのベクトル版、ベクトルフィールドのベクトルの方向がそのまま法線となる為、微分して法線ベクトルを計算する必要はない。またベクトルデータが保存されているため、ベクトルフィールドを多段で掛けることが出来る。符号ありディスタンスフィールドへの変換はlengthを掛けるだけ。※虚数の補正は必要

まずは基本のlength3兄弟

円(length)

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

float l=length(p);

これは、いわゆるユークリッド(EUCLID)距離です。L2距離とも呼ばれています。

l=sqrt(p.x*p.x+p.y*p.y);

シンプルに、相手との距離を測る関数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の方が良いです。

ちなみに、これはユークリッド距離のsqrtを取ったものと同じです

l=p.x*p.x+p.y*p.y;

正方形

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を無限にした場合ですね。 L∞距離とも呼ばれています。
チェビシェフ距離とも呼ばれています。
関数としてはmaxabs関数です

  • 正方形 45度バージョン L1距離

これは、マンハッタン距離です。

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に相当します。
関数としてはsumabs関数ですね

円と正方形の間 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 = 一垓)と入れるとほぼ正方形になります。
まぁ、計算量が無駄ですので、素直に、正方形の描き方の方を使いましょう。

十字

maxがN∞だったらminはN-∞

image.png

    float l=min(abs(p.x),abs(p.y));

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

これはノルム(lpnorm)です。
ミンコフスキー距離とも言われています。

ノルム(n) 図形 数学的距離名 関数名
-∞ 十字 - minabs
.5 アステロイド -
1 正方形(45度) マンハッタン距離(L1距離) sumabs
2 ユークリッド距離(L2距離) length
正方形 チェビシェフ距離 maxabs
n - ミンコフスキー距離 lpnorm(lengthN)

多角形

多角形

image.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  = acos(-1.);
const float TAU = PI * 2.;

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

void main(void){
    vec2 p = (gl_FragCoord.xy * 2.0 - r) / min(r.x, r.y);
    float l=lPoly(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文で分岐するのが一番楽でしょう。

線分の仕方もいくつあるが、直線の描画方法にもいくつかの種類がある。
長いので他にまとめました!
* 2次元ディスタンスフィールド線分まとめ

楕円

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

7セグメント(デジタル)数字

image.png

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

float seg(vec2 p,float s,float k){p=abs(p*k);return max(s+p.x,s*.5+p.y+p.x);}
float seg7(vec2 p,int n,float s,float k){
    float l=5.,w=s*.5;
    vec2 q=p.yx,h=vec2(s,0.),v=vec2(-w,w);
    l = (n!=1 && n!=4                ?min(seg(q-h,s,k),l):l);
    l = (n!=1 && n!=2 && n!=3 && n!=7?min(seg(p-v,s,k),l):l);
    l = (n!=5 && n!=6                ?min(seg(p-w,s,k),l):l);
    l = (n!=0 && n!=1 && n!=7        ?min(seg(q  ,s,k),l):l);
    l = (n==0 || n==2 || n==6 || n==8?min(seg(p+w,s,k),l):l);
    l = (n!=2                        ?min(seg(p+v,s,k),l):l);
    l = (n!=1 && n!=4 && n!=7        ?min(seg(q+h,s,k),l):l);
    return l;
}
void main(void){
  vec2 p = (gl_FragCoord.xy * 2.0 - r) / min(r.x, r.y);

  float l=seg7(p,int(mod(t,10.)),.8,1.3);
  gl_FragColor = vec4(vec3(l), 1.0);
}

ハートの距離関数

関連

7CIT
glslでフルシェーダーでゲームのエフェクトを作っています。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away