Flash與3D編程探祕(五)- 攝像機旋轉和移動

上一篇討論了關於旋轉攝像機的一些基礎知識,在這篇文章中我將介紹如何在程序中使用這些知識定點旋轉攝像機,以及把移動和旋轉攝像機結合在一起。當只運用旋轉攝像機時,在屏幕上看到物體圍繞着攝像機旋轉,動畫並不是那麼的"3D",不過這個是必經之路,等你完全的明白了旋轉這個概念後,再添加上攝像機在3D空間移動,那樣你就不會覺得乏味了。首先來看一個定點旋轉攝像機的例子當作熱身。這個例子,還是使用我們的小P,不過是很多的小P,這樣使的場景看起來更加的有層次感。運行程序(效果如下),所有的物體都在圍繞攝像機旋轉,我想你會有攝像機在不停的旋轉的錯覺(或者沒有...)。

 


定點旋轉攝像機

動畫製作步驟:

1. 一開始還是一些老步驟,設定原點,建立一個舞臺,還有定義攝像機,這些都是前幾篇所討論過的,就不再過多解釋了。

<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

-->// same as usual
var origin = new Object();
origin.x 
= stage.stageWidth/2;
origin.y 
= stage.stageHeight/2;
origin.z 
= 0;

var scene 
= new Sprite();
scene.x 
= origin.x;
scene.y 
= origin.y;
this.addChild(scene);

var camera 
= new Object();
camera.x 
= 0;
camera.y 
= 0;
camera.z 
= 0;
camera.panning 
= 0;

var focal_length 
= 300;


2. 下面定義一些常量,比如物體的總數量,PI和物體z間距。

<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

-->// constants
var MAX_OBJ = 100;
var PI 
= 3.1415926535897932384626433832795;
var DISTANCE_Z 
= 20;                // the distance to your camera

 

3. 下面是初始化所有的物體,運用隨機數產生小P所在的角度(對於攝像機),遞增小P所在點到攝像機的距離(3D空間的),使用三角函數就可以得到小P的x和z,同樣使用隨機數產生它的y,最後把它添加到舞臺上。

<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

