zoukankan      html  css  js  c++  java
  • 前端小玩意儿:用three.js开发的手机太空穿越VR游戏,特效非常猛

    hello,今天给大家用three.js开发了一个手机太空穿越VR游戏,确实不容易,小编的头发又少了一大截。Ok,废话少说,先看效果。

    效果图

    首页index.html

    <!DOCTYPE html>
    <html lang="en" >
    
    <head>
    <meta charset="UTF-8">
    <title>Three.js Mobile VR Sonic</title>
    
    
    
    <link rel="stylesheet" href="css/style.css">
    
    
    </head>
    
    <body>
    
    <script src="js/jquery-1.12.4.min.js"></script>
    <script src="js/d3.v4.min.js"></script>
    <script src="js/three.min.js"></script>
    <script src="js/GLTFLoader.js"></script>
    
    
    <script src="js/TweenMax.min.js"></script>
    <script src="js/CSSPlugin.min.js"></script>
    <script src="js/EasePack.min.js"></script>
    
    
    
    <script src='js/AssimpJSONLoader.js'></script>
    <script src="js/DeviceOrientationControls.js"></script>
    <script src="js/OrbitControls.js"></script>
    <script src="js/StereoEffect.js"></script>
    <script src="js/tween.min.js"></script>
    <script src="js/dat.gui.min.js"></script>
    <!-- glowing effect scripts -->
    <script src="js/EffectComposer.js"></script>
    <script src="js/RenderPass.js"></script>
    <script src="js/MaskPass.js"></script>
    <script src="js/ShaderPass.js"></script>
    <script src="js/CopyShader.js"></script>
    <script src="js/FXAAShader.js"></script>
    <script src="js/ConvolutionShader.js"></script>
    <script src="js/LuminosityHighPassShader.js"></script>
    <!-- unreal bloom -->
    <script src="js/UnrealBloomPass.js"></script>
    
    
        <!-- VR Button -->
        <button id='VR' class='button toggleVR' onclick='toggleVR()' title='Toggle VR Mode for Mobile Devices Only'>
          <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" x="0px" y="0px" viewBox="0 0 62.7 52.375" enable-background="new 0 0 62.7 41.9" xml:space="preserve"><path d="M53.4,5.5h-44c-2.1,0-3.7,1.7-3.7,3.7v22.6c0,2.1,1.7,3.7,3.7,3.7h13.4c1.1,0,2.1-0.6,2.5-1.6l3-7.5c1.2-2.6,4.9-2.5,6,0.1  l2.6,7.3c0.4,1,1.4,1.7,2.5,1.7h13.9c2.1,0,3.7-1.7,3.7-3.7V9.3C57.2,7.2,55.5,5.5,53.4,5.5z M20.4,27c-3.2,0-5.7-2.6-5.7-5.7  s2.6-5.7,5.7-5.7s5.7,2.6,5.7,5.7S23.6,27,20.4,27z M42.4,27c-3.2,0-5.7-2.6-5.7-5.7s2.6-5.7,5.7-5.7s5.7,2.6,5.7,5.7  S45.6,27,42.4,27z"/><text x="0" y="56.9" fill="#000000" font-size="5px" font-weight="bold" font-family="'Helvetica Neue', Helvetica, Arial-Unicode, Arial, Sans-serif">Created by Nick Bluth</text><text x="0" y="61.9" fill="#000000" font-size="5px" font-weight="bold" font-family="'Helvetica Neue', Helvetica, Arial-Unicode, Arial, Sans-serif">from the Noun Project</text></svg>
        </button>
    
        <div id="info">SUPER!</div>
        <audio id="bflat" src="sonic_ring_sound.mp3"></audio>
      
      
    
        <script  src="js/index.js"></script>
    
    
    
    
    </body>
    
    </html>

    核心代码js

    //===================================================== full screen
    var requestFullscreen = function(ele) {
      if (ele.requestFullscreen) {
        ele.requestFullscreen();
      } else if (ele.webkitRequestFullscreen) {
        ele.webkitRequestFullscreen();
      } else if (ele.mozRequestFullScreen) {
        ele.mozRequestFullScreen();
      } else if (ele.msRequestFullscreen) {
        ele.msRequestFullscreen();
      } else {
        console.log("Fullscreen API is not supported.");
      }
    };
    var exitFullscreen = function(ele) {
      if (ele.exitFullscreen) {
        ele.exitFullscreen();
      } else if (ele.webkitExitFullscreen) {
        ele.webkitExitFullscreen();
      } else if (ele.mozCancelFullScreen) {
        ele.mozCancelFullScreen();
      } else if (ele.msExitFullscreen) {
        ele.msExitFullscreen();
      } else {
        console.log("Fullscreen API is not supported.");
      }
    };
    //===================================================== add canvas
    let renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
    renderer.setSize(window.innerWidth, window.innerHeight);
    renderer.setClearColor(0xd8e7ff, 0);
    document.body.appendChild(renderer.domElement);
    //===================================================== resize
    window.addEventListener("resize", function() {
      let width = window.innerWidth;
      let height = window.innerHeight;
      renderer.setSize(width, height);
      camera.aspect = width / height;
      camera.updateProjectionMatrix();
    });
    //===================================================== add Scene
    let scene = new THREE.Scene();
    //===================================================== add Camera
    let camera = new THREE.PerspectiveCamera(
      45,
      window.innerWidth / window.innerHeight,
      1,
      10000
    );
    let cameraTarget = new THREE.Vector3(0, 0, 0);
    //===================================================== add Group
    group = new THREE.Group();
    scene.add(group);
    //===================================================== add Controls
    var controls = new THREE.OrbitControls(camera, renderer.domElement);
    controls.enableDamping = true;
    controls.dampingFactor = 0.25;
    controls.enableZoom = true;
    //How far you can orbit vertically, upper and lower limits. The maximum is Pi / 2 (90deg). You wont see below the below the line of the horizon
    controls.maxPolarAngle = Math.PI / 2.1;
    //===================================================== add VR
    renderer.setPixelRatio(window.devicePixelRatio); //VR
    effect = new THREE.StereoEffect(renderer); //VR
    effect.setSize(window.innerWidth, window.innerHeight); //VR
    
    var VR = false;
    function toggleVR() {
      if (VR) {
        VR = false;
        controls = new THREE.OrbitControls(camera, renderer.domElement);
      } else {
        VR = true;
        controls = new THREE.DeviceOrientationControls(camera);
        requestFullscreen(document.documentElement);
      }
      renderer.setSize(window.innerWidth, window.innerHeight);
    }
    //===================================================== curve points exported from blender using python
    var points = [
      /*[2.873088836669922, 1.886053442955017, 2.807666063308716] ,
    [2.8677191734313965, 1.901498556137085, 0.9702248573303223] ,
    [4.261392116546631, 1.1370995044708252, 2.199495315551758] ,
    [5.4436726570129395, -0.46564579010009766, 1.1223225593566895] ,
    [4.683673858642578, -5.823459148406982, 0.29172343015670776] ,
    [4.683673858642578, -5.823459148406982, 0.29172343015670776] ,
    [1.0320820808410645, -8.896514892578125, -0.25123584270477295] ,
    [-6.009271621704102, -5.854086875915527, -1.619685411453247] ,
    [-5.8719940185546875, 1.664353609085083, 1.4598760604858398] ,
    [-3.664644718170166, 5.1350908279418945, 0.8891280889511108] ,
    [0.3946661949157715, 9.023353576660156, 2.472759246826172] ,
    [6.545413017272949, 7.975126266479492, 4.807941913604736] ,
    [9.380702018737793, 3.8875434398651123, -1.2000198364257812] ,
    [3.980130672454834, -3.3519601821899414, -1.5907882452011108] ,
    [0.10054226964712143, -4.0724077224731445, -2.6255977153778076] ,
    [0.11306309700012207, -4.062130451202393, -2.625786781311035] ,
    [-1.5915921926498413, 2.2223830223083496, 1.9054492712020874] ,
    [-0.7800552845001221, 2.729933738708496, 2.711679458618164] ,
    [2.2788195610046387, 1.5061609745025635, 3.6167585849761963] ,
    [3.330962896347046, -1.753382682800293, 3.5841569900512695] ,
    [3.3268532752990723, -1.7394065856933594, 3.584031343460083] ,
    [1.8003381490707397, -2.6099541187286377, 4.496334552764893] ,
    [1.809736728668213, -2.6042258739471436, 4.490817546844482] ,
    [-1.5139055252075195, -2.2446377277374268, 3.513293743133545] ,
    [-1.5139055252075195, -2.2446377277374268, 3.513293743133545] ,
    [-1.5139055252075195, -2.2446377277374268, 1.7960444688796997] ,
    [-1.9393141269683838, -0.623103678226471, 1.3167498111724854] ,
    [-1.5, 0.0, 0.0] ,
    [-1.0, 1.0, 0.0] ,
    [1.0, 1.0, 0.0] ,
    [1.5, 0.0, 0.0] ,*/
    
      [1.8117204904556274, 5.987488269805908, 0.29106736183166504],
      [6.005367279052734, 1.7647128105163574, -1.5591322183609009],
      [1.435487985610962, -6.016839504241943, 2.1336286067962646],
      [-4.118395805358887, -6.886471271514893, -0.7294682264328003],
      [-4.732693195343018, 3.405961036682129, 3.1304938793182373],
      [8.304193496704102, 7.593861103057861, 0.3412821292877197],
      [8.038525581359863, -4.391696453094482, 2.687108278274536],
      [1.488401174545288, -9.993440628051758, -2.2956111431121826],
      [-5.277090549468994, -8.481210708618164, -0.719127893447876],
      [-7.250330448150635, -0.9653520584106445, -0.3089699447154999],
      [-6.526705741882324, 5.678538799285889, 0.15560221672058105],
      [-0.885545015335083, 6.678538799285889, 1.5724562406539917],
      [1.614454984664917, 5.678538799285889, 0.24559785425662994],
      [1.8117204904556274, 5.987488269805908, 0.29106736183166504]
    ];
    
    var scale = 5;
    
    //Convert the array of points into vertices
    for (var i = 0; i < points.length; i++) {
      var x = points[i][0] * scale;
      var y = points[i][1] * scale;
      var z = points[i][2] * scale;
      points[i] = new THREE.Vector3(x, z, -y);
    }
    
    //Create a path from the points
    var carPath = new THREE.CatmullRomCurve3(points);
    var radius = 0.25;
    
    var geometry = new THREE.TubeGeometry(carPath, 600, radius, 10, false);
    
    //Set a different color on each face
    for (var i = 0, j = geometry.faces.length; i < j; i++) {
      geometry.faces[i].color = new THREE.Color(
        "hsl(" + Math.floor(Math.random() * 290) + ",50%,50%)"
      );
    }
    
    var material = new THREE.MeshBasicMaterial({
      side: THREE.BackSide,
      vertexColors: THREE.FaceColors,
      side: THREE.DoubleSide,
      transparent: true,
      opacity: 1
    });
    var tube = new THREE.Mesh(geometry, material);
    scene.add(tube);
    
    //===================================================== add lighta
    var light = new THREE.DirectionalLight(0xefefff, 1.25);
    light.position.set(1, 1, 1).normalize();
    scene.add(light);
    var light = new THREE.DirectionalLight(0xffefef, 1.25);
    light.position.set(-1, -1, -1).normalize();
    scene.add(light);
    
    //Create a point light in our scene. Makes everything gloomy
    var light = new THREE.PointLight(0xffffff, 1, 100);
    scene.add(light);
    
    //===================================================== particles
    var mergedGeometry = new THREE.Geometry();
    
    var boxGeometry = new THREE.TetrahedronGeometry(0.25, 0);
    
    var material = new THREE.MeshNormalMaterial({
      color: new THREE.Color("white")
    });
    
    for (var i = 0; i < 1000; i++) {
      var x = Math.random() * 125 - 75;
      var y = Math.random() * 125 - 75;
      var z = Math.random() * 125 - 75;
    
      boxGeometry.translate(x, y, z);
    
      mergedGeometry.merge(boxGeometry);
    
      boxGeometry.translate(-x, -y, -z);
    }
    
    var cubes = new THREE.Mesh(mergedGeometry, material);
    scene.add(cubes);
    
    //===================================================== Loader
    //3d model from
    var loader = new THREE.GLTFLoader();
    var model;
    loader.load(
      "sky-island.glb",
      function(gltf) {
        gltf.scene.traverse(function(node) {
          if (node instanceof THREE.Mesh) {
            node.castShadow = true;
            node.material.side = THREE.DoubleSide;
          }
        });
    
        model = gltf.scene;
        model.scale.set(3, 3, 3);
        model.position.set(0, -20, -10);
        //model.rotateY(Math.PI);
        scene.add(model);
      }
    );
    
    //===================================================== Loader
    //3d model from https://3dwarehouse.sketchup.com/user/0438052632930067253040161/wingedkoopa67?nav=models
    var clock = new THREE.Clock();
    var mixer = null;
    var firstObject;
    var loader = new THREE.GLTFLoader();
    loader.load(
      "sonic.glb",
      function(gltf) {
        gltf.scene.traverse(function(node) {
          if (node instanceof THREE.Mesh) {
            node.castShadow = true;
            node.material.side = THREE.DoubleSide;
          }
        });
    
        firstObject = gltf.scene;
        firstObject.scale.set(0.65, 0.65, 0.65);
        group.add(firstObject);
    
        console.log(gltf.animations); //shows all animations imported into the dopesheet in blender
    
        mixer = new THREE.AnimationMixer(firstObject);
        mixer.clipAction(gltf.animations[0]).play();
    
        document.body.addEventListener("click", jump);
        function jump() {
          mixer.clipAction(gltf.animations[0]).stop();
          mixer.clipAction(gltf.animations[1]).play();
          setTimeout(function() {
            mixer.clipAction(gltf.animations[1]).stop();
            mixer.clipAction(gltf.animations[0]).play();
          }, 900);
        }
      }
    );
    
    //===================================================== add model
    var size = 0.05;
    var meshList = [];
    
    for (var i = 0; i < carPath.points.length; i++) {
      var x = carPath.points[i].x;
      var y = carPath.points[i].y;
      var z = carPath.points[i].z;
    
      var geometry = new THREE.TorusGeometry(3, 0.5, 8, 50);
      var material = new THREE.MeshBasicMaterial({
        color: new THREE.Color("yellow")
      });
      var secondObject = new THREE.Mesh(geometry, material);
      secondObject.position.set(x, y + 0.75, z);
      secondObject.scale.set(size, size, size);
      meshList.push(secondObject);
      scene.add(secondObject);
    }
    
    //===================================================== Collision Detection
    function PlaySound() {
      bflat.play();
    }
    
    //calculate distance of the main object
    firstBB = new THREE.Box3().setFromObject(group);
    
    //calculate distance for all other objects
    for (var i = 0; i < meshList.length; i++) {
      secondBB = new THREE.Box3().setFromObject(meshList[i]);
    }
    
    var count = 0;
    function hit() {
      //recalculate distance for the main object
      firstBB = new THREE.Box3().setFromObject(group);
      //recalcuate distance for all the other objects
      for (var i = 0; i < meshList.length; i++) {
        secondBB = new THREE.Box3().setFromObject(meshList[i]);
    
        if (firstBB.isIntersectionBox(secondBB)) {
          PlaySound();
          info.style.color = "hsl(" + Math.floor(Math.random() * 290) + ",50%,50%)";
          info.innerHTML =
            Math.random() > 0.25
              ? "Superb!"
              : Math.random() > 0.25 ? "Oustanding!" : "Awesome!";
    
          TweenLite.to(info, 0.75, {
            css: { fontSize: "50px", opacity: 1 },
            ease: Power4.easeOut,
            onComplete: function() {
              TweenLite.to(info, 0.75, {
                css: { fontSize: "14px", opacity: 0 },
                ease: Power4.easeOut
              }); //end tween
            } //end onComplete
          }); //end tween
        } //end if
      } //end for
    } //end hit
    
    //===================================================== bloom
    var renderScene = new THREE.RenderPass(scene, camera);
    var shaderActive = "none";
    var gui = new dat.GUI();
    dat.GUI.toggleHide();
    var composer;
    
    var parameters = {
      x: 0,
      y: 30,
      z: 0,
      bloomStrength: 1.0,
      bloomRadius: 1.0,
      bloomThreshold: 0.45,
      useShaderNone: function() {
        setupShaderNone();
      },
      useShaderBloom: function() {
        setupShaderBloom();
      }
    };
    
    gui.add(parameters, "useShaderNone").name("Display Original Scene");
    
    var folderBloom = gui.addFolder("Bloom");
    var bloomStrengthGUI = folderBloom
      .add(parameters, "bloomStrength")
      .min(0.0)
      .max(2.0)
      .step(0.01)
      .name("Strength")
      .listen();
    bloomStrengthGUI.onChange(function(value) {
      setupShaderBloom();
    });
    var bloomRadiusGUI = folderBloom
      .add(parameters, "bloomRadius")
      .min(0.0)
      .max(5.0)
      .step(0.01)
      .name("Radius")
      .listen();
    bloomRadiusGUI.onChange(function(value) {
      setupShaderBloom();
    });
    var bloomThresholdGUI = folderBloom
      .add(parameters, "bloomThreshold")
      .min(0)
      .max(0.99)
      .step(0.01)
      .name("Threshold")
      .listen();
    bloomThresholdGUI.onChange(function(value) {
      setupShaderBloom();
    });
    folderBloom.add(parameters, "useShaderBloom").name("Use Bloom Shader");
    folderBloom.open();
    
    //===================================================== functions
    
    function setupShaderNone() {
      shaderActive = "none";
    }
    
    function setupShaderBloom() {
      composer = new THREE.EffectComposer(renderer);
      composer.addPass(new THREE.RenderPass(scene, camera));
    
      /*unreal bloom*/
      var effectFXAA = new THREE.ShaderPass(THREE.FXAAShader);
      effectFXAA.uniforms["resolution"].value.set(
        1 / window.innerWidth,
        1 / window.innerHeight
      );
    
      var copyShader = new THREE.ShaderPass(THREE.CopyShader);
      copyShader.renderToScreen = true;
    
      var bloomPass = new THREE.UnrealBloomPass(
        new THREE.Vector2(window.innerWidth, window.innerHeight),
        parameters.bloomStrength,
        parameters.bloomRadius,
        parameters.bloomThreshold
      );
    
      composer = new THREE.EffectComposer(renderer);
      composer.setSize(window.innerWidth, window.innerHeight);
      composer.addPass(renderScene);
      composer.addPass(effectFXAA);
      composer.addPass(bloomPass);
      composer.addPass(copyShader);
      shaderActive = "bloom";
    }
    
    function isShaderActive() {
      if (shaderActive == "none") {
        renderer.render(scene, camera);
      } else {
        composer.render();
      }
    }
    
    //active bloom on load
    setupShaderBloom();
    
    //===================================================== Animate
    var percentage = 0;
    var prevTime = Date.now();
    function POV() {
      percentage += 0.00045;
      var p1 = carPath.getPointAt(percentage % 1);
      var p2 = carPath.getPointAt((percentage + 0.01) % 1);
      var p3 = carPath.getPointAt((percentage + 0.01 / 2) % 1);
      var p4 = carPath.getPointAt((percentage + 0.01 / 4) % 1);
    
      camera.lookAt(p2);
     
    
      group.position.set(p3.x, p3.y + 0.25, p3.z);
      group.lookAt(p2);
      camera.position.x = p4.x + 2;
      camera.position.y = p4.y + 1;
      camera.position.z = p4.z + 2;
      camera.lookAt(group.position);
    }
    
    function animate() {
      POV();
      hit();
      var delta = clock.getDelta();
      if (mixer != null) mixer.update(delta);
    
      //VR
      if (VR) {
        effect.render(scene, camera);
      } else {
        //renderer.render( scene, camera );
        isShaderActive();
      }
    
      requestAnimationFrame(animate);
      controls.update();
    }
    animate();
    
    //set camera position
    camera.position.x = 40;
    camera.position.y = 50;
    camera.position.z = 50;

    更多学习内容:请关注我的知乎专栏    高级前端工程师前端学习教程,从基础到进阶,看完保证让你的薪资上升一个台阶,你也能成为阿里人(持续更新)

  • 相关阅读:
    标签的讲解
    属性分类
    LeetCode 003. 无重复字符的最长子串 双指针
    Leetcode 136. 只出现一次的数字 异或性质
    Leetcode 231. 2的幂 数学
    LeetCode 21. 合并两个有序链表
    象棋博弈资源
    acwing 343. 排序 topsort floyd 传播闭包
    Leetcode 945 使数组唯一的最小增量 贪心
    Leetcode 785 判断二分图 BFS 二分染色
  • 原文地址:https://www.cnblogs.com/coderhf/p/12917297.html
Copyright © 2011-2022 走看看