2017-06-30

[Flash] Starling DynamicLighting

[Flash] Starlingの学習 DynamicLighting

環境:

  • Starling 2.1
  • Starling Extension - Dynamic Lighting 2.0
  • Adobe AIR SDK 22
  • FlashDevelop 5.2
  • Windows 7 64bit

[話者] Dynamic Lightingについて学ぶぞ。

http://wiki.starling-framework.org/extensions/dynamic_lighting で例示されてる機能だ。

そこで気になってるのが、Normap Mapなんだよ。色がついているだろ。

[合いの手] この色ってなんの意味があるの?

[話者] そのページで紹介されてるツールSpriteIlluminatorのページ見ると、色はAngle(角度)を表す、と書いてある。2Dグラフでいう接線の傾き、みたいなものかな。

[合いの手] ふーん。

[話者] でも、こういうのはHeight(高さ)情報だけあれば、ある程度は表現できるはずなんだよな。Height情報は色のないgray scaleで表せるので、色をつけるこのツール使わなくてもいけるのだが‥‥。

[合いの手] このSpriteIlluminatorというツールを使いたくないの?

[話者] このツール、WindowsXPで動作しないことを確認した。正直XPで動作しないツールは買いたくない‥‥。と言っても、ただの画像なので画像編集ソフトでも作れる。https://www.google.co.jp/search?q=normal+mapで検索すると Photoshopで作ってたりOnlineで作れたりするようだぞ。

というか それ以前に、キャラクターのアニメーション動作パターン1つ1つに色つきNormal Map作る作業は大変だ。 色つけず、gray scaleでいいのなら、まだやりようがある。

というわけで、色を外したNormal Mapでどう表示されるか確認した。

Colorless Normal Map = Height Map ?

まず http://wiki.starling-framework.org/extensions/dynamic_lighting のdownloadで、Githubに提供されてるサンプルを取得する。

PhotoshopCS6でNormalMapを [Image - Mode - GrayScale] に変換する。これでHeight map(ハイトマップ)として使えるデータになった。

  • 色つきのNormal Map(法線マップ)は高さ(Height)と角度(法線ベクトル、Angle)の情報を持つ。
  • 色なしのHeight Map(ハイトマップ)は高さ(Height)情報のみ持つ。

これを同じ条件でDynamic Lightingしてみよう。

compare Normal Map with Height Map

Main.as は、これまでの違いは横幅を650pxにしている箇所だけ。このアニメーションキャラが横幅325pxだったので、重ねずに2つ並べると650pxになる。

package
{
    import flash.display.Sprite;
    import flash.display.StageAlign;
    import flash.display.StageScaleMode;
    import flash.events.Event;
    import starling.core.Starling;
    
    /**
     * ...
     * @author foo
     */
    [SWF(width="650",height="480",frameRate="60",backgroundColor="#002143")]
    public class Main extends Sprite 
    {
        private var stln:Starling;
        public static var frameRate:uint = 0;
        
        public function Main() 
        {
            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
            stage.scaleMode = StageScaleMode.NO_SCALE;
            stage.align = StageAlign.TOP_LEFT;
            stage.stage3Ds[0].addEventListener(Event.CONTEXT3D_CREATE, onContext3dCreated);
            stln = new Starling(Game, stage);
            stln.showStats = true;
            stln.antiAliasing = 0;
            stln.start();
        }
        
        private function onContext3dCreated(e:Event):void 
        {
            stage.stage3Ds[0].removeEventListener(Event.CONTEXT3D_CREATE, onContext3dCreated);

            if (Starling.context.driverInfo.toLowerCase().indexOf("software") != -1) {
                // GPU disable
                Starling.current.nativeStage.frameRate = 30;
            } else {
                // GPU enable
            }
            frameRate = Starling.current.nativeStage.frameRate;
        }
    }
}

Game.as は、Githubに提供されてるサンプルを参考にした。

