Flash制作3D类动画的教程

作者:网络 来源:佚名 更新时间:2009-11-24 07:12:29 点击:

最近对flash3d效果产生了兴趣,下决心学习一下sandy。一下将学习的体会的感受写下来,一来帮助大家熟悉sandy,二来加强自己的记忆。

先从基础开始吧(基础解释转自flash3d研究所)

原理解释:

  • 窗口:
    用户观看的窗口,简单的可以想成就是flash里面的画布大小。窗口也可以理解成渲染的尺寸,否则画面就无限大了
  • 场景:
    场景是指整个三维的场景。
  • 摄像机:
    很多人要问,为什么有了摄像机还要窗口呢?摄像机是用来拍画面的,看画面还是得电视机/窗口不是吗,^_^
    渲染器:如果没有这个东西,所有以上的东西都只是数据,渲染器就是把所有数据变成图像的东西。

下面这幅图虽然并不算准确的表述,但希望能帮助我们理解:

然后开始写代码了(目前感觉sandy的代码还是比较简洁的):

要先将sandy的类库下载下来哦!!(在这里要谢谢 tenzn 的提醒,呵呵。)
官方网站:http://www.flashsandy.org
下载地址:http://sandy.googlecode.com/files/sandy3-1-1_src_rev1008.zip

先尝试创建一个立方体。

package
{
    import flash.display.sprite;
    import flash.events.event;
    import sandy.core.scene3d;
    import sandy.core.scenegraph.*;
    import sandy.primitive.*;
   
    /**
     * ...
     * @author ever5u
     */
    public class fuxi extends sprite
    {
        private var scene:scene3d;
        private var camera:camera3d;
        public function fuxi() {
            //创建一个摄像机
            camera = new camera3d(300, 300);
            camera.z = -300;
            //创建一个 group
            var root:group = createscene();
            //创建场景
            scene = new scene3d( "scene", this, camera, root );
            //创建实时侦听
            addeventlistener( event.enter_frame, enterframehandler );
        }
        var box = new box("box", 100, 100, 100);
        public function createscene() {
            var g:group = new group();
            g.addchild( box );
            return g;
        }
        public function enterframehandler(_evt:event) {
            box.rotatex = mousex;
            box.rotatey = mousey;
            scene.render();
        }
    }
   
}

成功了,效果如下:

|||

接下来尝试给这个立方体着色。

这里需要用到 sandy.materials.attributes 类。

其中为线着色的方法 lineattributes 有三个属性:
lineattributes(p_nthickness:uint = 1,  p_ncolor:uint = 0,  p_nalpha:number = 1)

  • p_nthickness:uint (default = 1) — 线的粗细
  • p_ncolor:uint (default = 0) — 线的颜色
  • p_nalpha:number (default = 1) —  线的透明度

这里设置是否使用光,需要先设置 lightingenable = true 。
lightattributes(p_bbright:boolean = false, p_nambient:number = 0.3)

  • p_bbright:boolean (default = false) — 设置是否支持光
  • p_nambient:number (default = 0.3) — 设置光的亮度(数值范围是 0 - 1)

为立方体渲染用
colormaterial(p_ncolor:uint = 0x00, p_nalpha:number = 1, p_oattr:materialattributes = null)

  • p_ncolor:uint (default = 0x00) — 颜色
  • p_nalpha:number (default = 1) — 透明度
  • p_oattr:materialattributes (default = null) — 线设置

代码如下:

package
{
    import flash.display.sprite;
    import flash.events.event;
    import sandy.core.scene3d;
    import sandy.core.scenegraph.*;
    import sandy.primitive.*;
    import sandy.materials.*;
    import sandy.materials.attributes.*;
   
    /**
     * ...
     * @author ever5u
     */
    public class fuxi extends sprite
    {
        private var scene:scene3d;
        private var camera:camera3d;
        public function fuxi() {
            //创建一个摄像机
            camera = new camera3d(300, 300);
            camera.z = -300;
            //创建一个 group
            var root:group = createscene();
            //创建场景
            scene = new scene3d( "scene", this, camera, root );
            //创建实时侦听
            addeventlistener( event.enter_frame, enterframehandler );
        }
        var box = new box("box", 100, 100, 100);
        public function createscene() {
            var g:group = new group();
            //设置立方体的颜色、线条色和环境光
            material.lightingenable = true;
            var materialattr:materialattributes = new materialattributes(
               new lineattributes( 0.5, 0x000000, 0.4 ),
               new lightattributes( true, 0.2)
            );
            var material:material = new colormaterial( 0xcc3300, 1, materialattr );
            var app:appearance = new appearance( material );
           
            box.appearance = app;
           
            g.addchild( box );
            return g;
        }
        public function enterframehandler(_evt:event) {
            box.rotatex = mousex;
            box.rotatey = mousey;
            scene.render();
        }
    }
   
}

