一种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类,添加一个帧总数和下一帧控制。

1
2
3
4
5
6
7
8
9
10
11
12
13
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;
}

 

1
2
3
4
5
6
7
8
9
10
11
12
/**
 * 显示下一帧
 */
public function next():void{
    _frameIndex++;
    if(_frameIndex >= totalFrames)
    {
        _frameIndex = 0;
    }
    _currentTime =  endTime / totalFrames * _frameIndex;
    this._isStepping = true;
}

增加一个stepping方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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方法:

1
2
3
4
5
6
7
8
9
10
11
/**
 * Update.
 */
public function update() : void
{
    if(_isStepping)
    {
        stepping();
        return;
    }

 

简单说下DAE模型,他使用了时间去控制帧,因此需要计算开始时间、结束之间、总帧数,来换算控制下一帧播放。具体代码我会给出来。

 

然后完成我们的pv3dLoader:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
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

 

  

 

 

 

  

 

 

 

 

posted @     阅读(2990)  评论(0编辑  收藏  举报
编辑推荐:
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
阅读排行:
· 单线程的Redis速度为什么快?
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 展开说说关于C#中ORM框架的用法!
· SQL Server 2025 AI相关能力初探
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
历史上的今天:
2007-12-24 Acess查询的错误
IT民工
点击右上角即可分享
微信分享提示