package 
{
    import flash.display.Bitmap;
    import starling.animation.Juggler;
    import starling.core.Starling;
    import starling.display.MovieClip;
    import starling.display.Quad;
    import starling.display.Sprite;
    import starling.events.Event;
    import starling.events.Touch;
    import starling.events.TouchEvent;
    import starling.events.TouchPhase;
    import starling.text.TextField;
    import starling.textures.Texture;
    import starling.textures.TextureAtlas;
    import starling.extensions.lighting.LightSource;
    import starling.extensions.lighting.LightStyle;
    
    /**
     * ...
     * @author foo
     */
    public class Game extends Sprite 
    {
        [Embed(source="../assets/character.png")]
        private static const CharacterTexture:Class;

        [Embed(source="../assets/character_n.png")]
        private static const CharacterNormalTexture:Class;

        [Embed(source="../assets/character_h.png")]
        private static const CharacterHeightTexture:Class;
        
        [Embed(source="../assets/character.xml", mimeType="application/octet-stream")]
        private static const CharacterXml:Class;

        private var stgWidth:int;
        private var stgHeight:int;

        public function Game() 
        {
            if (stage) init()
            else addEventListener(Event.ADDED_TO_STAGE, init);
        }
        
        private function init(e:Event = null):void 
        {
            removeEventListener(Event.ADDED_TO_STAGE, init);
            addEventListener(Event.ENTER_FRAME, onGetGpuStyle);
        }
        
        private function onGetGpuStyle(e:Event):void  
        {
            if (Main.frameRate <= 0) return;
            removeEventListener(Event.ENTER_FRAME, onGetGpuStyle);
            
            addItem();
        }
        
        private function addItem():void 
        {
            stgWidth = stage.stageWidth;
            stgHeight = stage.stageHeight;

            addCharcters();
            addLights();
        }
        
        private function addLights():void 
        {
            var ambl:LightSource = LightSource.createAmbientLight();
            ambl.x = stgWidth * 0.5;
            ambl.y = stgHeight * 0.5;
            ambl.z = -50;
            //ambl.showLightBulb = true;
            
            var pla:LightSource = LightSource.createPointLight(0xffff00);
            pla.x = stgWidth * 0.2;
            pla.y = stgHeight * 0.3;
            pla.z = -50;
            pla.showLightBulb = true;
            
            var plb:LightSource = LightSource.createPointLight(0xffff00);
            plb.x = stgWidth * 0.8;
            plb.y = stgHeight * 0.3;
            plb.z = -50;
            plb.showLightBulb = true;
            
            var dirl:LightSource = LightSource.createDirectionalLight();
            dirl.x = stgWidth * 0.5;
            dirl.y = stgHeight * 0.3;
            dirl.z = -150;
            dirl.rotationY = -1.0;
            dirl.showLightBulb = true;
            
            addChild(ambl);
            addChild(pla);
            addChild(plb);
            //addChild(dirl);
        }
        
        private function addCharcters():void 
        {
            var chrTx:Texture = Texture.fromEmbeddedAsset(CharacterTexture);
            var chrNormalTx:Texture = Texture.fromEmbeddedAsset(CharacterNormalTexture);
            var chrHeightTx:Texture = Texture.fromEmbeddedAsset(CharacterHeightTexture);
            var chrXML:XML = XML(new CharacterXml());
            var txAt:TextureAtlas = new TextureAtlas(chrTx, chrXML);
            var normalTxAt:TextureAtlas = new TextureAtlas(chrNormalTx, chrXML);
            var heightTxAt:TextureAtlas = new TextureAtlas(chrHeightTx, chrXML);
            
            var tx:Vector.<Texture> = txAt.getTextures();
            var ntx:Vector.<Texture> = normalTxAt.getTextures();
            var htx:Vector.<Texture> = heightTxAt.getTextures();
            
            var mc1:MovieClip = addCharacter(tx, ntx);
            mc1.scaleX = -1;
            mc1.x = mc1.width + 100;
            mc1.y = 150;
            //var qd:Quad = new Quad(mc1.width, mc1.height, 0xcccccc);
            //qd.x = mc1.x; qd.y = mc1.y;
            //addChild(qd);
            addChild(mc1);
            Starling.juggler.add(mc1);

            var msg:TextField = new TextField(120, 24, 'NormalMap');
            msg.format.color = 0xFFFFFF;
            msg.x = stgWidth / 2 - 120;;
            msg.y = mc1.y - 48;
            addChild(msg);
            
            var mc2:MovieClip = addCharacter(tx, htx);
            mc2.x = mc2.width - 100;
            mc2.y = 150;
            addChild(mc2);
            Starling.juggler.add(mc2);
            
            msg = new TextField(120, 24, 'HeightMap');
            msg.format.color = 0xFFFFFF;
            msg.x = stgWidth / 2;
            msg.y = mc2.y - 48;
            addChild(msg);
        }
        
        private function addCharacter(tx:Vector.<Texture>, ntx:Vector.<Texture>):MovieClip
        {
            var mc:MovieClip = new MovieClip(tx, tx.length);
            var styl:LightStyle = new LightStyle(ntx[0]);
            styl.ambientRatio = 0.2;
            styl.diffuseRatio = 0.8;
            styl.specularRatio = 0.2;
            styl.shininess = 16;
            mc.style = styl;
            for (var i:int = 0; i < mc.numFrames; i++) {
                mc.setFrameAction(i, updateStyle);
            }
            return mc;
            
            function updateStyle(mc:MovieClip, frameId:int):void 
            {
                styl.normalTexture = ntx[frameId];
            }
        }
    }
}

実Flash。操作可。ポイントライトをマウスドラッグで移動できる。ダブルクリックで消灯・点灯可能。

[合いの手] 違いがあるね。Normal Mapのほうがやはり見栄えがよいよね。

[話者] Normal Mapでいくべきだろうなー、これは。

[合いの手] この照明は光を強くするとかできるの?

[話者] 光量を変えることはできないよ。その代わりPointLightのZ位置をShift+マウスドラッグで変更できるぞ。Z位置を変えることで光源を近く/遠くにすれば光量をコントロールできる。

[合いの手] なるほど。‥‥うまく位置変更できないなー。

[話者] Light側は、ソースコードレベルでもX/Y/Z位置と色の指定だけ可能で、光量は指定できない。Lightには3種類ある。

  • ambientLight (位置・角度に関係なくすべての物体を照らす。Global Illuminationでの物体同士の相互反射による間接光表現を簡易的に再現する)
  • pointLight (位置が近いものだけ照らす。ろうそくの光のような照明)
  • directionalLight (位置には関係なく、特定の角度から照らす。日光のような照明)

光が当たる物体側では以下の4要素を指定できる。

  • ambient(位置や角度に関わらず、ambientLightを表面で反射した光)
  • diffuse(角度によって表面の反射の仕方が異なる。散乱光)
  • specular(表面に当たった光源の反射。鏡面反射)
  • shininess(specularが大きくぼんやりしてるか、小さくくっきりしてるか)

0 件のコメント:

コメントを投稿

人気記事