关于javascript:Threejs教程如何用3D纹理构造简单的汽车

14次阅读

共计 7318 个字符,预计需要花费 19 分钟才能阅读完成。

应用 Three.js 在浏览器中组合 3D 场景就像在玩乐高玩具一样。咱们将一些盒子放在一起,增加灯光,定义相机,而后 Three.js 渲染 3D 图像。

在本教程中,咱们将从盒子中组装一辆简洁的汽车,并学习如何在其上绘制纹理。

DEMO

如何设置 Three.js 我的项目

Three.js 是一个内部库,因而首先咱们须要将其增加到咱们的我的项目中。我应用 NPM 将其装置到我的我的项目中,而后将其导入 JavaScript 文件的结尾。

import * as THREE from "three"; 
const scene = new THREE.Scene();
. . .

首先,咱们须要定义场景。场景是一个容器,其中蕴含咱们要与灯光一起显示的所有 3D 对象。咱们将向该场景增加汽车,但首先让咱们设置灯光,相机和渲染器。

如何设置灯光

咱们将向场景增加两个灯光:环境光和定向光。咱们通过设置色彩和强度来定义。

色彩定义为十六进制值。在这种状况下,咱们将其设置为红色。强度是介于 0 和 1 之间的数字,并且当它们同时发光时,咱们心愿这些值在 0.5 左右。

. . . 
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(200, 500, 300);
scene.add(directionalLight); 
. . .

环境光从各个方向发光,为咱们的几何图形提供了根底色,而定向光则模仿了太阳。

定向光从很远的中央收回平行光线。咱们为此光线设置一个地位,以定义这些光线的方向。

这个地位可能有点令人困惑,所以让我解释一下。在所有平行光线中,咱们特地定义一个。该特定光线将从咱们定义的地位(200,500,300)发光到 0,0,0 坐标。其余的将与之平行。

因为光线是平行的,并且它们从很远的中央收回光线,因而此处的准确坐标无关紧要,而是它们的比例无关紧要。

三个地位参数别离是 X,Y 和 Z 坐标。默认状况下,Y 轴指向上方,并且 Y 轴的值最高(500),这意味着咱们的汽车顶部受到的光照最多。因而它将是最亮堂的。

其余两个值定义为沿 X 和 Z 轴蜿蜒的光线量,也就是汽车的前部和侧面将接管的光线量。

如何设置相机

接下来,让咱们设置定义咱们如何对待该场景的摄像机。

这里有两个选项–透视相机和正交相机。电子游戏次要应用透视相机,但咱们将应用正交摄影机以使外观看起来更简洁。

在上一篇文章中,咱们更具体地探讨了这两个相机之间的区别。因而,在本教程中,咱们将仅探讨如何设置正交摄影机。

对于相机,咱们须要定义一个视锥。这是 3D 空间中要投影到屏幕上的区域。

对于正交摄影机,这是一个盒子。相机会将此盒子内的 3D 对象投射到其一侧。因为每条投影线都是平行的,因而正交摄影机不会扭曲几何形态。

. . .
// Setting up camera
const aspectRatio = window.innerWidth / window.innerHeight;
const cameraWidth = 150;
const cameraHeight = cameraWidth / aspectRatio;
const camera = new THREE.OrthographicCamera(
  cameraWidth / -2, // left
  cameraWidth / 2, // right
  cameraHeight / 2, // top
  cameraHeight / -2, // bottom
  0, // near plane
  1000 // far plane
);
camera.position.set(200, 200, 200);
camera.lookAt(0, 10, 0);
. . .

要设置正交摄影机,咱们必须定义从视点到视锥的每一边有多远。咱们定义左侧为左侧 75 个单位,右侧立体为右侧 75 个单位,依此类推。

在这里,这些单位不代表屏幕像素。渲染图像的大小将在渲染器中定义。在这里,这些值具备咱们在 3D 空间中应用的任意单位。稍后,当在 3D 空间中定义 3D 对象时,咱们将应用雷同的单位来设置其大小和地位。

