Threejs 适合初学者的教程 ( 2 ) - 基础

139 阅读17分钟

script:

声明 threejs 并且在页面中创建画布

// 从 'three' 模块导入所有的 THREE 对象,通常用于进行 3D 渲染和处理场景、相机、光照等
import * as THREE from "three";

// 创建一个 WebGL 渲染器实例,用于在浏览器中使用 WebGL 渲染 3D 内容
const renderer = new THREE.WebGLRenderer();

// 设置渲染器的尺寸为当前浏览器窗口的宽度和高度,使渲染的内容能够填满整个浏览器窗口
renderer.setSize(window.innerWidth, window.innerHeight);

// 将渲染器生成的 <canvas> 元素(renderer.domElement)添加到网页的 <body> 元素中,
// 使得渲染内容能够显示在网页上
document.body.appendChild(renderer.domElement);

此时终端运行

parcel ./src/index.html

在浏览器中打开随机生成的地址,会看到一个黑色的画布

1. 场景和相机

// 创建一个场景实例,所有的 3D 对象、光源、相机等都将被添加到场景中
const scene = new THREE.Scene();

// 创建一个透视相机(PerspectiveCamera),用于模拟人眼的视角效果
// 参数:
// - 75: 相机的视野角度(Field of View,FOV),决定了相机所能看到的视野范围。
// - window.innerWidth / window.innerHeight: 相机的纵横比(aspect ratio),通常是窗口的宽高比。
// - 0.1: 近剪裁面(near clipping plane),相机能看到的最短距离。
// - 1000: 远剪裁面(far clipping plane),相机能看到的最远距离。
const camera = new THREE.PerspectiveCamera(
  75,
  window.innerWidth / window.innerHeight,
  0.1,
  1000
);

// 调用渲染器的 `render` 方法,渲染场景(scene)并使用相机(camera)来查看场景内容。
// `render` 方法的第一个参数是场景,第二个参数是相机。
renderer.render(scene, camera);

2. 坐标:

// 创建一个 AxesHelper 实例,生成一个坐标轴辅助工具。
// 参数 5 表示坐标轴的长度,X、Y、Z 轴的长度为 5 单位。
// 默认情况下,X 轴为红色,Y 轴为绿色,Z 轴为蓝色。
const axesHelper = new THREE.AxesHelper(5);

// 将坐标轴辅助工具添加到场景中,
// 这样在渲染时,坐标轴会显示在场景中,帮助你理解 3D 场景的坐标系方向。
scene.add(axesHelper);

// 设置相机的位置,使其沿 Z 轴偏移 5 个单位。
// camera.position.z 影响相机在 Z 轴上的位置,Z 轴是指向场景的前后方向。
// 设置为 5 使得相机稍微远离原点,能够看到场景中的内容。
camera.position.z = 5;

// 设置相机在 Y 轴上的位置。
// camera.position.y 影响相机在 Y 轴上的位置,Y 轴通常表示场景的上下方向。
// 设置为 2,使相机略微偏高于场景的水平面,这有助于更好地查看场景中的内容。
camera.position.y = 2;

在场景中添加一个坐标轴辅助工具,用来可视化 X、Y、Z 轴。

设置相机的位置,使得相机从稍远的位置(Z=5)并稍高的位置(Y=2)查看场景,以确保能够看到场景中的内容。

我们可以简写:

camera.position.set(0, 2, 5);

image.png

完整的代码:

import * as THREE from "three";

const renderer = new THREE.WebGLRenderer();

renderer.setSize(window.innerWidth, window.innerHeight);

document.body.appendChild(renderer.domElement);

const scene = new THREE.Scene();

const camera = new THREE.PerspectiveCamera(
  75,
  window.innerWidth / window.innerHeight,
  0.1,
  1000
);

const axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper);

camera.position.set(0, 2, 5);

renderer.render(scene, camera);

3. 几何体:

// 创建一个立方体几何体(BoxGeometry),
// BoxGeometry 表示一个长方体(立方体是特殊的长方体),
// 该构造函数没有参数,表示默认大小为 1x1x1。
const boxGeometry = new THREE.BoxGeometry();

// 创建一个基本材质(MeshBasicMaterial),
// MeshBasicMaterial 是最简单的材质,它没有光照效果,始终呈现指定的颜色。
// 这里设置颜色为绿色(0x00ff00)。
const boxMaterial = new THREE.MeshBasicMaterial({
  color: 0x00ff00, // 设置材质的颜色为绿色
});

// 创建一个网格对象(Mesh),由几何体和材质组成。
// 将之前定义的 boxGeometry(几何体)和 boxMaterial(材质)传给 Mesh 来创建一个可渲染的 3D 对象。
const box = new THREE.Mesh(boxGeometry, boxMaterial);

