Skip to content

基础入门

本地环境配置

1. html 文件引入

html
<script src="./build/three.js"></script>

2. type="importmap"配置路径

通过配置<script type="importmap">,让学习环境.html 文件,也能和 vue 或 react 开发环境中一样方式引入 threejs 扩展库

html
<script type="importmap">
  {
    "imports": {
      "three": "./three.js/build/three.module.js",
      "three/addons/": "./three.js/examples/jsm/"
    }
  }
</script>

3. ES6 import 方式引入

html
<!-- 配置type="importmap",.html文件也能和项目开发环境一样方式引入threejs -->
<script type="module">
  import * as THREE from "three";
  // 扩展库OrbitControls.js
  import { OrbitControls } from "three/addons/controls/OrbitControls.js";
  // 扩展库GLTFLoader.js
  import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
</script>

创建 3d 案例

创建3d场景

1. 场景Scene

  1. 创建 3d 场景

    js
    // 创建3D场景对象Scene
    const scene = new THREE.Scene();
  2. 创建一个几何体

    • 几何体Geometry: 表示三维物体的几何形状 , 包括长方体、圆柱体、球体等

      js
      //创建一个长方体几何对象Geometry
      const geometry = new THREE.BoxGeometry(100, 100, 100);
    • 材质Material: 定义几何体的外观效果

      js
      //创建一个材质对象Material
      const material = new THREE.MeshBasicMaterial({
        color: 0xff0000, //0xff0000设置材质颜色为红色
      });
    • 网格模型Mesh: 定义几何体

      js
      // 两个参数分别为几何体geometry、材质material
      const mesh = new THREE.Mesh(geometry, material); //网格模型对象Mesh
  3. 模型位置.position(定义模型在三维场景中所处的位置)

    js
    const mesh = new THREE.Mesh(geometry, material); //网格模型对象Mesh
    //设置网格模型在三维空间中的位置坐标,默认是坐标原点
    mesh.position.set(0, 10, 0);
  4. 方法.add()(将模型添加到三维场景中)

    js
    scene.add(mesh);

2. 相机Camera

  1. 创建透视投影相机

    js
    // 实例化一个透视投影相机对象, 本质模拟人眼观察
    const camera = new THREE.PerspectiveCamera();
  2. 相机位置

    js
    //相机在Three.js三维坐标系中的位置
    // 根据需要设置相机位置具体值
    camera.position.set(200, 200, 200);
  3. 相机观察目标

    js
    //相机观察目标指向Threejs 3D空间中某个位置
    camera.lookAt(0, 0, 0); //坐标原点
    camera.lookAt(mesh.position); //指向mesh对应的位置
  4. 定义相机渲染输出的画布尺寸 视椎体

    • Canvas 画布: 虚拟相机渲染三维场景在浏览器网页上呈现的结果称为 Canvas 画布

    • 视椎体: 视锥体之内的物体,才会渲染出来,视锥体范围之外的物体不会显示在 Canvas 画布上

      js
      // width和height用来设置Three.js输出的Canvas画布尺寸(像素px)
      const width = 800; //宽度
      const height = 500; //高度
      // 30:视场角度, width / height:Canvas画布宽高比, 1:近裁截面, 3000:远裁截面
      const camera = new THREE.PerspectiveCamera(30, width / height, 1, 3000);

3. 渲染器Renderer

  1. 创建WebGL渲染器对象

    js
    const renderer = new THREE.WebGLRenderer();
  2. 设置 Canvas 画布尺寸

    js
    // 定义threejs输出画布的尺寸(单位:像素px)
    const width = 800; //宽度
    const height = 500; //高度
    renderer.setSize(width, height); //设置three.js渲染区域的尺寸(像素px)
  3. 生成渲染

    js
    renderer.render(scene, camera); //执行渲染操作
  4. 获得画布并插入 html 页面.domElement

    html
    <div id="webgl" style="margin-top: 200px;margin-left: 100px;"></div>
    js
    document.getElementById("webgl").appendChild(renderer.domElement);

三维坐标系

辅助观察坐标系

js
// AxesHelper:辅助观察的坐标系
const axesHelper = new THREE.AxesHelper(150);
scene.add(axesHelper);

材质透明度

设置材质半透明,这样可以看到坐标系的坐标原点。

