「译」如何使用 Three.js 创建天空盒
码农天地 -原文地址:How to Create a Skybox with Three.js原文作者:Cody Pearce
天空盒通常用于视频游戏中,用来产生远距三维背景的视觉效果。天空盒本质上是一个立方体,在立方体的每一侧都有纹理。然后将播放器或摄像机放置在立方体中,使得所有立方体的六个纹理围绕它们,从而给人一种位于更大的环境中的错觉。reactnativeinfinity.com 利用这项技术创造了在太空旋转的错觉。
Three.js 设置首先,建立一个包含 scene
, camera
以及 renderer
的内部初始化函数,我们将调用 three.js
进行初始化。通过使用 PerspectiveCamera,把它的位置会被缩小得很远,使得我们可以在进入页面后看到立方体。我们还将使用 THREE.WEbGLRenderer
,并将其附加到页面的主体。最后,该 animate
函数将更新我们所添加的内容,及时处理场景的重新渲染。
let scene, camera, renderer, skyboxGeo, skybox;
function init() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(
55,
window.innerWidth / window.innerHeight,
45,
30000
);
camera.position.set(1200, -250, 20000);
renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.domElement.id = "canvas";
document.body.appendChild(renderer.domElement);
animate();
}
function animate() {
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
init();
导入 Three.js
核心库。
<script src="https://threejs.org/build/three.min.js"></script>
将主体高度设置为视口高度,并在主体上添加灰色背景,以便我们可以看到立方体。
body {
margin: 0;
height: 100vh;
background: #bdc3c7;
}
由于我们还没有添加任何对象,因此我们现在只会看到灰色背景。
添加 Three.js 立方体我们可以布置一个盒子 THREE.BoxGeometry
用 width
, height
以及 depth
设置为 10000
。然后使用 THREE.Mesh
对其添加纹理,在这种情况下,它将默认为纯白色纹理。最后,调用 animate()
函数内的 init()
函数之前,将对象添加到场景中。
function init() {
...
skyboxGeo = new THREE.BoxGeometry(10000, 10000, 10000);
skybox = new THREE.Mesh(skyboxGeo);
scene.add(skybox);
animate();
即使是立方体,它看起来也像正方形,因为我们是直接观看它。为了验证它是一个立方体,我们可以在 animate()
函数中添加旋转动画效果:
function animate() {
skybox.rotation.x += 0.005;
skybox.rotation.y += 0.005;
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
天空盒网格材质您可以在 opengameart.org 上找到免费的天空盒图片,也可以在 Google 上搜索“免费的天空盒图片”。通常会有六个与立方体的每一侧对应的图像,它们无缝地契合在一起。例如,对于React Native Infinity
,六个空间图像对应于不同的边,如下所示。
每个图像应根据其对应的侧面进行命名,例如,purplenebula_ft.png
是正面图像,purplenebula_rt.png
右侧图像和 purplenebula_dn.png
底部图像。我们需要遵循三个步骤将这些图像添加到多维数据集:
可以使用 TextureLoader().load()
函数在 Three.js
中加载纹理。该 load()
方法将图像的路径作为参数。我们可以通过创建六个 TextureLoader()
函数来加载每个图像,如下所示:
const ft = new THREE.TextureLoader().load("purplenebula_ft.jpg");
const bk = new THREE.TextureLoader().load("purplenebula_bk.jpg");
const up = new THREE.TextureLoader().load("purplenebula_up.jpg");
const dn = new THREE.TextureLoader().load("purplenebula_dn.jpg");
const rt = new THREE.TextureLoader().load("purplenebula_rt.jpg");
const lf = new THREE.TextureLoader().load("purplenebula_lf.jpg");
但是最好创建一个可重用的函数,为我们循环遍历所有图像。创建一个函数 createPathStrings()
,该函数将从文件映像名称创建路径字符串数组 filename
。
function createPathStrings(filename) {
const basePath = "./static/skybox/";
const baseFilename = basePath + filename;
const fileType = ".png";
const sides = ["ft", "bk", "up", "dn", "rt", "lf"];
const pathStings = sides.map(side => {
return baseFilename + "_" + side + fileType;
});
return pathStings;
}
这应该创建一个字符串数组,这些字符串代表每个图像的路径:
['./static/skybox/purplenebula_ft.jpg', './static/skybox/purplenebula_bk.jpg', ...]
接下来,TextureLoader().load()
通过映射到上面的数组来加载每个纹理。让我们创建另一个函数,createMaterialArray()
以生成一个新的加载纹理数组。我们还将 filename
参数传入 createPathStrings
函数。
let skyboxImage = "purplenebula";
function createMaterialArray(filename) {
const skyboxImagepaths = createPathStrings(filename);
const materialArray = skyboxImagepaths.map(image => {
let texture = new THREE.TextureLoader().load(image);
return texture;
});
return materialArray;
}
2. 将纹理映射到网格阵列通过 Three.js
的 MeshBasicMaterial()
方法,将允许我们处理相应纹理材料,无需创建另一个函数来执行此操作,我们只需修改 createMaterialArray()
函数,用来返回 Three.js
材质,而不是加载纹理。
function createMaterialArray(filename) {
const skyboxImagepaths = createPathStrings(filename);
const materialArray = skyboxImagepaths.map(image => {
let texture = new THREE.TextureLoader().load(image);
return new THREE.MeshBasicMaterial({
map: texture,
side: THREE.BackSide
});
});
return materialArray;
}
3. 将网格阵列添加天空盒我们终于可以将网格数组添加到上面创建的天空盒中了。首先,skyboxImage
使用基本文件名创建一个变量。将该变量传递到中 createMaterialArray
以生成我们的网格数组。最后,将该数组传递给 new Three.Mesh()
函数的第二个参数。
const skyboxImage = 'purplenebula';
function init() {
...
const materialArray = createMaterialArray(skyboxImage);
skyboxGeo = new THREE.BoxGeometry(10000, 10000, 10000);
skybox = new THREE.Mesh(skyboxGeo, materialArray);
scene.add(skybox);
animate();
}
现在,我们的立方体应具有网格阵列,单击“外部框”按钮以查看其外观。
将相机放在立方体内我们可以将 camera
的 z
位置从 20000
更改为 2000
,以便将相机放置在立方体中。
function init()
...
camera.position.set(1200, -250, 2000);
...
}
轨道控制尽管上面的方法可以将我们放到立方体中,但是如果可以用鼠标控制摄像头并环顾四周,那就更好了。Three.js
的 Orbit Controls
包使我们可以添加 <script>
导入:
<script src="https://threejs.org/build/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
首先,在顶部的初始化中添加另一个变量 controls
,然后在 OrbitControls()
方法中传递 camera
,将该变量分配给 domElement()
方法,通过设置启用控件 controls.enabled
为 true
。最后,设置 minDistance
和 maxDistance
,以使用户无法在立方体之外进行缩放。
let scene, camera, renderer, skyboxGeo, skybox, controls;
function init() {
...
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enabled = true;
controls.minDistance = 700;
controls.maxDistance = 1500;
...
animate();
}
接下来,删除 animate()
函数中的旋转方法,并添加 controls.update()
;
function animate() {
controls.update();
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
现在,您应该可以单击并拖动周围的环境以查看所需的任何部分。如果您希望环境再次旋转,就像您在空间旋转中一样,则可以使用 autoRotate
属性:
function init() {
...
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enabled = true;
controls.minDistance = 700;
controls.maxDistance = 1500;
controls.autoRotate = true;
controls.autoRotateSpeed = 1.0;
...
animate();
}
调整窗口大小如果在初始化后调整浏览器窗口的大小,则画布将不会调整大小以适应新的窗口大小。要解决此问题,请创建一个函数来将 camera.aspect
和 renderer
大小重新定义为窗口的高度和宽度:
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
然后在 window
、 resize
事件上添加事件侦听器,并传递此新函数。将此事件侦听器添加到最先调用 init()
的 animate()
函数中。
function init() {
...
window.addEventListener('resize', onWindowResize, false);
animate();
}
现在,画布将随窗口调整大小。
结论天空盒是一种快速创建 3D
环境错觉的巧妙方法。虽然通常用于视频游戏,但是您可以通过一些创造性的方式在 Web
项目中运用它们。