(轉)Three.JS學習 9:WEBVR 入門demo

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

本文參考文檔主要來源: 
https://www.sitepoint.com/bringing-vr-to-web-google-cardboard-three-js/php

本文內容是介紹基於Three.js建立一個可使用谷歌眼鏡演示的WEB虛擬現實網頁。css

準備工做

首先須要準備一些js 
下載項目: 
https://github.com/sitepoint-editors/VRWeatherParticleshtml

使用本身熟悉的開發環境建立一個web項目,把上面下載項目裏的/js 、 /textures放到項目裏,新建一個index.html文件。 
這裏寫圖片描述java

下載的項目裏有完整的源碼,本文基本是對其程序說明作簡單翻譯。git

引用的文件說明

  • three.min.js : Threejs庫
  • StereoEffect.js:容許咱們把普通的Three.js分紅兩個顯示的場景合併到一塊兒顯示,這是VR體驗的基本需求
  • DeviceOrientationControls.js:告訴Three.js設備的朝向、向哪移動。
  • OrbitControls.js:容許咱們經過拖動、點擊事件來控制場景(在DeviceOrientation事件無效的時候,好比電腦模擬時適用)
  • helvetiker_regular.typeface.js:將在Three.js顯示文本用到的字體

下面是init()函數,其中有一些內容在前幾章已有說起,這裏不會徹底詳述。github

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);

 

把粒子放到一塊兒

先定義一些粒子相關的公用變量,而且建立了一個粒子對象,用來保存浮動的粒子,後面會詳細講解這些變量。web

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,若是想增長場景裏的粒子數量,能夠把這個值增大。json

下面遍歷粒子並把它們加入到了粒子對象裏,咱們須要把粒子對象升高以讓它旋浮起來。api

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 { width: 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

相關文章
相關標籤/搜索