定义摄像机后,咱们还须要将其定位并朝一个方向旋转。咱们将相机在每个维度上挪动 200 个单位,而后将其设置为向回 0,10,0 坐标。这简直是起源。咱们朝着稍微高于高空的地位看,这就是咱们汽车的核心所在的地位。

如何设置渲染器

咱们须要设置的最初一块是渲染器,能够依据相机将场景渲染到浏览器中。咱们定义一个 WebGLRenderer 像这样:

. . .
// Set up renderer
const renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.render(scene, camera);
document.body.appendChild(renderer.domElement);

在这里,咱们还设置了画布的大小。这是咱们惟一设置像素大小的中央,因为咱们要设置它在浏览器中的显示方式。如果要填充整个浏览器窗口,请传递窗口的大小。

最初,最初一行将渲染的图像增加到咱们的 HTML 文档中。它创立一个 HTML Canvas 元素以显示渲染的图像并将其增加到 DOM。

如何在 Three.js 中构建汽车

当初,让咱们看看如何组成汽车。首先,咱们将创立没有纹理的汽车。这将是一个简洁的设计–咱们将只放四个盒子。

如何增加盒子

首先,咱们创立一对轮子。咱们将定义一个代表左右轮的灰色框。因为咱们从未从下方看到汽车,因而咱们不会留神到,除了领有一个独自的左右轮外,咱们只有一个大箱子。

咱们将在汽车的前部和后部都须要一对轮子,因而咱们能够创立可重用的性能。

. . . 
function createWheels() {const geometry = new THREE.BoxBufferGeometry(12, 12, 33);
  const material = new THREE.MeshLambertMaterial({color: 0x333333});
  const wheel = new THREE.Mesh(geometry, material);
  return wheel;
}
. . .

咱们将轮子定义为网格。网格是几何图形和资料的组合,它将代表咱们的 3D 对象。

几何形态定义了对象的形态。在这种状况下,咱们通过将其沿 X,Y 和 Z 轴的尺寸设置为 12、12 和 33 个单位来创立一个盒子。

而后,咱们传递一种将定义网格外观的材质。有不同的资料抉择。它们之间的次要区别是它们对光的反馈形式。

在本教程中,咱们将应用 MeshLambertMaterial。在MeshLambertMaterial 计算每个顶点的色彩。在画一个盒子的状况下,基本上就是每一面。

咱们能够看到它是如何工作的,因为盒子的每一侧都有不同的暗影。咱们定义了一个定向光,使其次要从上方收回光,因而盒子的顶部是最亮的。

其余一些资料不仅能够为每面而且还能够为面内的每个像素计算色彩。对于更简单的形态,它们能够产生更真切的图像。然而对于应用定向光照明的盒子,它们并没有太大的区别。

如何制作汽车的其余部分

而后以相似的形式让咱们创立汽车的其余部分。咱们定义了 createCar 返回 Group 的函数。该组是场景中的另一个容器。它能够包容 Three.js 对象。这很不便,因为如果咱们要在汽车中到处走动,咱们只需在团体内到处走动。

. . .
function createCar() {const car = new THREE.Group();
  
  const backWheel = createWheels();
  backWheel.position.y = 6;
  backWheel.position.x = -18;
  car.add(backWheel);
  
  const frontWheel = createWheels();
  frontWheel.position.y = 6;  
  frontWheel.position.x = 18;
  car.add(frontWheel);
  const main = new THREE.Mesh(new THREE.BoxBufferGeometry(60, 15, 30),
    new THREE.MeshLambertMaterial({color: 0x78b14b})
  );
  main.position.y = 12;
  car.add(main);
  const cabin = new THREE.Mesh(new THREE.BoxBufferGeometry(33, 12, 24),
    new THREE.MeshLambertMaterial({color: 0xffffff})
  );
  cabin.position.x = -6;
  cabin.position.y = 25.5;
  car.add(cabin);
  return car;
}
const car = createCar();
scene.add(car);
renderer.render(scene, camera);
. . .

