webxr-basics.html 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. <!DOCTYPE html><html lang="zh-CN"><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. <script type="importmap">
  14. {
  15. "imports": {
  16. "three": "../../build/three.module.js"
  17. }
  18. }
  19. </script>
  20. </head>
  21. <body>
  22. <div class="container">
  23. <div class="lesson-title">
  24. <h1>VR</h1>
  25. </div>
  26. <div class="lesson">
  27. <div class="lesson-main">
  28. <p>在 three.js 中制作一个 VR 应用相当简单。你基本上只需要告诉 three.js 你想使用 WebXR。关于 WebXR,有几点应该很容易理解。摄像机的朝向是由 VR 系统提供的,因为用户会转动头部来选择观看的方向。同样,视野范围(field of view)和长宽比也是由 VR 系统提供的,因为每个系统的视野和显示比例都不同。</p>
  29. <p>我们来看一个来自<a href="responsive.html">制作响应式网页</a>的示例,并让它支持 VR。</p>
  30. <p>在开始之前,你需要一台支持 VR 的设备,比如 Android 智能手机、Google Daydream、Oculus Go、Oculus Rift、Vive、Samsung Gear VR,或者一部安装了<a href="https://apps.apple.com/us/app/webxr-viewer/id1295998056">WebXR 浏览器</a>的 iPhone。</p>
  31. <p>接下来,如果你在本地运行,你需要像<a href="setup.html">设置教程</a>中提到的那样运行一个简单的 Web 服务器。</p>
  32. <p>如果你用于查看 VR 的设备不是运行服务的同一台电脑,那么你需要通过 https 来访问网页,否则浏览器将不允许使用 WebXR API。<a href="setup.html">设置教程</a>中提到的名为 <a href="https://greggman.github.io/servez" target="_blank">Servez</a> 的服务器支持启用 https。勾选该选项并启动服务器。</p>
  33. <div class="threejs_center"><img src="../resources/images/servez-https.png" class="nobg" style="width: 912px;"></div>
  34. <p>请注意 URL,你需要使用你电脑的本地 IP 地址。它通常会以 <code class="notranslate" translate="no">192</code>、<code class="notranslate" translate="no">172</code> 或 <code class="notranslate" translate="no">10</code> 开头。在 VR 设备的浏览器中输入完整地址,包括 <code class="notranslate" translate="no">https://</code> 部分。注意:你的电脑和 VR 设备必须在同一个本地网络或 WiFi 上,并且最好是在家庭网络中。注意:许多咖啡馆的网络配置不允许设备间直接通信。</p>
  35. <p>你可能会看到如下图所示的错误提示。点击“高级”,然后点击<em>继续</em>。</p>
  36. <div class="threejs_center"><img src="../resources/images/https-warning.gif"></div>
  37. <p>现在你可以运行示例代码了。</p>
  38. <p>如果你打算真正进行 WebXR 开发,你还应该了解一下 <a href="https://developers.google.com/web/tools/chrome-devtools/remote-debugging/">远程调试</a>,这样你就可以查看控制台警告、错误,当然也可以<a href="debugging-javascript.html">调试你的代码</a>。</p>
  39. <p>如果你只是想看看下面的代码是否可运行,你可以直接在本网站运行它。</p>
  40. <p>我们首先需要在引入 three.js 之后引入对 VR 的支持:</p>
  41. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">import * as THREE from 'three';
  42. +import {VRButton} from 'three/addons/webxr/VRButton.js'; // 引入 VR 按钮模块
  43. </pre>
  44. <p>然后我们需要启用 three.js 的 WebXR 支持,并将 VR 按钮添加到页面中:</p>
  45. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">function main() {
  46. const canvas = document.querySelector('#c');
  47. const renderer = new THREE.WebGLRenderer({antialias: true, canvas});
  48. + renderer.xr.enabled = true; // 启用 WebXR 支持
  49. + document.body.appendChild(VRButton.createButton(renderer)); // 将 VR 按钮添加到页面
  50. </pre>
  51. <p>我们需要让 three.js 来运行渲染循环。在此之前我们一直使用 <code class="notranslate" translate="no">requestAnimationFrame</code> 循环,但为了支持 VR,我们需要让 three.js 自己控制渲染循环。我们可以调用 <a href="/docs/#api/en/renderers/WebGLRenderer.setAnimationLoop"><code class="notranslate" translate="no">WebGLRenderer.setAnimationLoop</code></a> 并传入一个回调函数来实现:</p>
  52. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">function render(time) {
  53. time *= 0.001;
  54. if (resizeRendererToDisplaySize(renderer)) {
  55. const canvas = renderer.domElement;
  56. camera.aspect = canvas.clientWidth / canvas.clientHeight;
  57. camera.updateProjectionMatrix();
  58. }
  59. cubes.forEach((cube, ndx) =&gt; {
  60. const speed = 1 + ndx * .1;
  61. const rot = time * speed;
  62. cube.rotation.x = rot;
  63. cube.rotation.y = rot;
  64. });
  65. renderer.render(scene, camera);
  66. - requestAnimationFrame(render); // 原来的 requestAnimationFrame 被移除
  67. }
  68. -requestAnimationFrame(render); // 原调用被注释
  69. +renderer.setAnimationLoop(render); // 改为使用 WebXR 的渲染循环方式
  70. </pre>
  71. <p>还有一个细节:我们最好设置一个摄像机的高度,使其符合站立用户的平均视角高度。</p>
  72. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
  73. +camera.position.set(0, 1.6, 0); // 设置摄像机高度为 1.6 米,符合站立用户的平均视角
  74. </pre>
  75. <p>并将立方体上移,使其位于摄像机前方:</p>
  76. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const cube = new THREE.Mesh(geometry, material);
  77. scene.add(cube);
  78. cube.position.x = x;
  79. +cube.position.y = 1.6; // 与摄像机高度一致
  80. +cube.position.z = -2; // 放置在摄像机前方 2 米处
  81. </pre>
  82. <p>我们将 z 设置为 <code class="notranslate" translate="no">-2</code>,因为摄像机现在位于 <code class="notranslate" translate="no">z = 0</code>,默认朝向 -z 轴方向。</p>
  83. <p>这引出了一个非常重要的点:<strong>VR 中的单位是以米为单位</strong>。换句话说,<strong>一个单位 = 一米</strong>。这意味着摄像机在距离地面 1.6 米的位置,立方体的中心位于摄像机前方 2 米处。每个立方体的大小是 1x1x1 米。这一点非常关键,因为 VR 需要将虚拟世界中的尺寸与用户在现实世界中的动作相匹配。</p>
  84. <p>现在,我们应该可以在摄像机前方看到三个旋转的立方体,并且有一个进入 VR 的按钮。</p>
  85. <p></p><div translate="no" class="threejs_example_container notranslate">
  86. <div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/webxr-basic.html"></iframe></div>
  87. <a class="threejs_center" href="/manual/examples/webxr-basic.html" target="_blank">点击此处在新窗口中打开</a>
  88. </div>
  89. <p></p>
  90. <p>我发现 VR 效果更好一些时,是摄像机周围有一些参考物,比如一个房间。因此我们来添加一个简单的网格立方体贴图,就像我们在<a href="backgrounds.html">背景文章</a>中讲到的那样。我们会使用相同的网格纹理贴图在立方体的每一面上,这样可以创建一个“网格房间”。</p>
  91. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">const scene = new THREE.Scene();
  92. +{
  93. + const loader = new THREE.CubeTextureLoader(); // 创建立方体贴图加载器
  94. + const texture = loader.load([
  95. + 'resources/images/grid-1024.png', // 六个面的纹理都用同一张图
  96. + 'resources/images/grid-1024.png',
  97. + 'resources/images/grid-1024.png',
  98. + 'resources/images/grid-1024.png',
  99. + 'resources/images/grid-1024.png',
  100. + 'resources/images/grid-1024.png',
  101. + ]);
  102. + scene.background = texture; // 设置场景背景为加载的立方体贴图
  103. +}
  104. </pre>
  105. <p>这样看起来会更好一些。</p>
  106. <p></p><div translate="no" class="threejs_example_container notranslate">
  107. <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>
  108. <a class="threejs_center" href="/manual/examples/webxr-basic-w-background.html" target="_blank">点击此处在新窗口中打开</a>
  109. </div>
  110. <p></p>
  111. <p>注意:要实际看到 VR 效果,你需要一台兼容 WebXR 的设备。我相信大多数 Android 手机在使用 Chrome 或 Firefox 时都支持 WebXR。至于 iOS,你也许可以使用这个 <a href="https://apps.apple.com/us/app/webxr-viewer/id1295998056">WebXR 应用</a>,但总体上 WebXR 在 iOS 上的支持在 2019 年 5 月时仍处于不支持状态。</p>
  112. <p>在 Android 或 iPhone 上使用 WebXR,你需要一个<em>手机专用的 VR 头显</em>。你可以以很便宜的价格购买,比如用纸板做的只需约 5 美元,高端一些的可能需要 100 美元左右。不幸的是,我也不清楚该推荐哪款产品。我这些年买过 6 个设备,质量参差不齐,最贵的也没超过 25 美元。</p>
  113. <p>以下是一些可能遇到的问题:</p>
  114. <ol>
  115. <li><p>是否适配你的手机尺寸</p>
  116. <p>手机尺寸各异,因此 VR 头显需要与之匹配。很多头显声称支持多种尺寸。从我的经验来看,适配尺寸越多,实际效果越差,因为它们不得不在多个尺寸之间做出妥协。不幸的是,支持多尺寸的头显是最常见的类型。</p>
  117. </li>
  118. <li><p>是否能够调节焦距以适配你的脸型</p>
  119. <p>有些设备的可调节性更强。通常最多提供两种调节方式:镜片与眼睛之间的距离,以及两只眼睛之间的镜片间距。</p>
  120. </li>
  121. <li><p>镜片是否太反光</p>
  122. <p>许多头显的镜片连接区域是一段塑料通道。如果这些塑料材质是光滑或反光的,那么它会像镜子一样反射屏幕内容,造成强烈干扰。</p>
  123. <p>几乎没有评论会提及这个问题。</p>
  124. </li>
  125. <li><p>佩戴是否舒适</p>
  126. <p>大多数设备像眼镜一样压在鼻梁上。几分钟后可能就会感觉不适。有些设备配有环绕头部的固定带,有些还有一条从上方穿过头顶的第三条带子。这些可能或可能不会起到将设备固定在合适位置的作用。</p>
  127. <p>事实是,对大多数(甚至所有)设备来说,眼睛必须正对镜片中心。如果镜片略微偏高或偏低,图像就会变模糊。这可能非常令人沮丧,因为一开始图像是清晰的,但使用 45 到 60 秒后设备稍微移位 1 毫米,你会突然发现自己在努力看一个模糊的图像。</p>
  128. </li>
  129. <li><p>是否支持眼镜</p>
  130. <p>如果你戴眼镜,你需要查看评论确认该设备是否支持眼镜佩戴。</p>
  131. </li>
  132. </ol>
  133. <p>很遗憾,我没法给出推荐。<a href="https://vr.google.com/cardboard/get-cardboard/">Google 提供了一些便宜的纸板 VR 眼镜建议</a>,有些仅需 5 美元左右,不妨从那里开始尝试。如果你喜欢这个体验,再考虑升级。5 美元也就一杯咖啡的钱,试一试也无妨!</p>
  134. <p>VR 设备大致可以分为 3 种类型:</p>
  135. <ol>
  136. <li><p>三自由度(3DoF),无输入设备</p>
  137. <p>这通常指的是手机类设备,尽管有时也可以购买第三方输入设备。所谓三自由度是指你可以上下转头(1)、左右转头(2)、以及左右倾斜头部(3)。</p>
  138. </li>
  139. <li><p>三自由度(3DoF)+ 一个三自由度输入设备</p>
  140. <p>这类设备包括 Google Daydream 和 Oculus GO。</p>
  141. <p>它们同样支持三自由度,并配有一个小型控制器,在 VR 中像激光指针一样使用。激光指针本身也只有三自由度,系统只能识别它的指向方向,不能识别它的位置。</p>
  142. </li>
  143. <li><p>六自由度(6DoF)+ 六自由度输入设备</p>
  144. <p>这些是<em>真正的 VR 设备</em>(哈哈)。六自由度意味着设备不仅知道你头部的朝向,还知道你头部的实际位置。这意味着你左右移动、前后移动、或坐下/站起,设备都能感知并在 VR 中进行同步。</p>
  145. <p>体验非常真实,令人惊艳。在一个好的演示中你可能会被震撼到,我至今仍然会被打动。</p>
  146. <p>此外,这类设备通常配有两个控制器,分别对应左右手。系统可以准确识别你双手的位置和朝向,因此你可以在 VR 中通过触摸、推动、扭动等手势操作物体。</p>
  147. <p>支持六自由度的设备包括 Vive、Vive Pro、Oculus Rift、Quest 以及我相信所有 Windows MR 设备。</p>
  148. </li>
  149. </ol>
  150. <p>讲了这么多,我也不能完全确认哪些设备确实能与 WebXR 配合使用。但我 99% 确信,大多数 Android 手机在使用 Chrome 时是可以的。你可能需要在 <a href="about:flags"><code class="notranslate" translate="no">about:flags</code></a> 中启用 WebXR 支持。我也知道 Google Daydream 是可用的,同样需要在 <a href="about:flags"><code class="notranslate" translate="no">about:flags</code></a> 中启用支持。Oculus Rift、Vive、Vive Pro 可以通过 Chrome 或 Firefox 使用。我对 Oculus Go 和 Oculus Quest 不太确定,因为它们使用的是定制操作系统,但根据网络信息,它们似乎也是可以的。</p>
  151. <p>好了,介绍完 VR 设备和 WebXR,我们继续讲其他内容。</p>
  152. <ul>
  153. <li><p>同时支持 VR 和 非 VR 模式</p>
  154. <p>据我所知(截至 r112 版本),three.js 并没有提供一个简单的方法来同时支持 VR 和非 VR 模式。理想情况下,如果不处于 VR 模式,我们希望可以使用任何方式控制摄像机,例如使用 <a href="/docs/#examples/controls/OrbitControls"><code class="notranslate" translate="no">OrbitControls</code></a>,并且在切换进出 VR 模式时可以接收到事件,以便启用或禁用控制器。</p>
  155. </li>
  156. </ul>
  157. <p>如果 future 的 three.js 添加了支持,我会尝试更新本文。在此之前,你可能需要制作两个版本的页面,或者在 URL 中传入一个标记参数,例如:</p>
  158. <pre class="prettyprint showlinemods notranslate notranslate" translate="no">https://mysite.com/mycooldemo?allowvr=true
  159. </pre>
  160. <p>然后我们可以加一些链接来切换模式:</p>
  161. <pre class="prettyprint showlinemods notranslate lang-html" translate="no">&lt;body&gt;
  162. &lt;canvas id="c"&gt;&lt;/canvas&gt;
  163. + &lt;div class="mode"&gt;
  164. + &lt;a href="?allowvr=true" id="vr"&gt;启用 VR 模式&lt;/a&gt;
  165. + &lt;a href="?" id="nonvr"&gt;使用非 VR 模式&lt;/a&gt;
  166. + &lt;/div&gt;
  167. &lt;/body&gt;
  168. </pre>
  169. <p>并加上一些 CSS 来定位这些链接:</p>
  170. <pre class="prettyprint showlinemods notranslate lang-css" translate="no">body {
  171. margin: 0;
  172. }
  173. #c {
  174. width: 100%;
  175. height: 100%;
  176. display: block;
  177. }
  178. +.mode {
  179. + position: absolute;
  180. + right: 1em; /* 右上角显示 */
  181. + top: 1em;
  182. +}
  183. </pre>
  184. <p>你可以在代码中这样读取参数:</p>
  185. <pre class="prettyprint showlinemods notranslate lang-js" translate="no">function main() {
  186. const canvas = document.querySelector('#c');
  187. const renderer = new THREE.WebGLRenderer({antialias: true, canvas});
  188. - renderer.xr.enabled = true;
  189. - document.body.appendChild(VRButton.createButton(renderer));
  190. const fov = 75;
  191. const aspect = 2; // canvas 默认宽高比
  192. const near = 0.1;
  193. const far = 5;
  194. const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
  195. camera.position.set(0, 1.6, 0);
  196. + const params = (new URL(document.location)).searchParams;
  197. + const allowvr = params.get('allowvr') === 'true'; // 从 URL 中读取 allowvr 参数
  198. + if (allowvr) {
  199. + renderer.xr.enabled = true;
  200. + document.body.appendChild(VRButton.createButton(renderer));
  201. + document.querySelector('#vr').style.display = 'none'; // 隐藏“启用 VR”按钮
  202. + } else {
  203. + // 非 VR 模式,添加控制器
  204. + const controls = new OrbitControls(camera, canvas);
  205. + controls.target.set(0, 1.6, -2);
  206. + controls.update();
  207. + document.querySelector('#nonvr').style.display = 'none'; // 隐藏“非 VR 模式”按钮
  208. + }
  209. </pre>
  210. <p>这到底好不好我也说不准。我感觉 VR 模式和非 VR 模式之间所需的实现差异通常非常大,
  211. 所以除了最简单的应用场景外,或许制作两个单独的页面会更合适?你需要自己决定。</p>
  212. <p>注意:由于种种原因,这段代码在本网站的在线编辑器中是无法运行的,
  213. 所以如果你想试试看,可以<a href="../examples/webxr-basic-vr-optional.html" target="_blank">点击这里</a>。
  214. 页面会以非 VR 模式启动,你可以用鼠标或手指来移动摄像机。
  215. 点击“允许 VR”按钮后,页面会切换为支持 VR 模式,
  216. 如果你使用的是 VR 设备,就可以点击“进入 VR”按钮。</p>
  217. <ul>
  218. <li>
  219. <p>决定支持哪种等级的 VR 设备</p>
  220. <p>上文我们介绍了三种类型的 VR 设备。</p>
  221. <ul>
  222. <li>3DOF 无输入设备</li>
  223. <li>3DOF + 3DOF 输入设备</li>
  224. <li>6DOF + 6DOF 输入设备</li>
  225. </ul>
  226. <p>你需要决定你愿意投入多少精力来支持每种类型的设备。</p>
  227. <p>例如,对于最简单的无输入设备,你能做的通常就是在用户视野中放置一些按钮或物体,
  228. 当用户将视图中心的某个指示器对准这些物体大约 0.5 秒时,就触发点击。
  229. 常见的用户体验方式是在目标物体上显示一个小型的计时圈,
  230. 表示“如果你继续把视线保持在这里一会儿,这个按钮将被选中”。</p>
  231. <p>由于没有其他输入方式,这已经是你能做的最好的交互方式了。</p>
  232. <p>下一级别是用户拥有一个 3DOF 的输入设备。通常它可以用来指向目标,
  233. 并且用户至少有两个按钮可以使用。Daydream 控制器还有一个触控板,
  234. 可以提供常规的触摸输入。</p>
  235. <p>无论如何,如果用户使用这类设备,让他们使用控制器指向目标,
  236. 会比强迫他们通过头部移动去“看”目标舒适得多。</p>
  237. <p>一个类似等级的设备可能是 3DOF 或 6DOF 的头显配合游戏手柄使用。
  238. 你需要自己决定该如何支持这种情况。常见的方式是用户仍然需要转头瞄准目标,
  239. 而手柄只是用来触发按钮。</p>
  240. <p>最后一个层级是使用 6DOF 头显配合两个 6DOF 控制器的用户。
  241. 对于这类用户来说,如果你的应用只有 3DOF 的交互,
  242. 往往会让他们感到沮丧。同样,他们通常期望能够在 VR 中用手操作物体,
  243. 你需要决定是否要支持这种高度自由的交互方式。</p>
  244. </ul>
  245. <p>如你所见,入门 VR 开发相对简单,但如果你真的想做出一个可发布的 VR 应用,
  246. 那就需要大量的决策和设计。</p>
  247. <p>这篇文章只是使用 three.js 进行 VR 开发的简要介绍。
  248. 我们将在 <a href="webxr-look-to-select.html">后续文章</a> 中介绍各种输入方式。</p>
  249. </div>
  250. </div>
  251. </div>
  252. <script src="../resources/prettify.js"></script>
  253. <script src="../resources/lesson.js"></script>
粤ICP备19079148号