从摄影新手到三维光影师:Three.js 核心要素的故事

当我第一次学习摄影时,老师告诉我一句话:

"你不是在拍东西,而是在拍光。"

后来我学习 Three.js 时突然意识到:

这句话原来依旧成立。

Three.js 不只是一个 3D 引擎,更像是一台虚拟相机。要拍好这张"虚拟的照片",我们必须掌握三个核心要素:

场景(Scene)

相机(Camera)

灯光与材质(Light & Material)

于是,我把学习过程想象成一个摄影新手成长为三维光影师的故事。

空无一物的影棚 ------ Scene 场景

故事从一个空影棚开始。

当我第一次打开 Three.js 时,教程告诉我:

js 复制代码
const scene = new THREE.Scene();

这就像摄影师走进了一个空旷的工作室:

没有布景、没有模特、没有灯光,甚至连相机都还没架好, 在影棚这个场景中,摄影师可以在这个场景中放任何的东西:

  • 架好摄像机(Camera 📹)
  • 拍照的物体(Mesh 网格物体)、物体拥有着自己的形状(Geometry几何体)和材质(Material)
  • 摆设好灯光(Light)
  • 也可以是任意的对象 (Object3D)

摄影师往 Scene 里布置道具,而程序员的你往 Scene 里添加各种对象,因此 场景就是一个可以放任何东西的容器

找到你要观看的角度 ------ Camera 相机

刚学摄影时,我最常做的事情,就是移动、蹲下、趴着、绕圈......

只为了找到一个"对的角度"。

Three.js 的相机就是你的眼睛。创建相机就像准备拍摄时拿起单反:

