02 初識Three.js

上一節最後,我書寫了一個簡單案例代碼供大家一起查看Three.jsHELLO WORLD。案例沒有什麼複雜的東西,也就是一個旋轉的立方體。接下來,我帶着大家一起來講解一下是如何實現這個最簡單的案例,以及Three.js的一些最基本的構件。

構建場景

按照代碼的運行順序,首先,我們需要先將Three.js庫的js文件引入:

<script src="https://cdn.bootcss.com/three.js/92/three.js"></script>

然後在body上面添加了一個初始化的事件init

<body onload="init()">

頁面加載完畢後,回調會觸發我們設置的init事件。init函數裏面一共調用了五個方法,分別是:初始化渲染器,初始化場景,初始化相機,添加模型,添加動畫:

//初始化函數,頁面加載完成是調用
function init() {
    initRenderer();
    initScene();
    initCamera();
    initMesh();

    animate();
}

**使用Three.js顯示創建的內容,我們必須需要的三大件是:渲染器,相機和場景。**相機獲取到場景內顯示的內容,然後再通過渲染器渲染到畫布上面。接下來,我們將分步驟分別講解他們的實現:

創建渲染器

//初始化渲染器
function initRenderer() {
    renderer = new THREE.WebGLRenderer(); //實例化渲染器
    renderer.setSize(window.innerWidth, window.innerHeight); //設置寬和高
    document.body.appendChild(renderer.domElement); //添加到dom
}

查看initRenderer函數內的代碼,第一行我們實例化了一個THREE.WebGLRenderer,這是一個基於WebGL渲染的渲染器,當然,Three.js向下兼容,還有CanvasRendererCSS2DRendererCSS3DRendererSVGRenderer,這四個渲染器分別基於canvas2D,CSS2DCSS3DSVG渲染的渲染器。由於,作爲3D渲染,WebGL渲染的效果最好,並且支持的功能更多,我們以後的課程也只會用到THREE.WebGLRenderer,需要使用其他渲染器時,會重點提示。
第二行,調用了一個設置函數setSize方法,這個是設置我們需要顯示的窗口大小。案例我們是基於瀏覽器全屏顯示,所以設置了瀏覽器窗口的寬和高。
第三行,renderer.domElement是在實例化渲染器時生成的一個canvas畫布,渲染器渲染界面生成的內容,都將在這個畫布上顯示。所以,我們將這個畫布添加到了dom當中,來顯示渲染的內容。

創建相機

接着我們看一下相機的初始化:

//初始化相機
function initCamera() {
    camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 200); //實例化相機
    camera.position.set(0, 0, 15);
}

Three.js裏面有幾個不同的相機,我們這裏使用到的是THREE.PerspectiveCamera,這個相機的效果是模擬人眼看到的效果,就是具有透視的效果,近大遠小。
第一行,我們實例化了一個透視相機,需要四個值分別是視野,寬高比,近裁面和遠裁面。我們分別介紹一下這四個值:

  • 視野:當前相機視野的寬度,值越大,渲染出來的內容也會更多。
  • 寬高比:默認是按照畫布的顯示的寬高比例來設置,如果比例設置的不對,會發現渲染出來的畫面有拉伸或者壓縮的感覺。
  • 近裁面和遠裁面:這個是設置相機可以看到的場景內容的範圍,如果場景內的內容位置不在這兩個值內的話,將不會被顯示到渲染的畫面中。

第二行,我們設置了相機的位置,在講如何設置位置之前,我感覺應該先講一下WebGL的座標系統:
座標系
WebGL座標系統作爲3D座標,在原來的2D座標xy軸上面又多了一個z軸,大家注意z軸的方向,是座標軸朝向我們的方向是正軸,我們眼看去的方向是是z軸的負方向。
camera.position.set函數是設置當前相機的位置,函數傳的三個值分別是x軸座標,y軸座標和z軸座標。我們只是將相機的放到了z正軸座標軸距離座標原點的15的位置。相機默認的朝向是朝向0點座標的,我們也可以設置相機的朝向,這將在後面相機介紹是,專門介紹相機的相關。

創建場景

初始化場景方法裏面很簡單,只有一行代碼,就是實例化了一個場景對象:

//初始化場景
function initScene() {
    scene = new THREE.Scene(); //實例化場景
}

場景只是作爲一個容器,我們將需要顯示的內容都放到場景對象當中。如果我們需要將一個模型放入到場景當中,則可以使用scene.add方法,如:

scene.add(mesh); //添加一個網格(模型)到場景

創建第一個模型

渲染器,場景和相機都全了,是不是就能顯示東西了?不能!因爲場景內沒有內容,即使渲染出來也是一片漆黑,所以我們需要往場景裏面添加內容。接下來,我們將查看initMesh方法,看看如何創建一個最簡單的模型:

//創建模型
function initMesh() {
    geometry = new THREE.BoxGeometry( 2, 2, 2 ); //創建幾何體
    material = new THREE.MeshNormalMaterial(); //創建材質

    mesh = new THREE.Mesh( geometry, material ); //創建網格
    scene.add( mesh ); //將網格添加到場景
}

創建一個網格(模型)需要兩種對象:幾何體和材質。

  • 幾何體代表模型的形狀,它是由固定的點的位置組成,點繪製出面,面組成了模型。
  • 材質是我們看到當前模型顯示出來的效果,如顯示的顏色,質感等。

