• 一种Flash页游前端3D转2D显示技术——PV2D, 颠覆传统吧!


    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

      

      

  • 相关阅读:
    爬虫-selenium模块
    动画《区块链100问》第4集:第一个比特币诞生啦!
    动画《区块链100问》第5集:谁是中本聪?
    动画《区块链100问》第6集:密码朋克是什么?
    动画《区块链100问》第7集:比特币是怎么发行的?
    动画《区块链100问》第8集:披萨居然卖到3亿元?
    动画《区块链100问》第9集:中本聪的继任者是谁?
    动画《区块链100问》第10集:早期比特币还能白送!
    《区块链100问》第11集:比特币为什么还没挖完?
    《区块链100问》第12集:比特币如何实现总量恒定?
  • 原文地址:https://www.cnblogs.com/zc22/p/3489553.html
Copyright © 2020-2023  润新知