js 复制代码
const camera = new THREE.PerspectiveCamera(const camera = new THREE.PerspectiveCamera(
  50, // 相机视野角度,摄像机的视野角度越大,摄像机看到的场景就越大,反之越小
  window.innerWidth / window.innerHeight, // 宽高比
  0.1, // 近平面(近端渲染距离),指定从距离相机多近的位置开始渲染,推荐默认值0.1
  1000 // 远平面(远端渲染距离)指定摄像机从它所在的位置最远能看到多远,太小场景中的远处物体会看不见,太大会浪费资源影响性能,推荐默认值1000
);

// 2.1 设置相机的位置,放在不同的位置看到的风景当然不一样
camera.position.set(5, 10, 10); // x, y, z

camera.lookAt(0, 0, 0); // 设置相机方向(这就是你女朋友让你找最佳角度的原因)

摄影师会说:"我走两步,让模特在背景中更突出。"

程序员会说:

js 复制代码
camera.position.z = 3;
camera.lookAt(0, 0, 0)

本质完全一样:
都是在调整观察世界的方式。

让世界真正亮起来 ------ Light & Material 灯光与材质

你可以有再漂亮的模特、再好的相机,如果没有光------

一切都会变成漆黑一片。

Three.js 也是如此。你搭了一个完美的 3D 模型,如果没有光,它看起来只是纯黑。

于是我制作"虚拟布光":

js 复制代码
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(5, 10, 5);
scene.add(light);

摄影师打灯,而我在代码里放置光源:

  • DirectionalLight(平行光)= 太阳光
  • PointLight(点光源)= 想象灯泡发光,由点向八方发射
  • SpotLight(聚光灯)= 舞台灯,从上打下来,呈现圆锥体,它离光越远,它的尺寸就越大。这种光源会产生阴影
  • AmbientLight(环境光)= 影棚柔光,环境光没有特定的来源方向,不会产生阴影

同时材质(Material)也等同于现实世界的"被光击中时的反应":

  • 皮肤 = standard material
  • 金属 = metalness 高
  • 塑料 = roughness 较高
  • 玻璃 = transparent=True + envMap

想要一个皮肤质感的物体?

那么你就得给材质加入 roughness、metalness、normalMap 就像摄影师在打柔光,为人物皮肤创造质感。

光与材质的搭配,就是 Three.js 里的"布光艺术"。

最终章:按下快门 ------ Renderer 渲染器

当场景布好、相机调好、灯光到位后------

摄影师要做的就是按下快门。

在 Three.js 里:

js 复制代码
renderer.render(scene, camera);

渲染器就是那个"快门",

真正把世界投射到屏幕上。

摄影师用快门把现实世界的光记录下来;

Three.js 用 GPU 把虚拟世界的光影计算出来。

本质上,两者做的是同一件事:

把真实或虚拟的三维世界,投射成一张二维图像。

js 复制代码
import * as THREE from "three";

// 1. 创建场景
const scene = new THREE.Scene();

// 2. 创建相机(透视投影相机)
const camera = new THREE.PerspectiveCamera(
  50, // 相机视野角度,摄像机的视野角度越大,摄像机看到的场景就越大,反之越小
  window.innerWidth / window.innerHeight, // 宽高比
  0.1, // 近平面(近端渲染距离),指定从距离相机多近的位置开始渲染,推荐默认值0.1
  1000 // 远平面(远端渲染距离)指定摄像机从它所在的位置最远能看到多远,太小场景中的远处物体会看不见,太大会浪费资源影响性能,推荐默认值1000
);

// 2.1 设置相机的位置
camera.position.set(5, 10, 10); // x, y, z

camera.lookAt(0, 0, 0); // 设置相机方向(默认看向场景原点)

// 3. 创建渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true }); // 开启抗锯齿,使边缘更平滑
// 3.1 设置渲染器的大小
renderer.setSize(window.innerWidth, window.innerHeight);
// 3.2 将渲染器的canvas内容添加到body
document.body.appendChild(renderer.domElement);

// 4. 创建一个立方体几何体
const geometry = new THREE.BoxGeometry(4, 4, 4); // 宽、高、深

// 为了让光源有效果,我们使用 MeshLambertMaterial 或 MeshPhongMaterial
//  创建材质 MeshLambertMaterial (兰伯特材质) 是一种非光泽材质,会受光照影响,但没有镜面高光
const material = new THREE.MeshLambertMaterial({
  color: 0x00ff00, // 颜色
  // wireframe: true, // 如果需要线框效果可以加上
});

// 6. 创建一个网格模型(网格模型由几何体和材质组成)
// Mesh 构造函数通常只接受一个材质。如果需要多材质,Three.js 有专门的 MultiMaterial 或 Group 来处理
const cube = new THREE.Mesh(geometry, material); // 使用 MeshLambertMaterial

// 6.1 将几何模型添加到场景中
scene.add(cube);

// 6.2 设置相机看向物体(拍摄对象)的位置(默认状态下相机看向的是场景的原点(0,0,0))
camera.lookAt(cube.position);

// 7. 创建光源
const spotLight = new THREE.SpotLight(0xffffff); // 创建聚光灯,颜色为白色
// 7.1 设置光源的位置
spotLight.position.set(0, 20, 20); // 调整光源位置,使其能够照亮立方体
// 7.2 设置光源照射的强度,默认值为1, 越大越亮
spotLight.intensity = 2;
// 7.3 将光源添加到场景中
scene.add(spotLight);

// 8. 为了方便观察 3D 图像,添加三维坐标系对象
const axesHelper = new THREE.AxesHelper(6); // 参数表示坐标系的大小 (x轴红色, y轴绿色, z轴蓝色)
scene.add(axesHelper); // 将坐标系添加到场景中

// 9. 渲染函数
function animate() {
  requestAnimationFrame(animate); // 请求再次执行渲染函数animate,形成循环

  // 让立方体动起来
  cube.rotation.x += 0.01; // 沿x轴旋转
  cube.rotation.y += 0.01; // 沿y轴旋转
  cube.rotation.z += 0.01;

  renderer.render(scene, camera); // 使用渲染器,通过相机将场景渲染出来
}

animate(); // 执行渲染函数,进入无限循环,完成渲染
相关推荐
南囝coding2 小时前
2025年CSS新特性大盘点
前端·css
c***V3232 小时前
前端框架对比:10个主流框架优缺点分析
前端·前端框架
栀秋6662 小时前
当我把 proto 打印出来那一刻,我懂了JS的原型链
前端·javascript
Cassie燁2 小时前
element-plus源码解读1——useNamespace
前端·vue.js
一直在学习的小白~2 小时前
npm发布脚手架流程
前端·npm·node.js
ErMao2 小时前
TypeScript的泛型工具集合
前端·javascript
涔溪2 小时前
如何解决微前端架构中主应用和微应用的通信问题?
前端·架构
重铸码农荣光3 小时前
深入理解 JavaScript 原型链:从 Promise.all 到动态原型的实战探索
前端·javascript·promise
我叫黑大帅3 小时前
什么叫可迭代对象?为什么要用它?
前端·后端·python