js
const material = new THREE.MeshBasicMaterial({
  color: 0x0000ff, //设置材质颜色
  transparent: true, //开启透明
  opacity: 0.5, //设置透明度
});

AxesHelper的 xyz 轴

three.js 坐标轴颜色红 R、绿 G、蓝 B 分别对应坐标系的 x、y、z 轴,对于 three.js 的 3D 坐标系默认 y 轴朝上

相机控件 OrbitControls

平时开发调试代码,或者展示模型的时候,可以通过相机控件 OrbitControls 实现旋转缩放预览效果。

1.引入扩展库 OrbitControls.js

js
// 引入轨道控制器扩展库OrbitControls.js
import { OrbitControls } from "three/addons/controls/OrbitControls.js";

2. 使用 OrbitControls

js
// 设置相机控件轨道控制器OrbitControls
const controls = new OrbitControls(camera, renderer.domElement);
// 如果OrbitControls改变了相机参数,重新调用渲染器渲染三维场景
controls.addEventListener("change", function () {
  renderer.render(scene, camera); //执行渲染操作
}); //监听鼠标、键盘事件

本质上就是改变相机的参数,比如相机的位置属性,改变相机位置也可以改变相机拍照场景中模型的角度,实现模型的 360 度旋转预览效果,改变透视投影相机距离模型的距离,就可以改变相机能看到的视野范围

光源对物体表面影响

模拟光照 Light 对网格模型 Mesh 表面的影响

提示

threejs 提供的网格材质,有的受光照影响,有的不受光照影响:

  • 基础网格材质 MeshBasicMaterial 不会受到光照影响。
  • 漫反射网格材质 MeshLambertMaterial等其它材质会受到光照影响

光源

视椎体 创建一个点光源:

js
// 参数1:0xffffff是纯白光,表示光源颜色
// 参数2:1.0,表示光照强度,可以根据需要调整
const pointLight = new THREE.PointLight(0xffffff, 1.0);
//光源位置
pointLight.position.set(400, 0, 0); //点光源放在x轴上
// 添加
scene.add(pointLight); //点光源添加到场景中

光源辅助观察

生成一个光源来源的辅助观察对象, 可以通过相机控件旋转缩放三维场景预览

js
// 光源辅助观察: PointLightHelper(点光源), DirectionalLightHelper(平行光)...
const pointLightHelper = new THREE.PointLightHelper(pointLight, 10);
scene.add(pointLightHelper);

环境光

环境光没有特定方向,整体改变场景的光照明暗

js
const ambient = new THREE.AmbientLight(0xffffff, 0.4);
scene.add(ambient);

平行光

沿着特定方向发射的光源

js
// 平行光
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
// 设置光源的方向:通过光源position属性和目标指向对象的position属性计算
directionalLight.position.set(80, 100, 50);
// 方向光指向对象网格模型mesh,可以不设置,默认的位置是0,0,0
directionalLight.target = mesh;
scene.add(directionalLight);

动画循环渲染

借助 HTML5 的 API 请求动画帧window.requestAnimationFrame实现动画渲染

js
const renderer = new THREE.WebGLRenderer();
renderer.setSize(width, height);
// renderer.render(scene, camera); //执行渲染操作
document.body.appendChild(renderer.domElement);

// 渲染函数
function render() {
  renderer.render(scene, camera); //执行渲染操作
  mesh.rotateY(0.01); //每次绕y轴旋转0.01弧度
  requestAnimationFrame(render); //请求再次执行渲染函数render,渲染下一帧
}
render();

TIP

设置了渲染循环, 相机控件OrbitControls就不用再通过事件change执行renderer.render(scene, camera)

Canvas 画布布局和渲染帧率

全屏渲染

js
// width和height用来设置Three.js输出的Canvas画布尺寸(像素px)
const width = window.innerWidth; //窗口文档显示区的宽度作为画布宽度
const height = window.innerHeight; //窗口文档显示区的高度作为画布高度
const renderer = new THREE.WebGLRenderer();
document.body.appendChild(renderer.domElement);
html
<style>
  body {
    overflow: hidden;
    margin: 0px;
  }
</style>

canvas 画布宽高度动态变化

当缩放浏览器窗口时, 实时更新渲染保证显示正常