在創建模型函數的第一行代碼裏,我們實例化了一個THREE.BoxGeometry立方體的幾何體對象,實例化的三個傳值分別代表着立方體的長度,寬度和高度。我們全部設置的相同的值,將渲染出一個標準的正立方體。
在第二行裏面,我們實例化了一個THREE.MeshNormalMaterial材質,這種材質的特點是,它會根據面的朝向不同,顯示不同的顏色。
緊接着,通過THREE.Mesh方法實例化創建了一個網格對象,THREE.Mesh實例化需要傳兩個值,分別是幾何體對象和材質對象,纔可以實例化成功。
最後,我們通過場景的add方法,將網格添加到了場景當中,我們沒有給立方體設置位置,立方體則會默認顯示在座標的原點上面。這時,相機就可以拍攝到我們創建的立方體的畫面了。

讓場景動起來

動畫,就是多幅圖片一直切換就顯示出來動畫的效果,爲了能顯示動畫的效果,我們首先了解一個函數requestAnimationFrame,這個函數是專門爲了動畫而出現的一個函數。它與setInterval相比的優勢在於,不需要設置多長時間重新渲染,而是在當前線程內js空閒時自動渲染,並且最大幀數控制在一秒60幀。所以,我們書寫了一個可以循環調用的函數:

function animate() {
    requestAnimationFrame(animate); //循環調用函數
	...
}

在循環調用的函數中,每一幀我們都讓頁面重新渲染相機拍攝下來的內容:

renderer.render( scene, camera ); //渲染界面

渲染的render方法需要兩個值,第一個值是場景對象,第二個值是相機對象。這意味着,你可以有多個相機和多個場景,可以通過渲染不同的場景和相機讓畫布上顯示不同的畫面。
但是,如果現在一直渲染的話,我們發現就一個立方體在那,也沒有動,我們需要做的是讓立方體動起來:

mesh.rotation.x += 0.01; //每幀網格模型的沿x軸旋轉0.01弧度
mesh.rotation.y += 0.02; //每幀網格模型的沿y軸旋轉0.02弧度

每一個實例化的網格對象都有一個rotation的值,通過設置這個值可以讓立方體旋轉起來。在每一幀裏,我們讓立方體沿x軸方向旋轉0.01弧度,沿y軸旋轉0.02弧度(1π弧度等於180度角度)。

Three.js的性能檢測插件

Three.js裏面,遇到的最多的問題就是性能問題,所以我們需要時刻檢測當前的Three.js的性能。現在Three.js常使用的一款插件叫stats。接下來我們看看如何將stats插件在Three.js的項目中使用:

  • 首先將插件代碼在頁面中引入:
<script src="http://www.wjceo.com/lib/js/libs/stats.min.js"></script>

我這裏也直接給出了一個cdn的地址,可以直接引入即可。

  • 然後,我們需要實例化一個stats對象,然後把對象內生成的dom添加到頁面當中。
stats = new Stats();
document.body.appendChild(stats.dom);
  • 最後一步,我們需要在requestAnimationFrame的回調裏面進行更新每次渲染的時間:
function animate() {
    requestAnimationFrame(animate); //循環調用函數
    stats.update(); //更新性能插件
	renderer.render( scene, camera ); //渲染界面
}

在場景當中,我們能夠看到左上角會有一個性能檢測的小框:
性能檢測插件
前面的數值代表當前每秒的渲染幀率,後面的括號內的值是當前的場景渲染的幀率範圍。

說到這裏,我們的Three.jsHELLO WORLD也就實現了,我將在下一章裏面,給大家講解場景的數據結構和開發的輔助插件。

使用了性能檢測插件以後,我們的案例代碼結構應該是這樣的:

<!DOCTYPE html>
<html>
<head>
    <meta charset=utf-8>
    <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Stats插件案例</title>
    <style>
        body {
            margin: 0;
        }

        canvas {
            width: 100%;
            height: 100%;
            display: block;
        }
    </style>
</head>
<body onload="init()">
<script src="https://cdn.bootcss.com/three.js/92/three.js"></script>
<script src="http://www.wjceo.com/lib/js/libs/stats.min.js"></script>
<script>
    //聲明一些全局變量
    var renderer, camera, scene, geometry, material, mesh, stats;

    //初始化渲染器
    function initRenderer() {
        renderer = new THREE.WebGLRenderer(); //實例化渲染器
        renderer.setSize(window.innerWidth, window.innerHeight); //設置寬和高
        document.body.appendChild(renderer.domElement); //添加到dom
    }

    //初始化場景
    function initScene() {
        scene = new THREE.Scene(); //實例化場景
    }

    //初始化相機
    function initCamera() {
        camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 200); //實例化相機
        camera.position.set(0, 0, 15);
    }

    //創建模型
    function initMesh() {
        geometry = new THREE.BoxGeometry( 2, 2, 2 ); //創建幾何體
        material = new THREE.MeshNormalMaterial(); //創建材質

        mesh = new THREE.Mesh( geometry, material ); //創建網格
        scene.add( mesh ); //將網格添加到場景
    }

    //運行動畫
    function animate() {
        requestAnimationFrame(animate); //循環調用函數

        mesh.rotation.x += 0.01; //每幀網格模型的沿x軸旋轉0.01弧度
        mesh.rotation.y += 0.02; //每幀網格模型的沿y軸旋轉0.02弧度

        stats.update(); //更新性能檢測框

        renderer.render( scene, camera ); //渲染界面
    }

    //性能檢測框
    function initStats() {
        stats = new Stats();
        document.body.appendChild(stats.dom);
    }

    //初始化函數,頁面加載完成是調用
    function init() {
        initRenderer();
        initScene();
        initCamera();
        initMesh();
        initStats();

        animate();
    }
</script>
</body>
</html>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章