Help us understand the problem. What is going on with this article?

glsl atan 仕様まとめ

More than 1 year has passed since last update.

glslのatanの仕様は

https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/atan.xhtml

重要な項目は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);
}

atan22.png

画素を表示するときには通常マイナスは使わないのであまり使われない

角度を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);
}

normal.png

角度を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);
}

atansin.png
* https://goo.gl/3Rnwqa

角度を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);
}

atan.png

関数にすると正常に動作しない
座標(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にマイナスを付けて反転させるとバグる

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