-->// now create lots of balls around your camera
for (var i = 0; i < MAX_OBJ; i++)
{
    var ball 
= new Sphere();
    ball.angle 
= Math.random()*(0-PI*2+ PI*2;            // this is the rotate angle on the xz plane
    ball.dist_center = 140 + (MAX_OBJ-i)* DISTANCE;     // the distance to your camera
    ball.x_3d = Math.cos(ball.angle) * ball.dist_center;    // then we use trig to get x
    ball.z_3d = Math.sin(ball.angle) * ball.dist_center;     // and z
    ball.y_3d = Math.random()*(-240-240+ 240;          // now put the ball at random y
    scene.addChild(ball);                                            // add the ball to the collection
}

 

4. 對於每一個物體,在攝像機轉動角度時刷新它的大小和位置。於是下一步寫一個函數來達到目的,首先要確定小P相對於攝像機的旋轉角度。然後根據這個角度和攝像機和小P之間的垂直距離,算出它到攝像機的x,z和y的距離。最後,還是運用之前學過的算法,縮放和移動物體。

<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

-->// update ball size and position
function update(obj)
{
    
// get the angle relative to your camera panning angle
    var angle = obj.angle - camera.panning;
    var x_pos 
= Math.cos(angle)*obj.dist_center - camera.x;        // use trig calculate the x
    var z_pos = Math.sin(angle)*obj.dist_center - camera.z;        // and z
    var y_pos = obj.y_3d - camera.y;                       // calculate the relative y
    
    
if (z_pos > 0)                                                  // if the ball isin front of the camera
    {
        
if (!obj.visible)                                
            obj.visible 
= true;                                    // make the ball visible anyway
            
        var scale 
= focal_length/(focal_length+z_pos);      // cal the scale of the ball
        obj.x = x_pos*scale;                              // calcualte the x position in a camera view 
        obj.y = y_pos*scale;                             // and y position
        obj.scaleX = obj.scaleY = scale;              // scale the ball to a proper state
    }
    
else
    {
        obj.visible 
= false;
    }
}

 

5. 寫一個循環函數,在每一次執行時,遞增攝像機的角度,並且刷新舞臺上的所有的物體。

<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

-->function run(e:Event)
{
    camera.panning 
+= 0.01;                           // increase the panning angle
    
    
if (camera.panning > 2*PI)
        camera.panning 
-= 2*PI;
    
if (camera.panning < -1*2*PI)
        camera.panning 
+= 2*PI;
    
    
for (var i = 0; i < scene.numChildren; i++)    // update all the balls on the screen
    {
        update(scene.getChildAt(i));
    }
}

// add loop event listener

this.addEventListener(Event.ENTER_FRAME, run);

 

注意:

這裏提到的旋轉,都是在保持y不變的情況下,橫向旋轉攝像機,換句話說,讓攝像機繞着y軸旋轉,當然同理也可以寫出攝像機圍繞着x軸旋轉的函數。不過如何同時進行上述兩種旋轉,我將在後面的文章裏進行介紹。


移動和旋轉的組合

現在你已經知道如何橫向旋轉攝像機,同時前幾篇文章中也已經介紹瞭如何移動攝像機,如果把這兩個操作結合在一起,那一定很棒。我想你應該覺得不會很 困難,因爲前面已經把兩個分開操作學會了,下面所要做的只是把這兩種操作組合在一起。來看一個動畫,其中發灰的攝像機是運動前的位置,另外一個是向後(沿攝像機鏡頭的反方向)移動後位置(當攝像機鏡頭垂直向上看得時候移動得到),從動畫中可以看到,對於攝像機鏡頭來說,景物的位置是不一樣的。


移動加旋轉攝像機


再來看一個圖例,在這個圖片中,攝像機沿BO方向向後移動,我們可以看出,攝像機的轉角是不變的。那麼就可以結合攝像機移動的位置和三角函數就可以算出它的x移動量(圖中紅色實線)和y移動量(圖中藍色實線),進而便可以算出對於移動後攝像機而言,小P的x和y。

 

移動和旋轉角度的圖解

 

需要注意的是,當你首先旋轉攝像機,然後向後或者向前移動攝像機,那麼攝像機是沿着攝像機旋轉過後的角度運動的,至於移動量和物體到現在攝像機的距離,一樣可以使用三角函數得到(三角函數!Nice!)。下面就看一個應用的例子:

 



定點旋轉攝像機,WS前後移動攝像機,AD旋轉



動畫製作步驟

1. 重複前面的3步。

<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

-->// constants
var MAX_OBJ = 100;
var PI 
= 3.1415926535897932384626433832795;
var DISTANCE_Z 
= 20;                                                 // the z distance to your camera

// same as usual
var origin = new Object();
origin.x 
= stage.stageWidth/2;
origin.y 
= stage.stageHeight/2;
origin.z 
= 0;

var scene 
= new Sprite();
scene.x 
= origin.x;
scene.y 
= origin.y;
this.addChild(scene);

var camera 
= new Object();
camera.x 
= 0;
camera.y 
= 0;
camera.z 
= 0;
camera.panning 
= 0;

var movement 
= 0;

var focal_length 
= 300;

var pan_left;
var pan_right;
var move_forward;
var move_backward;

// now create lots of balls around your camera
for (var i = 0; i < MAX_OBJ; i++)
{
    var ball 
= new Sphere();
    ball.angle 
= Math.random()*(0-PI*2+ PI*2;                // this is the rotate angle on the xz plane
    ball.dist_center = (MAX_OBJ-i)* DISTANCE_Z;              // the z distance to your camera
    ball.x_3d = Math.cos(ball.angle) * ball.dist_center;        // then we use trig to get x
    ball.z_3d = Math.sin(ball.angle) * ball.dist_center;         // and z
    ball.y_3d = Math.random()*(-300-300+ 300;              // now put the ball at random y
    scene.addChild(ball);                                                // add the ball to the collection
}

 

2. 下面這個函數是和上面例子中不同的主要部分。首先要得到物體和攝像機的x,y和z距離,然後使用反三角函數就可以得出物體所在的角度,同時使用勾股定理得到物體和攝像機的距離(注意y距離爲0),同理使用三角函數便可以得到在攝像機移動之後物體的x和z。然後再根據物體的x和z對物體進行2D空間的縮放和移動。

<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

-->// update ball size and position
function display(obj)
{
    var x_pos 
= obj.x_3d - camera.x;            // calculate the x distance from obbject to the camera
    var y_pos = obj.y_3d - camera.y;            // and y distance
    var z_pos = obj.z_3d - camera.z;            // and z distance
    
    var angle 
= Math.atan2(z_pos, x_pos);                    // caculate the relative angle
    
// now get the actual object radius around camera
    var radius = Math.sqrt(z_pos*z_pos + x_pos*x_pos);
    
    x_pos 
= Math.cos(angle+camera.panning)*radius;    // get the x position after panning
    z_pos = Math.sin(angle+camera.panning)*radius;     // and y position
    
    
if (z_pos > 0)                                                    // if the ball isin front of the camera
    {
        
if (!obj.visible)                                
            obj.visible 
= true;                                      // make the ball visible anyway
            
        var scale 
= focal_length/(focal_length+z_pos);  // cal the scale of the ball
        obj.x = x_pos*scale;                                     // calcualte the x position in a camera view 
        obj.y = y_pos*scale;                                    // and y position
        obj.scaleX = obj.scaleY = scale;                     // scale the ball to a proper state
    }
    
else
    {
        obj.visible 
= false;
    }
    
    txt_z.text 
= int(camera.z)+"";
    txt_panning.text 
= Number(camera.panning*(180/Math.PI)).toFixed(1+ "";
}

 

3. 寫一個循環函數,在每一次執行時刷新舞臺上的所有的物體。

<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

-->function run(e:Event)
{
    
if (camera.panning > 2*PI)
        camera.panning 
-= 2*PI;
    
if (camera.panning < -1*2*PI)
        camera.panning 
+= 2*PI;
    
    
for (var i = 0; i < scene.numChildren; i++)                    // update all the balls on the screen
    {
        display(scene.getChildAt(i));
    }
}

 

4. 下面設置一些鍵盤相應事件,使用WS可以使攝像機前進和後退,AD旋轉攝像機。鍵盤事件在前面提到過,就不多說了,如果有什麼問題的話可以查看一下前面的例子。
<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

-->function run(e:Event)
{
    
if (camera.panning > 2*PI)
        camera.panning 
-= 2*PI;
    
if (camera.panning < -1*2*PI)
        camera.panning 
+= 2*PI;
    
    
for (var i = 0; i < scene.numChildren; i++)             // update all the balls on the screen
    {
        display(scene.getChildAt(i));
    }
}

function key_down(e:KeyboardEvent):
void
{
    
if (e.keyCode == 65)
        pan_left 
= true;
    
if (e.keyCode == 68)
        pan_right 
= true;
    
if (e.keyCode == 87)
        move_forward 
= true;
    
if (e.keyCode == 83)
        move_backward 
= true;
}
function key_up(e:KeyboardEvent):
void
{
    
if (e.keyCode == 65)
        pan_left 
= false;
    
if (e.keyCode == 68)
        pan_right 
= false;
    
if (e.keyCode == 87)
        move_forward 
= false;
    
if (e.keyCode == 83)
        move_backward 
= false;
}
function key_response(e:Event):
void
{
    
if (pan_left)
        camera.panning 
+= 0.015;                    // increase the panning angle
    if (pan_right)
        camera.panning 
-= 0.015;                    // decrease the panning angle
    if (move_forward)
    {
        movement 
= 20;
    }
    
if (move_backward)
    {
        movement 
= -20;
    }
    
if (move_forward || move_backward)
    {
        camera.x 
+= Math.sin(camera.panning)*movement;
        camera.z 
+= Math.cos(camera.panning)*movement;
    }
}

// add loop event listener
this.addEventListener(Event.ENTER_FRAME, run);
this.addEventListener(Event.ENTER_FRAME, key_response);
stage.addEventListener(KeyboardEvent.KEY_DOWN, key_down);
stage.addEventListener(KeyboardEvent.KEY_UP, key_up);

 

 

基本的陰影效果

當舞臺上的物體非常多的時候,我們希望能夠讓物體層次分明。你應該還記得第一篇文章裏是怎樣給物體加上層次感的,是根據物體在舞臺上的z來進行排序,離攝像機最近的物體的層次就最高。當然還有其他的技巧還給物體添加層次感,比如可以利用陰影效果來給舞臺上的物體加上層次感,還比如可以利用光源,日出日落等等因素給物體分層次,使場景變得更真實。光源這個課題相對來說比較複雜,需要加進大量的數學運算,將在後面的物體篇介紹。在這篇文章裏,我將給你介紹如何在3D空間裏使用簡單霧的效果。


霧影效果

 

製作步驟

下面把前面的旋轉攝像機的例子加工一下來達成霧影效果。添加霧的原理是這樣的,離攝像機越遠的物體,那麼它的亮度就越大(因爲場景是白色),大部分步驟都是和這篇文章的第一個例子的相同,只需要在每一次刷新物體縮放和位置的函數裏添加如下代碼。首先求出物體和攝像機的z距離,然後算出物體RGB的值,並對物體着色。非常的簡單,就把這個例子當作是小練習。

<!--

Code highlighting produced by Actipro CodeHighlighter (freeware)
http://www.CodeHighlighter.com/

-->var tint =  Math.min(z_pos/5255);
var color_trans:ColorTransform 
= new ColorTransform();
color_trans.redOffset 
= tint;
color_trans.greenOffset 
= tint;
color_trans.blueOffset 
= tint;
obj.transform.colorTransform 
= color_trans;

 

注意:

在這兩個些例子裏,並沒有涉及到物體層次,你在開發的時候,最好加上一個層次排序。這個算法在第一篇文章裏就已經實現,你可以試着把那個函數添加到這兩個例子裏。

建議:

在開發的時候,我建議你使用面向對象的書寫方式,這樣便於你的管理。我一直沒有使用OO的寫法,是因爲我不想給讀者的閱讀造成不必要的困惑,你可以試着把例子中的代碼寫成類,然後從.fla文件調用。例如你可以把例子中的小P寫成一個類,它可以具有x_3d,y_3d,z_3d等屬性。

 

上一篇          目錄          下一篇

非常抱歉,文中暫時不提供源文件下載,如果你需要源文件,請來信或者留言給我。筆者利用工作之餘寫這些文章,付出了很多汗水,希望讀者和轉載者能夠尊重作者的勞動。

作者:Yang Zhou
出處:http://yangzhou1030.cnblogs.com
本文版權歸作者和博客園共有,轉載未經作者同意必須保留此段聲明。請在文章頁面明顯位置給出原文連接,作者保留追究法律責任的權利。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章