// 将创建的 box(立方体)添加到场景中,这样它就能显示在渲染结果中。
// 你可以在场景中添加多个物体,然后通过相机和渲染器来渲染它们。
scene.add(box);

image.png

3.1 几何体的旋转:

// 旋转立方体(box)绕 X 轴旋转。
// box.rotation.x 设置的是绕 X 轴旋转的角度,单位是弧度。
// 这里设置为 5 弧度,表示绕 X 轴旋转 5 弧度。
box.rotation.x = 5;

// 旋转立方体(box)绕 Y 轴旋转。
// box.rotation.y 设置的是绕 Y 轴旋转的角度,单位是弧度。
// 这里设置为 5 弧度,表示绕 Y 轴旋转 5 弧度。
box.rotation.y = 5;

image.png

3.2 让几何体动起来:

// 定义一个动画函数 animate,用于更新物体的状态并重新渲染场景。
// 该函数会在每一帧调用,执行物体的旋转以及渲染操作。
function animate() {
  // 每次调用时,增加 box 的旋转角度,使其继续旋转。
  // 绕 X 轴的旋转角度每次增加 0.01 弧度
  box.rotation.x += 0.01;
  
  // 绕 Y 轴的旋转角度每次增加 0.01 弧度
  box.rotation.y += 0.01;

  // 使用渲染器重新渲染场景。 
  // `scene` 是我们创建的 3D 场景,`camera` 是相机对象,确定渲染时的视角。
  renderer.render(scene, camera);
}

// 使用渲染器的 `setAnimationLoop` 方法来启动动画循环。
// `animate` 函数会在每一帧自动调用,从而更新物体的状态并渲染场景。
// 这是 `THREE.js` 中的推荐方式,可以保证在浏览器帧率变化时流畅地播放动画。
renderer.setAnimationLoop(animate);

image.png

完整的代码:

import * as THREE from "three";

const renderer = new THREE.WebGLRenderer();

renderer.setSize(window.innerWidth, window.innerHeight);

document.body.appendChild(renderer.domElement);

const scene = new THREE.Scene();

const camera = new THREE.PerspectiveCamera(
  75,
  window.innerWidth / window.innerHeight,
  0.1,
  1000
);

const axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper);

camera.position.set(0, 2, 5);

const boxGeometry = new THREE.BoxGeometry();
const boxMaterial = new THREE.MeshBasicMaterial({
  color: 0x00ff00,
});
const box = new THREE.Mesh(boxGeometry, boxMaterial);
scene.add(box);

function animate(time) {
  box.rotation.x = time / 1000;
  box.rotation.y = time / 1000;

  renderer.render(scene, camera);
}
renderer.setAnimationLoop(animate);

3.3 让场景和几何体跟随鼠标旋转角度并且鼠标滚轮放大缩小:

import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";

……

const orbit = new OrbitControls(camera,renderer.domElement);

……

orbit.update();

image.png

完整的代码:

import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";

const renderer = new THREE.WebGLRenderer();

renderer.setSize(window.innerWidth, window.innerHeight);

document.body.appendChild(renderer.domElement);

const scene = new THREE.Scene();

const camera = new THREE.PerspectiveCamera(
  75,
  window.innerWidth / window.innerHeight,
  0.1,
  1000
);

const orbit = new OrbitControls(camera,renderer.domElement);

const axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper);

camera.position.set(0, 2, 5);

orbit.update();

const boxGeometry = new THREE.BoxGeometry();
const boxMaterial = new THREE.MeshBasicMaterial({
  color: 0x00ff00,
});
const box = new THREE.Mesh(boxGeometry, boxMaterial);
scene.add(box);

function animate(time) {
  box.rotation.x = time / 1000;
  box.rotation.y = time / 1000;

  renderer.render(scene, camera);
}
renderer.setAnimationLoop(animate);

3.4 创建基础地面或背景

// 创建一个平面几何体(PlaneGeometry),宽度和高度都为 30 单位。
// PlaneGeometry 创建一个矩形平面,可以指定宽度和高度。
const planeGeometry = new THREE.PlaneGeometry(30, 30);

// 创建一个基本材质(MeshBasicMaterial),表示平面的材质。
// MeshBasicMaterial 是最简单的材质,颜色不受光照影响,始终为指定的颜色。
// 这里设置颜色为白色(0xffffff)。
const planeMaterial = new THREE.MeshBasicMaterial({
  color: 0xffffff, // 设置平面材质的颜色为白色
});

// 创建一个网格(Mesh)对象,将几何体和材质结合起来。
// 这里将创建一个平面网格,材质为白色,大小为 30x30 单位。
const plane = new THREE.Mesh(planeGeometry, planeMaterial);

// 将平面网格(plane)添加到场景中,这样它才能被渲染出来。
// 将场景中的物体(如平面)添加到渲染循环后,渲染器才能显示出来。
scene.add(plane);

image.png

4. 场景 - 网格

