Three.js 从0开始
一、认识
在three.js的世界中,想渲染一个3D场景,有3个必要因素:场景(scene)、摄像机(camera)、渲染器(renderer)
场景(scene)
代表一个三维空间,用于容纳和呈现所有物质,包括物体、灯光等。场景本身深邃无物,想要呈现3D物体,必须将物体加载到 scene 中,采用右手坐标系。一切物质、要素,及其运动变化,都均处于场景中,故相当于“世界”,所有炫目繁杂的三维效果,都初始于此。
摄像机(camera)
相当于人眼,有摄像机才可以看见场景里面的一切物体和光源,连接three.js世界的通道。这个世界很大,场景下的哪一部分被取景和展示,便由此决定。three.js 内置两种摄像机。
透视相机(PerspectiveCamera)
模拟人眼所看到的景象,是渲染3D场景中使用最普遍的投影模式,此模式下,能呈现近大远小的视觉效果(透视),很符合我们人眼看事物的感觉。需要模拟现实的场景,基本都是用此相机。
正交相机(OrthographicCamera)
是一个矩形可视区域,物体只有在这个区域内才是可见的,物体无论距离摄像机是远或近,物体都会被渲染成统一比例的尺寸大小。
渲染器(renderer)
把所有的内容渲染到页面上,场景(Scene)和摄像机(Camera)都需要通过渲染器的计算,才能在此三维世界中渲染成一个二维图片显示在画布(Canvas)上
二、使用
可以使用npm以及现代构建工具来安装 three.js ,也可以用静态主机或是 CDN 来快速上手。
安装
使用 npm 模块安装 three ,可运行
npm install three -S
并在业务中引用
import * as THREE from 'three'
const scene = new THREE.Scene();
若使用 CDN 安装 three ,由于 three.js 依赖于ES module,因此任何引用它的 script 标签必须使用type="module"
,如下:
<script type="module">
import * as THREE from 'https://unpkg.com/three@0.126.0/build/three.module.js';
import { OrbitControls } from 'https://unpkg.com/three@0.126.0/examples/jsm/controls/OrbitControls.js';
const scene = new THREE.Scene();
</script>
初始化
回顾前文,初始化3个基础元素
// 场景
const scene = new THREE.Scene();
// 摄像机
const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
// 渲染器
const renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
其中,创建摄像机使用的是PerspectiveCamera(透视摄像机)。
第一个参数是视野角度(FOV)。视野角度就是无论在什么时候,你所能在显示器上看到的场景的范围,它的单位是角度(与弧度区分开)。
第二个参数是长宽比(aspect ratio)。 也就是你用一个物体的宽除以它的高的值。需保证这个长宽比等同于挂载在 dom 中的 <canvas/>
的长宽比,否则会看到图像仿佛是被压扁的。
接下来的两个参数是近截面(near)和远截面(far),当物体某些部分比摄像机的远截面远或者比近截面近的时候,该这些部分将不会被渲染到场景中。
此外,渲染器需要设置一个渲染尺寸,使渲染器渲染出的场景填充满我们的应用程序。并将renderer(渲染器)的dom元素(renderer.domElement)添加到我们的HTML文档中,即 dom 中的 <canvas>
元素。
到这一步,完成基础搭建,由于尚未添加任何物体,此时页面空洞,呈现一片黑暗。
增加内容
在计算机里,3D世界是由点组成的,一条线由两个点组成,一个面是3个点组成,一个物体由多个3点组成的面组成。此即网格模型。
在three.js中,组成物体的网格模型(mesh)由 几何体(geometry)和材质(material)构成。
几何体(geometry)
框架中自带的物体的基础几何体,使用其构造函数,即可生成结构
材质(material)
构成物体的材料,决定了该物体的物理性质,镜面感、亮暗、颜色、透明、反光等
例如,创建一个球体:
const geometry = new THREE.SphereGeometry( 3, 32, 32 );
const material = new THREE.MeshBasicMaterial( { color: 0x215289 } );
const cube = new THREE.Mesh( geometry, material );
scene.add( cube );
此时,物体已经添加到场景中,但是摄像机的视角是重叠在物体中心位置的,无法观察到物体本身。
修改摄像机位置,并用渲染器进行渲染
camera.position.set(10, 10, 10);
renderer.render( scene, camera );
光源
光源能是物体趋近于更真实的视觉效果。three.js 中的常见光源有
环境光:环境光充满所有的几何体表面,不产生投影;
平行光:沿着特定方向发射的光,可用来模拟太阳光的效果,可以投射阴影;
点光源:从一个点向各个方向发射的光源,可模拟一个灯泡发出的光,可以投射阴影;
聚光灯:光线从一个点沿一个方向射出,随着光线照射的变远,光线圆锥体的尺寸也逐渐增大,可以投射阴影。
我们添加一个点光源:
const light = new THREE.PointLight( 0xffffff, 1, 100 );
light.position.set( 0, 5, 20 );
scene.add( light );
想要物体产生投影,还需要使用能产生投影的材料,将前面的 cube
材料进行调整
const geometry = new THREE.SphereGeometry( 3, 32, 32 );
const material = new THREE.MeshStandardMaterial( { color: 0x215289 } );
const cube = new THREE.Mesh( geometry, material );
scene.add( cube );
还需要设置光源支持产生阴影、物体产生阴影
light.castShadow = true;
cube.castShadow = true;
cube.receiveShadow = true;
控制器
接下来,我们还需要设置控制器,能响应鼠标操作,three.js 中附加了 OrbitControls 等控制组件,引入即可
// npm
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
// cdn
import { OrbitControls } from 'https://unpkg.com/three@0.126.0/examples/jsm/controls/OrbitControls.js';
// 使用
const controls = new OrbitControls(camera, renderer.domElement);
controls.update();
鼠标的操作,需要在下一帧体现出来,因此,要在 requestAnimationFrame
中执行每一帧的渲染
function animate() {
requestAnimationFrame( animate );
controls.update();
renderer.render( scene, camera );
}
animate();
此时,即可拖动、缩放场景。
代码如下:
<script type="module">
import * as THREE from 'https://unpkg.com/three@0.126.0/build/three.module.js';
import { OrbitControls } from 'https://unpkg.com/three@0.126.0/examples/jsm/controls/OrbitControls.js';
// 1. 场景
const scene = new THREE.Scene();
// 2. 相机
const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 1, 1000 );
camera.position.set(10, 10, 10);
// 3. 渲染器
const renderer = new THREE.WebGLRenderer({preserveDrawingBuffer: true});
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
// 4. 控制器
const controls = new OrbitControls(camera, renderer.domElement);
controls.update();
const light = new THREE.PointLight( 0xffffff, 1, 100 );
light.position.set( 0, 5, 20 );
scene.add( light );
const geometry = new THREE.SphereGeometry( 3, 32, 32 );
// const material = new THREE.MeshStandardMaterial( { color: 0x215289 } );
const texturePainting = new THREE.TextureLoader().load( 'https://img0.baidu.com/it/u=1424057713,469873542&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=500')
const material = new THREE.MeshStandardMaterial( { color: 0xffffff, map: texturePainting } );
const cube = new THREE.Mesh( geometry, material );
scene.add( cube );
light.castShadow = true;
cube.castShadow = true;
cube.receiveShadow = true;
function animate() {
requestAnimationFrame( animate );
controls.update();
renderer.render( scene, camera );
}
animate();
</script>
三、案例问题
模型点击选中
光线投射Raycaster
用于进行鼠标拾取(在三维空间中计算出鼠标移过了什么物体)。
原理:以点击的位置坐标为起点,向屏幕摄像机发射射线,检测射线是否与其他模型上的点相交,若相交则按照距离由近及远排列选中点,即可选取点击的该模型。
需要注意的是,在webGL中,世界坐标系是以屏幕中心为原点(0, 0, 0),且是始终不变的。面对屏幕时,右边是x正轴,上面是y正轴,屏幕向外的方向为z正轴。长度单位定义如下:窗口范围按此单位恰好是(-1,-1)到(1,1),即屏幕左下角坐标为(-1,-1),右上角坐标为(1,1)。因此,将鼠标坐标归一化为设备坐标时,x 和 y 方向的取值范围是 (-1,+1)。
参考:光线投射Raycaster。
代码如下:
onChoose (event) {
const ele = this.eleContainer // three.js 挂载容器
let Sx = event.clientX; // 鼠标单击位置横坐标
let Sy = event.clientY; // 鼠标单击位置纵坐标
let { left: offsetX, top: offsetY } = ele.getBoundingClientRect()
// 屏幕坐标转WebGL标准设备坐标
let x = ((Sx - offsetX) / ele.clientWidth) * 2 - 1; // WebGL标准设备横坐标
let y = -((Sy - offsetY ) / ele.clientHeight) * 2 + 1; // WebGL标准设备纵坐标
// 创建一个射线投射器`Raycaster`
let raycaster = new THREE.Raycaster();
// 通过鼠标单击位置标准设备坐标和相机参数计算射线投射器`Raycaster`的射线属性.ray
raycaster.setFromCamera(new THREE.Vector2(x, y), this.camera);
// 返回.intersectObjects()参数中射线选中的网格模型对象
// 未选中对象返回空数组[],选中一个数组1个元素,选中两个数组两个元素
let intersects = raycaster.intersectObjects([...this.meshList]);
// 交给其他方法处理后续
this.handleChoose(intersects?.[0]?.object)
}
按钮缩放模型
在使用OrbitControls
控制器时为实现自由控制缩放,我们尝试从此控制器上着手突破,在文档 轨道控制器(OrbitControls) 中查询相关api,并无开放接口可直接控制窗口缩放,但是可以在源码中看到控制缩放的方法dollyIn
。
dollyIn
是 THREE.OrbitControls
的私有函数,我们可以通过在 OrbitControls.js
中放入两个公共方法供外部使用,即可实现此需求。
this.dollyIn = function () {
dollyIn( getZoomScale() );
scope.update();
};
this.dollyOut = function () {
dollyOut( getZoomScale() );
scope.update();
};
在外部调用时,直接访问controls
实例的这两个方法即可:
zoomVal > 0 ? this.controls.dollyIn() : this.controls.dollyOut();
- end -
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异