stage3D很強大,但是客戶端硬件加速支持有限。
出來的圖形鋸齒嚴重,看上去和果凍一樣。
Stage3d不兼容2d模式。
總的來說,3D很美好,現實很殘酷。但是3D有無可比擬的優勢:那就是節省90%的帶寬和提升無限的顯示效果。
本文根據前輩的經驗,總結一種在中低模型下,3D顯示為2D的技術。顛覆傳統吧!
前言——為什么用3D?
在頁游界,不要相信3D所謂華麗的效果。至少2014年結束,也不需要去幻想。端游就另當別論。
但是3D只需要一個模型+一個貼圖,就完成了所有需要的人物顯示。如果用傳統序列圖,8個方向,每個方向12幀,每幀15K來計算,就需要1440K,接近1.5M。但是3D不超過200K。
多么誘人的性能啊。要知道節省了10倍帶寬,那就是一個服務器節省了幾千塊錢的帶寬了。
而且,任意角度、任意視覺,不需要美術處理。
所以,3D,必須的。
3D轉2D核心技術——PV2D
無論用alway3D, unity3D, stage3D,starling什么的,都不可能實現3D轉2D。所以必須把老祖宗拿出來,那就是 papervision3D.
哦。原來是這個。相信很多資深前端主程已經不屑一顧了。但是,就是這個papervision3D,現在還能再一次顛覆傳統。
但是pv3D有些缺陷,需要修改animationController類,添加一個幀總數和下一幀控制。
public function get totalFrames():int{ var count:int = 0; if(!this._channels) return count; for each(var _channel:Channel3D in this._channels) { if(_channel.output == null) continue; count = Math.max(_channel.output.length, count); } return count; }
/** * 顯示下一幀 */ public function next():void{ _frameIndex++; if(_frameIndex >= totalFrames) { _frameIndex = 0; } _currentTime = endTime / totalFrames * _frameIndex; this._isStepping = true; }
增加一個stepping方法
private function stepping():void { var channel : Channel3D; var et : Number = _clip ? _clip.endTime : endTime; var clipName :String = _clip ? _clip.name : "all"; if(_currentTime > et) { if (_dispatchEvents) { dispatchEvent(new AnimationEvent(AnimationEvent.COMPLETE, et, clipName)); } if(!_loop) { stop(); return; } if(_clip) { _currentTimeStamp -= (_clip.startTime * 1000); } _currentTime = _clip ? _clip.startTime : startTime; } for each(channel in _channels) { channel.update(_currentTime); } if (_isPlaying && _dispatchEvents) { dispatchEvent(new AnimationEvent(AnimationEvent.NEXT_FRAME, _currentTime, clipName)); } }
最后修改update方法:
/** * Update. */ public function update() : void { if(_isStepping) { stepping(); return; }
簡單說下DAE模型,他使用了時間去控制幀,因此需要計算開始時間、結束之間、總幀數,來換算控制下一幀播放。具體代碼我會給出來。
然后完成我們的pv3dLoader:
package com.xtar.loader.utils { import com.xtar.common.FilterCoder; import com.xtar.common.MovieClipInfo; import com.xtar.interfaces.IDisposable; import flash.display.BitmapData; import flash.display.DisplayObject; import flash.events.Event; import flash.events.EventDispatcher; import flash.events.IOErrorEvent; import flash.events.ProgressEvent; import flash.filters.GlowFilter; import flash.geom.Matrix; import flash.geom.Rectangle; import flash.utils.Dictionary; import flash.utils.getTimer; import org.papervision3d.events.FileLoadEvent; import org.papervision3d.objects.parsers.DAE; import org.papervision3d.view.layer.util.ViewportLayerSortMode; //http://www.flashquake.cn/?tag=pv3d 破圖的解決方法 //http://stackoverflow.com/questions/549766/papervision-render-to-bitmap public class Pv3dLoader extends EventDispatcher implements IDisposable { private var view:Pv3dContainer = new Pv3dContainer; private var dae:DAE = null; private var config:Pv3dConfig = new Pv3dConfig; private var frames:Dictionary = new Dictionary; private var timeStart:Number = -1; public function Pv3dLoader() { view.viewport.containerSprite.sortMode = ViewportLayerSortMode.Z_SORT; FilterCoder.addFilter(view, new GlowFilter(0x000000, 1, 1.5, 1.5, 2)); } public function load(config:Pv3dConfig):void{ this.config = config; if(config.width > 0) view.viewport.viewportWidth = config.width; if(config.height > 0) view.viewport.viewportHeight = config.height; view.viewport.autoScaleToStage = false; dae = new DAE(false); dae.addEventListener(FileLoadEvent.LOAD_COMPLETE, daeComplete); dae.addEventListener(IOErrorEvent.IO_ERROR, daeError); dae.addEventListener(FileLoadEvent.ANIMATIONS_PROGRESS, daeProgress); dae.load(config.url, null, false); timeStart = getTimer(); } public function get content():*{ return this.frames; } public function dispose():void{ frames = null; view = null; dae = null; config = null; } private function daeComplete(e:FileLoadEvent):void{ trace(getTimer() - timeStart); timeStart = getTimer(); dae.stop(); view.scene.addChild(dae); view.camera.z = -1 * (config.distance * Math.cos(Math.PI / 180 * config.angleGround)); view.camera.x = 0; view.camera.y = config.distance * Math.sin(Math.PI / 180 * config.angleGround); var rect:Rectangle = new Rectangle(-this.view.viewport.viewportWidth / 2, -this.view.viewport.viewportHeight / 2, this.view.viewport.viewportWidth, this.view.viewport.viewportHeight); for each(var direction:Number in config.directions) { dae.animation.next(); view.nextFrame();view.nextFrame(); for(var i:int = 1; i<dae.animation.totalFrames;i++) { dae.rotationY = direction; dae.animation.next(); view.nextFrame();view.nextFrame(); getFrames(direction).push(transferToBitmapData(view, rect)); } } trace(getTimer() - timeStart); loadComplete(); } private function transferToBitmapData(obj:DisplayObject, rect:Rectangle):MovieClipInfo{ var bitmap:BitmapData = new BitmapData(Math.ceil(rect.width), Math.ceil(rect.height), true, 0); bitmap.draw(obj); var info:MovieClipInfo = new MovieClipInfo; info.frameIndex = 0; info.x = rect.x; info.y = rect.y; info.data = bitmap; return info; } private function getFrames(direction:Number):Array{ if(frames[direction]) return frames[direction]; frames[direction] = new Array; return frames[direction]; } private function daeError(e:Event):void{ if(this.hasEventListener(IOErrorEvent.IO_ERROR)) this.dispatchEvent(new IOErrorEvent(IOErrorEvent.IO_ERROR, e.bubbles, e.cancelable, config.url)); } private function daeProgress(e:FileLoadEvent):void{ if(this.hasEventListener(ProgressEvent.PROGRESS)) this.dispatchEvent(new ProgressEvent(ProgressEvent.PROGRESS, e.bubbles, e.cancelable, e.bytesLoaded, e.bytesTotal)); } private function loadComplete():void{ this.dispatchEvent(new Event(Event.COMPLETE)); } } }
這樣,就可以通過截圖,得到了3D的序列圖,最后使用一個XMovieClip顯示出來:
當然,為了效果更好,我使用了一點點外發光。
后續——性能與可行性分析:
如果加載1500個面的3D模型,幾乎沒有性能問題。也許這個就是pv3D的瓶頸。如果超過了1500面,就會出現停頓問題。
主要性能問題集中在:
DAE的XML解析、bitmapData.draw方法非常慢。
因此,一個游戲,除了BOSS、主角,基本上其他的角色都可以用這個方法進行加載。
而且是 任意尺寸、任意角度、任意動作!!!!
代碼下載:
pv2d,轉換核心引擎
https://app.box.com/s/6pwhv6b65o9uylpzmjeo
修改的pv3d版本:
https://app.box.com/s/wayokxv5feldgjp9gexf
文章列表