「译」如何使用 Three.js 创建天空盒

时间:2020-9-2 作者:admin

天空盒通常用于视频游戏中,用来产生远距三维背景的视觉效果。天空盒本质上是一个立方体,在立方体的每一侧都有纹理。然后将播放器或摄像机放置在立方体中,使得所有立方体的六个纹理围绕它们,从而给人一种位于更大的环境中的错觉。reactnativeinfinity.com利用这项技术创造了在太空旋转的错觉。

「译」如何使用 Three.js 创建天空盒

Three.js 设置

首先,建立一个包含 scenecamera以及 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://www.geekschool.org/wp-content/uploads/2020/09/1599033048.4212158.jpg"></script>

将主体高度设置为视口高度,并在主体上添加灰色背景,以便我们可以看到立方体。

body {
    margin: 0;
    height: 100vh;
    background: #bdc3c7;
}

由于我们还没有添加任何对象,因此我们现在只会看到灰色背景。

添加 Three.js 立方体

我们可以布置一个盒子 THREE.BoxGeometrywidthheight以及 depth设置为 10000。然后使用 THREE.Mesh对其添加纹理,在这种情况下,它将默认为纯白色纹理。最后,调用 animate()函数内的 init()函数之前,将对象添加到场景中。

function init() {
    ...
    skyboxGeo = new THREE.BoxGeometry(10000, 10000, 10000);
    skybox = new THREE.Mesh(skyboxGeo);
    scene.add(skybox);

    animate();

「译」如何使用 Three.js 创建天空盒

即使是立方体,它看起来也像正方形,因为我们是直接观看它。为了验证它是一个立方体,我们可以在 animate()函数中添加旋转动画效果:

function animate() {
    skybox.rotation.x += 0.005;
    skybox.rotation.y += 0.005;
    renderer.render(scene, camera);
    requestAnimationFrame(animate);
}

「译」如何使用 Three.js 创建天空盒

天空盒网格材质

您可以在 opengameart.org上找到免费的天空盒图片,也可以在 Google 上搜索“免费的天空盒图片”。通常会有六个与立方体的每一侧对应的图像,它们无缝地契合在一起。例如,对于React Native Infinity,六个空间图像对应于不同的边,如下所示。

「译」如何使用 Three.js 创建天空盒

每个图像应根据其对应的侧面进行命名,例如,purplenebula_ft.png是正面图像,purplenebula_rt.png右侧图像和 purplenebula_dn.png底部图像。我们需要遵循三个步骤将这些图像添加到多维数据集:

  • 将每个图像加载为纹理
  • 将每个纹理映射到材质数组
  • 将网格阵列添加天空盒

1. 将图像加载为纹理

可以使用 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.jsMeshBasicMaterial()方法,将允许我们处理相应纹理材料,无需创建另一个函数来执行此操作,我们只需修改 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();
}

现在,我们的立方体应具有网格阵列,单击“外部框”按钮以查看其外观。

「译」如何使用 Three.js 创建天空盒

将相机放在立方体内

我们可以将 cameraz位置从 20000更改为 2000,以便将相机放置在立方体中。

function init()
...
camera.position.set(1200, -250, 2000);
...
}

「译」如何使用 Three.js 创建天空盒

轨道控制

尽管上面的方法可以将我们放到立方体中,但是如果可以用鼠标控制摄像头并环顾四周,那就更好了。Three.jsOrbit Controls包使我们可以添加 <script>导入:

<script src="https://www.geekschool.org/wp-content/uploads/2020/09/1599033048.4212158.jpg"></script>
<script src="https://www.geekschool.org/wp-content/uploads/2020/09/1599060514.5215135.jpg"></script>

首先,在顶部的初始化中添加另一个变量 controls,然后在 OrbitControls()方法中传递 camera,将该变量分配给 domElement()方法,通过设置启用控件 controls.enabledtrue。最后,设置 minDistancemaxDistance,以使用户无法在立方体之外进行缩放。

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.aspectrenderer大小重新定义为窗口的高度和宽度:

function onWindowResize() {
    camera.aspect = window.innerWidth / window.innerHeight;

    camera.updateProjectionMatrix();
    renderer.setSize(window.innerWidth, window.innerHeight);
}

然后在 windowresize事件上添加事件侦听器,并传递此新函数。将此事件侦听器添加到最先调用 init()animate()函数中。

function init() {
    ...
    window.addEventListener('resize', onWindowResize, false);
    animate();
}

现在,画布将随窗口调整大小。

结论

天空盒是一种快速创建 3D环境错觉的巧妙方法。虽然通常用于视频游戏,但是您可以通过一些创造性的方式在 Web项目中运用它们。

声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。