Edited at

符号なし距離関数で作る極座標変換


極座標変換

ぐぐっても小難しい説明しか出てこないので、Photoshopの極座標フィルターの動画を見てみてください。

https://helpx.adobe.com/jp/photoshop/how-to/use-polar-coordinates-filter.html

ようするに、画面の中央の点を中心にぐるっと360度回転させる変換のことです。

image.png

この画像を極座標変換するとこうなります。


ユークリッド

image.png

vec2 mPolar(vec2 p){

float a = atan(p.y,p.x);
float r = 0.;
r = length(p);
return vec2(a/PI, r);
}

通常、この変換にはlength(p)すなわちユークリッド距離が使われています。が距離?ってことは符号なし距離関数に置き換えられるということなので置き換えてみましょう。

サンプルのように、ディスタンスフィールドの周りに、fbmで作った炎をまとわせたり、こいつ、結構使えます!

fbmで作った炎をまとわせるために、レイマーチングの法線を求めるように、前後の差分で勾配求めて法線を出している人(激重)を結構みかけますが、符号なし距離関数自体が法線なので、極座標変換で直接使えば法線を計算する必要がないです。もちろん、符号なし距離関数1つで出来ない場合はそのアプローチしかありませんがw

2次元ディスタンスフィールドの一覧はこちら


マンハッタン

image.png

  r = sumabs(p);


チェビシェフ

image.png

  r = maxabs(p);


ミンコフスキー(p=3)

image.png

  r = lpnorm(p,3.);


ミンコフスキー(p=0.5) アステロイド

image.png

  r = lpnorm(p,.5);


六角形

image.png

r = lPoly(p,6.);


image.png

r = star(p,6.);


その他


極座標の開始点と終了点のつなぎめが気になる?

シームレスに出来ますよ。興味のある方は以下の方法を試してみてください。

サンプルプログラムが長くなるので処理してません。


応用

fbmの調整によって水をまとわせてもよいし、スパークをまとわせてもいい、疑似ハイライトもできる


スパーク

image.png


水しぶき+疑似ハイライト

babble.gif

疑似ハイライトは水泡の形を歪ませても追従する


サンプルプログラム

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

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

float Hash( vec2 p, in float s ){
return fract(sin(dot(vec3(p.xy,10.0 * abs(sin(s))),vec3(27.1,61.7, 12.4)))*273758.5453123);
}

float noise(vec2 p,float s){
vec2 i = floor(p);
vec2 f = fract(p);
s = s * s * (3.0 - 2.0 * s);
return mix(
mix(Hash(i + vec2(0.,0.), s), Hash(i + vec2(1.,0.), s),f.x),
mix(Hash(i + vec2(0.,1.), s), Hash(i + vec2(1.,1.), s),f.x),f.y) * s;
}

float fbm(vec2 p){
float v = 0.0;
v += noise(p , .1);
v += noise(p*40., .2);
return v;
}

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

float lStar(vec2 p,float n){
return min(lPoly(p,n*.5),lPoly(mod(n,2.)!=0.?vec2(-p.x,p.y):p*rot(TAU/n),n*.5));
}

float dot2(vec2 p){return dot(p,p);}
float sumabs(vec2 p){return abs(p.x)+abs(p.y);}
float maxabs(vec2 p){return max(abs(p.x), abs(p.y));}
float minabs(vec2 p){return min(abs(p.x), abs(p.y));}
float lpnorm(vec2 p, float n){vec2 t=pow(abs(p),vec2(n));return pow(t.x+t.y,1./n);}
vec2 mPolar(vec2 p){
float a = atan(p.y,p.x);
float r = 0.;
r = length(p);
r = sumabs(p);
r = maxabs(p);
r = minabs(p);
r = lpnorm(p,3.);
r = lpnorm(p,.5);
r = lpnorm(p,abs(sin(t*.5)*4.));
r = lPoly(p,6.);
r = lStar(p*rot(PI),5.);
return vec2(a/PI, r);
}

void main( void ) {
vec2 p = (gl_FragCoord.xy * 2.0 - r) / min(r.x, r.y);
p=mPolar(p);
p.y-=.8;
float l = (1./((p.y - fbm( p + vec2(sin(t*.2),t*0.1))) * 50.));
gl_FragColor = vec4( l * vec3( 0.75, 0.5, .05 ), 1.0 );
}