note.x

[PV3D2.x] DisplayObject3D.lookAt problem

tkinjoさんが、wonderflに投稿されたコードを見て。

sceneの原点(0, 0, 0)にターゲット指定したカメラを x=0, y=1000, z=0 のように、ワールド座標のY軸線上に配置すると何も表示されなくなる。
回避策として、tkinjoさんも採用している x, y座標を0にしないってのが一番ラクチン。
x=0, y=1000, z=-0.1 とか x=0.01, y=1000, z=-0.05 とかを指定する方法。
ただし、x軸、z軸のどっちを使ってY軸からずらすかによって結果が異なる。また数値の±によっても結果が異なる点に注意。大抵はz軸にマイナスの値を入れるといいんじゃないかと。

別の方法として、カメラのターゲット指定をせずに

camera.target = null;
camera.x = 0;
camera.y = 1000;
camera.z = 0;
camera.rotationX = 90;

という設定で同じ状況にした場合も、問題を回避できる。

にしても、なんでなんだぜ?
今まで何が原因なのかをちゃんと調べてなかったので、この機会に調べてみた。
多分、随分前に遭遇したこのトラブルとも関連がありそう。

カメラにターゲット指定しなけりゃ問題ねーってことなんで、lookAt メソッドが怪しいと睨んで DisplayObject3D.lookAt の該当部分を見てみると、

[1]まず、目標にカメラを向けた時のz軸ベクトルを、ターゲットの位置ベクトルからカメラの位置ベクトルから減算して正規化。

_zAxis.copyFrom(_lookatTarget);
_zAxis.minusEq(_position);
_zAxis.normalize();

[2]1で求めたカメラの正規化されたz軸ベクトルと、カメラのupperベクトル(通常は Number3D(0, 1, 0) )との外積をとって、x軸ベクトルを求める。

