webxr-basics.html 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. <!DOCTYPE html><html lang="en"><head>
  2. <meta charset="utf-8">
  3. <title>VR</title>
  4. <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
  5. <meta name="twitter:card" content="summary_large_image">
  6. <meta name="twitter:site" content="@threejs">
  7. <meta name="twitter:title" content="Three.js – VR">
  8. <meta property="og:image" content="https://threejs.org/files/share.png">
  9. <link rel="shortcut icon" href="../../files/favicon_white.ico" media="(prefers-color-scheme: dark)">
  10. <link rel="shortcut icon" href="../../files/favicon.ico" media="(prefers-color-scheme: light)">
  11. <link rel="stylesheet" href="../resources/lesson.css">
  12. <link rel="stylesheet" href="../resources/lang.css">
  13. <!-- Import maps polyfill -->
  14. <!-- Remove this when import maps will be widely supported -->
  15. <script async src="https://unpkg.com/[email protected]/dist/es-module-shims.js"></script>
  16. <script type="importmap">
  17. {
  18. "imports": {
  19. "three": "../../build/three.module.js"
  20. }
  21. }
  22. </script>
  23. </head>
  24. <body>
  25. <div class="container">
  26. <div class="lesson-title">
  27. <h1>VR</h1>
  28. </div>
  29. <div class="lesson">
  30. <div class="lesson-main">
  31. <p>Making a VR app in three.js is pretty simple. You basically just have to tell
  32. three.js you want to use WebXR. If you think about it a few things about WebXR
  33. should be clear. Which way the camera is pointing is supplied by the VR system
  34. itself since the user turns their head to choose a direction to look. Similarly
  35. the field of view and aspect will be supplied by the VR system since each system
  36. has a different field of view and display aspect.</p>
  37. <p>Let's take an example from the article on <a href="responsive.html">making a responsive webpage</a>
  38. and make it support VR.</p>
  39. <p>Before we get started you're going to need a VR capable device like an Android
  40. smartphone, Google Daydream, Oculus Go, Oculus Rift, Vive, Samsung Gear VR., an
  41. iPhone with a <a href="https://apps.apple.com/us/app/webxr-viewer/id1295998056">WebXR browser</a>.</p>
  42. <p>Next, if you are running locally you need to run a simple web server like is
  43. covered in <a href="setup.html">the article on setting up</a>. </p>
  44. <p>If the device you are using to view VR is not the same computer you're running
  45. on you need to serve your webpage via https or else the browser will not allow using
  46. the WebXR API. The server mentioned in <a href="setup.html">the article on setting up</a>
  47. called <a href="https://greggman.github.io/servez">Servez</a> has an option to use https.
  48. Check it and start the server. </p>
  49. <div class="threejs_center"><img src="../resources/images/servez-https.png" class="nobg" style="width: 912px;"></div>
  50. <p>The note the URLs. You need the one that is your computer's local ipaddress.
  51. It will usually start with <code class="notranslate" translate="no">192</code>, <code class="notranslate" translate="no">172</code> or <code class="notranslate" translate="no">10</code>. Type that full address, including the <code class="notranslate" translate="no">https://</code> part
  52. into your VR device's browser. Note: Your computer and your VR device need to be on the same local network
  53. or WiFi and you probably need to be on a home network. note: Many cafes are setup to disallow this kind of
  54. machine to machine connection.</p>
  55. <p>You'll be greeted with an error something like the one below. Click "advanced" and then click
  56. <em>proceed</em>.</p>
  57. <div class="threejs_center"><img src="../resources/images/https-warning.gif"></div>
  58. <p>Now you can run your examples.</p>
  59. <p>If you're really going to do WebXR development another thing you should learn about is
  60. <a href="https://developers.google.com/web/tools/chrome-devtools/remote-debugging/">remote debugging</a>
  61. so that you can see console warnings, errors, and of course actually
  62. <a href="debugging-javascript.html">debug your code</a>.</p>
  63. <p>If you just want to see the code work below you can just run the code from
  64. this site.</p>
  65. <p>The first thing we need to do is include the VR support after
  66. including three.js</p>
  67. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">import * as THREE from 'three';
  68. +import {VRButton} from 'three/addons/webxr/VRButton.js';
  69. </pre>
  70. <p>Then we need to enable three.js's WebXR support and add its
  71. VR button to our page</p>
  72. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">function main() {
  73. const canvas = document.querySelector('#c');
  74. const renderer = new THREE.WebGLRenderer({antialias: true, canvas});
  75. + renderer.xr.enabled = true;
  76. + document.body.appendChild(VRButton.createButton(renderer));
  77. </pre>
  78. <p>We need to let three.js run our render loop. Until now we have used a
  79. <code class="notranslate" translate="no">requestAnimationFrame</code> loop but to support VR we need to let three.js handle
  80. our render loop for us. We can do that by calling
  81. <a href="/docs/#api/en/renderers/WebGLRenderer.setAnimationLoop"><code class="notranslate" translate="no">WebGLRenderer.setAnimationLoop</code></a> and passing a function to call for the loop.</p>
  82. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">function render(time) {
  83. time *= 0.001;
  84. if (resizeRendererToDisplaySize(renderer)) {
  85. const canvas = renderer.domElement;
  86. camera.aspect = canvas.clientWidth / canvas.clientHeight;
  87. camera.updateProjectionMatrix();
  88. }
  89. cubes.forEach((cube, ndx) =&gt; {
  90. const speed = 1 + ndx * .1;
  91. const rot = time * speed;
  92. cube.rotation.x = rot;
  93. cube.rotation.y = rot;
  94. });
  95. renderer.render(scene, camera);
  96. - requestAnimationFrame(render);
  97. }
  98. -requestAnimationFrame(render);
  99. +renderer.setAnimationLoop(render);
  100. </pre>
  101. <p>There is one more detail. We should probably set a camera height
  102. that's kind of average for a standing user.</p>
  103. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
  104. +camera.position.set(0, 1.6, 0);
  105. </pre>
  106. <p>and move the cubes up to be in front of the camera</p>
  107. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const cube = new THREE.Mesh(geometry, material);
  108. scene.add(cube);
  109. cube.position.x = x;
  110. +cube.position.y = 1.6;
  111. +cube.position.z = -2;
  112. </pre>
  113. <p>We set them to <code class="notranslate" translate="no">z = -2</code> since the camera will now be at <code class="notranslate" translate="no">z = 0</code> and
  114. camera defaults to looking down the -z axis.</p>
  115. <p>This brings up an extremely important point. <strong>Units in VR are in meters</strong>.
  116. In other words <strong>One Unit = One Meter</strong>. This means the camera is 1.6 meters above 0.
  117. The cube's centers are 2 meters in front of the camera. Each cube
  118. is 1x1x1 meter large. This is important because VR needs to adjust things to the
  119. user <em>in the real world</em>. That means we need the units used in three.js to match
  120. the user's own movements.</p>
  121. <p>And with that we should get 3 spinning cubes in front
  122. of the camera with a button to enter VR.</p>
  123. <p></p><div translate="no" class="threejs_example_container notranslate">
  124. <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/webxr-basic.html"></iframe></div>
  125. <a class="threejs_center" href="/manual/examples/webxr-basic.html" target="_blank">click here to open in a separate window</a>
  126. </div>
  127. <p></p>
  128. <p>I find that VR works better if we have something surrounding the camera like
  129. room for reference so let's add a simple grid cubemap like we covered in
  130. <a href="backgrounds.html">the article on backgrounds</a>. We'll just use the same grid
  131. texture for each side of the cube which will give as a grid room.</p>
  132. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const scene = new THREE.Scene();
  133. +{
  134. + const loader = new THREE.CubeTextureLoader();
  135. + const texture = loader.load([
  136. + 'resources/images/grid-1024.png',
  137. + 'resources/images/grid-1024.png',
  138. + 'resources/images/grid-1024.png',
  139. + 'resources/images/grid-1024.png',
  140. + 'resources/images/grid-1024.png',
  141. + 'resources/images/grid-1024.png',
  142. + ]);
  143. + scene.background = texture;
  144. +}
  145. </pre>
  146. <p>That's better.</p>
  147. <p></p><div translate="no" class="threejs_example_container notranslate">
  148. <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/webxr-basic-w-background.html"></iframe></div>
  149. <a class="threejs_center" href="/manual/examples/webxr-basic-w-background.html" target="_blank">click here to open in a separate window</a>
  150. </div>
  151. <p></p>
  152. <p>Note: To actually see VR you will need a WebXR compatible device.
  153. I believe most Android Phones can support WebXR using Chrome or Firefox.
  154. For iOS you might be able to use this <a href="https://apps.apple.com/us/app/webxr-viewer/id1295998056">WebXR App</a>
  155. though in general WebXR support on iOS is unsupported as of May 2019.</p>
  156. <p>To use WebXR on Android or iPhone you'll need a <em>VR Headset</em>
  157. for phones. You can get them for anywhere from $5 for one made of cardboard
  158. to $100. Unfortunately I don't know which ones to recommend. I've purchased
  159. 6 of them over the years and they are all of varying quality. I've
  160. never paid more than about $25.</p>
  161. <p>Just to mention some of the issues</p>
  162. <ol>
  163. <li><p>Do they fit your phone</p>
  164. <p>Phones come in a variety of sizes and so the VR headsets need to match.
  165. Many headsets claim to match a large variety of sizes. My experience
  166. is the more sizes they match the worse they actually are since instead
  167. of being designed for a specific size they have to make compromises
  168. to match more sizes. Unfortunately multi-size headsets are the most common type.</p>
  169. </li>
  170. <li><p>Can they focus for your face</p>
  171. <p>Some devices have more adjustments than others. Generally there
  172. are at most 2 adjustments. How far the lenses are from your eyes
  173. and how far apart the lenses are.</p>
  174. </li>
  175. <li><p>Are they too reflective</p>
  176. <p>Many headsets of a cone of plastic from your eye to the phone.
  177. If that plastic is shinny or reflective then it will act like
  178. a mirror reflecting the screen and be very distracting.</p>
  179. <p>Few if any of the reviews seem to cover this issue.</p>
  180. </li>
  181. <li><p>Are the comfortable on your face.</p>
  182. <p>Most of the devices rest on your nose like a pair of glasses.
  183. That can hurt after a few minutes. Some have straps that go around
  184. your head. Others have a 3rd strap that goes over your head. These
  185. may or may not help keep the device at the right place.</p>
  186. <p>It turns out for most (all?) devices, you eyes need to be centered
  187. with the lenses. If the lenses are slightly above or below your
  188. eyes the image gets out of focus. This can be very frustrating
  189. as things might start in focus but 45-60 seconds later the device
  190. has shifted up or down 1 millimeter and you suddenly realize you've
  191. been struggling to focus on a blurry image.</p>
  192. </li>
  193. <li><p>Can they support your glasses.</p>
  194. <p>If you wear eye glasses then you'll need to read the reviews to see
  195. if a particular headset works well with eye glasses.</p>
  196. </li>
  197. </ol>
  198. <p>I really can't make any recommendations unfortunately. <a href="https://vr.google.com/cardboard/get-cardboard/">Google has some
  199. cheap recommendations made from cardboard</a>
  200. some of them as low as $5 so maybe start there and if you enjoy it
  201. then consider upgrading. $5 is like the price of 1 coffee so seriously, give it try!</p>
  202. <p>There are also 3 basic types of devices.</p>
  203. <ol>
  204. <li><p>3 degrees of freedom (3dof), no input device</p>
  205. <p>This is generally the phone style although sometimes you can
  206. buy a 3rd party input device. The 3 degrees of freedom
  207. mean you can look up/down (1), left/right(2) and you can tilt
  208. your head left and right (3).</p>
  209. </li>
  210. <li><p>3 degrees of freedom (3dof) with 1 input device (3dof)</p>
  211. <p>This is basically Google Daydream and Oculus GO</p>
  212. <p>These also allow 3 degrees of freedom and include a small
  213. controller that acts like a laser pointer inside VR.
  214. The laser pointer also only has 3 degrees of freedom. The
  215. system can tell which way the input device is pointing but
  216. it can not tell where the device is.</p>
  217. </li>
  218. <li><p>6 degrees of freedom (6dof) with input devices (6dof)</p>
  219. <p>These are <em>the real deal</em> haha. 6 degrees of freedom
  220. means not only do these device know which way you are looking
  221. but they also know where your head actually is. That means
  222. if you move from left to right or forward and back or stand up / sit down
  223. the devices can register this and everything in VR moves accordingly.
  224. It's spookily and amazingly real feeling. With a good demo
  225. you'll be blown away or at least I was and still am.</p>
  226. <p>Further these devices usually include 2 controllers, one
  227. for each hand and the system can tell exactly where your
  228. hands are and which way they are oriented and so you can
  229. manipulate things in VR by just reaching out, touching,
  230. pushing, twisting, etc...</p>
  231. <p>6 degree of freedom devices include the Vive and Vive Pro,
  232. the Oculus Rift and Quest, and I believe all of the Windows MR devices.</p>
  233. </li>
  234. </ol>
  235. <p>With all that covered I don't for sure know which devices will work with WebXR.
  236. I'm 99% sure that most Android phones will work when running Chrome. You may
  237. need to turn on WebXR support in <a href="about:flags"><code class="notranslate" translate="no">about:flags</code></a>. I also know Google
  238. Daydream will also work and similarly you need to enable WebXR support in
  239. <a href="about:flags"><code class="notranslate" translate="no">about:flags</code></a>. Oculus Rift, Vive, and Vive Pro will work via
  240. Chrome or Firefox. I'm less sure about Oculus Go and Oculus Quest as both of
  241. them use custom OSes but according to the internet they both appear to work.</p>
  242. <p>Okay, after that long detour about VR Devices and WebXR
  243. there's some things to cover</p>
  244. <ul>
  245. <li><p>Supporting both VR and Non-VR</p>
  246. <p>AFAICT, at least as of r112, there is no easy way to support
  247. both VR and non-VR modes with three.js. Ideally
  248. if not in VR mode you'd be able to control the camera using
  249. whatever means you want, for example the <a href="/docs/#examples/controls/OrbitControls"><code class="notranslate" translate="no">OrbitControls</code></a>,
  250. and you'd get some kind of event when switching into and
  251. out of VR mode so that you could turn the controls on/off.</p>
  252. </li>
  253. </ul>
  254. <p>If three.js adds some support to do both I'll try to update
  255. this article. Until then you might need 2 versions of your
  256. site OR pass in a flag in the URL, something like</p>
  257. <pre class="prettyprint showlinemods notranslate notranslate" translate="no">https://mysite.com/mycooldemo?allowvr=true
  258. </pre><p>Then we could add some links in to switch modes</p>
  259. <pre class="prettyprint showlinemods notranslate lang-html" translate="no">&lt;body&gt;
  260. &lt;canvas id="c"&gt;&lt;/canvas&gt;
  261. + &lt;div class="mode"&gt;
  262. + &lt;a href="?allowvr=true" id="vr"&gt;Allow VR&lt;/a&gt;
  263. + &lt;a href="?" id="nonvr"&gt;Use Non-VR Mode&lt;/a&gt;
  264. + &lt;/div&gt;
  265. &lt;/body&gt;
  266. </pre>
  267. <p>and some CSS to position them</p>
  268. <pre class="prettyprint showlinemods notranslate lang-css" translate="no">body {
  269. margin: 0;
  270. }
  271. #c {
  272. width: 100%;
  273. height: 100%;
  274. display: block;
  275. }
  276. +.mode {
  277. + position: absolute;
  278. + right: 1em;
  279. + top: 1em;
  280. +}
  281. </pre>
  282. <p>in your code you could use that parameter like this</p>
  283. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">function main() {
  284. const canvas = document.querySelector('#c');
  285. const renderer = new THREE.WebGLRenderer({antialias: true, canvas});
  286. - renderer.xr.enabled = true;
  287. - document.body.appendChild(VRButton.createButton(renderer));
  288. const fov = 75;
  289. const aspect = 2; // the canvas default
  290. const near = 0.1;
  291. const far = 5;
  292. const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
  293. camera.position.set(0, 1.6, 0);
  294. + const params = (new URL(document.location)).searchParams;
  295. + const allowvr = params.get('allowvr') === 'true';
  296. + if (allowvr) {
  297. + renderer.xr.enabled = true;
  298. + document.body.appendChild(VRButton.createButton(renderer));
  299. + document.querySelector('#vr').style.display = 'none';
  300. + } else {
  301. + // no VR, add some controls
  302. + const controls = new OrbitControls(camera, canvas);
  303. + controls.target.set(0, 1.6, -2);
  304. + controls.update();
  305. + document.querySelector('#nonvr').style.display = 'none';
  306. + }
  307. </pre>
  308. <p>Whether that's good or bad I don't know. I have a feeling the differences
  309. between what's needed for VR and what's needed for non-VR are often
  310. very different so for all but the most simple things maybe 2 separate pages
  311. are better? You'll have to decide.</p>
  312. <p>Note for various reasons this will not work in the live editor
  313. on this site so if you want to check it out
  314. <a href="../examples/webxr-basic-vr-optional.html" target="_blank">click here</a>.
  315. It should start in non-VR mode and you can use the mouse or fingers to move
  316. the camera. Clicking "Allow VR" should switch to allow VR mode and you should
  317. be able to click "Enter VR" if you're on a VR device.</p>
  318. <ul>
  319. <li><p>Deciding on the level of VR support</p>
  320. <p>Above we covered 3 types of VR devices. </p>
  321. <ul>
  322. <li>3DOF no input</li>
  323. <li>3DOF + 3DOF input</li>
  324. <li>6DOF + 6DOF input</li>
  325. </ul>
  326. <p>You need to decide how much effort you're willing to put in
  327. to support each type of device.</p>
  328. <p>For example the simplest device has no input. The best you can
  329. generally do is make it so there are some buttons or objects in the user's view
  330. and if the user aligns some marker in the center of the display
  331. on those objects for 1/2 a second or so then that button is clicked.
  332. A common UX is to display a small timer that will appear over the object indicating
  333. if you keep the marker there for a moment the object/button will be selected.</p>
  334. <p>Since there is no other input that's about the best you can do</p>
  335. <p>The next level up you have one 3DOF input device. Generally it
  336. can point at things and the user has at least 2 buttons. The Daydream
  337. also has a touchpad which provides normal touch inputs.</p>
  338. <p>In any case if a user has this type of device it's far more
  339. comfortable for the user to by able to point at things with
  340. their controller than it is to make them do it with their
  341. head by looking at things.</p>
  342. <p>A similar level to that might be 3DOF or 6DOF device with a
  343. game console controller. You'll have to decide what to do here.
  344. I suspect the most common thing is the user still has to look
  345. to point and the controller is just used for buttons.</p>
  346. <p>The last level is a user with a 6DOF headset and 2 6DOF controllers.
  347. Those users will find an experience that is only 3DOF to often
  348. be frustrating. Similarly they usually expect to be able to
  349. virtually manipulate things with their hands in VR so you'll
  350. have to decide if you want to support that or not.</p>
  351. </li>
  352. </ul>
  353. <p>As you can see getting started in VR is pretty easy but
  354. actually making something shippable in VR will require
  355. lots of decision making and design.</p>
  356. <p>This was a pretty brief intro into VR with three.js. We'll
  357. cover some of the input methods in <a href="webxr-look-to-select.html">future articles</a>.</p>
  358. </div>
  359. </div>
  360. </div>
  361. <script src="../resources/prettify.js"></script>
  362. <script src="../resources/lesson.js"></script>
  363. </body></html>