Three.js 封装了什么?
Three.js 的核心目标是极大地简化在浏览器中进行 3D 图形编程的复杂性。它通过封装底层的 WebGL API(Web Graphics Library)来实现这一点。WebGL 本身是一个非常底层的接口,直接使用它需要编写大量冗余且复杂的代码来处理着色器、缓冲区、矩阵变换等。Three.js 在此基础上提供了更高层次、更易于使用的抽象。
主要封装内容包括:
-
渲染器 (Renderers) :
WebGLRenderer
: 主要的渲染器,利用 WebGL 在 GPU 上高效渲染场景。封装了 WebGL 的状态管理、着色器编译链接、绘制调用等。- 还包括一些其他渲染器,如
CSS2DRenderer
,CSS3DRenderer
(用于将 DOM 元素与 3D 场景结合) 和SVGRenderer
(用于将场景渲染为 SVG)。
-
场景 (Scene) :
Scene
: 3D 世界的容器,所有要渲染的物体、光源、相机等都必须添加到场景中。它维护了一个场景图 (Scene Graph) 的数据结构。
-
相机 (Cameras) :
PerspectiveCamera
: 透视相机,模拟人眼的视觉效果,物体近大远小。OrthographicCamera
: 正交相机,物体大小不随距离变化,常用于 2D 游戏或工程制图。- 封装了视图矩阵 (View Matrix) 和投影矩阵 (Projection Matrix) 的计算。
-
物体 (Objects) :
Object3D
: 所有 3D 物体的基类,提供了位置 (position)、旋转 (rotation/quaternion)、缩放 (scale) 等属性,以及层级关系(父子关系)。Mesh
: 表示由几何体 (Geometry) 和材质 (Material) 构成的网格物体,是最常见的可见物体。Points
: 表示点云物体。Line
,LineSegments
,LineLoop
: 表示线框物体。Group
: 用于组织多个Object3D
,方便整体变换。SkinnedMesh
: 用于骨骼动画。InstancedMesh
: 用于高效渲染大量相同几何体和材质但具有不同变换的实例。
-
几何体 (Geometries) :
BoxGeometry
,SphereGeometry
,PlaneGeometry
,CylinderGeometry
,TorusGeometry
等:内置的参数化几何体。BufferGeometry
: 更底层的几何体表示,允许开发者直接定义顶点位置、法线、UV 坐标、颜色等属性的缓冲区数据。Three.js 的所有内置几何体最终都转换为BufferGeometry
。- 封装了顶点数据、面数据、法线、UV 坐标等的管理。
-
材质 (Materials) :
MeshBasicMaterial
: 基础材质,不受光照影响,常用于测试或简单着色。MeshLambertMaterial
: Lambertian 光照模型,计算漫反射,表面比较粗糙。MeshPhongMaterial
: Phong 光照模型,计算漫反射和高光反射,表面更光滑。MeshStandardMaterial
(PBR): 基于物理的渲染材质,提供更真实的金属度 (metalness) 和粗糙度 (roughness) 控制。MeshPhysicalMaterial
(PBR):MeshStandardMaterial
的扩展,增加了清漆 (clearcoat)、透光性 (transmission) 等高级 PBR 属性。ShaderMaterial
,RawShaderMaterial
: 允许开发者使用自定义的顶点着色器和片元着色器。PointsMaterial
,LineBasicMaterial
,LineDashedMaterial
等。- 封装了颜色、纹理贴图、光照响应方式、透明度、混合模式等。
-
纹理 (Textures) :
Texture
: 用于将图像数据应用到材质上。CubeTexture
: 用于环境贴图、天空盒。CanvasTexture
,VideoTexture
,DepthTexture
,DataTexture
等。- 封装了纹理的加载、过滤、环绕模式等。
-
光源 (Lights) :
AmbientLight
: 环境光,均匀地照亮场景中的所有物体。DirectionalLight
: 平行光,模拟太阳光。PointLight
: 点光源,从一个点向所有方向发射光线。SpotLight
: 聚光灯,有方向和角度的锥形光源。HemisphereLight
: 半球光,模拟天空和地面的反射光。RectAreaLight
: 矩形区域光(仅MeshStandardMaterial
和MeshPhysicalMaterial
支持)。- 封装了光源的颜色、强度、位置、方向、衰减等属性。
-
数学库 (Math) :
Vector2
,Vector3
,Vector4
: 二维、三维、四维向量。Matrix3
,Matrix4
: 三阶、四阶矩阵,用于变换。Quaternion
: 四元数,用于表示旋转,避免万向节锁。Euler
: 欧拉角,另一种表示旋转的方式。Color
: 颜色对象。Box3
,Sphere
,Plane
,Ray
,Triangle
等几何辅助对象。MathUtils
: 提供常用的数学函数,如角度弧度转换、随机数生成、钳制等。
-
加载器 (Loaders) :
TextureLoader
: 加载图像纹理。FileLoader
,ImageLoader
,ImageBitmapLoader
.GLTFLoader
: 加载 glTF 和 GLB 格式的 3D 模型(推荐)。OBJLoader
,FBXLoader
,ColladaLoader
等:加载其他格式的 3D 模型。FontLoader
: 加载字体用于TextGeometry
。- 封装了异步加载资源的过程和解析。
-
动画 (Animation) :
AnimationClip
: 存储动画数据,如关键帧序列。AnimationMixer
: 动画混合器,用于播放和控制AnimationClip
。KeyframeTrack
: 定义特定属性(如位置、旋转、缩放)随时间变化的轨迹。
-
辅助对象 (Helpers) :
AxesHelper
: 显示三维坐标轴。GridHelper
: 显示网格平面。CameraHelper
,DirectionalLightHelper
,PointLightHelper
,SpotLightHelper
等:可视化相机和光源的位置及范围。BoxHelper
,Box3Helper
: 可视化物体的包围盒。
-
控制器 (Controls) :
OrbitControls
: 允许用户通过鼠标交互(旋转、缩放、平移)来控制相机围绕目标点观察。TrackballControls
,FlyControls
,FirstPersonControls
等。
-
后期处理 (Post-processing) :
EffectComposer
: 用于实现后期处理效果,如模糊、辉光、景深、颜色校正等。通过组合不同的Pass
来实现。
Three.js 的核心原理是什么?
-
基于 WebGL:
-
Three.js 的核心渲染能力来自于 WebGL。它将用户定义的场景、物体、材质、光照等高级概念转换为 WebGL 可以理解的指令,例如:
- 将几何体数据(顶点、索引、法线、UV)组织成 WebGL 的顶点缓冲对象 (VBO) 和索引缓冲对象 (IBO)。
- 根据材质和光照类型,动态生成或选择合适的 GLSL 着色器程序 (Vertex Shader 和 Fragment Shader)。
- 设置着色器的 uniforms (如模型视图投影矩阵、光照参数、纹理采样器等)。
- 调用 WebGL 的
gl.drawArrays()
或gl.drawElements()
进行绘制。
-
-
场景图 (Scene Graph) :
- Three.js 使用场景图来组织 3D 世界中的所有元素。场景图是一个树状结构,
Scene
对象是根节点。 Object3D
对象可以有子对象,形成层级关系。- 当父对象进行变换(平移、旋转、缩放)时,其所有子对象也会相应地进行变换。这是通过矩阵乘法实现的:子对象的最终世界变换矩阵 = 父对象的世界变换矩阵 × 子对象的局部变换矩阵。
Object3D
内部维护了matrix
(局部变换矩阵) 和matrixWorld
(世界变换矩阵)。updateMatrixWorld()
方法会递归地更新场景图中所有对象的世界变换矩阵。
- Three.js 使用场景图来组织 3D 世界中的所有元素。场景图是一个树状结构,
-
渲染循环 (Render Loop) :
-
3D 图形通常需要持续不断地重新绘制到屏幕上以创建动画或响应用户交互。这通过渲染循环实现,通常使用
requestAnimationFrame
API。 -
在每一帧:
- 更新状态: 更新场景中的物体(如动画、用户输入导致的变换)、相机位置、光照等。
- 渲染: 调用
renderer.render(scene, camera)
。
-
renderer.render(scene, camera)
内部会:-
更新相机矩阵 (视图矩阵、投影矩阵)。
-
遍历场景图中的可见物体。
-
对于每个物体:
- 更新其世界变换矩阵 (
object.updateMatrixWorld()
)。 - 设置 WebGL 的渲染状态(如深度测试、混合模式)。
- 绑定物体的几何体数据 (VBOs, IBOs)。
- 绑定材质对应的着色器程序。
- 传递 uniforms (模型矩阵、视图矩阵、投影矩阵、材质属性、光照信息等) 给着色器。
- 执行绘制命令。
- 更新其世界变换矩阵 (
-
-
-
材质与着色器 (Materials and Shaders) :
- 材质决定了物体表面的外观(颜色、纹理、对光的反应等)。
- Three.js 的每种内置材质内部都对应一套或多套预定义的 GLSL 着色器代码。
- 例如,
MeshPhongMaterial
会使用实现了 Phong 光照模型的着色器。 - 当使用
ShaderMaterial
或RawShaderMaterial
时,开发者可以直接提供自定义的 GLSL 代码。 - 渲染时,材质的属性(如
color
,map
,metalness
,roughness
)会作为 uniforms 传递给着色器。
-
数据驱动:
- 几何体由顶点数据(位置、法线、UV、颜色等)定义,这些数据存储在
BufferAttribute
中。 - 材质的属性也是数据。
- 这种数据驱动的方式使得更新和操作 3D 对象更加灵活。
- 几何体由顶点数据(位置、法线、UV、颜色等)定义,这些数据存储在
-
事件与交互:
- 虽然 Three.js 本身不直接处理 DOM 事件,但它提供了工具如
Raycaster
来实现 3D 空间中的拾取(判断鼠标点击到了哪个物体)。 - 控制器如
OrbitControls
内部监听鼠标和触摸事件,并据此更新相机的位置和朝向。
- 虽然 Three.js 本身不直接处理 DOM 事件,但它提供了工具如
如何使用 Three.js?(详细代码讲解)
下面是一个相对详细的示例,展示了 Three.js 的许多核心概念。我们将创建一个场景,包含一些基本物体、光源、纹理、加载一个 GLTF 模型,并使用 OrbitControls
进行交互。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Three.js 详细示例</title>
<style>
body { margin: 0; overflow: hidden; }
canvas { display: block; }
</style>
</head>
<body>
<!-- Three.js 和相关库将通过 CDN 引入 -->
<!-- 在实际项目中,你可能会使用 npm 和模块打包工具 -->
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/three@0.164.1/build/three.module.js",
"three/addons/": "https://unpkg.com/three@0.164.1/examples/jsm/"
}
}
</script>
<script type="module">
// 导入 Three.js 核心库
import * as THREE from 'three';
// 导入 OrbitControls 用于相机交互
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
// 导入 GLTFLoader 用于加载 GLTF 模型
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
// 导入 RGBELoader 用于加载 HDR 环境贴图 (可选,但能提升 PBR 材质效果)
import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
// 导入 dat.GUI 用于创建简单的 UI 控制面板 (可选)
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
// --- 全局变量 ---
let scene, camera, renderer;
let controls;
let cube, sphere, plane;
let pointLight, spotLight, directionalLight;
let textureLoader, gltfLoader, rgbeLoader;
let mixer; // 用于模型动画
const clock = new THREE.Clock(); // 用于动画和时间相关的更新
let gui; // dat.GUI 实例
// --- 初始化函数 ---
function init() {
// 1. 创建场景
scene = new THREE.Scene();
scene.background = new THREE.Color(0x87ceeb); // 天蓝色背景
// scene.fog = new THREE.Fog(0x87ceeb, 10, 100); // 添加雾效 (可选)
// 2. 创建相机
// PerspectiveCamera(fov, aspect, near, far)
// fov: 视野角度
// aspect: 宽高比 (通常是渲染区域的宽高比)
// near: 近裁剪面
// far: 远裁剪面
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(5, 8, 15); // 设置相机位置
camera.lookAt(scene.position); // 相机看向场景原点
// 3. 创建渲染器
renderer = new THREE.WebGLRenderer({ antialias: true }); //开启抗锯齿
renderer.setSize(window.innerWidth, window.innerHeight); // 设置渲染尺寸
renderer.setPixelRatio(window.devicePixelRatio); // 设置设备像素比,以获得更清晰的图像
renderer.shadowMap.enabled = true; // 开启阴影渲染
renderer.shadowMap.type = THREE.PCFSoftShadowMap; // 柔和阴影
// renderer.toneMapping = THREE.ACESFilmicToneMapping; // 色调映射,用于 HDR (可选)
// renderer.toneMappingExposure = 1.0; // 色调映射曝光度 (可选)
document.body.appendChild(renderer.domElement); // 将渲染器的 canvas 元素添加到 DOM 中
// 4. 创建控制器 (OrbitControls)
controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true; // 启用阻尼效果,使交互更平滑
controls.dampingFactor = 0.05;
// controls.autoRotate = true; // 自动旋转 (可选)
controls.minDistance = 5; // 相机最小距离
controls.maxDistance = 100; // 相机最大距离
// controls.maxPolarAngle = Math.PI / 2 - 0.1; // 限制相机垂直旋转角度,防止看到地面以下
// 5. 添加光源
// 环境光 (AmbientLight): 无特定方向,均匀照亮场景
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5); // 颜色, 强度
scene.add(ambientLight);
// 平行光 (DirectionalLight): 模拟太阳光
directionalLight = new THREE.DirectionalLight(0xffffff, 1.0);
directionalLight.position.set(10, 15, 5);
directionalLight.castShadow = true; // 光源产生阴影
// 设置阴影属性
directionalLight.shadow.mapSize.width = 2048;
directionalLight.shadow.mapSize.height = 2048;
directionalLight.shadow.camera.near = 0.5;
directionalLight.shadow.camera.far = 50;
directionalLight.shadow.camera.left = -15;
directionalLight.shadow.camera.right = 15;
directionalLight.shadow.camera.top = 15;
directionalLight.shadow.camera.bottom = -15;
scene.add(directionalLight);
// scene.add(new THREE.CameraHelper(directionalLight.shadow.camera)); // 可视化平行光阴影相机
// 点光源 (PointLight)
pointLight = new THREE.PointLight(0xffaa00, 2, 50, 1); // 颜色, 强度, 距离, 衰减
pointLight.position.set(-5, 5, 5);
pointLight.castShadow = true;
scene.add(pointLight);
// scene.add(new THREE.PointLightHelper(pointLight, 1)); // 可视化点光源
// 聚光灯 (SpotLight)
spotLight = new THREE.SpotLight(0x00ff00, 5, 100, Math.PI / 6, 0.2, 1); // 颜色, 强度, 距离, 角度, penumbra(半影衰减), 衰减
spotLight.position.set(8, 10, -5);
spotLight.target.position.set(0, 0, 0); // 聚光灯目标
spotLight.castShadow = true;
scene.add(spotLight);
scene.add(spotLight.target); // 需要将 target 也加入场景
// scene.add(new THREE.SpotLightHelper(spotLight)); // 可视化聚光灯
// 6. 创建物体 (几何体 + 材质 = 网格)
// 创建一个地面平面
const planeGeometry = new THREE.PlaneGeometry(50, 50);
const planeMaterial = new THREE.MeshStandardMaterial({
color: 0xcccccc,
side: THREE.DoubleSide, // 双面可见
roughness: 0.8,
metalness: 0.2
});
plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.rotation.x = -Math.PI / 2; // 旋转使其平铺在 xz 平面
plane.position.y = -0.5; // 向下移动一点,避免与物体底部重叠
plane.receiveShadow = true; // 平面接收阴影
scene.add(plane);
// 创建一个立方体
const cubeGeometry = new THREE.BoxGeometry(2, 2, 2); // 宽, 高, 深
const cubeMaterial = new THREE.MeshPhongMaterial({
color: 0xff0000, // 红色
shininess: 80, // 高光强度
// wireframe: true // 线框模式 (可选)
});
cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
cube.position.set(-3, 1.5, 0);
cube.castShadow = true; // 立方体投射阴影
cube.receiveShadow = true;
scene.add(cube);
// 创建一个球体
const sphereGeometry = new THREE.SphereGeometry(1.5, 32, 32); // 半径, 水平分段数, 垂直分段数
const sphereMaterial = new THREE.MeshStandardMaterial({
color: 0x0000ff, // 蓝色
roughness: 0.1, // 粗糙度
metalness: 0.9 // 金属度 (使其看起来像金属)
});
sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
sphere.position.set(3, 1.5, 2);
sphere.castShadow = true;
sphere.receiveShadow = true;
scene.add(sphere);
// 7. 加载纹理 (Texture)
textureLoader = new THREE.TextureLoader();
textureLoader.load(
'https://threejs.org/examples/textures/uv_grid_opengl.jpg', // 纹理图片 URL
function (texture) { // 加载成功回调
// 将纹理应用到立方体的材质上
cube.material.map = texture;
cube.material.needsUpdate = true; // 通知材质更新
console.log("Texture loaded successfully.");
},
undefined, // onProgress 回调 (可选)
function (err) { // 加载错误回调
console.error('An error happened during texture loading:', err);
}
);
// 8. 加载 GLTF 模型
gltfLoader = new GLTFLoader();
gltfLoader.load(
'https://threejs.org/examples/models/gltf/DamagedHelmet/glTF/DamagedHelmet.gltf', // GLTF 模型 URL
function (gltf) {
const model = gltf.scene;
model.scale.set(2, 2, 2); // 缩放模型
model.position.set(0, 1, -5);
// 遍历模型中的所有网格,使其能够投射和接收阴影
model.traverse(function (child) {
if (child.isMesh) {
child.castShadow = true;
child.receiveShadow = true;
}
});
scene.add(model);
console.log("GLTF model loaded successfully.");
// 如果模型有动画
if (gltf.animations && gltf.animations.length) {
mixer = new THREE.AnimationMixer(model);
const action = mixer.clipAction(gltf.animations[0]); // 播放第一个动画
action.play();
}
},
function (xhr) { // 加载进度回调
console.log((xhr.loaded / xhr.total * 100) + '% loaded');
},
function (error) { // 加载错误回调
console.error('An error happened during GLTF loading:', error);
}
);
// (可选) 加载 HDR 环境贴图 (用于 PBR 材质的环境光照和反射)
rgbeLoader = new RGBELoader();
rgbeLoader.load('https://threejs.org/examples/textures/equirectangular/royal_esplanade_1k.hdr', function (texture) {
texture.mapping = THREE.EquirectangularReflectionMapping;
// scene.background = texture; // 可以将 HDR 作为背景
scene.environment = texture; // 应用为环境贴图
console.log("HDR environment map loaded.");
});
// 9. 添加辅助对象 (Helpers)
const axesHelper = new THREE.AxesHelper(5); // 参数为坐标轴长度
scene.add(axesHelper);
const gridHelper = new THREE.GridHelper(50, 50); // 网格尺寸, 网格细分数
// gridHelper.position.y = -0.51; // 略低于地面,避免与地面重叠闪烁
scene.add(gridHelper);
// 10. 添加 GUI 控制 (dat.GUI)
gui = new GUI();
const cubeFolder = gui.addFolder('Cube');
cubeFolder.add(cube.position, 'x', -5, 5).name('Position X');
cubeFolder.add(cube.position, 'y', -5, 5).name('Position Y');
cubeFolder.add(cube.position, 'z', -5, 5).name('Position Z');
cubeFolder.add(cube.rotation, 'x', 0, Math.PI * 2).name('Rotation X');
cubeFolder.add(cube.material, 'wireframe').name('Wireframe');
cubeFolder.addColor(cube.material, 'color').name('Color');
// cubeFolder.open(); // 默认展开
const lightFolder = gui.addFolder('Lights');
lightFolder.add(directionalLight, 'intensity', 0, 2).name('Dir Intensity');
lightFolder.add(pointLight, 'intensity', 0, 10).name('Point Intensity');
lightFolder.add(spotLight, 'intensity', 0, 10).name('Spot Intensity');
lightFolder.add(spotLight, 'angle', 0, Math.PI / 2).name('Spot Angle');
lightFolder.add(spotLight, 'penumbra', 0, 1).name('Spot Penumbra');
// 11. 监听窗口大小变化事件,实现响应式
window.addEventListener('resize', onWindowResize, false);
}
// --- 窗口大小调整函数 ---
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight; // 更新相机宽高比
camera.updateProjectionMatrix(); // 更新相机投影矩阵
renderer.setSize(window.innerWidth, window.innerHeight); // 更新渲染器尺寸
}
// --- 动画循环函数 ---
function animate() {
requestAnimationFrame(animate); // 请求下一帧动画
const delta = clock.getDelta(); // 获取自上一帧以来的时间差
// 更新物体动画 (示例:让立方体和球体旋转)
if (cube) {
cube.rotation.x += 0.01;
cube.rotation.y += 0.005;
}
if (sphere) {
sphere.rotation.y -= 0.008;
// 让球体上下浮动
sphere.position.y = 1.5 + Math.sin(clock.getElapsedTime() * 2) * 0.5;
}
// 更新动画混合器 (如果加载的模型有动画)
if (mixer) {
mixer.update(delta);
}
// 更新控制器 (如果启用了阻尼或自动旋转)
controls.update();
// 渲染场景
renderer.render(scene, camera);
}
// --- 启动 ---
init();
animate();
</script>
</body>
</html>
代码讲解:
-
HTML 结构:
- 基本的 HTML5 骨架。
<style>
用于移除 body 的默认 margin 和隐藏滚动条,确保 canvas 占满视口。<script type="importmap">
: 这是现代浏览器支持的一种方式,用于简化 ES6 模块的导入路径。我们为three
核心库和three/addons/
(包含控制器、加载器等)定义了别名。这样就不需要在每个 import 语句中写完整的 URL。<script type="module">
: 我们的 JavaScript 代码将作为 ES6 模块执行。
-
导入模块:
import * as THREE from 'three';
: 导入 Three.js 核心库的所有导出,并将其放入THREE
命名空间。import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
: 从addons
中导入轨道控制器。import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
: 导入 GLTF 模型加载器。import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
: 导入 HDR 环境贴图加载器。import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
: 导入 lil-gui (一个轻量级的 GUI 库,dat.GUI 的替代品)。
-
全局变量:
scene, camera, renderer
: Three.js 的三大核心组件。controls
:OrbitControls
实例。cube, sphere, plane
: 我们创建的几何物体。pointLight, spotLight, directionalLight
: 光源实例。textureLoader, gltfLoader, rgbeLoader
: 加载器实例。mixer
: 用于 GLTF 模型动画的动画混合器。clock
:THREE.Clock
实例,用于获取时间增量,使动画独立于帧率。gui
:GUI
实例。
-
init()
函数 (初始化所有内容) :-
scene = new THREE.Scene();
: 创建场景。scene.background
: 设置场景背景色。scene.fog
: (可选) 添加雾效,参数为雾的颜色、近处开始距离、远处完全遮挡距离。
-
camera = new THREE.PerspectiveCamera(...)
: 创建透视相机。- 参数:
fov
(视野角度),aspect
(宽高比),near
(近裁剪面),far
(远裁剪面)。 camera.position.set(x, y, z)
: 设置相机在世界坐标系中的位置。camera.lookAt(scene.position)
: 让相机朝向场景的原点 (0,0,0)。
- 参数:
-
renderer = new THREE.WebGLRenderer({ antialias: true });
: 创建 WebGL 渲染器。antialias: true
: 开启抗锯齿,使边缘更平滑。renderer.setSize()
: 设置渲染输出的 canvas 尺寸。renderer.setPixelRatio()
: 适配高 DPI 屏幕。renderer.shadowMap.enabled = true;
: 启用阴影贴图。renderer.shadowMap.type = THREE.PCFSoftShadowMap;
: 设置阴影类型为柔和阴影。document.body.appendChild(renderer.domElement);
: 将渲染器生成的<canvas>
元素添加到 HTML body 中。
-
controls = new OrbitControls(camera, renderer.domElement);
: 创建轨道控制器。- 参数:要控制的
camera
和监听事件的DOM element
。 controls.enableDamping = true;
: 启用阻尼,使拖拽停止后有惯性效果。- 其他属性如
minDistance
,maxDistance
用于限制缩放范围。
- 参数:要控制的
-
光源 (Lights) :
-
AmbientLight
: 提供基础的环境光照,使场景不至于全黑。 -
DirectionalLight
: 模拟平行光(如太阳光)。directionalLight.position.set()
: 设置光源位置(对于平行光,位置主要决定方向)。directionalLight.castShadow = true;
: 使此光源能投射阴影。directionalLight.shadow.mapSize
: 设置阴影贴图的分辨率(越高阴影越清晰,但性能开销越大)。directionalLight.shadow.camera
: 平行光的阴影是通过一个正交相机来计算的,这里设置该阴影相机的视锥体范围。
-
PointLight
: 从一点向四周发光。- 参数:颜色, 强度, 距离 (光线能照射到的最大距离,0 表示无限远), 衰减 (物理上通常是 2)。
pointLight.castShadow = true;
-
SpotLight
: 聚光灯。- 参数:颜色, 强度, 距离,
angle
(光锥角度),penumbra
(光锥边缘的半影衰减程度, 0-1),decay
(衰减)。 spotLight.target.position.set()
: 设置聚光灯照射的目标点。spotLight.target
也需要加入场景。spotLight.castShadow = true;
- 参数:颜色, 强度, 距离,
-
-
物体 (Objects) :
-
地面 (Plane) :
-
new THREE.PlaneGeometry(width, height)
: 创建平面几何体。 -
new THREE.MeshStandardMaterial(...)
: 创建标准 PBR 材质。color
: 材质颜色。side: THREE.DoubleSide
: 使平面两面都可见(默认只渲染正面)。roughness
,metalness
: PBR 材质的关键参数。
-
plane = new THREE.Mesh(planeGeometry, planeMaterial)
: 合并几何体和材质创建网格。 -
plane.rotation.x = -Math.PI / 2;
: 将平面旋转90度,使其水平。 -
plane.position.y = -0.5;
: 调整位置。 -
plane.receiveShadow = true;
: 使平面能接收其他物体投射的阴影。
-
-
立方体 (Cube) :
-
new THREE.BoxGeometry(width, height, depth)
: 创建立方体几何体。 -
new THREE.MeshPhongMaterial(...)
: 创建 Phong 材质,能表现高光。shininess
: 高光反射强度。
-
cube.castShadow = true;
: 使立方体能投射阴影。 -
cube.receiveShadow = true;
: (可选) 使立方体也能接收阴影。
-
-
球体 (Sphere) :
new THREE.SphereGeometry(radius, widthSegments, heightSegments)
: 创建球体几何体。分段数越多,球体越平滑。new THREE.MeshStandardMaterial(...)
: 再次使用 PBR 材质,调整roughness
和metalness
可以得到不同质感。sphere.castShadow = true;
-
-
加载纹理 (TextureLoader) :
-
textureLoader = new THREE.TextureLoader();
-
textureLoader.load(url, onLoad, onProgress, onError)
: 异步加载纹理。onLoad
回调中,将加载的texture
赋值给cube.material.map
。cube.material.needsUpdate = true;
: 通知 Three.js 材质已更新,需要重新编译着色器或更新 uniform。
-
-
加载 GLTF 模型 (GLTFLoader) :
-
gltfLoader = new GLTFLoader();
-
gltfLoader.load(url, onLoad, onProgress, onError)
: 异步加载 GLTF 模型。-
onLoad
回调中,gltf.scene
是加载到的模型根节点 (一个THREE.Group
或THREE.Object3D
)。 -
model.scale.set()
: 调整模型大小。 -
model.position.set()
: 调整模型位置。 -
model.traverse(...)
: 遍历模型的所有子节点。如果子节点是Mesh
(child.isMesh
),则设置其castShadow
和receiveShadow
属性。 -
scene.add(model);
: 将模型添加到场景。 -
处理动画: 如果
gltf.animations
数组不为空,说明模型带有动画。mixer = new THREE.AnimationMixer(model);
: 为该模型创建一个动画混合器。mixer.clipAction(gltf.animations)
: 获取第一个动画剪辑的控制器 (AnimationAction
)。action.play()
: 播放动画。
-
-
-
加载 HDR 环境贴图 (RGBELoader) : (可选,但对 PBR 材质效果提升巨大)
rgbeLoader = new RGBELoader();
- 加载
.hdr
格式的图片。HDR 图片能提供更宽广的动态范围光照信息。 texture.mapping = THREE.EquirectangularReflectionMapping;
: 告诉 Three.js 这是一张全景反射贴图。scene.environment = texture;
: 将此 HDR 纹理用作场景的环境贴图。PBR 材质会使用它来进行基于图像的光照 (IBL),从而产生真实的反射和环境光。
-
辅助对象 (Helpers) :
AxesHelper
: 在场景原点显示红(X)、绿(Y)、蓝(Z)三色坐标轴,方便调试。GridHelper
: 在 XZ 平面显示一个网格,方便感知空间和物体位置。
-
GUI 控制 (lil-gui) :
-
gui = new GUI();
: 创建 GUI 实例。 -
gui.addFolder(name)
: 创建一个可折叠的文件夹。 -
folder.add(object, property, min, max, step).name(displayName)
: 添加一个控制器。object
: 要控制的对象。property
: 要控制的属性名 (字符串)。min, max, step
: (可选) 对于数值型属性,定义范围和步长。.name()
: 设置在 GUI 中显示的名称。
-
folder.addColor(object, property).name(displayName)
: 添加颜色选择器。
-
-
窗口大小调整监听:
window.addEventListener('resize', onWindowResize, false);
: 当浏览器窗口大小改变时,调用onWindowResize
函数。
-
-
onWindowResize()
函数:- 当窗口大小改变时,需要相应地更新相机的宽高比和渲染器的尺寸,以避免场景变形或显示不全。
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
: 非常重要! 更改相机参数(如aspect
,fov
,near
,far
)后,必须调用此方法来重新计算相机的投影矩阵。renderer.setSize(window.innerWidth, window.innerHeight);
-
animate()
函数 (渲染循环) :-
requestAnimationFrame(animate);
: 这是创建平滑动画循环的标准方法。浏览器会尝试以每秒 60 帧 (FPS) 的速率调用animate
函数。 -
const delta = clock.getDelta();
: 获取自上次调用clock.getDelta()
以来经过的时间(秒)。这对于创建与帧率无关的动画非常重要。 -
更新物体:
cube.rotation.x += 0.01;
: 简单地让立方体沿 X 轴旋转。sphere.position.y = 1.5 + Math.sin(clock.getElapsedTime() * 2) * 0.5;
: 使用clock.getElapsedTime()
(获取自时钟创建以来总共经过的时间) 和Math.sin()
来让球体上下平滑浮动。
-
更新动画混合器:
if (mixer) { mixer.update(delta); }
: 如果存在mixer
(即模型有动画),则调用其update
方法并传入时间增量delta
,以驱动模型动画的播放。
-
更新控制器:
controls.update();
: 如果OrbitControls
的enableDamping
设置为true
,则必须在动画循环中调用controls.update()
来应用阻尼效果。
-
渲染:
renderer.render(scene, camera);
: 最关键的一步,命令渲染器使用指定的相机来渲染指定的场景。
-
-
启动:
init();
: 调用初始化函数,设置好所有东西。animate();
: 启动动画循环。
这个示例覆盖了 Three.js 的许多基本但重要的方面。通过修改其中的参数、尝试不同的几何体、材质、光源和模型,你可以进一步探索 Three.js 的强大功能。记住,官方文档和示例是学习 Three.js 的最佳资源。