ここ数日、Astro (FP10) の3D機能をいろいろやってみてるのですが、Matrix関係やdrawTriangles()メソッドによって確かに便利になっていると思います。
しかし、3Dプログラミングをしようと思うと必ず必要な、ある重要な機能が抜けているのです・・・。
上は、私が試しにつくった球の3D表示のswfをキャプチャしたものですが、その機能がないおかげで、これだけでもかなり大変でした。。
シェーディング機能が全く用意されていない
実は、Flash Player10の3D機能にはシェーディング(3Dプログラミングにおいての)の機能が全く用意されていないのです。
これはどういうことかというと、DirectXやOpenGLなどの一般的な3DのAPIでは標準で用意されている光源やその光源によってどのような色で各ピクセルを出力するかといった計算を行う機能がAstroにはないということです。つまり、シェーディング部分を自前で実装する必要があります。
3Dのシェーディングは一般的なものにフラットシェーディング、グーローシェーディング、フォンシェーディングなどがありますが、これらをAction Scriptで実装するにはどうすればよいか、ちょっと考えてみました。
フラットシェーディング
フラットシェーディングとは面単位で色が決定されるシェーディング方法で、面法線をもとにランバート反射を計算するものです。
これは、三角形を描画する毎にbeginFill()でその色を塗ればよさそうです。三角形ごとに色がことなるので、今回追加されたdrawTriangles()は使えないと思います。
グーローシェーディング
これは、頂点単位の法線を用いて頂点毎のランバート反射を計算し、各頂点間の色を線形補完することによるシェーディング方法です。
これについては、beginGradiantFill()でいけるのかな?パラメータを見る限りちょっと難しそう・・・。ひょっとしたらbeginShaderFill()とかを使わないと無理かも。
フォンシェーディング
これは頂点単位の法線を補完してピクセル単位で法線をもとにランバート反射やフォンの鏡面反射モデルを適用するものですが、まともに計算するのはまず無理です(パフォーマンス的に計算量が多くなりすぎる)。
全く方法が思いつかなかったのですがPapervision3Dでは実現できてるみたいなので、ソースを見てみたところ、やはりまともなやり方を正面からやるのではなくライトマップで近似しているようです。
ライトマップとは、ライティングの計算を省略するためにテクスチャにライト情報をもたせてそれをもとに色をつける方法で、具体的にはテクスチャマッピングを行った後、このライトマップテクスチャを乗算します。
今回作ったもの
今回は、テクスチャマッピングが面倒だった(UVの用意とかが要るので・・)ので、ライトマップ自体に色をつけてそれっぽくしてみました。
ライトマップのビットマップデータの生成は以下のような感じでやっています(ほぼPapervisionのまんま・・)。
public function makeLightMap(lightColor:int, diffuseColor:int, ambientColor:int, specularLevel:int):BitmapData { var w:Number = 1024; var mat:Matrix = new Matrix(); var s:Sprite = new Sprite(); mat.createGradientBox(w,w,0,0,0); s.graphics.beginGradientFill(GradientType.RADIAL, [lightColor,diffuseColor,ambientColor], [1,1,1], [0,255-specularLevel, 255], mat); s.graphics.drawRect(0,0,w,w); s.graphics.endFill(); var bmp:BitmapData = new BitmapData(w,w,false,0x0000ff); bmp.draw(s); return bmp; }
放射状のグラデーションを使って、スペキュラハイライト〜ディフューズ色の減衰のようになるようにしています(全然正確ではないので、あくまでそれっぽい感じです^^;)。
これをどうやって物体に貼り付けるか、ですが、光線ベクトルと視線ベクトルのハーフベクトルを計算して、そのハーフベクトルの先から物体を見たときの法線ベクトルの位置をテクスチャ座標としてサンプリングしています(意味わかるかな・・)。ハーフベクトルを正規化した座標を視点とする行列を生成して、その行列で法線ベクトルを変換しているわけです。
public function calcUV(mat:Matrix3D):void { uvts = new Vector.<Number>(); for(var index:int = 0; index < vertices.length; index+=3) { var vertex:Vector3D = new Vector3D(vertices[index], vertices[index+1], vertices[index+2]); vertex = mat.transformVector(vertex); vertex.normalize(); // -1 〜 1 の範囲に uvts.push((vertex.x + 1.0) / 2.0); uvts.push((vertex.y + 1.0) / 2.0); uvts.push(1.0); // t は仮の値を入れる } }
こんな感じです。
ちょっと、環境によっては重いかもしれないのでswfを貼るのは今回はやめておきます。ソースは以下からダウンロードできるので興味がある方は見てみてください。
astro_phong.zip