// 创建一个 GridHelper 网格辅助工具。
// GridHelper 是一个用于在场景中显示网格的辅助工具,通常用于帮助开发者理解坐标系和物体的位置。
// 默认情况下,GridHelper 会在场景中显示一个 XY 平面的网格,尺寸为 10x10 单位,细分为 10 行/列。
const gridHelper = new THREE.GridHelper();

// 将网格辅助工具添加到场景中。
// 这样网格就会在渲染时显示出来,帮助开发者更容易理解场景中的物体位置和尺度。
scene.add(gridHelper);

image.png

4.1

旋转基础地面和网格一致

plane.rotation.x = -0.5 * Math.PI;

让(基础地面)材质同时渲染几何体的正面和反面

const planeMaterial = new THREE.MeshBasicMaterial({
  side: THREE.DoubleSide,
});

完整的代码:

import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";

const renderer = new THREE.WebGLRenderer();

renderer.setSize(window.innerWidth, window.innerHeight);

document.body.appendChild(renderer.domElement);

const scene = new THREE.Scene();

const camera = new THREE.PerspectiveCamera(
  45,
  window.innerWidth / window.innerHeight,
  0.1,
  1000
);

const orbit = new OrbitControls(camera, renderer.domElement);

const axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper);

camera.position.set(-10, 30, 30);

orbit.update();

const boxGeometry = new THREE.BoxGeometry();
const boxMaterial = new THREE.MeshBasicMaterial({
  color: 0x00ff00,
});
const box = new THREE.Mesh(boxGeometry, boxMaterial);
scene.add(box);

const planeGeometry = new THREE.PlaneGeometry(30, 30);
const planeMaterial = new THREE.MeshBasicMaterial({
  color: 0xffffff,
  side: THREE.DoubleSide,
});
const plane = new THREE.Mesh(planeGeometry, planeMaterial);
scene.add(plane);
plane.rotation.x = -0.5 * Math.PI;

const gridHelper = new THREE.GridHelper(30, 30);
scene.add(gridHelper);

function animate(time) {
  box.rotation.x = time / 1000;
  box.rotation.y = time / 1000;

  renderer.render(scene, camera);
}
renderer.setAnimationLoop(animate);

image.png image.png

4.2 创建球体

const sphereGeometry = new THREE.SphereGeometry(4);
const sphereMaterial = new THREE.MeshBasicMaterial({
  color: 0x0000ff,
});
const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
scene.add(sphere);

image.png

4.3 线框模式 wireframe

image.png

完整的代码:

import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";

const renderer = new THREE.WebGLRenderer();

renderer.setSize(window.innerWidth, window.innerHeight);

document.body.appendChild(renderer.domElement);

const scene = new THREE.Scene();

const camera = new THREE.PerspectiveCamera(
  45,
  window.innerWidth / window.innerHeight,
  0.1,
  1000
);

const orbit = new OrbitControls(camera, renderer.domElement);

const axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper);

camera.position.set(-10, 30, 30);

orbit.update();

const sphereGeometry = new THREE.SphereGeometry(4);
const sphereMaterial = new THREE.MeshBasicMaterial({
  color: 0x0000ff,
  wireframe: true
});
const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
scene.add(sphere);

const planeGeometry = new THREE.PlaneGeometry(30, 30);
const planeMaterial = new THREE.MeshBasicMaterial({
  color: 0xffffff,
  side: THREE.DoubleSide,
});
const plane = new THREE.Mesh(planeGeometry, planeMaterial);
scene.add(plane);
plane.rotation.x = -0.5 * Math.PI;

const gridHelper = new THREE.GridHelper(30);
scene.add(gridHelper);

function animate(time) {
  renderer.render(scene, camera);
}
renderer.setAnimationLoop(animate);

4.4 比较常见的材质类型:

  • MeshBasicMaterial:最简单的材质,不受光照影响。

  • MeshLambertMaterial:适用于没有反射的表面,受光照影响。

  • MeshPhongMaterial:适用于带有高光效果的表面,支持反射和光照。

  • MeshStandardMaterial:基于物理渲染(PBR),提供真实的光照和反射效果。

  • MeshPhysicalMaterial:在 MeshStandardMaterial 的基础上,提供更多物理属性的控制。

  • ShaderMaterial:自定义着色器材质,适用于特殊渲染效果。

  • LineBasicMaterial:用于绘制线条,不受光照影响。

  • PointsMaterial:用于粒子系统,适用于粒子效果。

4.5 几何体的位置:

const sphereGeometry = new THREE.SphereGeometry(4);
const sphereMaterial = new THREE.MeshLambertMaterial({
  color: 0x0000ff,
  wireframe: false
});
const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
scene.add(sphere);

sphere.position.x = -10

image.png

也可以直接用set去描述,三个值,分别是x,y,z

sphere.position.set(-10, 0, 0);

