Oculus + Node.js + Three.js 打造VR世界
Oculus Rift 是一款為電子遊戲設計的頭戴式顯示器。這是一款虛擬現實裝置。這款裝置很可能改變未來人們遊戲的方式。
週五Hackday Showcase的時候,突然有了點小靈感,便將閒置在公司的Oculus DK2借回家了——已經都是灰塵了~~。
在嘗試一個晚上的開發環境搭建後,我放棄了開發原生應用的想法。一是沒有屬於自己的電腦(如果Raspberry Pi II不算的話)——沒有Windows、沒有GNU/Linux,二是公司配的電腦是Mac OS。對於嵌入式開發和遊戲開發來說,Mac OS簡直是手機中的Windows Phone——坑爹的LLVM、GCC(Mac OS )、OpenGL、OGLPlus、C++11。並且官方對Mac OS和Linux的SDK的支援已經落後了好幾個世紀。
說到底,還是Web的開發環境到底還是比較容易搭建的。這個repo的最後效果圖如下所示:
效果:
- WASD控制前進、後退等等。
- 旋轉頭部 = 真實的世界。
- 附加效果: 看久了頭暈。
現在,讓我們開始構建吧。
Node Oculus Services
這裡,我們所要做的事情便是將感測器返回來的四元數(Quaternions)與尤拉角(Euler angles)以API的形式返回到前端。
安裝Node NMD
Node.js上有一個Oculus的外掛名為node-hmd,hmd即面向頭戴式顯示器。它就是Oculus SDK的Node介面,雖說年代已經有些久遠了,但是似乎是可以用的——官方針對 Mac OS和Linux的SDK也已經很久沒有更新了。
在GNU/Linux系統下,你需要安裝下面的這些東西的
freeglut3-dev
mesa-common-dev
libudev-dev
libxext-dev
libxinerama-dev
libxrandr-dev
libxxf86vm-dev
Mac OS如果安裝失敗,請使用Clang來,以及GCC的C標準庫(PS: 就是 Clang + GCC的混合體,它們之間就是各種複雜的關係。。):
export CXXFLAGS=-stdlib=libstdc++
export CC=/usr/bin/clang
export CXX=/usr/bin/clang++
(PS: 我使用的是Mac OS El Captian + Xcode 7.0. 2)clang版本如下:
Apple LLVM version 7.0.2 (clang-700.1.81)
Target: x86_64-apple-darwin15.0.0
Thread model: posix
反正都是會報錯的:
ld: warning: object file (Release/obj.target/hmd/src/platform/mac/LibOVR/Src/Service/Service_NetClient.o) was built for newer OSX version (10.7) than being linked (10.5)
ld: warning: object file (Release/obj.target/hmd/src/platform/mac/LibOVR/Src/Tracking/Tracking_SensorStateReader.o) was built for newer OSX version (10.7) than being linked (10.5)
ld: warning: object file (Release/obj.target/hmd/src/platform/mac/LibOVR/Src/Util/Util_ImageWindow.o) was built for newer OSX version (10.7) than being linked (10.5)
ld: warning: object file (Release/obj.target/hmd/src/platform/mac/LibOVR/Src/Util/Util_Interface.o) was built for newer OSX version (10.7) than being linked (10.5)
ld: warning: object file (Release/obj.target/hmd/src/platform/mac/LibOVR/Src/Util/Util_LatencyTest2Reader.o) was built for newer OSX version (10.7) than being linked (10.5)
ld: warning: object file (Release/obj.target/hmd/src/platform/mac/LibOVR/Src/Util/Util_Render_Stereo.o) was built for newer OSX version (10.7) than being linked (10.5)
node-hmd@0.2.1 node_modules/node-hmd
不過,有最後一行就夠了。
Node.js Oculus Hello,World
現在,我們就可以寫一個Hello,World了,直接來官方的示例~~。
var hmd = require('node-hmd');
var manager = hmd.createManager("oculusrift");
manager.getDeviceInfo(function(err, deviceInfo) {
if(!err) {
console.log(deviceInfo);
}
else {
console.error("Unable to retrieve device information.");
}
});
manager.getDeviceOrientation(function(err, deviceOrientation) {
if(!err) {
console.log(deviceOrientation);
}
else {
console.error("Unable to retrieve device orientation.");
}
});
執行之前,記得先連上你的Oculus。會有類似於下面的結果:
{ CameraFrustumFarZInMeters: 2.5,
CameraFrustumHFovInRadians: 1.29154372215271,
CameraFrustumNearZInMeters: 0.4000000059604645,
CameraFrustumVFovInRadians: 0.942477822303772,
DefaultEyeFov:
[ { RightTan: 1.0923680067062378,
LeftTan: 1.0586576461791992,
DownTan: 1.3292863368988037,
UpTan: 1.3292863368988037 },
{ RightTan: 1.0586576461791992,
LeftTan: 1.0923680067062378,
DownTan: 1.3292863368988037,
UpTan: 1.3292863368988037 } ],
DisplayDeviceName: '',
DisplayId: 880804035,
DistortionCaps: 66027,
EyeRenderOrder: [ 1, 0 ],
...
接著,我們就可以實時返回這些資料了。
Node Oculus WebSocket
在網上看到http://laht.info/WebGL/DK2Demo.html這個虛擬現實的電影,並且發現了它有一個WebSocket,然而是Java寫的,只能拿來當參考程式碼。
現在我們就可以寫一個這樣的Web Services,用的仍然是Express + Node.js + WS。
var hmd = require("node-hmd"),
express = require("express"),
http = require("http").createServer(),
WebSocketServer = require('ws').Server,
path = require('path');
// Create HMD manager object
console.info("Attempting to load node-hmd driver: oculusrift");
var manager = hmd.createManager("oculusrift");
if (typeof(manager) === "undefined") {
console.error("Unable to load driver: oculusrift");
process.exit(1);
}
// Instantiate express server
var app = express();
app.set('port', process.env.PORT || 3000);
app.use(express.static(path.join(__dirname + '/', 'public')));
app.set('views', path.join(__dirname + '/public/', 'views'));
app.set('view engine', 'jade');
app.get('/demo', function (req, res) {
'use strict';
res.render('demo', {
title: 'Home'
});
});
// Attach socket.io listener to the server
var wss = new WebSocketServer({server: http});
var id = 1;
wss.on('open', function open() {
console.log('connected');
});
// On socket connection set up event emitters to automatically push the HMD orientation data
wss.on("connection", function (ws) {
function emitOrientation() {
id = id + 1;
var deviceQuat = manager.getDeviceQuatSync();
var devicePosition = manager.getDevicePositionSync();
var data = JSON.stringify({
id: id,
quat: deviceQuat,
position: devicePosition
});
ws.send(data, function (error) {
//it's a bug of websocket, see in https://github.com/websockets/ws/issues/337
});
}
var orientation = setInterval(emitOrientation, 1000);
ws.on("message", function (data) {
clearInterval(orientation);
orientation = setInterval(emitOrientation, data);
});
ws.on("close", function () {
setTimeout(null, 500);
clearInterval(orientation);
console.log("disconnect");
});
});
// Launch express server
http.on('request', app);
http.listen(3000, function () {
console.log("Express server listening on port 3000");
});
總之,就是連上的時候不斷地發現裝置的資料:
var data = JSON.stringify({
id: id,
quat: deviceQuat,
position: devicePosition
});
ws.send(data, function (error) {
//it's a bug of websocket, see in https://github.com/websockets/ws/issues/337
});
上面有一行註釋是我之前一直遇到的一個坑,總之需要callback就是了。
Three.js + Oculus Effect + DK2 Control
在最後我們需要如下的畫面:
當然,如果你已經安裝了Web VR這一類的東西,你就不需要這樣的效果了。如標題所說,你已經知道要用Oculus Effect,它是一個Three.js的外掛。
在之前的版本中,Three.js都提供了Oculus的Demo,當然只能用來看。並且互動的介面是HTTP,感覺很難玩~~。
Three.js DK2Controls
這時,我們就需要根據上面傳過來的四元數
(Quaternions)與尤拉角(Euler angles)來作相應的處理。
{
"position": {
"x": 0.020077044144272804,
"y": -0.0040545957162976265,
"z": 0.16216422617435455
},
"quat": {
"w": 0.10187230259180069,
"x": -0.02359195239841938,
"y": -0.99427556991577148,
"z": -0.021934293210506439
}
}
尤拉角與四元數
(ps: 如果沒copy好,麻煩提出正確的說法,原諒我這個掛過高數的人。我只在高中的時候,看到這些資料。)
尤拉角是一組用於描述剛體姿態的角度,尤拉提出,剛體在三維歐氏空間中的任意朝向可以由繞三個軸的轉動複合生成。通常情況下,三個軸是相互正交的。
對應的三個角度又分別成為roll(橫滾角),pitch(俯仰角)和yaw(偏航角),就是上面的postion裡面的三個值。。
roll = (rotation about Z);
pitch = (rotation about (Roll • Y));
yaw = (rotation about (Pitch • Raw • Z));”
-- 引自《Oculus Rift In Action》
轉換成程式碼。。
this.headPos.set(sensorData.position.x * 10 - 0.4, sensorData.position.y * 10 + 1.75, sensorData.position.z * 10 + 10);
四元數是由愛爾蘭數學家威廉·盧雲·哈密頓在1843年發現的數學概念。
從明確地角度而言,四元數是複數的不可交換延伸。如把四元數的集合考慮成多維實數空間的話,四元數就代表著一個四維空間,相對於複數為二維空間。
反正就是用於描述三維空間的旋轉變換
。
結合下程式碼:
this.headPos.set(sensorData.position.x * 10 - 0.4, sensorData.position.y * 10 + 1.75, sensorData.position.z * 10 + 10);
this.headQuat.set(sensorData.quat.x, sensorData.quat.y, sensorData.quat.z, sensorData.quat.w);
this.camera.setRotationFromQuaternion(this.headQuat);
this.controller.setRotationFromMatrix(this.camera.matrix);
就是,我們需要設定camera和controller的旋轉。
這使我有足夠的理由相信Oculus就是一個手機 + 一個6軸運動處理元件的升級板——因為,我玩過MPU6050這樣的感測器,如圖。。。
Three.js DK2Controls
雖然下面的程式碼不是我寫的,但是還是簡單地說一下。
/*
Copyright 2014 Lars Ivar Hatledal
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
THREE.DK2Controls = function (camera) {
this.camera = camera;
this.ws;
this.sensorData;
this.lastId = -1;
this.controller = new THREE.Object3D();
this.headPos = new THREE.Vector3();
this.headQuat = new THREE.Quaternion();
var that = this;
var ws = new WebSocket("ws://localhost:3000/");
ws.onopen = function () {
console.log("### Connected ####");
};
ws.onmessage = function (evt) {
var message = evt.data;
try {
that.sensorData = JSON.parse(message);
} catch (err) {
console.log(message);
}
};
ws.onclose = function () {
console.log("### Closed ####");
};
this.update = function () {
var sensorData = this.sensorData;
if (sensorData) {
var id = sensorData.id;
if (id > this.lastId) {
this.headPos.set(sensorData.position.x * 10 - 0.4, sensorData.position.y * 10 + 1.75, sensorData.position.z * 10 + 10);
this.headQuat.set(sensorData.quat.x, sensorData.quat.y, sensorData.quat.z, sensorData.quat.w);
this.camera.setRotationFromQuaternion(this.headQuat);
this.controller.setRotationFromMatrix(this.camera.matrix);
}
this.lastId = id;
}
this.camera.position.addVectors(this.controller.position, this.headPos);
if (this.camera.position.y < -10) {
this.camera.position.y = -10;
}
if (ws) {
if (ws.readyState === 1) {
ws.send("get\n");
}
}
};
};
開啟WebSocket的時候,不斷地獲取最新的感測器狀態,然後update。誰在呼叫update方法?Three.js
我們需要在我們的初始化程式碼裡初始化我們的control:
var oculusControl;
function init() {
...
oculusControl = new THREE.DK2Controls( camera );
...
}
並且不斷地呼叫update方法。
function animate() {
requestAnimationFrame( animate );
render();
stats.update();
}
function render() {
oculusControl.update( clock.getDelta() );
THREE.AnimationHandler.update( clock.getDelta() * 100 );
camera.useQuaternion = true;
camera.matrixWorldNeedsUpdate = true;
effect.render(scene, camera);
}
最後,新增相應的KeyHandler就好了~~。
Three.js KeyHandler
KeyHandler對於習慣了Web開發的人來說就比較簡單了:
this.onKeyDown = function (event) {
switch (event.keyCode) {
case 87: //W
this.wasd.up = true;
break;
case 83: //S
this.wasd.down = true;
break;
case 68: //D
this.wasd.right = true;
break;
case 65: //A
this.wasd.left = true;
break;
}
};
this.onKeyUp = function (event) {
switch (event.keyCode) {
case 87: //W
this.wasd.up = false;
break;
case 83: //S
this.wasd.down = false;
break;
case 68: //D
this.wasd.right = false;
break;
case 65: //A
this.wasd.left = false;
break;
}
};
然後就是萬惡的if語句了:
if (this.wasd.up) {
this.controller.translateZ(-this.translationSpeed * delta);
}
if (this.wasd.down) {
this.controller.translateZ(this.translationSpeed * delta);
}
if (this.wasd.right) {
this.controller.translateX(this.translationSpeed * delta);
}
if (this.wasd.left) {
this.controller.translateX(-this.translationSpeed * delta);
}
this.camera.position.addVectors(this.controller.position, this.headPos);
if (this.camera.position.y < -10) {
this.camera.position.y = -10;
}
快接上你的HMD試試吧~~
結語
如我在《RePractise前端篇: 前端演進史》一文中所說的,這似乎就是新的"前端"。
相關文章
- 谷歌VR繪圖軟體 正式登入Oculus Rift谷歌VR繪圖
- Oculus和Gear VR新功能 可分享朋友圈?VR
- Vue + Node.js + Three.js 注意點VueNode.js
- Oculus舉辦線上遊戲展,VR遊戲何時現拐點?遊戲VR
- VR頭盔巨頭Oculus大手筆,給開發者派送頭盔!VR
- HTC Vive對比Oculus Rift 誰是最佳PC平臺VRVR
- 蘋果VR眼鏡曝光 這是輕薄款的Oculus麼蘋果VR
- 天翼雲實時雲渲染,助力打造世界VR產業大會雲上之城VR產業
- 利用three.js七步實現VR看房JSVR
- HTC擊敗Oculus,成為遊戲開發者最愛的VR平臺遊戲開發VR
- 移動VR社交新進展,Oculus讓Parties和Rooms上線GearVRVROOM
- Oculus感測器開賣了!實現房間級VR體驗VR
- Oculus&HTC:VR裝置何時能像智慧手機一樣普及?VR
- 谷歌VR地圖實現對Oculus Rift和Touch控制器的支援谷歌VR地圖
- GDC:16%遊戲開發者正在開發VR遊戲 Oculus平臺最受歡迎遊戲開發VR
- 索尼不打自招:PS VR的確沒有Oculus Rift技術高VR
- Merge VR欲打造VR遙控器 可以支援ios系統VRiOS
- Three.js - 走進3D的奇妙世界JS3D
- Oculus釋出新技術:可使低端電腦使用者獲得VR體驗VR
- VR奇葩應用 VR眼鏡竟然還能維護世界和平?VR
- 3D世界-Three.js的探究與實踐3DJS
- 為什麼JavaScript也將征服VR世界JavaScriptVR
- Unity預計年底推出VR編輯器,定製你的VR遊戲世界UnityVR遊戲
- 獨家揭祕國內唯一一家獲得Oculus投資的VR遊戲團隊VR遊戲
- Three.js系列: 在元宇宙看電影,享受 VR 視覺盛宴JS元宇宙VR視覺
- 《西部世界:覺醒》開發商分享VR技巧VR
- Javascript和Node.js正在蠶食世界JavaScriptNode.js
- 讓世界更精彩,中興通訊亮相2023世界VR大會VR
- R星開發3A開放世界VR新作 《黑色洛城VR》團隊負責VR
- go pro推出無人機?搭載VR功能玩轉世界Go無人機VR
- Oculus Rift的3D互動VR影片今年公佈 不出門也能隨心旅遊3DVR
- VR開放世界有多好玩? 《故土》:網易VR遊戲戰略佈局的里程碑VR遊戲
- 軟通動力與華為共同打造文旅AR解決方案並獲世界VR產業大會年度創新獎VR產業
- 麥當勞推出“HappyGoggles”,讓兒童體驗VR世界APPGoVR
- 如何在開源世界打造自己的明星 Project?Project
- VR遊戲英雄時代登陸青睞之光 大幅致敬魔獸世界VR遊戲
- 網易首款VR新遊《Nostos》製作分享:初探VR,如何從零開始構建虛擬世界?VR虛擬世界
- VR/AR賦能,注入產業新力量|綠盟科技深度參與2021世界VR產業大會VR產業