1. 程式人生 > >ThreeJS之動畫交互邏輯及特效

ThreeJS之動畫交互邏輯及特效

坐標 -s function ces shader reload cto osi xtu

工作需要,研究了一下 threejs 簡單邏輯動畫交互方法。寫了一個小示例,分享一下,挺醜的。

第一步

當然就是初始化 threejs 的渲染場景了。

var camera; //相機
var scene;//場景
var renderer;//webGL渲染器
var controls;//軌道控件,用於特定場景,模擬軌道中的衛星,可以用鼠標和鍵盤在場景中遊走 
var raycaster;//THREE.Raycaster對象從屏幕上的點擊位置想場景中發射一束光線,返回射線穿透物體的數組
var composer;//後期特效合成器,給場景選中物體添加發光特效

第二步

在 ThreeJs Editor 中建立簡單的示例模型,“Export Scene”,導出。並導入示例程序。免去了在示例程序中自己建模的麻煩,不過因為示例程序要加載本地的json,所以可以設置一個簡單的 nodejs 服務器。

在 nodejs 的 anywhere 下運行該示例:

技術分享

加載模型文件,將文件中的相關 object 加入 group 中:

          var url = ‘nofloor.json‘;
                var loader= new THREE.ObjectLoader();
          var geometry = new THREE.Geometry();//存放objects的position坐標,為之後線條的起始點坐標服務 loader.load( url,
function ( loadedScene ) {
//scene = loadedScene; var objects = loadedScene.children; for(var i=0;i<objects.length;i++){ if(objects[i].type == ‘Mesh‘ ){ objects[i].receiveShadow = true; objects[i].castShadow
= true; geometry.vertices.push(objects[i].position); group.add(objects[i]); } } } , onProgress, onError);

導入的模型差不多就是這樣子(醜一點,擔待),並在示例程序中加了stats 和 dat.gui 用來檢測渲染效果和改變特效參數。

技術分享

第三步

完成的交互目標是,點擊上圖中某個柱體選中,出現相應的連線,並且讓選中的柱體和連線發光。

現在先利用 raycaster 選中物體:

var mouse = new THREE.Vector2(); //鼠標經過或者點擊的屏幕 canvas 上的位置           
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;//將 canvas 坐標系轉換為 WebGL 坐標系
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
raycaster.setFromCamera( mouse, camera );// raycaster的作用場景
var intersects = raycaster.intersectObjects( [group], true );//從鼠標 mouse 位置發射線,選中 group 組中的objects ,並返回 objects 給 intersects 

一旦 intersects 不為空,intersects[0].object 就是鼠標選中的物體,可以是上圖中的正方體,也可以是上圖中的地板。

接下來,皆可以根據選中物體來連線了。連線呢有兩種方法。

第一種

技術分享

代碼如下:

var material = new THREE.LineBasicMaterial({
    color: 0x0000ff
});

var geometry = new THREE.Geometry();
geometry.vertices.push(
    //各個柱體的 position 坐標,也就是上面加載模型文件時候生成的 geometry.vertices
 );
// THREE.Line 會將 geometry.vertices 中所有坐標點連成一條連續線,但不是首尾相接。
//比如 geometry.vertices 中存放了 v1,v2,v3,v4四個三維坐標點,就會生成v1->v2->v3->v4 連續線,中間有三條線。
var line = new THREE.Line( geometry, material );

但是有一個缺點就是由於受限於 角度層(ANGLE layer),在Windows平臺上使用 WebGL,線寬將總是為1而不管設置的值。這樣一旦模型是五六米高,可是線條只有不到1cm的寬度,看起來模型就會很奇怪了。這時候就要用第二種方法。

第二種

畫圓柱體,用圓柱體代替線段,但是生成圓柱體就比線段復雜多了。大家知道,threejs 只會指定一個object的position(中心),而不能指定兩端的位置(我還沒發現,若有錯誤,請指正),所以畫的初始圓柱體是直立的,如下圖

技術分享

讓我們要的是下圖這樣的圓柱體(下面統稱為“柱子線”)

技術分享

因為半徑是 0.02,所以看起來像線段,這也是為什麽要用圓柱體模擬線段的原因了,逼真,而且還能根據模型大小調整這個柱子線的“linewidth”。

下面我們看看怎麽怎麽根據正方體個球體的坐標動態生成柱子線吧。

上面我們講了如何選中物體,選中之後,我們把這個物體相鄰的物體(建模時候,將所有物體坐標順序放在 geometry.vertices 中,此處默認坐標相鄰就是物體相鄰)的 position 坐標加入 geometryChange.vertices 中

               var object = intersects[0].object;
                        geometryChange = new THREE.Geometry();
                        var position = intersects[0].object.position;//當前選中物體的坐標
                        //搜索 geometry.vertices 中的 position 重新繪制選中物體相關linet
                        var p = geometry.vertices.length;
                        for(i=0;i<p;i++){
                            if(geometry.vertices[i] == position){

                                if (i == p - 1){//將最後一個物體的前一個物體坐標加入
                                    geometryChange.vertices.push(geometry.vertices[p - 2]);
                                    geometryChange.vertices.push(position);
                                }
                                else if(i == 0){//將第一個物體的後一個坐標加入
                                    geometryChange.vertices.push(position);
                                    geometryChange.vertices.push(geometry.vertices[i+1]);
                                }

                                else{
                                    geometryChange.vertices.push(geometry.vertices[i-1]);
                                    geometryChange.vertices.push(position);//將物體前後相鄰的加入
                                    geometryChange.vertices.push(geometry.vertices[i+1]);
                                  
                                }
                            }
                        }