image.png

const sphereGeometry = new THREE.SphereGeometry(4);
const sphereMaterial = new THREE.MeshBasicMaterial({
  color: 0x0000ff,
  wireframe: false,
});
const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
scene.add(sphere);

sphere.position.set(-10, 10, 0);

5. Threejs 添加 GUI 工具

npm install dat.gui

5.1 如何使用?

导入GUI

import * as dat from "dat.gui";
const gui = new dat.GUI();

const options = {
  sphereColor:'#0000ff'
};
gui.addColor(options,'sphereColor').onChange((e) => {
  sphere.material.color.set(e);
})

此时,刷新浏览器就会看到右上角出现一个可以选择颜色的功能

image.png

完整代码:

import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import * as dat from "dat.gui";

const renderer = new THREE.WebGLRenderer();

renderer.setSize(window.innerWidth, window.innerHeight);

document.body.appendChild(renderer.domElement);

const scene = new THREE.Scene();

const camera = new THREE.PerspectiveCamera(
  45,
  window.innerWidth / window.innerHeight,
  0.1,
  1000
);

const orbit = new OrbitControls(camera, renderer.domElement);

const axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper);

camera.position.set(-10, 30, 30);

orbit.update();

const boxGeometry = new THREE.BoxGeometry();
const boxMaterial = new THREE.MeshBasicMaterial({
  color: 0x00ff00,
});
const box = new THREE.Mesh(boxGeometry, boxMaterial);
scene.add(box);

const sphereGeometry = new THREE.SphereGeometry(4);
const sphereMaterial = new THREE.MeshBasicMaterial({
  color: 0x0000ff,
  wireframe: false,
});
const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
scene.add(sphere);

sphere.position.set(-10, 10, 0);

const gui = new dat.GUI();

const options = {
  sphereColor:'#0000ff'
};
gui.addColor(options,'sphereColor').onChange((e) => {
  sphere.material.color.set(e);
})

const planeGeometry = new THREE.PlaneGeometry(30, 30);
const planeMaterial = new THREE.MeshBasicMaterial({
  color: 0xffffff,
  side: THREE.DoubleSide,
});
const plane = new THREE.Mesh(planeGeometry, planeMaterial);
scene.add(plane);
plane.rotation.x = -0.5 * Math.PI;

const gridHelper = new THREE.GridHelper(30);
scene.add(gridHelper);

function animate(time) {
  box.rotation.x = time / 1000;
  box.rotation.y = time / 1000;

  renderer.render(scene, camera);
}
renderer.setAnimationLoop(animate);

我们也可以继续拓展其他的功能,比如我们想要实现一个切换轮廓的效果

const options = {
  sphereColor: "#0000ff",
  wireframe: false,
};
gui.addColor(options, "sphereColor").onChange((e) => {
  sphere.material.color.set(e);
});
gui.add(options, "wireframe").onChange((e) => {
  sphere.material.wireframe = e;
});

image.png

5.2 小球的运动

原理非常简单,我们只需要控制几何体的x y z轴,给他们动态绑定变化的数值就可以实现了

假设我们希望做一个有规律的运动轨迹,

let step = 0;
let speed = 0.01;

function animate() {
  step += speed;
  const value = 10 * Math.abs(Math.sin(step));
  console.log(value);

  requestAnimationFrame(animate);
}

animate();

每次打印的 value 值随着 step 的递增而变化,形成一种波动模式

4522f2b3884abe76a254fa904b26ac0.png

完整代码:

import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import * as dat from "dat.gui";

const renderer = new THREE.WebGLRenderer();

renderer.setSize(window.innerWidth, window.innerHeight);

document.body.appendChild(renderer.domElement);

const scene = new THREE.Scene();

const camera = new THREE.PerspectiveCamera(
  45,
  window.innerWidth / window.innerHeight,
  0.1,
  1000
);

const orbit = new OrbitControls(camera, renderer.domElement);

const axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper);

camera.position.set(-10, 30, 30);

orbit.update();

const boxGeometry = new THREE.BoxGeometry();
const boxMaterial = new THREE.MeshBasicMaterial({
  color: 0x00ff00,
});
const box = new THREE.Mesh(boxGeometry, boxMaterial);
scene.add(box);

const sphereGeometry = new THREE.SphereGeometry(4);
const sphereMaterial = new THREE.MeshBasicMaterial({
  color: 0x0000ff,
  wireframe: false,
});
const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
scene.add(sphere);

sphere.position.set(-10, 10, 0);

const gui = new dat.GUI();

const options = {
  sphereColor: "#0000ff",
  wireframe: false,
  speed: 0.01,
};
gui.addColor(options, "sphereColor").onChange((e) => {
  sphere.material.color.set(e);
});
gui.add(options, "wireframe").onChange((e) => {
  sphere.material.wireframe = e;
});
gui.add(options, "speed", 0, 0.1);

