今までのプログラムでは、一体化したオブジェクトの運動は次のような形で記述されていた。
Matrix4x4 sclObj3 = ... ;
Matrix4x4 rotObj3 = ... ;
Matrix4x4 traObj3 = ... ;
Matrix4x4 localObj3 = traObj3 * rotObj3 * sclObj3;
Matrix4x4 sclObj2 = ... ;
Matrix4x4 rotObj2 = ... ;
Matrix4x4 traObj2 = ... ;
Matrix4x4 localObj2 = traObj2 * rotObj2 * sclObj2;
Matrix4x4 sclObj1 = ... ;
Matrix4x4 rotObj1 = ... ;
Matrix4x4 traObj1 = ... ;
Matrix4x4 localObj1 = traObj1 * rotObj1 * sclObj1;
Matrix4x4 worldObj3 = localObj1 * localObj2 * localObj3;
Matrix4x4 worldObj2 = localObj1 * localObj2;
Matrix4x4 worldObj1 = localObj1;
Obj3.SetMatrix(worldObj3);
Obj2.SetMatrix(worldObj2);
Obj1.SetMatrix(worldObj1);
このプログラムでは3つのオブジェクトObj
1、Obj
2、Obj
3が使われており、それらの階層構造は図1に示されるとおりである。
まず、各オブジェクトの独自の運動をローカル行列に定義するが、そのローカル行列はスケール行列、回転行列、平行移動行列の積である。例えば Obj
3の場合では、ローカル行列は
localObj3 であり、
localObj3 は
traObj3、
rotObj3、
sclObj3 の積である (4行目)。
最終的にオブジェクトに実行される行列はワールド行列であり、ワールド行列は各オブジェクトのローカル行列の積である。例えば、Obj
2の場合では、ワールド行列は
worldObj2 であり、
worldObj2 は
localObj1、
localObj2 の積である (17行目)。
では、図1の階層構造を持つ3つのオブジェクト Obj
1、Obj
2、Obj
3 がある状態で
静止しているときに、何らかの変化が発生したとして、その変化による影響についてそれぞれの場合で見ていこう。
プログラム実行中のあるフレームにおいて Obj
3に変化が発生したとしよう。例えば、Obj
3のスケール行列
sclObj3 の内容が更新されたとする。
Matrix4x4 sclObj3 = .. 何らかの値の更新 .. ;
Matrix4x4 rotObj3 = ... ;
Matrix4x4 traObj3 = ... ;
Matrix4x4 localObj3 = traObj3 * rotObj3 * sclObj3;
Matrix4x4 sclObj2 = ... ;
Matrix4x4 rotObj2 = ... ;
Matrix4x4 traObj2 = ... ;
Matrix4x4 localObj2 = traObj2 * rotObj2 * sclObj2;
Matrix4x4 sclObj1 = ... ;
Matrix4x4 rotObj1 = ... ;
Matrix4x4 traObj1 = ... ;
Matrix4x4 localObj1 = traObj1 * rotObj1 * sclObj1;
Matrix4x4 worldObj3 = localObj1 * localObj2 * localObj3;
Matrix4x4 worldObj2 = localObj1 * localObj2;
Matrix4x4 worldObj1 = localObj1;
Obj3.SetMatrix(worldObj3);
Obj2.SetMatrix(worldObj2);
Obj1.SetMatrix(worldObj1);
上のプログラムにおいて赤いフォントで表示されている変数は、行列を表す変数のうちで
sclObj3 の変化によって、(行列の)内容が変化する変数である (以降のプログラムにおいても行列を表す変数で赤く表示されているものは、行列の内容がそれ以前のものとは変化していることを示している)。
4行目の
localObj3 は3つの行列の積であり、その1つに
sclObj3を含むので、
sclObj3の変化の影響を受ける。16行目の
worldObj3 も3つの行列の積であり、その1つに
localObj3を含むので、やはり
sclObj3が変化した際には(間接的にではあるが)影響を受けることになる。
したがって、Obj
3に変化が発生した場合、ワールド行列の中では
worldObj3だけが それ以前のフレームのものと内容が異なるが、他の2つのワールド行列はそれ以前のフレームのものと内容に違いはない。つまり、Obj
3に変化が発生した場合には、Obj
3のみが動き、他の2つのオブジェクトは静止したままであるということである。
次に、あるフレームにおいて Obj
2に変化が発生した場合を見てみよう。例えば、Obj
2の回転行列
rotObj2 の内容が更新されたとする。
Matrix4x4 sclObj3 = ... ;
Matrix4x4 rotObj3 = ... ;
Matrix4x4 traObj3 = ... ;
Matrix4x4 localObj3 = traObj3 * rotObj3 * sclObj3;
Matrix4x4 sclObj2 = ... ;
Matrix4x4 rotObj2 = .. 何らかの値の更新 .. ;
Matrix4x4 traObj2 = ... ;
Matrix4x4 localObj2 = traObj2 * rotObj2 * sclObj2;
Matrix4x4 sclObj1 = ... ;
Matrix4x4 rotObj1 = ... ;
Matrix4x4 traObj1 = ... ;
Matrix4x4 localObj1 = traObj1 * rotObj1 * sclObj1;
Matrix4x4 worldObj3 = localObj1 * localObj2 * localObj3;
Matrix4x4 worldObj2 = localObj1 * localObj2;
Matrix4x4 worldObj1 = localObj1;
Obj3.SetMatrix(worldObj3);
Obj2.SetMatrix(worldObj2);
Obj1.SetMatrix(worldObj1);
9行目の
localObj2 は3つの行列の積であり、その1つに
rotObj2を含むので、
rotObj2の変化の影響を受ける。また、16行目の
worldObj3、17行目の
worldObj2はローカル行列同士の積であるが、その1つに
localObj2を含むので、やはり
rotObj2の内容が変化すれば それらのワールド行列の内容も変化する。
したがって、Obj
2に変化が生じた場合、2つのワールド行列
worldObj3、
worldObj2の内容が変化することになる。これは、静止状態にあった3つのオブジェクトのうちで、Obj
2に変化が発生した場合には Obj
2、Obj
3の2つのオブジェクトが動くことを意味している。
最後に、あるフレームにおいて Obj
1に変化が発生した場合を見てみよう。例えば、Obj
1の平行移動行列
traObj1 の内容が更新されたとする。
Matrix4x4 sclObj3 = ... ;
Matrix4x4 rotObj3 = ... ;
Matrix4x4 traObj3 = ... ;
Matrix4x4 localObj3 = traObj3 * rotObj3 * sclObj3;
Matrix4x4 sclObj2 = ... ;
Matrix4x4 rotObj2 = ... ;
Matrix4x4 traObj2 = ... ;
Matrix4x4 localObj2 = traObj2 * rotObj2 * sclObj2;
Matrix4x4 sclObj1 = ... ;
Matrix4x4 rotObj1 = ... ;
Matrix4x4 traObj1 = .. 何らかの値の更新 .. ;
Matrix4x4 localObj1 = traObj1 * rotObj1 * sclObj1;
Matrix4x4 worldObj3 = localObj1 * localObj2 * localObj3;
Matrix4x4 worldObj2 = localObj1 * localObj2;
Matrix4x4 worldObj1 = localObj1;
Obj3.SetMatrix(worldObj3);
Obj2.SetMatrix(worldObj2);
Obj1.SetMatrix(worldObj1);
14行目の
localObj1 は3つの行列の積であり、その1つに
traObj1を含むので、
traObj1が変化すれば
localObj1の内容も変化する。また、16行目~18行目の3つのワールド行列はいずれも
localObj1を用いた計算の結果(あるいは
localObj1のコピー)であるため、
traObj1の内容が変化すれば、この3つのワールド行列の内容も変化することになる。
したがって、Obj
1に変化が生じた場合、3つのワールド行列
worldObj3、
worldObj2、
worldObj1の内容が変化することになる。これは、静止状態にあった3つのオブジェクトのうちで、Obj
1に変化が発生した場合には3つのオブジェクトすべてが動くことを意味している。
上で述べたことは、一体化するオブジェクトの数が増えても変わらない。
すなわち、あるオブジェクトが平行移動、あるいは回転、スケールによって、オブジェクトの状態に変化が発生した場合、そのオブジェクトのローカル行列、ワールド行列、そしてそのオブジェクトのすべての子オブジェクトのワールド行列の内容が変化する (「すべての子オブジェクト」とは2階層下の子、3階層下の子、あるいはそれより下の階層の子オブジェクトすべてのことである)。
しかし、そのオブジェクトより上の階層のオブジェクト(1階層上の親、2階層上の親、あるいはそれより上の親オブジェクト)のワールド行列は変化しない(もちろんローカル行列も変化しない)。
これは、あるオブジェクトに変化が発生した場合、そのオブジェクトより下の階層の子オブジェクトには変化が発生するが、そのオブジェクトより上の階層のオブジェクトには何も変化は起こらないということを意味する (オブジェクトに最終的に実行されるのはワールド行列であるから、ワールド行列に変化がなければオブジェクトの状態は変化しない)。
要約すると、あるオブジェクトに(平行移動、回転、スケールによる)変化が発生した場合には、そのオブジェクトのローカル行列とワールド行列、及び そのオブジェクトのすべての子オブジェクトのワールド行列 に対して更新処理が必要となる。そのオブジェクトの親オブジェクトを含む上の階層のオブジェクトに対する更新処理については何も行う必要はない。
上のプログラムでは毎フレームすべてのオブジェクトのローカル行列、ワールド行列の計算が行われているので、処理的には冗長なコードとなっている。仮に、各オブジェクトのローカル行列、ワールド行列をインスタンス変数にして、それらの変数については必要な場合にだけ更新が起こるようにプログラムを以下のように書き換えたとしよう。
bool fWorld1 =false;
bool fWorld2 =false;
bool fWorld3 =false;
bool fLocal3 = .. Obj3に何らかの変化があれば true .. ;
if(fLocal3){
Matrix4x4 sclObj3 = ... ;
Matrix4x4 rotObj3 = ... ;
Matrix4x4 traObj3 = ... ;
i_localObj3 = traObj3 * rotObj3 * sclObj3;
fWorld3 = true;
}
bool fLocal2 = .. Obj2に何らかの変化があれば true .. ;
if(fLocal2){
Matrix4x4 sclObj2 = ... ;
Matrix4x4 rotObj2 = ... ;
Matrix4x4 traObj2 = ... ;
i_localObj2 = traObj2 * rotObj2 * sclObj2;
fWorld2 = true;
}
bool fLocal1 = .. Obj1に何らかの変化があれば true .. ;
if(fLocal1){
Matrix4x4 sclObj1 = ... ;
Matrix4x4 rotObj1 = ... ;
Matrix4x4 traObj1 = ... ;
i_localObj1 = traObj1 * rotObj1 * sclObj1;
fWorld1 = true;
}
if(fWorld1){
i_worldObj3 = i_localObj1 * i_localObj2 * i_localObj3;
i_worldObj2 = i_localObj1 * i_localObj2;
i_worldObj1 = i_localObj1;
Obj3.SetMatrix(i_worldObj3);
Obj2.SetMatrix(i_worldObj2);
Obj1.SetMatrix(i_worldObj1);
}
else if(fWorld2){
i_worldObj3 = i_localObj1 * i_localObj2 * i_localObj3;
i_worldObj2 = i_localObj1 * i_localObj2;
Obj3.SetMatrix(i_worldObj3);
Obj2.SetMatrix(i_worldObj2);
}
else if(fWorld3){
i_worldObj3 = i_localObj1 * i_localObj2 * i_localObj3;
Obj3.SetMatrix(i_worldObj3);
}
プログラムにおいてローカル行列、ワールド行列を表す変数には "i_" という接頭辞が追加されているが、これはそれらの変数がインスタンス変数であることを意味している。
このプログラムは各オブジェクトのローカル行列、ワールド行列が、必要な場合だけ更新されるように上のプログラムを変更したものである。
各オブジェクトの平行移動行列、回転行列、スケール行列に変化が生じた場合には
fLocal# という
bool型ローカル変数が
trueになる。そして、それぞれのオブジェクトに用意されているローカル行列更新用の
ifブロックにおいてローカル行列の更新を行うが、このときに そのオブジェクトのワールド行列、及び すべての子オブジェクトのワールド行列を更新するように
fWorld# という
bool型ローカル変数の値を
trueにする。例えば Obj
2に変化が発生し、
fLocal2 が
trueになったとき、15行目の
ifブロックに入るが、そこでは自身のローカル行列である
i_localObj2 を更新し、自身のワールド行列と子オブジェクトのワールド行列の更新処理が行われるように
fWorld2 の値を
trueにしている。32行目以降のワールド行列更新のための
if elseブロックでは、そのオブジェクトとそのオブジェクトのすべての子オブジェクトのワールド行列が更新される (ワールド行列更新用のフラグである
fWorld1、
fWorld2、
fWorld3は2つ同時、あるいは3つ同時に
trueになる場合があるので、32行目以降の
if elseブロックの順序には注意が必要である)
しかし、このプログラムは応急処置的な変更であるため見やすさの点では改善が必要であり、さらに 一体化するオブジェクトの数が増え階層構造がより複雑になるような場合を考えると、プログラムをより汎用的な構造にする必要がある。
次節ではこの問題に対する解決策として、各オブジェクトのローカル行列、ワールド行列の更新を再帰処理によって実装する。