这是效果:

|||

继续试试看用图片为立方体贴图。

首先导入一张位图到库里,并声明类名为 mypalm

package
{
    import flash.display.sprite;
    import flash.events.event;
    import flash.display.bitmap;
    import flash.display.bitmapdata;
    import flash.display.loader;
    import flash.net.urlrequest;
    import sandy.core.scene3d;
    import sandy.core.scenegraph.*;
    import sandy.primitive.*;
    import sandy.materials.*;
    import sandy.materials.attributes.*;
   
    /**
     * ...
     * @author ever5u
     */
    public class fuxi extends sprite
    {
        private var scene:scene3d;
        private var camera:camera3d;
        public function fuxi() {
            //创建一个摄像机
            camera = new camera3d(300, 300);
            camera.z = -300;
            //创建一个 group
            var root:group = createscene();
            //创建场景
            scene = new scene3d( "scene", this, camera, root );
            //创建实时侦听
            addeventlistener( event.enter_frame, enterframehandler );
        }
        var box = new box("box", 100, 100, 100);
        public function createscene() {
            var g:group = new group();
            //设置立方体的贴图
            var bitmap:bitmapdata = new mypalm(0, 0);
            var material:material = new bitmapmaterial( bitmap );
            var app:appearance = new appearance( material );
           
            box.appearance = app;
           
            g.addchild( box );
            return g;
        }
        public function enterframehandler(_evt:event) {
            box.rotatex = mousex;
            box.rotatey = mousey;
            scene.render();
        }
    }
   
}

效果:

|||

继续上次的学习,接下来要试试摄像机的移动了。
这里需要涉及几个概念,摄像机的坐标(x, y, z)和视觉角度(lookat)
摄像机的位置可以使用(x, y, z)来定位;
视觉角度可以定义如何通过窗口来看场景。
lookat(p_nx:number, p_ny:number, p_nz:number)
如:lookat(0,0,0);//可以理解为通过摄像机的位置看场景。

摄像机的移动方式比较有意思,如果直接修改x、y、z坐标,视觉效果会与现实看到的情况相同;
而tilt、pan则是与场景平行移动;
roll是以z轴移动,通过摄像机视野看上去是摄像机的旋转效果。

额外说一下 line3d 是在场景中绘制了线段,这里用做参考线,代码不难理解我就不过多解释了。

代码如下:

package
{
    import flash.display.sprite;
    import flash.events.*;
    import flash.ui.*;
    import flash.display.bitmap;
    import flash.display.bitmapdata;
    import flash.display.loader;
    import flash.net.urlrequest;
    import sandy.core.scene3d;
    import sandy.core.scenegraph.*;
    import sandy.primitive.*;
    import sandy.materials.*;
    import sandy.materials.attributes.*;
    import sandy.core.data.*;
    
    /**
     * ...
     * @author ever5u
     */
    public class fuxi extends sprite
    {
        private var scene:scene3d;
        private var camera:camera3d;
        public function fuxi() {
            //创建一个摄像机
            camera = new camera3d(300, 300);
            camera.x = 100;
            camera.y = 100;
            camera.z = -300;
            camera.lookat(0,0,0);
            //创建一个 group
            var root:group = createscene();
            //创建场景
            scene = new scene3d( "scene", this, camera, root );
            //创建实时侦听
            addeventlistener( event.enter_frame, enterframehandler );
            stage.addeventlistener(keyboardevent.key_down, keypressed);
        }
        var box = new box("box", 100, 100, 100);
        var plane:plane3d;
        public function createscene() {
            var g:group = new group();
            
            //在场景画一个坐标定位点
            plane = new plane3d("texture", 300, 300);
            var myxline:line3d = new line3d( "x-coord", new point3d( -20, 0, 0), new point3d( 20, 0, 0 ));
            var myyline:line3d = new line3d( "y-coord", new point3d(0, -20, 0), new point3d( 0, 20, 0 ));
            var myzline:line3d = new line3d( "z-coord", new point3d(0, 0, -20), new point3d( 0, 0, 20 ));
            
            g.addchild(myxline);
            g.addchild(myyline);
            g.addchild(myzline);
            g.addchild( box );
            return g;
        }
        public function enterframehandler(_evt:event) {
            box.rotatex = mousex;
            box.rotatey = mousey;
            scene.render();
        }
        public function keypressed(_evt:keyboardevent):void {
            switch(_evt.keycode) {
                case keyboard.up:
                    camera.tilt += 2;
                    //camera.y -= 2;
                    break;
                case keyboard.down:    
                    camera.tilt -= 2;
                    //camera.y += 2;
                    break;
                case keyboard.right:
                    camera.pan -= 2;
                    //camera.x += 2;
                    break;
                case keyboard.left:
                    camera.pan += 2;
                    //camera.x -= 2;
                    break;
                case keyboard.control:
                    camera.roll += 2;
                    break;    
                case keyboard.page_down:
                    camera.z -= 5;
                    break;
                case keyboard.page_up:
                    camera.z += 5;
                    break;    
            }
        }
    }
    
}