const planeGeometry = new THREE.PlaneGeometry(30, 30);
const planeMaterial = new THREE.MeshBasicMaterial({
  color: 0xffffff,
  side: THREE.DoubleSide,
});
const plane = new THREE.Mesh(planeGeometry, planeMaterial);
scene.add(plane);
plane.rotation.x = -0.5 * Math.PI;

const gridHelper = new THREE.GridHelper(30);
scene.add(gridHelper);

let step = 0;

function animate(time) {
  box.rotation.x = time / 1000;
  box.rotation.y = time / 1000;

  step += options.speed;
  sphere.position.y = 10 * Math.abs(Math.sin(step));

  renderer.render(scene, camera);
}
renderer.setAnimationLoop(animate);

6 场景 - 光照

// 创建环境光,给场景提供基础的环境光照明
const ambientLight = new THREE.AmbientLight(0x333333);  // 灰色环境光
scene.add(ambientLight);

// 创建平行光,模拟太阳等远距离光源
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);  // 白色平行光,强度为 1
scene.add(directionalLight);

需要将材质改成 MeshStandardMaterial 物理渲染类型,因为基于物理渲染(PBR),提供真实的光照和反射效果

需要注意的是:MeshBasicMaterial 对任何类型的光照都不起作用

6.1 常见的光照类型:

  • 环境光(AmbientLight) 用于提供全局光照。

  • 平行光(DirectionalLight) 模拟太阳光、远距离的光源。

  • 点光源(PointLight) 用于模拟从一点向四面八方散射的光源。

  • 聚光灯(SpotLight) 创建聚焦的光束,适合小范围照明。

  • 半球光(HemisphereLight) 模拟自然环境中光源的渐变效果。

  • 矩形区域光(RectAreaLight) 模拟面光源效果,适合灯具、窗口等场景。

  • 光探针(LightProbe) 用于增强全局光照和环境光的模拟。

image.png

6.2 添加光源辅助器

// 添加平行光的帮助器,帮助可视化光源的方向
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
const dLightHelper = new THREE.DirectionalLightHelper(directionalLight);
scene.add(dLightHelper);

image.png

6.3 设置平行光的位置

// 设置平行光的位置
directionalLight.position.set(-30, 50, 0);  // 设置光源的位置(负的 x 值表示从左侧照射)
const ambinetLight = new THREE.AmbientLight(0x333333);
scene.add(ambinetLight);

const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
scene.add(directionalLight);
directionalLight.position.set(-30, 50, 0);

const dLightHelper = new THREE.DirectionalLightHelper(directionalLight, 5);
scene.add(dLightHelper);

image.png

6.4 设置阴影

默认情况下,阴影是关闭的

  • 启用渲染器的阴影功能 (renderer.shadowMap.enabled = true)。

  • 启用光源的阴影(light.castShadow = true)。

  • 启用物体投射和接收阴影(object.castShadow = trueobject.receiveShadow = true)。

  • 配置阴影的质量、范围和外观。

const renderer = new THREE.WebGLRenderer();

// 启用阴影映射
renderer.shadowMap.enabled = true;

// 设置阴影类型(可选,PCFSoftShadowMap 使阴影更柔和)
renderer.shadowMap.type = THREE.PCFSoftShadowMap;

document.body.appendChild(renderer.domElement);

6.5 启用光源的阴影

平行光(DirectionalLight)、点光源(PointLight)和聚光灯(SpotLight)都可以投射阴影。需要为光源启用阴影。

const directionalLight = new THREE.DirectionalLight(0xffffff, 1);

directionalLight.position.set(5, 10, 5);

directionalLight.castShadow = true;   // 启用阴影

scene.add(directionalLight);

6.6 启用物体的阴影

物体本身也需要配置为能够 投射和接收阴影。如果不设置这些属性,阴影将不会显示在物体上。

// 创建一个简单的立方体物体
const cubeGeometry = new THREE.BoxGeometry(1, 1, 1);
const cubeMaterial = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);

// 启用立方体投射阴影和接收阴影
cube.castShadow = true;     // 立方体会投射阴影
cube.receiveShadow = true;  // 立方体会接收阴影

scene.add(cube);

6.7 配置阴影的接收物体

与投射阴影的物体不同,接收阴影的物体也需要启用接收阴影的设置。这对于像地面、墙壁等物体是必要的。

const groundGeometry = new THREE.PlaneGeometry(100, 100);

// 使用 ShadowMaterial,可以让阴影在地面上可见
const groundMaterial = new THREE.ShadowMaterial({ opacity: 0.5 });

const ground = new THREE.Mesh(groundGeometry, groundMaterial);

// 将地面旋转到水平位置
ground.rotation.x = -Math.PI / 2;

// 地面的位置稍微低于其他物体
ground.position.y = -1;

