LITTLE CLAP
透視補正をポリゴン内部のテクスチャUVにも適用する 2010/11/18
 3Dデータをテクスチャ付きで描画する上で、低レベルな環境だとよく問題になるのがUV透視補正です。
 もしその機能がない状態で、巨大なポリゴンをすぐ近くに描画してしまうと……
UV透視補正がない場合の表示

これはひどい
UV透視補正がある場合の表示
 はい、正常になりました。
説明
 Flash Player 10には根本的に3D表示を行う機能がないはずですが、クラスライブラリには3Dを意識したパッケージが見受けられます。
 今回のテストで使用したflash.display.Graphics#drawTrianglesには、なんとz値をそのまま受け入れる機能が備わっています。
 もうこれなら3D対応と呼んでいいんじゃないかと思うくらいですが、デプスバッファなしでポリゴンを描画するのはちょっと大変です。来年のメジャーバージョンアップでそっち関連の対応とグラフィックアクセラレータへのアクセスが可能になるとの噂ですので、期待しています。
 

 ところでUV透視補正という機能、以前リアルタイムの簡易ソフトウェアレンダを自作したときは実装しませんでした。CPUでやるには負荷がかかりすぎると感じたので。
 しかしFLASHにはそこそこ快適な速度で動作するUV透視補正が組み込まれています。いったいどうやって実現しているのかなぁ。
ソースコード
Main.as
package 
{
    import flash.display.Sprite;
    import flash.events.Event;
    
    /**
     * ルートクラス、子スプライトを登録するのみ
     * @author sc
     */
    public class Main extends Sprite 
    {
        
        public function Main():void 
        {
            if (stage) init();
            else addEventListener(Event.ADDED_TO_STAGE, init);
        }
        
        private function init(e:Event = null):void 
        {
            removeEventListener(Event.ADDED_TO_STAGE, init);
            // entry point
            var objFlashVars:Object = this.loaderInfo.parameters;
            var strPerspectiveUV:String = objFlashVars["PerspectiveUV"];
            if (strPerspectiveUV == null) { strPerspectiveUV = "yes"; }
            new TestSprite(this, 0, 0, stage.stageWidth, stage.stageHeight, strPerspectiveUV.toLowerCase() != "no");
        }
        
    }
    
}

TestSprite.as
package  
{
    import flash.display.Bitmap;
    import flash.display.BitmapData;
    import flash.display.GradientType;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.geom.Matrix;
    import flash.geom.Vector3D;
    
    /**
     * graphics.drawTriangles の描画テスト
     * @author sc
     */
    public class TestSprite extends Sprite 
    {
        private var w:Number;
        private var h:Number;
        private var bm:BitmapData;
        private var vecSource:Vector.<Vector3D>;// 頂点座標XYZ
        private var vecDest:Vector.<Number>;// 座標算出後の格納バッファXY
        private var ivec:Vector.<int>;// 2枚の三角形が参照する頂点番号
        private var uvt:Vector.<Number>;// UV座標、及び座標算出後の格納バッファZ
        private var rotateX:Number = 0;// 横回転角度(垂直軸、単位は度)
        private var floatY:Number = 0;// 縦浮遊用
        private var bPerspectiveUV:Boolean;// テクスチャの透視補正を行う
        
        /// コンストラクタ、生成した自分を即座に親クラスに貼り付ける
        public function TestSprite(parent:Sprite, x:Number, y:Number, width:Number, height:Number, bPerspectiveUV:Boolean) 
        {
            super();
            this.x = x;
            this.y = y;
            this.w = width;
            this.h = height;
            this.bPerspectiveUV = bPerspectiveUV;
            parent.addChild(this);
            // 画像読み込み
            [Embed(source = 'uv_color.png')]
            var res:Class;
            bm = Bitmap(new res).bitmapData;
            // 正方形の板を構築
            vecSource = new Vector.<Vector3D>();
            vecSource.push(new Vector3D( -200, -100, -200));
            vecSource.push(new Vector3D( +200, -100, -200));
            vecSource.push(new Vector3D( +200, -100, +200));
            vecSource.push(new Vector3D( -200, -100, +200));
            vecDest = new Vector.<Number>(8);
            ivec = new Vector.<int>();
            ivec.push(0); ivec.push(1); ivec.push(2);
            ivec.push(2); ivec.push(3); ivec.push(0);
            var sizeUV:int;
            sizeUV = bPerspectiveUV ? 3:2;
            uvt = new Vector.<Number>(sizeUV * 3);
            uvt[       0] = 0; uvt[         1] = 0;
            uvt[sizeUV  ] = 1; uvt[sizeUV  +1] = 0;
            uvt[sizeUV*2] = 1; uvt[sizeUV*2+1] = 1;
            uvt[sizeUV*3] = 0; uvt[sizeUV*3+1] = 1;
            // 毎フレーム処理登録
            addEventListener(Event.ENTER_FRAME, onEnterFrame);
        }
        
        /// 毎フレーム処理
        public function onEnterFrame(event:Event):void
        {
            var n:int;
            var x:Number;// 座標算出中間値
            var y:Number;
            var z:Number;
            var cx:Number;// スクリーンの中央座標
            var cy:Number;
            var rxs:Number;// 横回転用行列の一部
            var rxc:Number;
            var fyc:Number;// 縦浮遊用
            var matrix2:Matrix = new Matrix();
            var distance:Number = 400;// 視点から正方形の中心までの距離300
            // スクリーンをクリア
            graphics.clear();
            // 背景のグラデーションを描く
            matrix2.createGradientBox(w, h, Math.PI/2, 0, 0);
            graphics.beginGradientFill(GradientType.LINEAR, [0x80c0ff, 0xffffff], [1, 1], [0, 255], matrix2);
            graphics.moveTo(0, 0);
            graphics.lineTo(w, 0);
            graphics.lineTo(w, h);
            graphics.lineTo(0, h);
            graphics.endFill();
            // 全体演算
            rxs = Math.sin(rotateX * Math.PI / 180);
            rxc = Math.cos(rotateX * Math.PI / 180);
            fyc = Math.cos(floatY  * Math.PI / 180) + 0.5;
            cx = w * 0.5;
            cy = h * 0.5;
            // 頂点単位演算
            for (n = 0; n < vecSource.length; n++)
            {
                // 縦軸で回転、ついでに[X右,Y上,Z前]の座標系を[X右,Y下,Z奥]に変換
                x =  vecSource[n].x * rxc - vecSource[n].z * rxs;
                y = -vecSource[n].y * fyc;
                z = -vecSource[n].z * rxc - vecSource[n].x * rxs + distance;
                if (bPerspectiveUV)
                {
                    uvt[n * 3 + 2] = 1 / z;// z値の演算結果はUV透視補正にしか使用しないためUVと一緒に扱われる
                }
                // 最終スクリーン座標算出
                z = distance / z;
                vecDest[n*2  ] = cx + x * z;
                vecDest[n*2+1] = cy + y * z;
            }
            graphics.beginBitmapFill(bm, null, true, true);
            graphics.drawTriangles(vecDest, ivec, uvt);
            graphics.endFill();
            rotateX++;
            floatY += 0.35;
        }
    }

}

uv_color.png
uv_color.png
inserted by FC2 system