欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

使用threejs實(shí)現(xiàn)第一人稱視角的移動(dòng)的問題(示例代碼)

 更新時(shí)間:2022年02月21日 11:03:48   作者:冷豪  
第一人稱視角的場景巡檢主要需要解決兩個(gè)問題,人物在場景中的移動(dòng)和碰撞檢測。移動(dòng)與碰撞功能是所有三維場景首先需要解決的基本問題,今天我們就通過最基本的threejs來完成第一人稱視角的場景巡檢功能,感興趣的朋友一起看看吧

在數(shù)據(jù)可視化領(lǐng)域利用webgl來創(chuàng)建三維場景或VR已經(jīng)越來越普遍,各種開發(fā)框架也應(yīng)運(yùn)而生。今天我們就通過最基本的threejs來完成第一人稱視角的場景巡檢功能。如果你是一位threejs的初學(xué)者或正打算入門,我強(qiáng)烈推薦你仔細(xì)閱讀本文并在我的代碼基礎(chǔ)之上繼續(xù)深入學(xué)習(xí)。因?yàn)樗鼘⑹悄隳軌蛟诰W(wǎng)上找到的最好的免費(fèi)中文教程,通過本文你可以學(xué)習(xí)到一些基本的三維理論,threejs的api接口以及你應(yīng)該掌握的數(shù)學(xué)知識(shí)。當(dāng)然要想完全掌握threejs可能還有很長的路需要走,但至少今天我將帶你入門并傳授一些獨(dú)特的學(xué)習(xí)技巧。

第一人稱視角的場景巡檢主要需要解決兩個(gè)問題,人物在場景中的移動(dòng)和碰撞檢測。移動(dòng)與碰撞功能是所有三維場景首先需要解決的基本問題。為了方便理解,首先需要構(gòu)建一個(gè)簡單的三維場景并在遇到問題的時(shí)候向你演示如何解決它。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>平移與碰撞</title>
    <script src="js/three.js"></script>
    <script src="js/jquery3.4.1.js"></script>
