React VR 使您无需了解 three.js 即可轻松进行虚拟现实。js 是帮助实现 WebGL 的包装类,WebGL 本身就是原生 OpenGL 渲染库的一种形式。
React VR 相当包容,但与所有 API 一样,它不能做所有事情。幸运的是,我们已经预料到了这一点;如果 React VR 不支持某个功能,而您需要它,您可以自己构建该功能
在本章中,您将介绍以下主题:
- 从代码内部使用 three.js
- 基本的 three.js 过程代码
- 设置 three.js 与我们的 React VR 组件交互
- 使用 three.js 可视化地完成 three.js 较低级别的工作
也许你知道 three.js,需要使用它。React 本机模块是您的代码可以直接包含原始 three.js 编程的方式。如果您需要以编程方式创建原生 three.js 对象、修改材质属性或使用其他 three.js 代码,而这些代码不是 React VR 直接公开的,那么这非常有用。
您可能有一些处理业务逻辑的 JavaScript 代码,但不想或无法将其作为组件重写。您可能需要从 React VR 访问三个.js 或 WebVR 组件。您可能需要使用多个线程构建高性能数据库查询,以便主渲染循环不会减慢速度。使用 React Native,所有这些都是可能的。
这是一个相当高级的主题,通常不需要编写引人入胜、有效的 WebVR 演示;尽管如此,知道 React VR 和 React 是如此的可扩展,还是很奇妙的
首先,让我们来看看一个简单的盒子演示。让我们从一个新生成的站点开始。转到您的 node.js 命令行界面,关闭您正在运行的任何npm start窗口,并通过发出以下命令重新创建一个新的站点:
f:\ReactVR>React-vr init GoingNative第一个任务是转到vr文件夹并编辑client.js。到目前为止,我们还没有编辑这个文件;它包含样板代码。今天,我们将对其进行编辑,因为我们不只是做样板。以下代码中的粗体行是我们将添加到client.js的行:
// Auto-generated content.
// This file contains the boilerplate to set up your React app.
// If you want to modify your application, start in "index.vr.js"
// Auto-generated content.
import {VRInstance} from 'react-vr-web';
import {Module} from 'react-vr-web';
import * as THREE from 'three';
function init(bundle, parent, options) {
const scene = new THREE.Scene();
const cubeModule = new CubeModule();
const vr = new VRInstance(bundle, 'GoingNative', parent, {
// Add custom options here
cursorVisibility: 'visible',
nativeModules: [ cubeModule ],
scene: scene,
...options,
});
const cube = new THREE.Mesh(
new THREE.BoxGeometry(1, 1, 1),
new THREE.MeshBasicMaterial()
);
cube.position.z = -4;
scene.add(cube);
cubeModule.init(cube);
vr.render = function(timestamp) {
// Any custom behavior you want to perform on each frame goes here
//animate the cube
const seconds = timestamp / 1000;
cube.position.x = 0 + (1 * (Math.cos(seconds)));
cube.position.y = 0.2 + (1 * Math.abs(Math.sin(seconds)));
};
// Begin the animation loop
vr.start();
return vr;
};
window.ReactVR = {init};我们还需要创建对象 CubeModule。你可以把它放在一个单独的文件中,如果它变得复杂,你应该把它放在一个单独的文件中。现在,我们可以将其添加到 client.js 的底部:
export default class CubeModule extends Module {
constructor() {
super('CubeModule');
}
init(cube) {
this.cube = cube;
}
changeCubeColor(color) {
this.cube.material.color = new THREE.Color(color);
}
}不需要进行其他更改。现在,您将看到一个反弹的纯白色立方体。我们没有更改 index.vr.js,因此它仍然显示 hello 文本。这表明 React VR 和本机代码(在本例中为 3.js)同时运行。
好的,我们加入一个反弹立方体。这段代码的优点在于它显示了一些高级别的集成;然而,这是以一种非常干净的方式完成的。例如,const scene = new THREE.Scene()行为您提供了一个 3.js 可访问的场景,因此我们可以使用 3.js 做任何我们想做的事情,然而,所有 React VR 关键字都可以工作,因为它将使用现有场景。您不必将场景从一侧导入/导出到另一侧,也不必维护句柄/指针。正如 React-VR 所期望的那样,它是干净的、声明性的。我们在正常的 React-VR 语法之外创建了常规场景和对象
In our previous animations, we changed index.vr.js. In this case, with three.js objects, we make those changes directly in this part of client.js; right where the code generator suggests it:
vr.render = function(timestamp) {
// Any custom behavior you want to perform on each frame goes here
如果我们继续让这个对象与世界其他地方进行交互,你就能真正看到 React VR 的威力。要做到这一点,我们需要更改 index.VR.js。我们也将首次使用VrButton。
Note the spelling in VrButton. I was beating my head against the keyboard for a while on that one. I just naturally type "VR" instead of "Vr," but it does follow the React VR case standard.
The clue is that in the console you will see VRButton is not defined, which normally means that you forgot it in the import statement. In this particular case, you'll see an oddity of React; you can type import { YoMomma } from 'react-vr'; and you won't get an error; try it. React VR is apparently too scared to talk back to YoMomma.
当我们点击按钮时,沉浸的一个重要部分就是让他们点击。任何一个手机处于静音、无振动状态的人都知道我的意思;你按下手机,什么也听不到,以为它坏了。那么,让我们前往FreeSound.org下载一些点击。
我发现了IanStarGem的开关翻转,它是知识共享许可的。那么,让我们把它放在static_assets文件夹中:
- 首先,我们需要包括我们的
NativeModule声明;通常,您在import指令之后的顶部执行此操作,如下所示:
// Native Module defined in vr/client.js
const cubeModule = NativeModules.CubeModule;请注意,您可以调用您的对象CubeModule,但您可能会混淆实现与定义。它确实使打字更容易。JavaScript 可以非常宽容。这可能是好事,也可能不是好事。
- 无论如何,在
index.vr.js中,我们需要设置新的初始状态,否则会出现黑屏和错误:
class GoingNative extends React.Component {
constructor(props) {
super(props);
this.state = { btnColor: 'white', cubeColor: 'yellow' };
cubeModule.changeCubeColor(this.state.cubeColor);
}- 在同一个文件中,在
render()语句的正下方,将<View>定义更改为以下内容(请注意,我们仍在视图中,尚未关闭它):
<View
style={{
transform:[{translate: [0, 0, -3]}],
layoutOrigin: [0.5, 0, 0],
alignItems: 'center',
}}>我们在这里有点作弊,也就是说,向后移动视图,使对象在我们前面。
As React VR is not a CAD system, you can't edit visually, so you've got to think about the positioning of items when you do the code.
Layout graph paper might also help for something complicated.
- 在
<Pano>语句之后,</View>结束标记之前,插入以下内容(更改模板生成的文本语句):
<VrButton
style={{
backgroundColor: this.state.btnColor,
borderRadius: 0.05,
margin: 0.05,
}}
onEnter={() => { this.setState({ btnColor: this.state.cubeColor }) }}
onExit={() => { this.setState({ btnColor: 'white' }) }}
onClick={() => {
let hexColor = Math.floor(Math.random() * 0xffffff).toString(16);
// Ensure we always have 6 digits by padding with leading zeros.
hexColor = '#' + (('000000' + hexColor).slice(-6));
this.setState({ cubeColor: hexColor, btnColor: hexColor });
// Asynchronous call to custom native module; sends the new color.
cubeModule.changeCubeColor(hexColor);
}}
onClickSound={asset('freesound__278205__ianstargem__switch-flip-1.wav')}
>
<Text style={{
fontSize: 0.15,
paddingTop: 0.025,
paddingBottom: 0.025,
paddingLeft: 0.05,
paddingRight: 0.05,
textAlign: 'center',
textAlignVertical: 'center',
}}>
button
</Text>
</VrButton>刷新浏览器时,立方体仍会反弹,但您可以单击按钮,看到立方体颜色发生变化。当您将鼠标或控制器的光标移动到按钮上(作为<Text>组件可见)时,您将看到按钮更改为立方体的当前颜色
One neat thing you could do is pregenerate the new color of the cube in a static variable (so it doesn't go away like let will) and then make the mouse over color be that color.
A default color of white on white should be fixed too. Go ahead and try that; it's a fun exercise.
当我们播放声音时,浏览器控制台中会出现以下错误:
VrSoundEffects: must load sound before playing ../static_img/freesound__278205__ianstargem__switch-flip-1.wav您还可能会看到以下错误:
Failed to fetch audio: ../static_img/freesound__278205__ianstargem__switch-flip-1.wav
The buffer passed to decodeAudioData contains invalid content which cannot be decoded successfully.- 解决此问题的方法是确保您的浏览器具有正确的音频格式。正确的格式是:
- 音频文件需要是单声道的;这样就可以将它们转换为三维空间。
- 音频文件需要为 48 KHz 或更低。这似乎在 Firefox55 和 59 之间发生了变化,但最安全的做法是尽可能的通用。
- 如果文件格式错误,或者听不到声音,有两种可能的修复方法:
- 您可以使用 Audacity 或其他声音编辑工具修复这些问题。
- 你可以让我来修!我已经下载并转换了图书文件中的文件。但是,如果你不尝试进行修复,你将无法学习。你可以只下载 48 KHz 的 mono 文件并避免转换,但实际上这是相当罕见的。转换声音是很容易和大胆的免费,你只需要学习一点程序来做到这一点。在 VR 按钮内,我们只需加载修改后的单声道声音文件:
onClickSound={asset('freesound__278205__ianstargem__switch-flip-48kmono.wav')}I mentioned this in an earlier section, but it bears repeating that if you get unexplained errors and you exclaim "I know the file is there and it plays!", try checking the format of the sound file.
我们添加了很多代码;让我们总结一下我们现在的处境。React VR 有时会让人困惑,因为它是 JavaScript 和 XML“ish”代码(JSX)的混合体,所以这里是完整和完整的index.vr.js:
import React from 'react';
import {
AppRegistry,
Animated,
asset,
Easing,
NativeModules,
Pano,
Sound,
Text,
View,
VrButton
} from 'react-vr';
const cubeModule = NativeModules.CubeModule;
class GoingNative extends React.Component {
constructor(props) {
super(props);
this.state = { btnColor: 'white', cubeColor: 'yellow' };
cubeModule.changeCubeColor(this.state.cubeColor);
}
render() {
return (
<View
style={{
transform: [{ translate: [0, 0, -3] }],
layoutOrigin: [0.5, 0, 0],
alignItems: 'center',
}}>
<Pano source={asset('chess-world.jpg')} />
<VrButton
style={{
backgroundColor: this.state.btnColor,
borderRadius: 0.05,
margin: 0.05,
}}
onEnter={() => { this.setState({ btnColor: this.state.cubeColor }) }}
onExit={() => { this.setState({ btnColor: 'white' }) }}
onClick={() => {
let hexColor = Math.floor(Math.random() * 0xffffff).toString(16);
// Ensure we always have 6 digits by padding with leading zeros.
hexColor = '#' + (('000000' + hexColor).slice(-6));
this.setState({ cubeColor: hexColor, btnColor: hexColor });
// Asynchronous call to custom native module; sends the new color.
cubeModule.changeCubeColor(hexColor);
}}
onClickSound={asset('freesound__278205__ianstargem__switch-flip-48kmono.wav')}
>
<Text style={{
fontSize: 0.15,
paddingTop: 0.025,
paddingBottom: 0.025,
paddingLeft: 0.05,
paddingRight: 0.05,
textAlign: 'center',
textAlignVertical: 'center',
}}>
button
</Text>
</VrButton>
</View>
);
}
};
AppRegistry.registerComponent('GoingNative', () => GoingNative);在文件client.js中,在vr文件夹中(文件夹名称为小写),将如下所示:
import {VRInstance} from 'react-vr-web';
import {Module} from 'react-vr-web';
import * as THREE from 'three';
function init(bundle, parent, options) {
const scene = new THREE.Scene();
const cubeModule = new CubeModule();
const vr = new VRInstance(bundle, 'GoingNative', parent, {
cursorVisibility: 'visible',
nativeModules: [ cubeModule ],
scene: scene,
...options,
});
const cube = new THREE.Mesh(
new THREE.BoxGeometry(1, 1, 1),
new THREE.MeshBasicMaterial()
);
cube.position.z = -4;
scene.add(cube);
cubeModule.init(cube);
vr.render = function(timestamp) {
const seconds = timestamp / 1000;
cube.position.x = 0 + (1 * (Math.cos(seconds)));
cube.position.y = 0.2 + (1 * Math.abs(Math.sin(seconds)));
};
vr.start();
return vr;
};
window.ReactVR = {init};
export default class CubeModule extends Module {
constructor() {
super('CubeModule');
}
init(cube) {
this.cube = cube;
}
changeCubeColor(color) {
this.cube.material.color = new THREE.Color(color);
}
}虽然直接使用 three.js 的另一个重要原因是为了在渲染方面做一些 React VR 无法做的事情,但我们已经完成了一些简洁的交互,这非常棒。事实上,React-VR 可以通过本地方法做一些令人惊奇的事情,所以我们就这么做吧。
首先,让我们将立方体从弹跳变为旋转。当我们添加一些视觉效果时,它看起来会更令人印象深刻。
让我们在周围添加一些球体。我们需要一些反映。我选择反射作为一件令人印象深刻的事情,虽然我们可以通过环境映射来做一些非常接近它的事情,但目前你无法用 WebVR 实时完成。有关什么是环境映射的较长时间讨论,可以转到:http://bit.ly/ReflectMap
将以下代码添加到您现有的index.vr.js,位于</VrButton>下方:
<Sphere
radius={0.5}
widthSegments={20}
heightSegments={12}
style={{
color: 'blue',
transform: [{ translate: [-1, 0, -3] }],
}}
lit />
<Sphere
radius={1.5}
widthSegments={20}
heightSegments={12}
style={{
color: 'crimson',
transform: [{ translate: [1, -2, -3] }],
}}
lit />我们还将在顶层<View>内的index.vr.js顶部添加环境光和平行光:
<AmbientLight intensity={.3} />
<DirectionalLight
intensity={.7}
style={{ transform: [{
rotateZ: 45
}]
}}
/>继续加载,确保你看到一个漂亮的蓝色球体和一个大的红色球体。请注意,我编写的代码比正常情况稍微密集一些,所以这本书不会杀死更多的树或光子。我们的大部分更改将在client.js中进行。首先,初始化init下需要的所有变量:
var materialTorus;
var materialCube;
var torusCamera;
var cubeCamera;
var renderFrame;
var torus;
var texture;
var cube;然后,我们将为场景设置自定义背景。有趣的是,当我们有<Pano>语句时,这并没有出现,但这是一件好事,因为我们现在正在three.js中编码;它不懂虚拟现实,所以背景不太正确。这将在图片上显示一点,但修复这一点最好留给读者作为练习。要设置three.js的自定义背景,请继续添加代码,如下所示:
var textureLoader = new THREE.TextureLoader();
textureLoader.load('../static_img/chess-world.jpg', function (texture) {
texture.mapping = THREE.UVMapping;
scene.background = texture;
});然后,我们将创建一个圆环体和我们前面已经创建的立方体(请记住,这一切仍在init语句中):
torusCamera = new THREE.CubeCamera(.1, 100, 256);
torusCamera.renderTarget.texture.minFilter = THREE.LinearMipMapLinearFilter;
scene.add(torusCamera);
cubeCamera = new THREE.CubeCamera(.1, 100, 256);
cubeCamera.renderTarget.texture.minFilter = THREE.LinearMipMapLinearFilter;
scene.add(cubeCamera);我们在这里做的是创建一些额外的摄像头。我们将把这些摄影机移动到圆环体和反弹立方体所在的位置,然后将这些摄影机渲染到屏幕外缓冲区(不可见)。现在我们已经创建了这些摄影机,我们可以创建立方体和圆环体三个.js 对象;请注意,这是对早期多维数据集的一个轻微更改:
materialTorus = new THREE.MeshBasicMaterial({ envMap: torusCamera.renderTarget.texture });
materialCube = new THREE.MeshBasicMaterial({ envMap: cubeCamera.renderTarget.texture });
torus = new THREE.Mesh(new THREE.TorusKnotBufferGeometry(2, .6, 100, 25), materialTorus);
torus.position.z = -10; torus.position.x = 1;
scene.add(torus);
cube = new THREE.Mesh( new THREE.BoxGeometry(1, 1, 1), materialCube);
cube.position.z = -4;
scene.add(cube);
renderFrame = 0;
cubeModule.init(cube);注意,cubeModule.init(cube);语句应该已经存在。现在,我们只需要将人造锡箔纸包裹在我们的物体上;我们将在vr.render函数中执行此操作。以下是整个功能:
vr.render = function (timestamp) {
// Any custom behavior you want to perform on each frame goes here
const seconds = timestamp / 2000;
cube.position.x = 0 + (1 * (Math.cos(seconds)));
cube.position.y = 0.2 + (1 * Math.abs(Math.sin(seconds)));
cube.position.y = 0.2 + (1 * Math.sin(seconds));
var time = Date.now();
torus.rotation.x += 0.01;
torus.rotation.y += 0.02;
//we need to turn off the reflected objects,
//or the camera will be inside.
torus.visible = false;
torusCamera.position.copy(torus.position);
torusCamera.update(vr.player.renderer, scene)
materialTorus.envMap = torusCamera.renderTarget.texture;
torus.visible = true;
cube.visible = false;
cubeCamera.position.copy(cube.position);
cubeCamera.update(vr.player.renderer, scene);
materialCube.envMap = cubeCamera.renderTarget.texture;
cube.visible = true;
renderFrame++;
};
// Begin the animation loop
vr.start();
return vr;
};我通过移除正弦波周围的函数Math.abs(..),稍微改变了盒子,使其旋转一整圈;这样我们就可以看到反射贴图的优点和缺点。
希望我们把所有的东西都贴上了。你可以面带笑容地观看展览。整洁的铬结对象!当你盯着它看的时候,你会注意到有些事情不太对劲。您可以在方形框中看到伪造反射和真实反射之间的差异。它看起来有点“脱落”,但铬结看起来不错。
查看下图中的红色突出显示与绿色突出显示:
Creating good VR is all about reasonable compromises. In the case of reflections, they can look great, as the preceding image shows, but they can also look just a little disturbing. The box or a flat mirror would be a good example of something not to do. Curved objects look a lot more natural as you can see.
Games and real-time programming are as much about careful design as a faithful recreation of the real world. Remember that we do not create something real; all we have to do is create something which will seem real.
如果你想制作平面镜,three.js 中有一个真正的反射器叫做THREE.Reflector。这在 three.js 示例中有很好的文档记录。
有了这些技术和 React 本机桥接器,您就可以使用 React VR 完成一些惊人的事情,而不必深入到常规的 three.js 编程中
现在您可以看到 materials 的基本 three.js 语法,您可以查看各种 three.js 示例,并在中复制一些代码。不要只看屏幕上的样本。你也可以在虚拟现实中尝试。有些游戏技巧,如镜头反射或屏幕空间反射,在虚拟现实中看起来不太好。一如既往,测试,测试,测试。
我还稍微改变了按钮的颜色,当我们切换到 VR 模式时,我们没有光标,所以按下按钮并不总是有效。在下一章中,我将向您展示如何修复此问题,或者您可以自行调查
我还在源文件中加载了一个类似金属的反射纹理,名为static_img/metal_reflect.jpg。您不必进行摄影机渲染来获得看起来有光泽的东西,尤其是在反射很暗的情况下,并且可能不需要将帧速率减半的额外开销(所有这些摄影机渲染都需要时间)。如果是这种情况,则可以执行简单的环境贴图并跳过摄影机加载和渲染。
您还可以使用称为本机视图的东西来扩展 React VR 本身。“视图”一词可能会让您想到摄影机渲染,但在本例中,其含义略有不同。将这些视为原生 three.js 的新对象。它们非常有用。您可以使用我们刚才使用的 three.js 代码来混合原始 three.js 编程,但是您以这种方式使用声明性编程的能力有限。有没有更有效的方法?可以通过本机视图执行此操作。
实现本机视图时,可以控制属性和代码如何与其余运行时代码交互。这些注入通常是视觉的,尽管你也可以注入声音
还可以实现新的本机对象。编程与我们到目前为止所做的类似;实现基本属性,将 new 关键字公开给运行时,然后将其编码为 React VR 语言的一部分。还有一些额外的关键字和功能,可以让您根据道具和类型描述新的 React VR 视图。
要创建本机视图,请参阅以下文档:http://bit.ly/RCTNativeView.
现在,你已经到了一个关键点,你应该能够用 React VR 做一些令人惊奇的事情,我完全相信你可以挑选我的例子,扩展它们,玩得开心
在本章中,我们讨论了如何将 three.js 的全部功能用于 React VR。在学习这一点的同时,我们演示了如何放置本机代码和 React-VR 本机桥。我们直接通过 JavaScript 构建three.js网格,并添加声音,让世界看起来更生动。我们还使用 React 本机视图和本机桥进行定制渲染,包括将 chrome 添加到 VR 的反射贴图(与使用 chrome 查看 VR 相反)。我们还展示了如何通过vr.player.renderer访问 React VR 摄像头,以进行更多的 three.js 处理
有了全面的 three.js,我们真的可以用 React VR 做任何我们想做的事情。然而,我们应该在需要的地方使用 React VR,在需要更多细节的地方使用 three.js,否则 React VR 将是锦上添花。它容易生锈,容易脱落。