// 地面接收阴影
ground.receiveShadow = true;

scene.add(ground);

完整代码:

import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import * as dat from "dat.gui";

const renderer = new THREE.WebGLRenderer();

renderer.shadowMap.enabled = true;

renderer.setSize(window.innerWidth, window.innerHeight);

document.body.appendChild(renderer.domElement);

const scene = new THREE.Scene();

const camera = new THREE.PerspectiveCamera(
  45,
  window.innerWidth / window.innerHeight,
  0.1,
  1000
);

const orbit = new OrbitControls(camera, renderer.domElement);

const axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper);

camera.position.set(-10, 30, 30);

orbit.update();

const boxGeometry = new THREE.BoxGeometry();
const boxMaterial = new THREE.MeshBasicMaterial({
  color: 0x00ff00,
});
const box = new THREE.Mesh(boxGeometry, boxMaterial);
scene.add(box);

const sphereGeometry = new THREE.SphereGeometry(4);
const sphereMaterial = new THREE.MeshStandardMaterial({
  color: 0x0000ff,
  wireframe: false,
});
const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
scene.add(sphere);
sphere.position.set(-10, 10, 0);
sphere.castShadow = true;

const ambinetLight = new THREE.AmbientLight(0x333333);
scene.add(ambinetLight);

const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
scene.add(directionalLight);
directionalLight.position.set(-30, 50, 0);
directionalLight.castShadow = true;

const dLightHelper = new THREE.DirectionalLightHelper(directionalLight, 5);
scene.add(dLightHelper);

const gui = new dat.GUI();

const options = {
  sphereColor: "#0000ff",
  wireframe: false,
  speed: 0.01,
};
gui.addColor(options, "sphereColor").onChange((e) => {
  sphere.material.color.set(e);
});
gui.add(options, "wireframe").onChange((e) => {
  sphere.material.wireframe = e;
});
gui.add(options, "speed", 0, 0.1);

const planeGeometry = new THREE.PlaneGeometry(30, 30);
const planeMaterial = new THREE.MeshStandardMaterial({
  color: 0xffffff,
  side: THREE.DoubleSide,
});
const plane = new THREE.Mesh(planeGeometry, planeMaterial);
scene.add(plane);
plane.rotation.x = -0.5 * Math.PI;
plane.receiveShadow = true;

const gridHelper = new THREE.GridHelper(30);
scene.add(gridHelper);

let step = 0;

function animate(time) {
  box.rotation.x = time / 1000;
  box.rotation.y = time / 1000;

  step += options.speed;
  sphere.position.y = 10 * Math.abs(Math.sin(step));

  renderer.render(scene, camera);
}
renderer.setAnimationLoop(animate);

运行结果:

image.png

6.8 创建并添加平行光阴影相机的帮助器,帮助可视化阴影相机的范围

// 创建并添加平行光的帮助器,帮助可视化光源的方向
const dLightHelper = new THREE.DirectionalLightHelper(directionalLight, 5);  // 帮助器长度为 5
scene.add(dLightHelper);

// 创建并添加平行光阴影相机的帮助器,帮助可视化阴影相机的范围
const dLightShadowHelper = new THREE.CameraHelper(directionalLight.shadow.camera);
scene.add(dLightShadowHelper);

image.png

directionalLight.shadow.camera.bottom = -12;

image.png

7. 聚光灯 - SpotLight

const spotLight = new THREE.SpotLight(0xffffff);
scene.add(spotLight);

image.png

new THREE.SpotLight(0xffffff, 6400);

通过设置第二个值来调整光源的强度

image.png

完整代码:

import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import * as dat from "dat.gui";

const renderer = new THREE.WebGLRenderer();

renderer.shadowMap.enabled = true;

renderer.setSize(window.innerWidth, window.innerHeight);

document.body.appendChild(renderer.domElement);

const scene = new THREE.Scene();

const camera = new THREE.PerspectiveCamera(
  45,
  window.innerWidth / window.innerHeight,
  0.1,
  1000
);

const orbit = new OrbitControls(camera, renderer.domElement);

const axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper);

camera.position.set(-10, 30, 30);

orbit.update();

const boxGeometry = new THREE.BoxGeometry();
const boxMaterial = new THREE.MeshStandardMaterial({
  color: 0x00ff00,
});
const box = new THREE.Mesh(boxGeometry, boxMaterial);
scene.add(box);

const sphereGeometry = new THREE.SphereGeometry(4);
const sphereMaterial = new THREE.MeshStandardMaterial({
  color: 0x0000ff,
  wireframe: false,
});
const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
scene.add(sphere);
sphere.position.set(-10, 10, 0);
sphere.castShadow = true;

const ambinetLight = new THREE.AmbientLight(0x333333);
scene.add(ambinetLight);

const spotLight = new THREE.SpotLight(0xffffff, 6400);
scene.add(spotLight);
spotLight.position.set(-50, 50, 0);