</head>
<body>
<canvas id="mainCanvas"></canvas>
</body>
<script>
    let scene, camera, renderer, leftPress, cube;
    init();
    helper();
    createBoxer();
    animate();

    function init() {
        // 初始化場景
        scene = new THREE.Scene();
        scene.background = new THREE.Color(0xffffff);
        // 創(chuàng)建渲染器
        renderer = new THREE.WebGLRenderer({
            canvas: document.getElementById("mainCanvas"),
            antialias: true, // 抗鋸齒
            alpha: true
        });
        renderer.setSize(window.innerWidth, window.innerHeight);
        // 創(chuàng)建透視相機(jī)
        camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
        camera.position.set(0, 40, 30);
        camera.lookAt(0, 0, 0);
        // 參數(shù)初始化
        mouse = new THREE.Vector2();
        raycaster = new THREE.Raycaster();
        // 環(huán)境光
        var ambientLight = new THREE.AmbientLight(0x606060);
        scene.add(ambientLight);
        // 平行光
        var directionalLight = new THREE.DirectionalLight(0xBCD2EE);
        directionalLight.position.set(1, 0.75, 0.5).normalize();
        scene.add(directionalLight);
    }
    function helper() {
        var grid = new THREE.GridHelper(100, 20, 0xFF0000, 0x000000);
        grid.material.opacity = 0.1;
        grid.material.transparent = true;
        scene.add(grid);
        var axesHelper = new THREE.AxesHelper(30);
        scene.add(axesHelper);
    function animate() {
        requestAnimationFrame(animate);
        renderer.render(scene, camera);
    function createBoxer() {
        var geometry = new THREE.BoxGeometry(5, 5, 5);
        var material = new THREE.MeshPhongMaterial({color: 0x00ff00});
        cube = new THREE.Mesh(geometry, material);
        scene.add(cube);
    $(window).mousemove(function (event) {
        event.preventDefault();
        if (leftPress) {
            cube.rotateOnAxis(
                new THREE.Vector3(0, 1, 0),
                event.originalEvent.movementX / 500
            );
                new THREE.Vector3(1, 0, 0),
                event.originalEvent.movementY / 500
        }
    });
    $(window).mousedown(function (event) {
        leftPress = true;
    $(window).mouseup(function (event) {
        leftPress = false;
</script>
</html>

很多js的開發(fā)人員非常熟悉jquery,我引用它確實(shí)讓代碼顯得更加簡單。首先我在init()方法里初始化了一個(gè)場景。我知道在大部分示例中包括官方提供的demo里都是通過threejs動(dòng)態(tài)的在document下創(chuàng)建一個(gè)<canvas/>節(jié)點(diǎn)。我強(qiáng)烈建議你不要這樣做,因?yàn)樵诤芏鄦雾撁鎽?yīng)用中(例如:Vue和Angular)直接操作DOM都不被推薦。接下來我使用helper()方法創(chuàng)建了兩個(gè)輔助對(duì)象:一個(gè)模擬地面的網(wǎng)格和一個(gè)表示世界坐標(biāo)系的AxesHelper。最后我利用createBoxer()方法在視角中央擺放了一個(gè)綠色的立方體以及綁定了三個(gè)鼠標(biāo)動(dòng)作用來控制立方地旋轉(zhuǎn)。如圖:

你可以嘗試將代碼復(fù)制到本地并在瀏覽器中運(yùn)行,移動(dòng)鼠標(biāo)看看效果。接下來,為了讓方塊移動(dòng)起來,我們需要添加一些鍵盤響應(yīng)事件,以及給方塊的“正面”上色。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>平移與碰撞</title>
    <script src="js/three.js"></script>
    <script src="js/jquery3.4.1.js"></script>
</head>
<body>
<canvas id="mainCanvas"></canvas>
</body>
<script>
    let scene, camera, renderer, leftPress, cube;
    let left, right, front, back;
    init();
    helper();
    createBoxer();
    animate();

    function init() {
        // 初始化場景
        scene = new THREE.Scene();
        scene.background = new THREE.Color(0xffffff);
        // 創(chuàng)建渲染器
        renderer = new THREE.WebGLRenderer({
            canvas: document.getElementById("mainCanvas"),
            antialias: true, // 抗鋸齒
            alpha: true
        });
        renderer.setSize(window.innerWidth, window.innerHeight);
        // 創(chuàng)建透視相機(jī)
        camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
        camera.position.set(0, 40, 30);
        camera.lookAt(0, 0, 0);
        // 參數(shù)初始化
        mouse = new THREE.Vector2();
        raycaster = new THREE.Raycaster();
        // 環(huán)境光
        var ambientLight = new THREE.AmbientLight(0x606060);
        scene.add(ambientLight);
        // 平行光
        var directionalLight = new THREE.DirectionalLight(0xBCD2EE);
        directionalLight.position.set(1, 0.75, 0.5).normalize();
        scene.add(directionalLight);
    }
    function helper() {
        var grid = new THREE.GridHelper(100, 20, 0xFF0000, 0x000000);
        grid.material.opacity = 0.1;
        grid.material.transparent = true;
        scene.add(grid);
        var axesHelper = new THREE.AxesHelper(30);
        scene.add(axesHelper);
    function animate() {
        requestAnimationFrame(animate);
        renderer.render(scene, camera);
        if (front) {
            cube.translateZ(-1)
        }
        if (back) {
            cube.translateZ(1);
        if (left) {
            cube.translateX(-1);
        if (right) {
            cube.translateX(1);
    function createBoxer() {
        var geometry = new THREE.BoxGeometry(5, 5, 5);
        var mats = [];
        mats.push(new THREE.MeshPhongMaterial({color: 0x00ff00}));
        mats.push(new THREE.MeshPhongMaterial({color: 0xff0000}));
        cube = new THREE.Mesh(geometry, mats);
        for (let j = 0; j < geometry.faces.length; j++) {
            if (j === 8 || j === 9) {
                geometry.faces[j].materialIndex = 1;
            } else {
                geometry.faces[j].materialIndex = 0;
            }
        scene.add(cube);
    $(window).mousemove(function (event) {
        event.preventDefault();
        if (leftPress) {
            cube.rotateOnAxis(
                new THREE.Vector3(0, 1, 0),
                event.originalEvent.movementX / 500
            );
                new THREE.Vector3(1, 0, 0),
                event.originalEvent.movementY / 500
    });
    $(window).mousedown(function (event) {
        leftPress = true;
    $(window).mouseup(function (event) {
        leftPress = false;
    $(window).keydown(function (event) {
        switch (event.keyCode) {
            case 65: // a
                left = true;
                break;
            case 68: // d
                right = true;
            case 83: // s
                back = true;
            case 87: // w
                front = true;
    $(window).keyup(function (event) {
                left = false;
                right = false;
                back = false;
                front = false;
</script>
</html>

我們添加了keydown()事件和keyup()事件用來捕獲鍵盤響應(yīng)。我們還修改了createBoxer()方法,給朝向我們的那一面涂上紅色。你一定發(fā)現(xiàn)了BoxGeometry所代表的立方體雖然只有6個(gè)面,可是為了給“1個(gè)面”上色我們卻需要同時(shí)在“2個(gè)面”的材質(zhì)上著色。這是因?yàn)樵谌S場景中,“面”的含義表示由空間中3個(gè)點(diǎn)所代表的區(qū)域,而一個(gè)矩形由兩個(gè)三角形拼接而成。完成以后的樣子如下:

隨意拖動(dòng)幾下鼠標(biāo),我們可能會(huì)得到一個(gè)類似的狀態(tài):

設(shè)想一下在第一人稱視角的游戲中,我們抬高視角觀察周圍后再降低視角,地平線是否依然處于水平狀態(tài)。換句話說,無論我們?nèi)绾瓮蟿?dòng)鼠標(biāo),紅色的那面在朝向我們的時(shí)候都不應(yīng)該傾斜。要解釋這個(gè)問題,我們首先需要搞清楚三維場景中的坐標(biāo)系概念。在threejs的世界中存在兩套坐標(biāo)體系:世界坐標(biāo)系和自身坐標(biāo)系。世界坐標(biāo)系是整個(gè)場景的坐標(biāo)系統(tǒng),通過它可以定位場景中的物體。而自身坐標(biāo)系就比較復(fù)雜,實(shí)際上一個(gè)物體的自身坐標(biāo)系除了用來表示物體各個(gè)部分的相對(duì)關(guān)系以外主要用來表示物體的旋轉(zhuǎn)。想象一下月球的自轉(zhuǎn)和公轉(zhuǎn),在地月坐標(biāo)系中,月球圍繞地球公轉(zhuǎn),同時(shí)也繞著自身的Y軸旋轉(zhuǎn)。在我們上面的場景中,立方體自身的坐標(biāo)軸會(huì)隨著自身的旋轉(zhuǎn)而改變,當(dāng)我們的鼠標(biāo)自下而上滑動(dòng)后,Y軸將不再垂直于地面。如果這時(shí)我們?cè)贆M向滑動(dòng)鼠標(biāo)讓立方體繞Y軸旋轉(zhuǎn),自然整個(gè)面都會(huì)發(fā)生傾斜。如果你還不理解可以在自己的代碼中多嘗試幾次,理解世界坐標(biāo)系和自身坐標(biāo)系對(duì)于學(xué)習(xí)webgl尤其重要。很顯然,要模擬第一人稱的視角轉(zhuǎn)動(dòng)我們需要讓視角上下移動(dòng)的旋轉(zhuǎn)軸為自身坐標(biāo)系的X軸,左右移動(dòng)的旋轉(zhuǎn)軸固定為穿過自身中心的一條與世界坐標(biāo)系Y軸保持平行的軸線。理解這個(gè)問題很不容易,可是解決它卻非常簡單。threejs為我們提供了方法,我們只需要修改mousemove()方法:

$(window).mousemove(function (event) {
        event.preventDefault();
        if (leftPress) {
            cube.rotateOnWorldAxis(
                new THREE.Vector3(0, 1, 0),
                event.originalEvent.movementX / 500
            );
            cube.rotateOnAxis(
                new THREE.Vector3(1, 0, 0),
                event.originalEvent.movementY / 500
            );
        }
    });

有了控制視角的方式,接下來我們移動(dòng)一下方塊。新的問題又出現(xiàn)了:盒子的運(yùn)動(dòng)方向也是沿著自身坐標(biāo)系的。就和我們看著月亮行走并不會(huì)走到月亮上去的情形一樣,如果要模擬第一人稱視角的移動(dòng),視角的移動(dòng)方向應(yīng)該永遠(yuǎn)和世界坐標(biāo)系保持平行,那么我們是否可以通過世界坐標(biāo)系來控制物體的移動(dòng)呢:

function animate() {
        requestAnimationFrame(animate);
        renderer.render(scene, camera);
        if (front) {
            // cube.translateZ(-1)
            cube.position.z -= 1;
        }
        if (back) {
            // cube.translateZ(1);
            cube.position.z += 1;
        }
        if (left) {
            // cube.translateX(-1);
            cube.position.x -= 1;
        }
        if (right) {
            // cube.translateX(1);
            cube.position.x += 1;
        }
    }

很顯然也不行,原因是我們應(yīng)該讓物體的前進(jìn)方向與物體面對(duì)的方向保持一致:

盡管這個(gè)需求顯得如此合理,可是threejs似乎并沒有提供有效的解決方案,就連官方示例中提供的基于第一人稱的移動(dòng)也僅僅是通過固定物體Y軸數(shù)值的方法實(shí)現(xiàn)的。在射擊游戲中不能蹲下或爬上屋頂實(shí)在不能讓玩家接受。為了能夠在接下來的變換中分解問題和測試效果,我們?cè)谀P蜕咸砑觾蓚€(gè)箭頭表示物體的前后方向。

let arrowFront, arrowBack;

function animate() {
        requestAnimationFrame(animate);
        renderer.render(scene, camera);
        arrowFront.setDirection(cube.getWorldDirection(new THREE.Vector3()).normalize());
        arrowFront.position.copy(cube.position);
        arrowBack.setDirection(cube.getWorldDirection(new THREE.Vector3()).negate().normalize());
        arrowBack.position.copy(cube.position);
        if (front) {
            // cube.translateZ(-1)
            cube.position.z -= 1;
        }
        if (back) {
            // cube.translateZ(1);
            cube.position.z += 1;
        if (left) {
            // cube.translateX(-1);
            cube.position.x -= 1;
        if (right) {
            // cube.translateX(1);
            cube.position.x += 1;
    }
function createBoxer() {
        var geometry = new THREE.BoxGeometry(5, 5, 5);
        var mats = [];
        mats.push(new THREE.MeshPhongMaterial({color: 0x00ff00}));
        mats.push(new THREE.MeshPhongMaterial({color: 0xff0000}));
        cube = new THREE.Mesh(geometry, mats);
        for (let j = 0; j < geometry.faces.length; j++) {
            if (j === 8 || j === 9) {
                geometry.faces[j].materialIndex = 1;
            } else {
                geometry.faces[j].materialIndex = 0;
            }
        scene.add(cube);
        arrowFront = new THREE.ArrowHelper(cube.getWorldDirection(), cube.position, 15, 0xFF0000);
        scene.add(arrowFront);
        arrowBack = new THREE.ArrowHelper(cube.getWorldDirection().negate(), cube.position, 15, 0x00FF00);
        scene.add(arrowBack);

修改后的效果如下:

有了箭頭的輔助,我們能夠以比較直觀的方式測試算法是否有效。如果你能夠認(rèn)真讀到這里,可能已經(jīng)迫不及待想繼續(xù)了,但是還請(qǐng)稍安勿躁。進(jìn)入下個(gè)環(huán)節(jié)前,我們需要首先了解幾個(gè)重要的概念。

  • 三維向量(Vector3):可以表征三維空間中的點(diǎn)或來自原點(diǎn)(0,0,0)的矢量。需要注意,Vector3既可以表示空間中的一個(gè)點(diǎn)又可以表示方向。因此為了避免歧義,我建議在作為矢量的時(shí)候通過normalize()方法對(duì)向量標(biāo)準(zhǔn)化。具體api文檔參考。
  • 歐拉角(Euler):表示一個(gè)物體在其自身坐標(biāo)系上的旋轉(zhuǎn)角度,歐拉角也是一個(gè)很常見的數(shù)學(xué)概念,優(yōu)點(diǎn)是對(duì)于旋轉(zhuǎn)的表述相對(duì)直觀,不過我們?cè)陧?xiàng)目中并不常用。
  • 四元數(shù)(Quaternion):四元數(shù)是一個(gè)相對(duì)高深的數(shù)學(xué)概念,幾何含義與歐拉角類似。都可以用來表征物體的旋轉(zhuǎn)方向,優(yōu)點(diǎn)是運(yùn)算效率更高。
  • 四維矩陣(Matrix4):在threejs的世界中,任何一個(gè)對(duì)象都有它對(duì)應(yīng)的四維矩陣。它集合了平移、旋轉(zhuǎn)、縮放等操作。有時(shí)我們可以通過它來完成兩個(gè)對(duì)象的動(dòng)作同步。
  • 叉積(.cross() ):向量叉積表示由兩個(gè)向量所確定的平面的法線方向。叉積的用途很多,例如在第一人稱的視角控制下,實(shí)現(xiàn)左右平移就可以通過當(dāng)前視角方向z與垂直方向y做叉積運(yùn)算獲得:z.cross(y)。
  • 點(diǎn)積(.dot()):與向量叉積不同,向量點(diǎn)積為一個(gè)長度數(shù)據(jù)。vect_a.dot(vect_b)表示向量b在向量a上的投影長度,具體如何使用我們馬上就會(huì)看到

在理解了上面的概念以后,我們就可以實(shí)現(xiàn)沿視角方向平移的操作:我們知道,物體沿平面(XOZ)坐標(biāo)系運(yùn)動(dòng)都可以分解為X方向上的運(yùn)動(dòng)分量和Z軸方向上的運(yùn)動(dòng)分量。首先獲取視角的方向,以三維向量表示。接著我們需要以這個(gè)向量和X軸方向上的一個(gè)三維向量做點(diǎn)積運(yùn)算,從而得到一個(gè)投影長度。這個(gè)長度即代表物體沿視角方向移動(dòng)的水平x軸方向上的運(yùn)動(dòng)分量。同理,我們?cè)谟?jì)算與Z軸方向上的點(diǎn)積,又可以獲得物體沿視角方向移動(dòng)的z軸方向的運(yùn)動(dòng)分量。同時(shí)執(zhí)行兩個(gè)方向上的運(yùn)動(dòng)分量完成平移操作。

接下來,我們先通過實(shí)驗(yàn)觀察是否能夠獲得這兩個(gè)運(yùn)動(dòng)分量和投影長度。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>平移與碰撞</title>
    <script src="js/three.js"></script>
    <script src="js/jquery3.4.1.js"></script>
</head>
<body>
<canvas id="mainCanvas"></canvas>
</body>
<script>
    let scene, camera, renderer, leftPress, cube, arrowFront, arrowFrontX, arrowFrontZ;
    let left, right, front, back;
    init();
    // helper();
    createBoxer();
    animate();

    function init() {
        // 初始化場景
        scene = new THREE.Scene();
        scene.background = new THREE.Color(0xffffff);
        // 創(chuàng)建渲染器
        renderer = new THREE.WebGLRenderer({
            canvas: document.getElementById("mainCanvas"),
            antialias: true, // 抗鋸齒
            alpha: true
        });
        renderer.setSize(window.innerWidth, window.innerHeight);
        // 創(chuàng)建透視相機(jī)
        camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
        camera.position.set(0, 40, 30);
        camera.lookAt(0, 0, 0);
        // 參數(shù)初始化
        mouse = new THREE.Vector2();
        raycaster = new THREE.Raycaster();
        // 環(huán)境光
        var ambientLight = new THREE.AmbientLight(0x606060);
        scene.add(ambientLight);
        // 平行光
        var directionalLight = new THREE.DirectionalLight(0xBCD2EE);
        directionalLight.position.set(1, 0.75, 0.5).normalize();
        scene.add(directionalLight);
    }
    function helper() {
        var grid = new THREE.GridHelper(100, 20, 0xFF0000, 0x000000);
        grid.material.opacity = 0.1;
        grid.material.transparent = true;
        scene.add(grid);
        var axesHelper = new THREE.AxesHelper(30);
        scene.add(axesHelper);
    function animate() {
        requestAnimationFrame(animate);
        renderer.render(scene, camera);
        arrowFront.setDirection(cube.getWorldDirection(new THREE.Vector3()).normalize());
        arrowFront.position.copy(cube.position);
        let vect = cube.getWorldDirection(new THREE.Vector3());
        arrowFrontX.setDirection(new THREE.Vector3(1, 0, 0));
        arrowFrontX.setLength(vect.dot(new THREE.Vector3(15, 0, 0)));
        arrowFrontX.position.copy(cube.position);
        arrowFrontZ.setDirection(new THREE.Vector3(0, 0, 1));
        arrowFrontZ.setLength(vect.dot(new THREE.Vector3(0, 0, 15)));
        arrowFrontZ.position.copy(cube.position);
        if (front) {
            // cube.translateZ(-1)
            cube.position.z -= 1;
        }
        if (back) {
            // cube.translateZ(1);
            cube.position.z += 1;
        if (left) {
            // cube.translateX(-1);
            cube.position.x -= 1;
        if (right) {
            // cube.translateX(1);
            cube.position.x += 1;
    function createBoxer() {
        var geometry = new THREE.BoxGeometry(5, 5, 5);
        var mats = [];
        mats.push(new THREE.MeshPhongMaterial({color: 0x00ff00}));
        mats.push(new THREE.MeshPhongMaterial({color: 0xff0000}));
        cube = new THREE.Mesh(geometry, mats);
        for (let j = 0; j < geometry.faces.length; j++) {
            if (j === 8 || j === 9) {
                geometry.faces[j].materialIndex = 1;
            } else {
                geometry.faces[j].materialIndex = 0;
            }
        scene.add(cube);
        arrowFront = new THREE.ArrowHelper(cube.getWorldDirection(), cube.position, 15, 0xFF0000);
        scene.add(arrowFront);
        let cubeDirec = cube.getWorldDirection(new THREE.Vector3());
        arrowFrontX = new THREE.ArrowHelper(cubeDirec.setY(0), cube.position, cubeDirec.dot(new THREE.Vector3(0, 0, 15)), 0x0000ff);
        scene.add(arrowFrontX);
        arrowFrontZ = new THREE.ArrowHelper(cubeDirec.setY(0), cube.position, cubeDirec.dot(new THREE.Vector3(15, 0, 0)), 0xB5B5B5)
        scene.add(arrowFrontZ);
    $(window).mousemove(function (event) {
        event.preventDefault();
        if (leftPress) {
            cube.rotateOnWorldAxis(
                new THREE.Vector3(0, 1, 0),
                event.originalEvent.movementX / 500
            );
            cube.rotateOnAxis(
                new THREE.Vector3(1, 0, 0),
                event.originalEvent.movementY / 500
    });
    $(window).mousedown(function (event) {
        leftPress = true;
    $(window).mouseup(function (event) {
        leftPress = false;
    $(window).keydown(function (event) {
        switch (event.keyCode) {
            case 65: // a
                left = true;
                break;
            case 68: // d
                right = true;
            case 83: // s
                back = true;
            case 87: // w
                front = true;
    $(window).keyup(function (event) {
                left = false;
                right = false;
                back = false;
                front = false;
</script>
</html>

 

通過箭頭的輔助,我們很容易獲得以下圖形:

紅色箭頭表示物體的朝向,藍(lán)色表示物體沿x軸上的投影方向和長度?;疑硎狙貁軸上的投影方向和長度。在確認(rèn)方法可行以后,我們繼續(xù)實(shí)現(xiàn)平移操作。完整代碼如下,這個(gè)運(yùn)算的方式很重要,讀者應(yīng)該仔細(xì)比較兩段代碼的差別。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>平移與碰撞</title>
    <script src="js/three.js"></script>
    <script src="js/jquery3.4.1.js"></script>
</head>
<body>
<canvas id="mainCanvas"></canvas>
</body>
<script>
    let scene, camera, renderer, leftPress, cube, arrowFront, arrowFrontX, arrowFrontZ;
    let left, right, front, back;
    init();
    helper();
    createBoxer();
    animate();

    function init() {
        // 初始化場景
        scene = new THREE.Scene();
        scene.background = new THREE.Color(0xffffff);
        // 創(chuàng)建渲染器
        renderer = new THREE.WebGLRenderer({
            canvas: document.getElementById("mainCanvas"),
            antialias: true, // 抗鋸齒
            alpha: true
        });
        renderer.setSize(window.innerWidth, window.innerHeight);
        // 創(chuàng)建透視相機(jī)
        camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
        camera.position.set(0, 40, 30);
        camera.lookAt(0, 0, 0);
        // 參數(shù)初始化
        mouse = new THREE.Vector2();
        raycaster = new THREE.Raycaster();
        // 環(huán)境光
        var ambientLight = new THREE.AmbientLight(0x606060);
        scene.add(ambientLight);
        // 平行光
        var directionalLight = new THREE.DirectionalLight(0xBCD2EE);
        directionalLight.position.set(1, 0.75, 0.5).normalize();
        scene.add(directionalLight);
    }
    function helper() {
        var grid = new THREE.GridHelper(100, 20, 0xFF0000, 0x000000);
        grid.material.opacity = 0.1;
        grid.material.transparent = true;
        scene.add(grid);
        var axesHelper = new THREE.AxesHelper(30);
        scene.add(axesHelper);
    function animate() {
        requestAnimationFrame(animate);
        renderer.render(scene, camera);
        arrowFront.setDirection(cube.getWorldDirection(new THREE.Vector3()).normalize());
        arrowFront.position.copy(cube.position);
        let vect = cube.getWorldDirection(new THREE.Vector3());
        if (front) {
            cube.position.z += vect.dot(new THREE.Vector3(0, 0, 15)) * 0.01;
            cube.position.x += vect.dot(new THREE.Vector3(15, 0, 0)) * 0.01;
        }
    function createBoxer() {
        var geometry = new THREE.BoxGeometry(5, 5, 5);
        var mats = [];
        mats.push(new THREE.MeshPhongMaterial({color: 0x00ff00}));
        mats.push(new THREE.MeshPhongMaterial({color: 0xff0000}));
        cube = new THREE.Mesh(geometry, mats);
        for (let j = 0; j < geometry.faces.length; j++) {
            if (j === 8 || j === 9) {
                geometry.faces[j].materialIndex = 1;
            } else {
                geometry.faces[j].materialIndex = 0;
            }
        scene.add(cube);
        arrowFront = new THREE.ArrowHelper(cube.getWorldDirection(), cube.position, 15, 0xFF0000);
        scene.add(arrowFront);
    $(window).mousemove(function (event) {
        event.preventDefault();
        if (leftPress) {
            cube.rotateOnWorldAxis(
                new THREE.Vector3(0, 1, 0),
                event.originalEvent.movementX / 500
            );
            cube.rotateOnAxis(
                new THREE.Vector3(1, 0, 0),
                event.originalEvent.movementY / 500
    });
    $(window).mousedown(function (event) {
        leftPress = true;
    $(window).mouseup(function (event) {
        leftPress = false;
    $(window).keydown(function (event) {
        switch (event.keyCode) {
            case 65: // a
                left = true;
                break;
            case 68: // d
                right = true;
            case 83: // s
                back = true;
            case 87: // w
                front = true;
    $(window).keyup(function (event) {
                left = false;
                right = false;
                back = false;
                front = false;
</script>
</html>

向后和左右平移的操作留給大家自己實(shí)現(xiàn)。有了以上基礎(chǔ),如何控制Camera移動(dòng)就很簡單了。幾乎就是將cube的操作替換成camera即可:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>第一人稱視角移動(dòng)</title>
    <script src="js/three.js"></script>
    <script src="js/jquery3.4.1.js"></script>
</head>
<body>
<canvas id="mainCanvas"></canvas>
</body>
<script>
    let scene, camera, renderer, leftPress, cube, arrowFront, arrowFrontX, arrowFrontZ;
    let left, right, front, back;
    init();
    helper();
    animate();

    function init() {
        // 初始化場景
        scene = new THREE.Scene();
        scene.background = new THREE.Color(0xffffff);
        // 創(chuàng)建渲染器
        renderer = new THREE.WebGLRenderer({
            canvas: document.getElementById("mainCanvas"),
            antialias: true, // 抗鋸齒
            alpha: true
        });
        renderer.setSize(window.innerWidth, window.innerHeight);
        // 創(chuàng)建透視相機(jī)
        camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
        camera.position.set(0, 10, 30);
        // 參數(shù)初始化
        mouse = new THREE.Vector2();
        raycaster = new THREE.Raycaster();
        // 環(huán)境光
        var ambientLight = new THREE.AmbientLight(0x606060);
        scene.add(ambientLight);
        // 平行光
        var directionalLight = new THREE.DirectionalLight(0xBCD2EE);
        directionalLight.position.set(1, 0.75, 0.5).normalize();
        scene.add(directionalLight);
    }
    function helper() {
        var grid = new THREE.GridHelper(100, 20, 0xFF0000, 0x000000);
        grid.material.opacity = 0.1;
        grid.material.transparent = true;
        scene.add(grid);
        var axesHelper = new THREE.AxesHelper(30);
        scene.add(axesHelper);
    function animate() {
        requestAnimationFrame(animate);
        renderer.render(scene, camera);
        let vect = camera.getWorldDirection(new THREE.Vector3());
        if (front) {
            camera.position.z += vect.dot(new THREE.Vector3(0, 0, 15)) * 0.01;
            camera.position.x += vect.dot(new THREE.Vector3(15, 0, 0)) * 0.01;
        }
    $(window).mousemove(function (event) {
        event.preventDefault();
        if (leftPress) {
            camera.rotateOnWorldAxis(
                new THREE.Vector3(0, 1, 0),
                event.originalEvent.movementX / 500
            );
            camera.rotateOnAxis(
                new THREE.Vector3(1, 0, 0),
                event.originalEvent.movementY / 500
    });
    $(window).mousedown(function (event) {
        leftPress = true;
    $(window).mouseup(function (event) {
        leftPress = false;
    $(window).keydown(function (event) {
        switch (event.keyCode) {
            case 65: // a
                left = true;
                break;
            case 68: // d
                right = true;
            case 83: // s
                back = true;
            case 87: // w
                front = true;
    $(window).keyup(function (event) {
                left = false;
                right = false;
                back = false;
                front = false;
</script>
</html>

解決了平移操作以后,碰撞檢測其實(shí)就不那么復(fù)雜了。我們可以沿著攝像機(jī)的位置向上下前后左右六個(gè)方向做光線投射(Raycaster),每次移動(dòng)首先檢測移動(dòng)方向上的射線是否被阻擋,如果發(fā)生阻擋且距離小于安全距離,即停止該方向上的移動(dòng)。后面的部分我打算放在下一篇博客中介紹,如果大家對(duì)這篇文章敢興趣或有什么建議歡迎給我留言或加群討論。

到此這篇關(guān)于使用threejs實(shí)現(xiàn)第一人稱視角的移動(dòng)的方法的文章就介紹到這了,更多相關(guān)threejs第一人稱視角的移動(dòng)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論