js
// onresize 事件会在窗口被调整大小时发生
window.onresize = function () {
  // 重置渲染器输出画布canvas尺寸
  renderer.setSize(window.innerWidth, window.innerHeight);
  // 全屏情况下:设置观察范围长宽比aspect为窗口宽高比
  camera.aspect = window.innerWidth / window.innerHeight;
  // 渲染器执行render方法的时候会读取相机对象的投影矩阵属性projectionMatrix
  // 但是不会每渲染一帧,就通过相机的属性计算投影矩阵(节约计算资源)
  // 如果相机的一些属性发生了变化,需要执行updateProjectionMatrix ()方法更新相机的投影矩阵
  camera.updateProjectionMatrix();
};

查看渲染帧率 Stats

在浏览器上显示渲染的 fps, 场景越复杂渲染性能越低

  1. 引入性能监视器 Stats

    js
    import Stats from "three/addons/libs/stats.module.js";
  2. 使用 Stats

    js
    //创建stats对象
    const stats = new Stats();
    //stats.domElement:web页面上输出计算结果,一个div元素,
    document.body.appendChild(stats.domElement);
    // 渲染函数
    function render() {
      //requestAnimationFrame循环调用的函数中调用方法update(),来刷新时间
      stats.update();
      renderer.render(scene, camera); //执行渲染操作
      requestAnimationFrame(render); //请求再次执行渲染函数render,渲染下一帧
    }
    render();
  3. stats 方法 setMode(mode)
    设置首次打开页面,测试结果的显示模式,鼠标单击可以更换不同的显示模式。

    js
    // stats.domElement显示:渲染帧率  刷新频率,一秒渲染次数
    stats.setMode(0); //默认模式
    //stats.domElement显示:渲染周期 渲染一帧多长时间(单位:毫秒ms)
    stats.setMode(1);

常见几何体

js
//BoxGeometry:长方体
const geometry = new THREE.BoxGeometry(100, 100, 100);
// SphereGeometry:球体
const geometry = new THREE.SphereGeometry(50);
// CylinderGeometry:圆柱
const geometry = new THREE.CylinderGeometry(50, 50, 100);
// PlaneGeometry:矩形平面
const geometry = new THREE.PlaneGeometry(100, 50);
// CircleGeometry:圆形平面
const geometry = new THREE.CircleGeometry(50);

双面可见

Three.js 的材质默认正面可见,反面不可见; 平面几何体只能看到一面

js
new THREE.MeshBasicMaterial({
  //两面可见, 默认FrontSide
  side: THREE.DoubleSide,
});

高光网络材质 Phong

MeshPhongMaterial 提供一个镜面反射效果, 可以实现 MeshLambertMaterial 不能实现的高光反射效果

高光亮度属性.shininess

js
// 模拟镜面反射,产生一个高光效果
const material = new THREE.MeshPhongMaterial({
  color: 0xff0000,
  shininess: 20, //高光部分的亮度,默认30
});

高光颜色属性.specular

js
const material = new THREE.MeshPhongMaterial({
  color: 0xff0000,
  shininess: 20, //高光部分的亮度,默认30
  specular: 0x444444, //高光部分的颜色
});

WebGL 渲染器设置(锯齿模糊)

渲染器锯齿属性.antialias

js
const renderer = new THREE.WebGLRenderer({
  antialias: true,
});

或者

js
renderer.antialias = true;

获取设备像素比window.devicePixelRatio

js
// 不同硬件设备的屏幕的设备像素比window.devicePixelRatio值可能不同
console.log("查看当前屏幕设备像素比", window.devicePixelRatio);

设置设备像素比.setPixelRatio()

js
// 获取你屏幕对应的设备像素比.devicePixelRatio告诉threejs,以免渲染模糊问题
renderer.setPixelRatio(window.devicePixelRatio);

设置背景颜色.setClearColor()

js
renderer.setClearColor(0x444444, 1); //设置背景颜色

gui.js 库(可视化改变三维场景)

借助 dat.gui.js 可以快速创建控制三维场景的 UI 交互界面

1. 创建一个 GUI 对象

js
import { GUI } from "three/addons/libs/lil-gui.module.min.js";
// 实例化一个gui对象
const gui = new GUI();

2. 改变 GUI 界面默认的 style 属性

js
//改变交互界面style属性
gui.domElement.style.right = "0px";
gui.domElement.style.width = "300px";

3. 改变属性值