咱们应用咱们的性能生成两对车轮,而后定义汽车的次要局部。而后,咱们将机舱的顶部增加为第四网格。这些都是具备不同尺寸和色彩的盒子。

默认状况下,每个几何都将在两头,并且它们的核心将在 0,0,0 坐标处。

首先,咱们通过调整它们沿 Y 轴的地位来升高它们。咱们将轮子的高度进步一半 - 因而,轮子不会沉入高空,而是躺在高空上。而后,咱们还沿着 X 轴调整片段以达到其最终地位。

咱们将这些片段增加到汽车组中,而后将整个组增加到场景中。在渲染图像之前将汽车增加到场景中很重要,否则批改场景后,咱们将须要再次调用渲染。

如何向汽车增加纹理

当初咱们有了根本的汽车模型,让咱们为机舱增加一些纹理。咱们要给窗户涂油漆。咱们将为侧面定义纹理,为机舱的侧面和反面定义一个纹理。

当咱们应用资料设置网格的外观时,设置色彩不是惟一的抉择。咱们还能够映射纹理。咱们能够为每一侧提供雷同的纹理,也能够为阵列中的每一侧提供一种材质。

作为纹理,咱们能够应用图像。然而相同,咱们将应用 JavaScript 创立纹理。咱们将应用 HTML Canvas 和 JavaScript 对图像进行编码。

在持续之前,咱们须要辨别 Three.js 和 HTML Canvas。

Three.js 是一个 JavaScript 库。它应用引擎盖下的 WebGL 将 3D 对象渲染为图像,并将最终结果显示在 canvas 元素中。

另一方面,HTML Canvas 是 HTML 元素,就像 div 元素或段落标签一样。然而,让它不同凡响的是,咱们能够应用 JavaScript 在此元素上绘制形态。

这就是 Three.js 在浏览器中渲染场景的形式,这就是咱们要创立纹理的形式。让咱们看看它们是如何工作的。

如何在 HTML 画布上绘制

要在画布上绘制,首先咱们须要创立一个 canvas 元素。当咱们创立一个 HTML 元素时,该元素将永远不会成为咱们 HTML 构造的一部分。它自身不会显示在页面上。相同,咱们将其转换为 Three.js 纹理。

让咱们看看如何在此画布上绘制。首先,咱们定义画布的宽度和高度。这里的大小并没有定义画布将显示多大,它更像是画布的分辨率。无论大小如何,纹理都会拉伸到框的侧面。

function getCarFrontTexture() {const canvas = document.createElement("canvas");
  canvas.width = 64;
  canvas.height = 32;
  const context = canvas.getContext("2d");
  context.fillStyle = "#ffffff";
  context.fillRect(0, 0, 64, 32);
  context.fillStyle = "#666666";
  context.fillRect(8, 8, 48, 24);
  return new THREE.CanvasTexture(canvas);
}

而后,咱们取得 2D 绘图上下文。咱们能够应用此上下文执行绘图命令。

首先,咱们将用红色矩形填充整个画布。为此,首先咱们将填充款式设置为 while。而后通过设置矩形的左上角地位和大小来填充矩形。在画布上绘制时,默认状况下 0,0 坐标将位于左上角。

而后,咱们用灰色填充另一个矩形。这是从 8,8 坐标开始的,它不填充画布,仅绘制窗口。

就是这样–最初一行将 canvas 元素转换为纹理并返回它,因而咱们能够将其用于咱们的汽车。

function getCarSideTexture() {const canvas = document.createElement("canvas");
  canvas.width = 128;
  canvas.height = 32;
  const context = canvas.getContext("2d");
  context.fillStyle = "#ffffff";
  context.fillRect(0, 0, 128, 32);
  context.fillStyle = "#666666";
  context.fillRect(10, 8, 38, 24);
  context.fillRect(58, 8, 60, 24);
  return new THREE.CanvasTexture(canvas);
}