if( _zAxis.modulo > 0.1 )
    {
        _xAxis = Number3D.cross( _zAxis, upAxis || UP, _xAxis );
        _xAxis.normalize();

[3]1で求めたz軸ベクトルと、2で求めたx軸ベクトルの外積をとって、y軸のベクトルを求める。

        _yAxis = Number3D.cross( _zAxis, _xAxis, _yAxis );
        _yAxis.normalize();

こんな手順でカメラとターゲットとの位置関係を元に、カメラの姿勢を決めてる模様。

この方法だと、x=0, y=1000, z=0 にカメラがある場合、_zAxisが Number3D (0, 1, 0) になる。
こうなると _xAxisを求める際に外積を取るための、カメラのupperベクトル:Number3D (0, 1, 0) と平行になる。平行なベクトルの外積の大きさはゼロなので、_xAxis は Number3D (0, 0, 0)。
最後の_yAxis は _xAxis との外積だから、_yAxisも Number3D (0, 0, 0)。
って感じでz軸以外がゼロベクトルになっちゃって、カメラの姿勢が定まらない。結果、何も表示されないということみたいだ。これっていわゆるジンバルロックなのか?
むー。PV3Dサイドとしては、これは仕様だということにしてるんだろうか?

なんかアホっぽい方法で回避できそうな気がしたのでやってみた。
DisplayObject3Dの1213行目辺りから

            _zAxis.copyFrom(_lookatTarget);
            _zAxis.minusEq(_position);
            _zAxis.normalize();

            if( _zAxis.modulo > 0.1 )
            {
                _xAxis = Number3D.cross( _zAxis, upAxis || UP, _xAxis );
                _xAxis.normalize();

                _yAxis = Number3D.cross( _zAxis, _xAxis, _yAxis );
                _yAxis.normalize();

という部分を以下に書き換え。

            _zAxis.copyFrom(_lookatTarget);
            _zAxis.minusEq(_position);
           _zAxis.normalize();

           if( _zAxis.modulo > 0.1 )
            {
                _xAxis = Number3D.cross( _zAxis, upAxis || UP, _xAxis );
                _xAxis.normalize();

                if(_xAxis.x == 0 && _xAxis.y == 0 && _xAxis.z == 0)
                {
                    _yAxis = Number3D.cross( _zAxis, RIGHT, _yAxis );
                    _yAxis.normalize();

                    var tempYAxis:Number3D = new Number3D(-_yAxis.x, -_yAxis.y, -_yAxis.z);
                    _xAxis = Number3D.cross( _zAxis, tempYAxis, _xAxis );
                    _xAxis.normalize();
                }
                else
                {
                    _yAxis = Number3D.cross( _zAxis, _xAxis, _yAxis );
                    _yAxis.normalize();
                }

特異点でlookAtする場合のみ、_yAxisを先に求めるようにしたので、カメラが x=0, y=1000, z=0に配置された場合もそれっぽく表示されるようになる。強制的にLEFTベクトルとの外積を取るようにしちゃったので、カメラがアニメーションしてる場合、おかしな挙動になるケースもあるかもしれない力業。
それでも何にも表示されないよりはマシかなぁ。もうひとひねりしてクォータニオン使う方法も考えてみたい。

09/06/15追記:tkinjoさんのご指摘を受けてコード修正。
やっつけ気味なのは変わらず;


Trackback URL : http://blog.r3c7.net/as3-software-rendering/394/trackback/

COMMENTS

  1. tkinjo - 2009.06.14 20:54:39

    とりあげてもらってありがとうございます。
    PV3Dサイドで例外処理とかしてくれるとありがたいのですけどね。

  2. rect - 2009.06.15 1:19:56

    >tkinjoさん

    >PV3Dサイドで例外処理
    PV3Dに手を入れるのがイヤでなければ、DisplayObject3D.as を↑のように書き換えると、力業ですけど例外処理するようになりますよ。ってそういう事をおっしゃってるわけではない?

  3. tkinjo - 2009.06.15 8:15:44

    >PV3Dサイドで例外処理
    PV3Dの開発側がデフォルトで上記のように書いていたらなぁー、という話ですね。

    と、先ほど DisplayObject3D.as 上記のように書き変えると
    http://wonderfl.kayac.com/code/8f3b9cf3f4cc7fab0213362b9ba4346ca452d113
    のコードで試してみたところ、z = 0 の場合、表示はされるのですが、rotationX が 90 になってほしいところが -90 になってしまうようで。

    いやー、ベクトルとか外積とかに関してはすっかり忘れてしまってて原因の見当がつかず。

  4. rect - 2009.06.15 11:25:23

    >tkinjoさん

    >rotationX が 90 になってほしいところが -90 になってしまうようで。

    あー、たぶん例外処理の部分で常にcamerraのLEFTベクトルを基準にしてしてるせいですね;
    もうちょっとスマートに解決できる方法考えてみまッス。

  5. tkinjo - 2009.06.15 20:13:27

    修正確認しました。
    ありがたく使わせて頂きます!

  6. 通りすがりのPV3D学徒 - 2009.09.11 20:07:45

    OpenGLライブラリ群の中でgluLookAtという関数がありまして、これと同様に
    lookatは、target,camera,upvectorの3点を使ってカメラを固定するようです。
    gluLookAtでは、この3点が面積を持つ三角形を形成できるときのみカメラは固定されます。

    そして、おそらくlookatもデフォルトでは
    targetが0,0,0に、upvectorが0,1,0に設定されていると予想されますので、
    cameraの位置が0,y,0にある場合、3点が一直線上に並び、三角形を作れないので、
    カメラを固定することができません。(画角が決まらない)

  7. rect - 2009.09.12 2:20:32

    >通りすがりのPV3D学徒さん

    なるほどなるほど。御指南感謝します。
    gluLookAtを使っている状態で、カメラの位置が (0,y,0) に来る場合に、
    どういう対処をするかっていう定石みたいなものはあるんでしょうか?
    それとも、そういう軌道で動したい場合には、
    gluLookAtを使わないのが得策だったりするんですか?

  8. 通りすがりのPV3D学徒 - 2009.11.17 15:02:58

    お返事が遅くなりました。

    upvectorを(0,1,0)に固定している→カメラの位置が (0,y,0) に来る場合に不具合
    となるので、
    upvectorを適正に計算して設定してあげるのがよろしいかと思います。

    適正な計算とは、自分がどの様に表示したいかをはっきりすることです。
    言い換えると、現実世界では重力(0,-1,0)のベクトルがあり、上はその反対と決まっていて、
    カメラでモノを撮影するときに、必ず重力を意識した画角になるはずです。
    そして、モノの真上や真下から撮影するときはどちらを上にすればいいの?
    ということを計算できなくなっていますので、
    北とか東とか西とか南とか、物体の進行方向だとか、カメラの進行方向だとか
    そういう情報を教えてあげればいいのです。

    カメラが普段から(0,y,0)上を移動するような軌道を取るのなら、
    上方ベクトル指定を(0,1,0)に指定しなければいいだけの話です。
    基本的には(0,1,0)を指定して、モノを真上や真下から撮影したときだけ、
    北(0,0,1)を上にするなどの計算が必要です。

    (camerax=targetx,cameray=targety+1000,cameraz=targetz)
    カメラが常に上から見るような場合は、
    衛星写真的な絵にしたいなら、北が上になるように
    upvectorを(0,0,1)に指定すると良いと思います。

    カーナビのようにtargetの進行方向(heading)を意識して上から見るような場合は、
    upvectorを(headingx, 0,headingz)に指定すると良いです。

    また、比較的自由な動きをするカメラが、たまたまtargetの真上や真下に来る場合もあります。
    真上や真下で停止したり、通過したりする場合ですが、そのときは
    どのような映像を作りたいかでupvectorの設定が変わってきます。
    停止時、通過時に急激に画角が変わっても問題ないのであれば、upvectorはとりあえず北の
    (0,0,1)でよいと思います。

    lookatを使って、こだわりの映像を作りたい場合は常にupvectorを計算する必要があります。
    cameraが重力や上方を無視して自由に動くような場合には、
    targetかcameraの進行方向から計算したupvectorを常に使うことがよろしいかと思います。

  9. rect - 2009.11.18 15:59:36

    >通りすがりのPV3D学徒さん

    ご返答をいただくまでの間に、
    http://marupeke296.com/DXG_No56_CameraBaseTrans.html
    を読んで自己解決しておりましたので、良く理解できました。
    とても丁寧なご解説、ありがとうございました。

Leave a Reply