トップ 差分 一覧 ソース 検索 ヘルプ PDF RSS ログイン

三角関数_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θ
010
9001
180-10
2700-1
36010

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.