const sLightHelper = new THREE.SpotLightHelper(spotLight);
scene.add(sLightHelper);

const gui = new dat.GUI();

const options = {
  sphereColor: "#0000ff",
  wireframe: false,
  speed: 0.01,
};
gui.addColor(options, "sphereColor").onChange((e) => {
  sphere.material.color.set(e);
});
gui.add(options, "wireframe").onChange((e) => {
  sphere.material.wireframe = e;
});
gui.add(options, "speed", 0, 0.1);

const planeGeometry = new THREE.PlaneGeometry(30, 30);
const planeMaterial = new THREE.MeshStandardMaterial({
  color: 0xffffff,
  side: THREE.DoubleSide,
});
const plane = new THREE.Mesh(planeGeometry, planeMaterial);
scene.add(plane);
plane.rotation.x = -0.5 * Math.PI;
plane.receiveShadow = true;

const gridHelper = new THREE.GridHelper(30);
scene.add(gridHelper);

let step = 0;

function animate(time) {
  box.rotation.x = time / 1000;
  box.rotation.y = time / 1000;

  step += options.speed;
  sphere.position.y = 10 * Math.abs(Math.sin(step));

  renderer.render(scene, camera);
}
renderer.setAnimationLoop(animate);

7.1 设置阴影:

const spotLight = new THREE.SpotLight(0xffffff, 6400);
scene.add(spotLight);
spotLight.position.set(-50, 50, 0);

spotLight.castShadow = true;

image.png

7.2 设置聚光灯的角度,影响光束的宽度

const spotLight = new THREE.SpotLight(0xffffff, 6400);
scene.add(spotLight);
spotLight.position.set(-50, 50, 0);
spotLight.castShadow = true;

spotLight.angle = 0.2;

image.png

完整代码:

import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import * as dat from "dat.gui";

const renderer = new THREE.WebGLRenderer();

renderer.shadowMap.enabled = true;

renderer.setSize(window.innerWidth, window.innerHeight);

document.body.appendChild(renderer.domElement);

const scene = new THREE.Scene();

const camera = new THREE.PerspectiveCamera(
  45,
  window.innerWidth / window.innerHeight,
  0.1,
  1000
);

const orbit = new OrbitControls(camera, renderer.domElement);

const axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper);

camera.position.set(-10, 30, 30);

orbit.update();

const boxGeometry = new THREE.BoxGeometry();
const boxMaterial = new THREE.MeshStandardMaterial({
  color: 0x00ff00,
});
const box = new THREE.Mesh(boxGeometry, boxMaterial);
scene.add(box);

const sphereGeometry = new THREE.SphereGeometry(4);
const sphereMaterial = new THREE.MeshStandardMaterial({
  color: 0x0000ff,
  wireframe: false,
});
const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
scene.add(sphere);
sphere.position.set(-10, 10, 0);
sphere.castShadow = true;

const ambinetLight = new THREE.AmbientLight(0x333333);
scene.add(ambinetLight);

const spotLight = new THREE.SpotLight(0xffffff, 6400);
scene.add(spotLight);
spotLight.position.set(-50, 50, 0);
spotLight.castShadow = true;

const sLightHelper = new THREE.SpotLightHelper(spotLight);
scene.add(sLightHelper);

const gui = new dat.GUI();

const options = {
  sphereColor: "#0000ff",
  wireframe: false,
  speed: 0.01,
  angle: 0.2,
  penumbra: 0,
  intensity: 6500,
};
gui.addColor(options, "sphereColor").onChange((e) => {
  sphere.material.color.set(e);
});
gui.add(options, "wireframe").onChange((e) => {
  sphere.material.wireframe = e;
});
gui.add(options, "speed", 0, 0.1);

gui.add(options, "angle", 0, 1);
gui.add(options, "penumbra", 0, 1);
gui.add(options, "intensity", 0, 20000);

const planeGeometry = new THREE.PlaneGeometry(30, 30);
const planeMaterial = new THREE.MeshStandardMaterial({
  color: 0xffffff,
  side: THREE.DoubleSide,
});
const plane = new THREE.Mesh(planeGeometry, planeMaterial);
scene.add(plane);
plane.rotation.x = -0.5 * Math.PI;
plane.receiveShadow = true;

const gridHelper = new THREE.GridHelper(30);
scene.add(gridHelper);

let step = 0;

function animate(time) {
  box.rotation.x = time / 1000;
  box.rotation.y = time / 1000;

  step += options.speed;
  sphere.position.y = 10 * Math.abs(Math.sin(step));

  spotLight.angle = options.angle;
  spotLight.penumbra = options.penumbra;
  spotLight.intensity = options.intensity;
  sLightHelper.update();

  renderer.render(scene, camera);
}
renderer.setAnimationLoop(animate);