以相似的形式,咱们能够定义侧面纹理。咱们再次创立一个 canvas 元素,获取其上下文,而后首先将整个画布填充为基色,而后将窗口绘制为矩形。

如何将纹理映射到盒子

当初,让咱们看看如何在汽车上应用这些纹理。当咱们为机舱顶部定义网格时,咱们不只设置一种材质,而是为每一侧设置一种材质。咱们定义了六种资料的阵列。咱们将纹理映射到机舱的侧面,而顶部和底部仍将具备纯色。

. . .
function createCar() {const car = new THREE.Group();
  const backWheel = createWheels();
  backWheel.position.y = 6;
  backWheel.position.x = -18;
  car.add(backWheel);
  const frontWheel = createWheels();
  frontWheel.position.y = 6;
  frontWheel.position.x = 18;
  car.add(frontWheel);
  const main = new THREE.Mesh(new THREE.BoxBufferGeometry(60, 15, 30),
    new THREE.MeshLambertMaterial({color: 0xa52523})
  );
  main.position.y = 12;
  car.add(main);
  const carFrontTexture = getCarFrontTexture();
  const carBackTexture = getCarFrontTexture();
  const carRightSideTexture = getCarSideTexture();
  const carLeftSideTexture = getCarSideTexture();
  carLeftSideTexture.center = new THREE.Vector2(0.5, 0.5);
  carLeftSideTexture.rotation = Math.PI;
  carLeftSideTexture.flipY = false;
  const cabin = new THREE.Mesh(new THREE.BoxBufferGeometry(33, 12, 24), [new THREE.MeshLambertMaterial({ map: carFrontTexture}),
    new THREE.MeshLambertMaterial({map: carBackTexture}),
    new THREE.MeshLambertMaterial({color: 0xffffff}), // top
    new THREE.MeshLambertMaterial({color: 0xffffff}), // bottom
    new THREE.MeshLambertMaterial({map: carRightSideTexture}),
    new THREE.MeshLambertMaterial({map: carLeftSideTexture}),
  ]);
  cabin.position.x = -6;
  cabin.position.y = 25.5;
  car.add(cabin);
  return car;
}
. . .

这些纹理中的大多数将正确映射,无需进行任何调整。然而,如果咱们将汽车转过身,那么咱们能够看到窗户以谬误的程序呈现在左侧。

固定纹理前后的左右两侧

这是预期的,因为咱们在此处也将纹理用于右侧。咱们能够为左侧定义一个独自的纹理,也能够镜像右侧。

可怜的是,咱们不能程度翻转纹理。咱们只能垂直翻转纹理。咱们能够通过 3 个步骤来解决此问题。

首先,咱们将纹理旋转 180 度,这等于弧度的 PI。然而,在旋转它之前,咱们必须确保纹理围绕其核心旋转。这不是默认值–咱们必须将旋转核心设置为一半。咱们在两个轴上都设置了 0.5,这基本上意味着 50%。最初,咱们将纹理上下颠倒以使其处于正确的地位。

包起来

那么咱们在这里做了什么?咱们创立了一个蕴含咱们的汽车和灯光的场景。咱们用简略的盒子建造了汽车。

您可能认为这太根底了,然而如果您考虑一下,实际上是应用盒子创立了许多外观时尚的手机游戏。或者只是考虑一下 Minecraft,看看将盒子放在一起能走多远。

而后,咱们应用 HTML 画布创立纹理。HTML canvas 的性能远远超过咱们在此应用的性能。咱们能够用曲线和弧线绘制不同的形态,然而有时咱们只须要一个最小的设计即可。

最初,咱们定义了一个相机来建设咱们如何对待该场景,以及一个渲染器,将最终图像渲染到浏览器中。


如果您想应用该代码,能够在 CodePen 上找到源代码。

正文完
 0