.add()方法: 可以快速创建一个 UI 交互界面,比如一个拖动条; 用来改变一个 JavaScript 对象属性的属性值。

格式:.add(控制对象,对象具体属性,其他参数)

js
// 添加模型位置控制, 参数3和参数4定义一个区间范围
gui.add(mesh.position, "x", 0, 180);
gui.add(mesh.position, "y", 0, 180);
gui.add(mesh.position, "z", 0, 180);

gui 调试-颜色命名等

命名.name

.add()创建的交互界面,会默认显示所改变属性的名字。通过.name可以自定义显示名称

js
const gui = new GUI(); //创建GUI对象
gui.add(ambient, "intensity", 0, 2.0).name("环境光强度");
gui.add(directionalLight, "intensity", 0, 2.0).name("平行光强度");

步长.step()

js
gui.add(ambient, "intensity", 0, 2.0).name("环境光强度").step(0.1);

方法.onChange()

js
const obj = { x: 30 };
// 当obj的x属性变化的时候,就把此时obj.x的值value赋值给mesh的x坐标
gui.add(obj, "x", 0, 180).onChange(function (value) {
  mesh.position.x = value;
  // 可以写任何想跟着obj.x同步变化的事件
});

颜色值.addColor()

生成颜色值改变的交互界面

js
const obj = {
  color: 0x00ffff,
};
gui.addColor(obj, "color").onChange(function (value) {
  mesh.material.color.set(value);
});

gui 调试-下拉菜单、单选框

下拉菜单

  1. 传入数组

    js
    const obj = { scale: 0 };
    // 参数3数据类型:数组(下拉菜单)
    gui
      .add(obj, "scale", [-100, 0, 100])
      .name("y坐标")
      .onChange(function (value) {
        mesh.position.y = value;
      });
  2. 传入对象

    js
    const obj = { scale: 0 };
    // 参数3数据类型:对象(下拉菜单)
    gui
      .add(obj, "scale", {
        左: -100,
        中: 0,
        右: 100,
      })
      .name("位置选择")
      .onChange(function (value) {
        mesh.position.x = value;
      });

单选框

js
const obj = {
  bool: false,
};
// 改变的obj属性数据类型是布尔值,交互界面是单选框
gui.add(obj, "bool").name("是否旋转");

控制一个对象是否旋转:

js
gui.add(obj, "bool").name("旋转动画");

// 渲染循环
function render() {
  // 当gui界面设置obj.bool为true,mesh执行旋转动画
  if (obj.bool) mesh.rotateY(0.01);
  renderer.render(scene, camera);
  requestAnimationFrame(render);
}
render();

gui.js 库(分组)

当 GUI 交互界面需要控制的属性比较多的时候,为了避免混合,适当分组管理可以清晰

创建子菜单分组.addFolder()

  1. 创建材质子菜单

    js
    const gui = new GUI(); //创建GUI对象
    const obj = { color: 0x00ffff };
    
    const matFolder = gui.addFolder("材质");
    // 材质颜色color
    matFolder.addColor(obj, "color").onChange((value) => {
      material.color.set(value);
    });
    // 材质高光颜色specular
    matFolder.addColor(obj, "specular").onChange((value) => {
      material.specular.set(value);
    });
  2. 创建平行光子菜单

    js
    const dirFolder = gui.addFolder("平行光");
    dirFolder.add(directionalLight, "intensity", 0, 2);
    // 平行光位置
    dirFolder.add(directionalLight.position, "x", -400, 400);
    dirFolder.add(directionalLight.position, "y", -400, 400);
    dirFolder.add(directionalLight.position, "z", -400, 400);

关闭.close()和展开.open()交互界面

js
const gui = new GUI(); //创建GUI对象
gui.close(); //关闭菜单

// 创建材质子菜单
const matFolder = gui.addFolder("材质");
matFolder.close(); //关闭菜单

子菜单嵌套子菜单

js
// 平行光子菜单
const dirFolder = gui.addFolder("平行光");
// 平行光强度
dirFolder.add(directionalLight, "intensity", 0, 2);
const dirFolder2 = dirFolder.addFolder("位置"); //子菜单的子菜单
// 平行光位置
dirFolder2.add(directionalLight.position, "x", -400, 400);
dirFolder2.add(directionalLight.position, "y", -400, 400);
dirFolder2.add(directionalLight.position, "z", -400, 400);