运行效果:

image.png

8. new THREE.Fog 和 new THREE.FogExp2

都是用来模拟场景中的雾霾效果的,它们通过不同的方式实现雾霾的效果。它们的使用方式很简单,都可以通过在场景中添加雾霾效果来增加画面的深度感和气氛感。具体差异在于它们模拟雾霾的方式不同。

const scene = new THREE.Scene();

// 创建线性雾霾
const fog = new THREE.Fog(0x000000, 1, 100); // (颜色, 近裁剪距离, 远裁剪距离)
scene.fog = fog;
const scene = new THREE.Scene();

// 创建指数衰减雾霾
const fogExp2 = new THREE.FogExp2(0x000000, 0.1); // (颜色, 衰减系数)
scene.fog = fogExp2;

主要区别:

  • THREE.Fog线性衰减,雾霾的浓度是线性变化的。
  • THREE.FogExp2指数衰减,雾霾的浓度随着距离的增加呈指数增长,通常更符合自然界中雾霾或烟雾的扩散特性。

使用场景:

  • THREE.Fog:适合用来模拟较为均匀的雾霾效果,例如模拟远处被雾霾笼罩的场景。
  • THREE.FogExp2:适合用来模拟浓重的雾霾、烟雾等效果,常用于游戏或科幻场景中,能够更好地表现烟雾、废墟等复杂的环境效果。

9. setClearColor - 设置渲染器背景色

用于设置渲染器的背景色。通常,渲染器的背景颜色是画布的清除色,这个颜色会在每次渲染帧开始时清除画布,通常作为场景的背景颜色。

renderer.setClearColor(0xffcc00);

image.png

完整代码:

import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import * as dat from "dat.gui";

const renderer = new THREE.WebGLRenderer();

renderer.shadowMap.enabled = true;

renderer.setSize(window.innerWidth, window.innerHeight);

document.body.appendChild(renderer.domElement);

const scene = new THREE.Scene();

const camera = new THREE.PerspectiveCamera(
  45,
  window.innerWidth / window.innerHeight,
  0.1,
  1000
);

const orbit = new OrbitControls(camera, renderer.domElement);

const axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper);

camera.position.set(-10, 30, 30);

orbit.update();

const boxGeometry = new THREE.BoxGeometry();
const boxMaterial = new THREE.MeshStandardMaterial({
  color: 0x00ff00,
});
const box = new THREE.Mesh(boxGeometry, boxMaterial);
scene.add(box);

const sphereGeometry = new THREE.SphereGeometry(4);
const sphereMaterial = new THREE.MeshStandardMaterial({
  color: 0x0000ff,
  wireframe: false,
});
const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
scene.add(sphere);
sphere.position.set(-10, 10, 0);
sphere.castShadow = true;

const ambinetLight = new THREE.AmbientLight(0x333333);
scene.add(ambinetLight);

const spotLight = new THREE.SpotLight(0xffffff, 6400);
scene.add(spotLight);
spotLight.position.set(-50, 50, 0);
spotLight.castShadow = true;

const sLightHelper = new THREE.SpotLightHelper(spotLight);
scene.add(sLightHelper);

// scene.fog = new THREE.Fog(0xffffff, 0, 2000);
scene.fog = new THREE.FogExp2(0xffffff, 0.01);

renderer.setClearColor(0xffcc00);

const gui = new dat.GUI();

const options = {
  sphereColor: "#0000ff",
  wireframe: false,
  speed: 0.01,
  angle: 0.2,
  penumbra: 0,
  intensity: 6500,
};
gui.addColor(options, "sphereColor").onChange((e) => {
  sphere.material.color.set(e);
});
gui.add(options, "wireframe").onChange((e) => {
  sphere.material.wireframe = e;
});
gui.add(options, "speed", 0, 0.1);

gui.add(options, "angle", 0, 1);
gui.add(options, "penumbra", 0, 1);
gui.add(options, "intensity", 0, 20000);

const planeGeometry = new THREE.PlaneGeometry(30, 30);
const planeMaterial = new THREE.MeshStandardMaterial({
  color: 0xffffff,
  side: THREE.DoubleSide,
});
const plane = new THREE.Mesh(planeGeometry, planeMaterial);
scene.add(plane);
plane.rotation.x = -0.5 * Math.PI;
plane.receiveShadow = true;

const gridHelper = new THREE.GridHelper(30);
scene.add(gridHelper);

let step = 0;

function animate(time) {
  box.rotation.x = time / 1000;
  box.rotation.y = time / 1000;

  step += options.speed;
  sphere.position.y = 10 * Math.abs(Math.sin(step));

  spotLight.angle = options.angle;
  spotLight.penumbra = options.penumbra;
  spotLight.intensity = options.intensity;
  sLightHelper.update();

  renderer.render(scene, camera);
}
renderer.setAnimationLoop(animate);