這樣我們就把選中物體相鄰的物體坐標放在 geometryChange.vertices。現在我們知道柱子線的起點和終點坐標了,那柱子線怎麽畫,畫哪裏呢?

              var temp = geometryChange.vertices.length;
                        var xyz = geometryChange.vertices;
               //position(x,y,z),就是柱子線的中點位置,xw是起點和中點的 X 軸方向距離,zh是起點和中點的 Z 軸方向距離,cheight是起點和中點的空間距離
               var x,y,z,xw,zh,cheight;

先知道柱子線的position(x,y,z),xyz[i]是柱子線起點,xyz[i+1]是柱子線終點。

x= (xyz[i].x+xyz[i+1].x)/2;
y=0.1;//線我是畫在地面附近的,所以y默認0.1
z=(xyz[i].z+xyz[i+1].z)/2

再來求柱子線的長度

xw=xyz[i].x-xyz[i+1].x;
zh=xyz[i].z-xyz[i+1].z;
cheight=Math.sqrt(xw*xw+zh*zh);//圓柱體長度,勾股定理

這下畫柱子線

var material = new THREE.MeshPhongMaterial( {
                                        color: 0x156289,
                                        emissive: 0x00FFFF,
                                        side: THREE.DoubleSide,
                                        shading: THREE.FlatShading,
                                        vertexColors:THREE.FaceColors
                                    } );
var cylinder = new THREE.Mesh( geometryCylinderLine, material );
cylinder.position.set( x, y, z );//兩實體的中點,也就是柱子線的中點,自己理解

可是發現畫的柱子是豎直向上的

技術分享

這個時候就需要改變柱子線的模型矩陣的,對它做旋轉,達到我們理想的效果。

我們先分析一下怎麽旋轉,首先將繞 x 軸轉90° ,讓柱子線躺地上。

cylinder.rotation.x -= Math.PI * 0.5;

之後如下圖分析所示,紅線就是躺地上的柱子(自行腦補3D場景)。

技術分享

其中紅線和黑線長度相同,紅線只需要旋轉 θ 角度之後就可以和黑線重合,達到我們要的效果。

θ = Math.asin(xw/cheight);//弧度制

這個時候知道轉多少度了,轉就ok

                 //考慮到局部坐標系和全局坐標系的轉換,柱體是在全局坐標系下旋轉
                            if(xyz[i].x > xyz[i+1].x && xyz[i].z < xyz[i+1].z)
                                cylinder.rotation.z -= Math.asin(xw/cheight);//Math.asin(xw/cheight)為柱體要旋轉的角度
                            else if(xyz[i].x > xyz[i+1].x && xyz[i].z > xyz[i+1].z)
                                cylinder.rotation.z += Math.asin(xw/cheight);
                            else if(xyz[i].x < xyz[i+1].x && xyz[i].z < xyz[i+1].z)
                                cylinder.rotation.z -= Math.asin(xw/cheight);
                            else
                                cylinder.rotation.z += Math.asin(xw/cheight);

好了,柱子線畫出來了。

當然不止一條柱子線,把當前的都加入lineGroup 中

lineGroup.add( cylinder );
scene.add( lineGroup );

下次繪制的時候只要移除當前的 lineGroup 即可。

scene.remove( lineGroup );

第四步

加發光特效

借助threejs的 outlinePass 通道

          composer = new THREE.EffectComposer( renderer );

                var renderPass = new THREE.RenderPass( scene, camera );
                composer.addPass( renderPass );

                outlinePass = new THREE.OutlinePass( new THREE.Vector2( window.innerWidth, window.innerHeight ), scene, camera );
                composer.addPass( outlinePass );

                var onLoad = function ( texture ) {

                    outlinePass.patternTexture = texture;
                    texture.wrapS = THREE.RepeatWrapping;
                    texture.wrapT = THREE.RepeatWrapping;

                };

                var loader = new THREE.TextureLoader();

                loader.load( ‘tri_pattern.jpg‘, onLoad );

                effectFXAA = new THREE.ShaderPass( THREE.FXAAShader );
                effectFXAA.uniforms[ ‘resolution‘ ].value.set( 1 / window.innerWidth, 1 / window.innerHeight );
                effectFXAA.renderToScreen = true;
                composer.addPass( effectFXAA );

在選中時,將物體和相應柱子線加入 outlinePass 渲染目標中即可。

               selectedObjects = [];
                        selectedObjects.push( lineGroup );//給選中的線條和物體加發光特效
                        selectedObjects.push( intersects[ 0 ].object );
                        outlinePass.selectedObjects = selectedObjects;

ok,這就實現了,點擊交互的簡單特效。

技術分享

當然,這只是個示例,要把它用到復雜的3D場景中,還需要很多的事情要做,加油。

有錯誤敬請指正。

ThreeJS之動畫交互邏輯及特效