效果:

|||

还有场景自适应的问题,需要说明一下。

摄像机的镜头可视角度使用 fov(vertical field of view angle)来定义。

换算公式:

var fl:numer = (viewport.height / 2) / math.tan (camera.fov / 2 * (math.pi / 180));

代码:

package
{
    import flash.display.sprite;
    import flash.events.*;
    import flash.ui.*;
    import flash.display.bitmap;
    import flash.display.bitmapdata;
    import flash.display.loader;
    import flash.net.urlrequest;
    import flash.display.stage;
    import flash.display.stagealign;
    import flash.display.stagescalemode;
    import sandy.core.scene3d;
    import sandy.core.scenegraph.*;
    import sandy.primitive.*;
    import sandy.materials.*;
    import sandy.materials.attributes.*;
    import sandy.core.data.*;
   
    /**
     * ...
     * @author ever5u
     */
    public class fuxi extends sprite
    {
        private var scene:scene3d;
        private var camera:camera3d;
        public function fuxi() {
            stage.scalemode = stagescalemode.no_scale;
            stage.align = stagealign.top_left;
            //创建一个摄像机
            camera = new camera3d(300, 300);
            camera.x = 100;
            camera.y = 100;
            camera.z = -300;
            camera.lookat(0,0,0);
            //创建一个 group
            var root:group = createscene();
            //创建场景
            scene = new scene3d( "scene", this, camera, root );
            //创建实时侦听
            addeventlistener( event.enter_frame, enterframehandler );
            stage.addeventlistener (event.resize, onresize);
        }
        var box = new box("box", 100, 100, 100);
        var plane:plane3d;
        public function createscene() {
            var g:group = new group();
           
            //在场景画一个坐标定位点
            plane = new plane3d("texture", 300, 300);
            var myxline:line3d = new line3d( "x-coord", new point3d( -20, 0, 0), new point3d( 20, 0, 0 ));
            var myyline:line3d = new line3d( "y-coord", new point3d(0, -20, 0), new point3d( 0, 20, 0 ));
            var myzline:line3d = new line3d( "z-coord", new point3d(0, 0, -20), new point3d( 0, 0, 20 ));
           
            g.addchild(myxline);
            g.addchild(myyline);
            g.addchild(myzline);
            g.addchild( box );
            return g;
        }
        public function enterframehandler(_evt:event) {
            box.rotatex = mousex;
            box.rotatey = mousey;
            scene.render();
        }
        function onresize (e:event):void{
            // 获取场景宽高
            var w:number = stage.stagewidth;
            var h:number = stage.stageheight;
            // 设置视野宽高
            scene.camera.viewport.width = w;
            scene.camera.viewport.height = h;
            // 获取物体box与摄像机间的距离
            var d:number = box.getposition ("camera").getnorm ();
            // 保持摄像机观看比例
            scene.camera.fov = 2 * math.atan2 (h / 2, d) * (180 / math.pi);
            // 执行渲染
            scene.render();
        }
    }
   
}

效果: