glslのatanの仕様は
重要な項目はxが0の時返値はundefinedになる。
数学的にy/x x/0はゼロ除算になるので当然と言えば当然なのですが、それだと色々困るので、端末の実装によっては勝手に0とかにしてくれているものが多い。でも、すべての端末でそういった処理をしてくれるわけではないので、トラブル回避の為x=0の場合の処理を入れておかないと事故る。他のプログラミング系では自動で0とかにしてくれる仕様。
以下のようなラッパー関数を作って0除算を回避する
glslのatanは、他プログラミング言語実装のatan2の180度以上の値についての処理はされているが、0除算だけ対応してない。
float atan2(in float y, in float x){
return x == 0.0 ? sign(y)*PI/2 : atan(y, x);
}
角度を-π~πの間で出力したい
atan(p.x,p.y)
-π~πが返って来る。sinとかcosとか三角関数形の関数に使う場合はこのままの方が便利だが、画素の輝度に使いたい場合には都合が悪い
角度を-1~1の間で出力したい
単純にPIで割ればいい
atan(p.x,p.y)/PI
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.;
void main(void){
vec2 p = (gl_FragCoord.xy * 2.0 - r) / min(r.x, r.y);
float l =0.;
l = atan(p.y,p.x)/PI;
gl_FragColor = vec4(vec3(-l,max(l-1.,0.),l*step(l,1.)), 1.0);
}
画素を表示するときには通常マイナスは使わないのであまり使われない
角度を0~1の間で出力したい
一番目にするパターンがこれ
atan(p.y,p.x)+PIを足して、マイナスを打消し、全体をPI2で割る
これ、当たり前だが、πを足しているので、開始位置が180度ずれる。
大抵の場合、回転しても問題ないものならこれで大丈夫なのですが、たまに回転するとまずいものがある。
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.;
void main(void){
vec2 p = (gl_FragCoord.xy * 2.0 - r) / min(r.x, r.y);
float l =0.;
l = (atan(p.y,p.x)+PI)/PI2;
gl_FragColor = vec4(vec3(-l,max(l-1.,0.),l*step(l,1.)), 1.0);
}
角度を0~1の間で出力したい
おすすめはこちら、端数が出そうなPIの計算を極力抑え、開始位置も元に戻す
fract(atan(p.y,p.x)/PI2+1.)
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.;
void main(void){
vec2 p = (gl_FragCoord.xy * 2.0 - r) / min(r.x, r.y);
float l =0.;
l = fract(atan(p.y,p.x)/PI2+1.);
gl_FragColor = vec4(vec3(-l,max(l-1.,0.),l*step(l,1.)), 1.0);
}
角度を0~1の間で出力したい(失敗 番外編)
atan(-p.y,-p.x)/PI2+.5
こちらの方がシンプルに書け一見正常に動作しそうであるが....
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.;
void main(void){
vec2 p = (gl_FragCoord.xy * 2.0 - r) / min(r.x, r.y);
float l =0.;
l = atan(-p.y,-p.x)/PI2+.5;
gl_FragColor = vec4(vec3(-l,max(l-1.,0.),l*step(l,1.)), 1.0);
}
関数にすると正常に動作しない
座標(1.0)を引数に渡した時に意図する答えは0だが1が返って来る
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 angle(vec2 p){return atan(-p.y,-p.x)/PI2+.5;}
void main(void){
vec2 p = (gl_FragCoord.xy * 2.0 - r) / min(r.x, r.y);
float l =0.;
l = angle(vec2(1.,0.));
gl_FragColor = vec4(vec3(-l,max(l-1.,0.),l*step(l,1.)), 1.0);
}
見た目は変わっていなさそうだが0と2π境界角度0のときの挙動が異なる
今度は、関数化せず、直接値を入力してみる
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.;
void main(void){
vec2 p = (gl_FragCoord.xy * 2.0 - r) / min(r.x, r.y);
float l =0.;
l = atan(-0.,-1.)/PI2+.5;
gl_FragColor = vec4(vec3(-l,max(l-1.,0.),l*step(l,1.)), 1.0);
}
正常に0が帰って来る
l = atan(-0.,-1.)/PI2+.5;
↓ -0を0に変えてみる
l = atan(0.,-1.)/PI2+.5;
試しに、-0を0にすると答えが1になる。
つまり、atanは0と-0で返り値が異なるが、変数を介すると-0は0に変換されてバグる。。。
0=2π で同じものであるが、それぞれ0と-0に変換されているので、p.xやp.yにマイナスを付けて反転させるとバグる