zoukankan      html  css  js  c++  java
  • (转)Three.JS学习 9:WEBVR 入门demo

    原文地址:http://blog.csdn.net/xundh/article/details/54564657

    本文参考文档主要来源: 
    https://www.sitepoint.com/bringing-vr-to-web-google-cardboard-three-js/

    本文内容是介绍基于Three.js创建一个可以使用谷歌眼镜演示的WEB虚拟现实网页。

    准备工作

    首先需要准备一些js 
    下载项目: 
    https://github.com/sitepoint-editors/VRWeatherParticles

    使用自己熟悉的开发环境创建一个web项目,把上面下载项目里的/js 、 /textures放到项目里,新建一个index.html文件。 
    这里写图片描述

    下载的项目里有完整的源码,本文基本是对其程序说明做简单翻译。

    引用的文件说明

    • three.min.js : Threejs库
    • StereoEffect.js:允许我们把普通的Three.js分成两个显示的场景合并到一起显示,这是VR体验的基本需求
    • DeviceOrientationControls.js:告诉Three.js设备的朝向、向哪移动。
    • OrbitControls.js:允许我们通过拖动、点击事件来控制场景(在DeviceOrientation事件无效的时候,比如电脑模拟时适用)
    • helvetiker_regular.typeface.js:将在Three.js显示文本用到的字体

    下面是init()函数,其中有一些内容在前几章已有提及,这里不会完全详述。

            function init() {
                scene = new THREE.Scene();
                camera = new THREE.PerspectiveCamera(90, window.innerWidth / window.innerHeight, 0.001, 700);
                camera.position.set(0, 15, 0);
                scene.add(camera);
                /*
                我们需要一个元素来画场景,这里定义一个renderer,并且给HTML元素webglviewer声明一个变量
                */
                renderer = new THREE.WebGLRenderer();
                element = renderer.domElement;
                container = document.getElementById('webglviewer');
                container.appendChild(element);
                //为了有VR双屏的视图,需要StereoEffect
                effect = new THREE.StereoEffect(renderer);
    
                //控制摄像机
                controls = new THREE.OrbitControls(camera, element);
                controls.target.set(
                  camera.position.x + 0.15,
                  camera.position.y,
                  camera.position.z
                );
                controls.noPan = true;
                controls.noZoom = true;
                //加入设备事件,该事件返回的结果有三个属性值
                window.addEventListener('deviceorientation', setOrientationControls, true);
                //如果没有设备支持DeviceOrientation特性,还要给controls变量加上OrbitControls对象,并
                //且使用我们自己的DeviceOrientationControls对象替换它
                //接下来运行connect和update函数
                controls = new THREE.DeviceOrientationControls(camera, true);
                controls.connect();
                controls.update();
                //鼠标点击、全屏,这样在google cardboard里看起来效果更好
                element.addEventListener('click', fullscreen, false);
                //删除deviceorientation事件,因为已经定义了我们自己的DeviceOrientationControls对象
                window.removeEventListener('deviceorientation', setOrientationControls, true);
    
    
            }
            function setOrientationControls(e) {
                //通过alpha属性来确保监测的是我们需要的事件
                if (!e.alpha) {
                    return;
                }
            }

    创建灯光

    var light = new THREE.PointLight(0x999999, 2, 100);
    light.position.set(50, 50, 50);
    scene.add(light);
    
    var lightScene = new THREE.PointLight(0x999999, 2, 100);
    lightScene.position.set(0, 5, 0);
    scene.add(lightScene);

    创建地板(加载材质)

    var floorTexture = THREE.ImageUtils.loadTexture('textures/wood.jpg');
    floorTexture.wrapS = THREE.RepeatWrapping;
    floorTexture.wrapT = THREE.RepeatWrapping;
    floorTexture.repeat = new THREE.Vector2(50, 50);
    
    floorTexture.anisotropy = renderer.getMaxAnisotropy();
    //我们的地板需要texture和material,其中material控制我们的地板如何跟随灯光变化
    //我们使用了MeshPhoneMaterial可以让对象跟随灯光效果看起来更舒适
    var floorMaterial = new THREE.MeshPhongMaterial({
      color: 0xffffff,
      specular: 0xffffff,
      shininess: 20,
      shading: THREE.FlatShading,
      map: floorTexture
    });

    定义几何体

    var geometry = new THREE.PlaneBufferGeometry(1000, 1000);

    在场景里加入地板

    var floor = new THREE.Mesh(geometry, floorMaterial);
    floor.rotation.x = -Math.PI / 2;
    scene.add(floor);

    把粒子放到一起

    先定义一些粒子相关的公用变量,并且创建了一个粒子对象,用来保存浮动的粒子,后面会详细讲解这些变量。

    particles = new THREE.Object3D(),
    totalParticles = 200,
    maxParticleSize = 200,
    particleRotationSpeed = 0,
    particleRotationDeg = 0,
    lastColorRange = [0, 0.3],
    currentColorRange = [0, 0.3],

    现在在一个比较高的水平上来整体看一下代码。我们把一个透明的png图”textures/particle.png”初始化为texture。上面定义了总粒子数量为totalParticles,如果想增加场景里的粒子数量,可以把这个值增大。

    下面遍历粒子并把它们加入到了粒子对象里,我们需要把粒子对象升高以让它旋浮起来。

    var particleTexture = THREE.ImageUtils.loadTexture('textures/particle.png'),
        spriteMaterial = new THREE.SpriteMaterial({
        map: particleTexture,
        color: 0xffffff
      });
    
    for (var i = 0; i < totalParticles; i++) {
      // Code setting up all our particles!
    }
    
    particles.position.y = 70;
    scene.add(particles);

    接下来创建一个Three.js Sprite对象,并把spriteMaterial赋给它,然后把它缩放到64×64(与texture一样大)。我们希望粒子是围绕我们出现在随机的位置,所以把它设置有x和y值介于-0.5到0.5之间,z值在-0.75到0.25之间。关于为什么选取这些值,在一些实践之后,这些应该是最佳的实践值。

    for (var i = 0; i < totalParticles; i++) {
      var sprite = new THREE.Sprite(spriteMaterial);
    
      sprite.scale.set(64, 64, 1.0);
      sprite.position.set(Math.random() - 0.5, Math.random() - 0.5, Math.random() - 0.75);

    把每个粒子的尺寸都限制0到maxParticleSize之间

    sprite.position.setLength(maxParticleSize * Math.random());

    让粒子看起来平滑的一个关键点是THREE.AdditiveBlending ,它是Three.js里的弯曲风格。这个会给texture赋给它后面一种texture的颜色,以让整个粒子系统看起来更平滑。

    sprite.material.blending = THREE.AdditiveBlending;
    
      particles.add(sprite);
    }

    天气API

    目前已经有了一个拥有地板、灯光的静态场景。现在添加一个OpenWeatherMap API获取各城市的天气以让demo显得更有趣。

    OpenWeatherMap使用一个HTTP请求来获取多个城市天气。下面定义了cityIDs变量保存需要的各个城市,从网址: 
    http://78.46.48.103/sample/city.list.json.gz
    可以获取到城市列表。

    function adjustToWeatherConditions() {
      var cityIDs = '';
      for (var i = 0; i < cities.length; i++) {
        cityIDs += cities[i][1];
        if (i != cities.length - 1) cityIDs += ',';
      }

    我们的城市数组包括了名称和IDs,这样可以显示天气数据的时候也显示城市的名字。 
    为了调用API,还需要一个API key,可以到网站http://openweathermap.org创建一个账号 
    使用getURL()函数可以获取XMLHttpRequest请求。如果收到一个关于”crossorigin”错误,那需要改用JSONP。 
    这是调用示例:

    getURL('http://api.openweathermap.org/data/2.5/group?id=' + cityIDs + '&APPID=kj34723jkh23kj89dfkh2b28ey982hwm223iuyhe2c', function(info) {
      cityWeather = info.list;

    当然天气服务并非本文重点,下面跳过一部分内容。

    保存时间

    clock = new THREE.Clock();

    动起来

    在init()函数里已经调用了animate。

    我们还需要决定粒子要转动的方向,如果风力小于或等于180,那就顺时针转,否则就逆时针转。

    function animate() {
      var elapsedSeconds = clock.getElapsedTime(),
          particleRotationDirection = particleRotationDeg <= 180 ? -1 : 1;

    为了在Three.js动画的每一帧真实的旋转它们,我们需要计算动画已经运行了多少秒,乘上速度,这样计算出粒子y值。

    particles.rotation.y = elapsedSeconds * particleRotationSpeed * particleRotationDirection;

    同样我们还需要跟踪当前的和上次的颜色信息,这样我们知道在哪些帧里改变它们。这里新的光线值介于0.2到0.7之间。

    if (lastColorRange[0] != currentColorRange[0] && lastColorRange[1] != currentColorRange[1]) {
      for (var i = 0; i < totalParticles; i++) {
        particles.children[i].material.color.setHSL(currentColorRange[0], currentColorRange[1], (Math.random() * (0.7 - 0.2) + 0.2));
      }
    
      lastColorRange = currentColorRange;
    }

    接下来循环动画:

    requestAnimationFrame(animate);

    最后让一切平滑连运动起来:

    update(clock.getDelta())
    render(clock.getDelta()) 
    effect.render(scene, camera);

    源码

    其中去掉了天气部分

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <title>Connecting up Google Cardboard to web APIs</title>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
        <style>
          body {
            margin: 0px;
            overflow: hidden;
          }
          #webglviewer {
            bottom: 0;
            left: 0;
            position: absolute;
            right: 0;
            top: 0;
          }
        </style>
      </head>
      <body>
        <div id="webglviewer"></div>
    
        <script src="./js/three.min.js"></script>
        <script src="./js/StereoEffect.js"></script>
        <script src="./js/DeviceOrientationControls.js"></script>
        <script src="./js/OrbitControls.js"></script>
        <script src="./js/helvetiker_regular.typeface.js"></script>
    
        <script>
            var scene,
                camera,
                renderer,
                element,
                container,
                effect,
                controls,
                clock,
    
                // Particles
                particles = new THREE.Object3D(),
                totalParticles = 200,
                maxParticleSize = 200,
                particleRotationSpeed = 0,
                particleRotationDeg = 0,
                lastColorRange = [0, 0.3],
                currentColorRange = [0, 0.3],
    
                // City and weather API set up
                cities = [['Sydney', '2147714'], ['New York', '5128638'], ['Tokyo', '1850147'], ['London', '2643743'], ['Mexico City', '3530597'], ['Miami', '4164138'], ['San Francisco', '5391959'], ['Rome', '3169070']],
                cityWeather = {},
                cityTimes = [],
                currentCity = 0,
                currentCityText = new THREE.TextGeometry(),
                currentCityTextMesh = new THREE.Mesh();
    
            init();
    
            function init() {
                scene = new THREE.Scene();
                camera = new THREE.PerspectiveCamera(90, window.innerWidth / window.innerHeight, 0.001, 700);
                camera.position.set(0, 15, 0);
                scene.add(camera);
    
                renderer = new THREE.WebGLRenderer();
                element = renderer.domElement;
                container = document.getElementById('webglviewer');
                container.appendChild(element);
    
                effect = new THREE.StereoEffect(renderer);
    
                // Our initial control fallback with mouse/touch events in case DeviceOrientation is not enabled
                controls = new THREE.OrbitControls(camera, element);
                controls.target.set(
                  camera.position.x + 0.15,
                  camera.position.y,
                  camera.position.z
                );
                controls.noPan = true;
                controls.noZoom = true;
    
                // Our preferred controls via DeviceOrientation
                function setOrientationControls(e) {
                    if (!e.alpha) {
                        return;
                    }
    
                    controls = new THREE.DeviceOrientationControls(camera, true);
                    controls.connect();
                    controls.update();
    
                    element.addEventListener('click', fullscreen, false);
    
                    window.removeEventListener('deviceorientation', setOrientationControls, true);
                }
                window.addEventListener('deviceorientation', setOrientationControls, true);
    
                // Lighting
                var light = new THREE.PointLight(0x999999, 2, 100);
                light.position.set(50, 50, 50);
                scene.add(light);
    
                var lightScene = new THREE.PointLight(0x999999, 2, 100);
                lightScene.position.set(0, 5, 0);
                scene.add(lightScene);
    
                var floorTexture = THREE.ImageUtils.loadTexture('textures/wood.jpg');
                floorTexture.wrapS = THREE.RepeatWrapping;
                floorTexture.wrapT = THREE.RepeatWrapping;
                floorTexture.repeat = new THREE.Vector2(50, 50);
                floorTexture.anisotropy = renderer.getMaxAnisotropy();
    
                var floorMaterial = new THREE.MeshPhongMaterial({
                    color: 0xffffff,
                    specular: 0xffffff,
                    shininess: 20,
                    shading: THREE.FlatShading,
                    map: floorTexture
                });
    
                var geometry = new THREE.PlaneBufferGeometry(1000, 1000);
    
                var floor = new THREE.Mesh(geometry, floorMaterial);
                floor.rotation.x = -Math.PI / 2;
                scene.add(floor);
    
                var particleTexture = THREE.ImageUtils.loadTexture('textures/particle.png'),
                    spriteMaterial = new THREE.SpriteMaterial({
                        map: particleTexture,
                        color: 0xffffff
                    });
    
                for (var i = 0; i < totalParticles; i++) {
                    var sprite = new THREE.Sprite(spriteMaterial);
    
                    sprite.scale.set(64, 64, 1.0);
                    sprite.position.set(Math.random() - 0.5, Math.random() - 0.5, Math.random() - 0.75);
                    sprite.position.setLength(maxParticleSize * Math.random());
    
                    sprite.material.blending = THREE.AdditiveBlending;
    
                    particles.add(sprite);
                }
                particles.position.y = 70;
                scene.add(particles);
    
    
                clock = new THREE.Clock();
    
                animate();
            }
    
    
            function animate() {
                var elapsedSeconds = clock.getElapsedTime(),
                    particleRotationDirection = particleRotationDeg <= 180 ? -1 : 1;
    
                particles.rotation.y = elapsedSeconds * particleRotationSpeed * particleRotationDirection;
    
                // We check if the color range has changed, if so, we'll change the colours
                if (lastColorRange[0] != currentColorRange[0] && lastColorRange[1] != currentColorRange[1]) {
    
                    for (var i = 0; i < totalParticles; i++) {
                        particles.children[i].material.color.setHSL(currentColorRange[0], currentColorRange[1], (Math.random() * (0.7 - 0.2) + 0.2));
                    }
    
                    lastColorRange = currentColorRange;
                }
    
                requestAnimationFrame(animate);
    
                update(clock.getDelta());
                render(clock.getDelta());
            }
    
            function resize() {
                var width = container.offsetWidth;
                var height = container.offsetHeight;
    
                camera.aspect = width / height;
                camera.updateProjectionMatrix();
    
                renderer.setSize(width, height);
                effect.setSize(width, height);
            }
    
            function update(dt) {
                resize();
    
                camera.updateProjectionMatrix();
    
                controls.update(dt);
            }
    
            function render(dt) {
                effect.render(scene, camera);
            }
    
            function fullscreen() {
                if (container.requestFullscreen) {
                    container.requestFullscreen();
                } else if (container.msRequestFullscreen) {
                    container.msRequestFullscreen();
                } else if (container.mozRequestFullScreen) {
                    container.mozRequestFullScreen();
                } else if (container.webkitRequestFullscreen) {
                    container.webkitRequestFullscreen();
                }
            }
    
        </script>
      </body>
    </html>
    

    效果: 
    这里写图片描述

    另一个网友的作品

    https://github.com/ritterliu/WebVR_Demo 
    blog地址:http://blog.csdn.net/ritterliu/article/details/51386980

    效果: 
    这里写图片描述

    源码:

    <!DOCTYPE html>
    <html>
    <head>
      <title>WebVR Demo</title>
      <style>
      body {
         100%;
        height: 100%;
        background-color: #000;
      }
      </style>
    </head>
    <body>
      <script src="./js/three.min.js"></script>
      <script src="./js/StereoEffect.js"></script>
      <script src="./js/OrbitControls.js"></script>
      <script src="./js/DeviceOrientationControls.js"></script>
      <script src="./js/helvetiker_regular.typeface.js"></script>
    
      <script>
      var scene, camera, renderer, effect, element, controls, word = "HELLO VR World", cube;
      init();
    
      function init() {
        scene = new THREE.Scene();
        camera = new THREE.PerspectiveCamera(90, window.innerWidth / window.innerHeight, 0.001, 700);
        camera.position.set(0, 15, 0);
        scene.add(camera);
    
        renderer = new THREE.WebGLRenderer();
        element = renderer.domElement;
        document.body.appendChild(renderer.domElement);
    
        effect = new THREE.StereoEffect(renderer);
    
        //Handle mouse control
        controls = new THREE.OrbitControls(camera, renderer.domElement);
        controls.target.set(
          camera.position.x + 0.01,
          camera.position.y,
          camera.position.z
        );
        window.addEventListener('deviceorientation', setDeviceOrientationControls, true);
    
        //Create light
        var light = new THREE.PointLight( 0xffffff, 1.2, 0 );
        light.position.set(0, 50, 0);
        scene.add(light);
    
        // Create floor
        var floorTexture = THREE.ImageUtils.loadTexture('img/grass.jpg');
        floorTexture.wrapS = THREE.RepeatWrapping;
        floorTexture.wrapT = THREE.RepeatWrapping;
        floorTexture.repeat = new THREE.Vector2(50, 50);
        var floorMaterial = new THREE.MeshPhongMaterial({
          map: floorTexture
        });
        var floorGeometry = new THREE.PlaneBufferGeometry(1000, 1000);
        var floor = new THREE.Mesh(floorGeometry, floorMaterial);
        floor.rotation.x = -Math.PI / 2;
        scene.add(floor);
    
        // Create box
        var geometry = new THREE.BoxGeometry(6, 6, 6);
        var material = new THREE.MeshNormalMaterial();
        cube = new THREE.Mesh(geometry, material);
        cube.position.set(-15, 30, 10);
        scene.add(cube);
    
        //Create text
        var textGeometry = new THREE.TextGeometry(word, {
          size: 5,
          height: 1
        });
        var text = new THREE.Mesh(textGeometry, new THREE.MeshBasicMaterial({
          color: 0xffffff
        }));
        text.position.set(15, 15, -25);
        text.rotation.set(0, 30, 0);
        scene.add(text);
    
        animate();
      }
    
      // Our preferred controls via DeviceOrientation
      function setDeviceOrientationControls(e) {
        controls = new THREE.DeviceOrientationControls(camera, true);
        controls.connect();
        controls.update();
        window.removeEventListener('deviceorientation', setDeviceOrientationControls, true);
      }
    
      function animate() {
        requestAnimationFrame(animate);
    
        var width = window.innerWidth;
        var height = window.innerHeight;
    
        camera.aspect = width / height;
        camera.updateProjectionMatrix();
    
        renderer.setSize(width, height);
        effect.setSize(width, height);
    
        cube.rotation.x += 0.01;
        cube.rotation.y += 0.01;
    
        controls.update();
        effect.render(scene, camera);
      }
      </script>
    </body>
    </html>

    最后发现这篇文章,写得更详细: 
    https://zhuanlan.zhihu.com/p/21556998

  • 相关阅读:
    常见的线性结构
    Lambda表达式学习笔记
    Spring Security 入门 (二)
    Spring Security 入门(一)
    Eclipse 创建 Maven 项目
    初学 Spring MVC(基于 Spring in Action)
    蓝桥杯之入学考试
    Java 学习总结
    二叉搜索树和红黑树
    Detours 劫持
  • 原文地址:https://www.cnblogs.com/jingouli/p/7884605.html
Copyright © 2011-2022 走看看