三角関数_3DCG
三角関数
レンダラでは三角関数はよく使われます。cos/sinなどの意味合いを元に説明します。
sin(サイン)とcos(コサイン)
XY軸の空間で(0, 0)を中心として半径1.0の円を描きます。このときの円周を通る点を考えます。X軸プラス方向(1, 0)を開始位置としたときのできる角度をθとしたときの円周上の点の位置を、「(x, y) = (cosθ, sinθ)」とあらわします。
ちなみに、以下のように角度により位置情報をあらわす座標系を「極座標」と言います(一般のXY軸(またはXYZ軸)を持つ空間座標を「デカルト座標」と言います)。
ぐるっと円周を回るわけですので、0度〜90度〜180度〜270度〜360度(= 0度)でのcos/sinは以下の関係になります。
角度(θ) | X = cosθ | Y = sinθ |
---|---|---|
0 | 1 | 0 |
90 | 0 | 1 |
180 | -1 | 0 |
270 | 0 | -1 |
360 | 1 | 0 |
cosθは水平方向の-1.0〜+1.0の範囲を取り、sinθは垂直方向の-1.0〜+1.0の範囲を取ることが分かります。
θって何?
では、次はcosθ/sinθにおける「θ」の意味合いです。上記では「度(°)」で指定しましたが、C言語/数学では「ラジアン」という単位で指定します。
「360度 = 2π」というのが基礎となる単位と考えてください。「π」は円周率を表し「3.1415926535...」と延々に続く数値です。「360度 = 2π」なんで「180度 = π」、「90度 = 0.5π」ですね。
では「N度」では?
#define PI 3.1415926535 #define SITA(c) ((PI * (c)) / 180.0) double fValS, fValC; double n = 45.0; fValC = cos(SITA(n)); fValS = sin(SITA(n));
で計算してしまうのが手っ取り早いです。
ラジアンから度数を求めるには上記の逆で、
n = (rad * 180.0) / PI;
で度数を出すことができますね。
sinとcosの逆数
まずは極座標系で考えてください。
cosθ = X sinθ = Y
と(X, Y)が求まっているときにθを求めるには、アークコサイン(acos)・アークサイン(asin)というのを使います。これは、それぞれcosとsinの逆数を取っているとも言えます。
まず、使用する前に(X, Y)が作るベクトルの長さが1.0になるように正規化します。半径1.0の円の中心が(0 ,0)としたときの円周上の点の位置に持ってきます。
a = sqrt(X * X + Y * Y); X /= a; Y /= a;
この段階でX, Yともに-1.0〜+1.0の値になっているはずです。ここでacos/asinの出番です。
double a_c, a_s; a_c = acos(X); a_s = asin(Y);
これで「a_c」「a_s」というのが求まりますが、片一方の結果だけでは角度θは求まりません。そこで、以下のような処理を加えてやります。
if(a_s > 0.0) rad = a_c; else rad = PI + PI - a_c;
sinは垂直方向(Y)で-1.0〜+1.0を取る、cosは水平方向(X)で-1.0〜+1.0を取る、といったことを元に、極座標での角度(ラジアン)0〜2πの間の値になるように変換しています。
これで「rad」にはラジアンの形で角度が求まります。最後に、これを度数に変換します。
n = (rad * 180.0) / PI;
ここで求まったnが0.0〜360.0の角度を持つことになります。
2ベクトルの作る角度を求める
それでは上記を踏まえた上で、2つの正規化された(長さが1.0になっている)ベクトルが作る角度を求めてみましょう。
(px1, py1)と(px2, py2)の2ベクトルがあるとします。ともに正規化済みとします。
ここで角度(cos)を求めるには2ベクトルの内積を求めるといいですので
cVal = px1 * py1 + px2 * py2;
で、cValに-1.0〜+1.0の数値が求まります。プラスの値の場合は、2ベクトルの作る角度が0度〜90度の値ということになります。
ここで、実際の度数まで求めていきましょう。
rad = acos(cVal);
これで角度がラジアンとして求まります。逆に考えると、
cVal = cos(rad);
です(acosはcosの逆数を取っているの意味)。最後にラジアンを度数に変換します。
n = (rad * 180.0) / PI;
これで、nには2ベクトルの作る角度(0.0〜360.0)が求まります。3次元空間では、cValでの内積を求める計算でZ値を考慮するだけです。
cos/sinの負荷
オフラインレンダラなどでは、普通にANSI Cの「math.h」でのcos/sin関数を使っても速度が気になるということは少ないのですが、リアルタイムではそのままでは重いです。それくらい、ミリ秒単位の世界では三角関数の負荷が大きいとも言えます。
ですので、360度(一周)を256分割くらいして「ルックアップテーブル」で結果を引き出す、ということもよく使われます。
double angle; for(i = 0; i < 256; i++) { angle = (double)i * PI / 128.0; m_cos[i] = (float)cos(angle); m_sin[i] = (float)sin(angle); }
このとき、N度のcos/sinを求めるには以下のように計算できます(以下は90度でのcosとsinを求める場合)。
int n = 90; int index = (n << 8) / 360; valC = m_cos[index]; valS = m_sin[index];
実際は除算がネックになるため、「360度で1回転」という考えを「256度で1回転」という概念に置き換えて考えます。
「N / 256」回転する場合(Nは0以上の整数)のcos/sinは
N &= 0xff; valC = m_cos[N]; valS = m_sin[N];
Future's Laboratory 技術格納庫 2004-2013 Yutaka Yoshisaka.