跳至主要內容

Three.js基础入门

李雪雁大约 10 分钟

Three.js基础入门

前言

Three.js是一款基于原生WebGL封装通用Web 3D引擎

three.js中文网:three.js中文网open in new window

three.js官网:three.js官网open in new window

安装与引入

公司内部阿里云仓库已经发布了公司内部的three.js引擎:@cljs-earth/three,可直接安装使用


@cljs-earth/earth介绍,包括 @cljs-earth/threeopen in new window

// 安装three.js
npm install @cljs-earth/three --save;
// 引入three.js
import THREE from '@cljs-earth/three';

使用逻辑

获取容器-->添加渲染器Renderer-->添加场景Scene-->
添加相机Camera-->设置相机控制器-->添加元素-->
设置光源-->监听窗口变化-->动画渲染循环

通过three.js简单的创建一个几何体

1、获取容器

  • 若直接使用body作为画布
// 定义画布尺寸(单位:像素)
const width = document.getElementById("box").clientWidth;
const height = document.getElementById("box").clientHeight;
  • 若html中定义了id的div元素或canvas元素作为画布
var width = document.getElementById('id名').clientWidth;
var height = document.getElementById('id名').clientHeight;

2、添加渲染器Renderer

const renderer = new THREE.WebGLRenderer({
      antialias:true, //启动抗锯齿
});
// 设置设备像素比(一般都会设置),防止模糊
renderer.setPixelRatio(window.devicePixelRatio)
// 设置画布宽高
renderer.setSize(width, height)
  • 将渲染结果canvas画布,添加到网页页面上
    1. 若直接使用body作为画布
document.body.appendChild(renderer.domElement)
    1. 若选择在html添加一个id的div元素,然后动态添加canvas到div中:
document.getElementById("id名").appendChild(renderer.domElement);
    1. 若渲染器将与canvas元素进行绑定,如果html中定义了id的canvas元素
var renderer = new THREE.WebGLRenderer({
     canvas: document.getElementById('id名')
});

3、添加场景Scene

const scene = new THREE.Scene()

4、添加相机Camera

  • 相机有多种类型,详细信息请查阅官方文档:open in new window
  • 这里使用的是透视投影相机,特点是对物体近大远小。
  • 透视投影相机对象参数PerspectiveCamera(fov, aspect, near, far):
      1. fov--相机视椎体角度
      1. aspect--相机视椎体水平方向与竖直方向长度比一般设置为Canvas画布宽高比
      1. near--视锥体近端面距离
      1. far--视锥体近端面距离

注:透视投影相机,设置相机视椎体水平方向与竖直方向长度比一般设置为Canvas画布宽高比 ``` // 创建一个透视投影相机对象 const camera = new THREE.PerspectiveCamera(30, width / height, 0.1, 3000) // 设置相机位置 camera.position.set(200, 200, 200) ```

5、相机控制器

  • 相机控件OrbitControls,将相机围绕目标进行轨道运动,使其可以旋转(拖动鼠标左键)、缩放(滚动鼠标中键)、平移(拖动鼠标右键)目标。
    注:相机控制器OrbitControls会影响相机的lookAt设置,请手动设置OrbitControls的目标参数。
import { OrbitControls } from '@cljs-earth/three/examples/jsm/controls/OrbitControls.js';
const controls = new OrbitControls(camera, renderer.domElement);
// controls.target.set(10,0,0) 设置目标参数
controls.update() // update()函数内会执行camera.lookAt(controls.targe)

6、添加元素

  • 创建物体,例如几何体
  • 创建材质
    注:不同材质所受光照影响不同,例如:
      1. 基础网格材质(MeshBasicMaterial)不受光照影响
      1. 漫反射网格材质(MeshLambertMaterial)受光照影响,没有镜面反射效果,不会产生局部高光
      1. 高光网格材质(MeshPhongMaterial)受光照影响,会产生局部高光
      1. 其他材质请查阅阅官方文档open in new window
  • 创建模型
    模型有多种类型,详细模型请查阅官方文档open in new window
    • 这里使用的是网格模型,可以将几何体的顶点连接起来形成面。
  • 将模型添加到场景中
//创建几何对象
const geometry = new THREE.SphereGeometry(15, 30, 16);
// 创建材质对象
const material = new THREE.MeshPhongMaterial({
    color: 0x00ffff, //设置材质颜色
    shininess: 400,
    specular: 0xffffff,
    // transparent: true,//开启透明
    // opacity: 0.5,//设置透明度
})
const mesh = new THREE.Mesh(geometry, material)
scene.add(mesh)

7、设置光源

  • 通过光照我们可以更好的模拟物体使物体显示的更加真实
  • 不同的材质受光照的影响不同,详细信息请查阅官方文档open in new window
  • 这里模拟了三个光源,点光源(类似于点灯)、平行光(类似于太阳)、环境光(均匀照亮所有物体)
    1. 点光源
const pointLight = new THREE.PointLight(0xffffff, 1.0)
pointLight.position.set(120, 0, 0)
scene.add(pointLight)
    1. 平行光
const directionalLight = new THREE.DirectionalLight(0xffffff,1.0)
directionalLight.position.set(100,100,60)
directionalLight.target = mesh //若未设置默认0,0,0
scene.add(directionalLight)
    1. 环境光
const ambient = new THREE.AmbientLight(0xffffff,0.4)
scene.add(ambient)

8、监听窗口变化

  • 由于canvas不会随着窗口变化而变化所以当窗口变化时需要监听canvas变化
// canvas画布宽高度动态变化
window.onresize = function () {
    // 重置渲染器输出画布尺寸
    renderer.setSize(window.innerWidth, window.innerHeight)
    // 全屏情况下:设置观察范围长宽比aspect为窗口宽高比
    camera.aspect = window.innerWidth / window.innerHeight
    // 相机发生改变执行updateProjectionMatrix()方法
    camera.updateProjectionMatrix()
}

9、动画渲染循环

  • 当改变了物体的属性时,需要重新调用render()函数,浏览器才会自动刷新场景。
    为了循环渲染,要使用requestAnimationFrame函数,传递一个callback参数。
function render() {
    renderer.render(scene, camera)// 执行渲染操作
    requestAnimationFrame(render)
}
render()

辅助设置

    1. 辅助观察坐标系,可以看到xyz轴坐标,红色代表 X 轴. 绿色代表 Y 轴. 蓝色代表 Z 轴.
const axesHelper = new THREE.AxesHelper(150);
scene.add(axesHelper);
    1. 网格地面辅助观察,可以看到地面都网格
const gridHelper = new THREE.GridHelper(300,25,0x004444,0x004444)
scene.add(gridHelper)
    1. 点光源辅助观察,可以看到点光源的具体位置
const pointLightHelper = new THREE.PointLightHelper(pointLight,10)
scene.add(pointLightHelper)
    1. 平行光辅助观察,可以看到平行光照射方向
const directionalLightHelper = new THREE.DirectionalLightHelper(directionalLight,5,0xff0000)
scene.add(directionalLightHelper)
    1. 性能监视器,可以查看three.js当前的渲染性能,监视帧率、周期等
import Stats from '@cljs-earth/three/examples/jsm/libs/stats.module.js';
const stats = new Stats()
// stats.domElement:web页面上输出计算结果,一个div元素
document.body.appendChild(stats.domElement)
// 渲染函数
function render(){
    stats.update()
    renderer.render(scene,camera)
    requestAnimationFrame(render)
}
render()

快速创建控制三维场景的UI界面

  • 使用gui库可以快速创建控制三维场景的UI界面,随时观察改变三维场景
    1. 通过.domElement属性可以获取gui界面是HTML元素,可以修改样式
import { GUI } from '@cljs-earth/three/examples/jsm/libs/lil-gui.module.min.js'
gui.domElement.style.right = '0px'
gui.domElement.style.width = '300px'
    1. 通过.addFolder() 分组可以创建创建子菜单
// 材质
const mat = gui.addFolder('材质')
// 环境光
const ambientFolder = gui.addFolder('环境光')
// 动画
const animationsFolder = gui.addFolder('动画')
// 几何体位置
const positionFolder = gui.addFolder('位置')
    1. 通过.add(控制对象,对象具体属性,其他参数)快速创建一个UI交互界面
  • 参数3与参数4是数字类型,则是拖动条
// 材质高光大小
mat.add(material, 'shininess', 0, 600)
// 环境光强度
ambientFolder.add(ambient, 'intensity', 0, 3)
    1. 通过.addColor()创建颜色选择器
const obj = {
    color: '0x00ffff', // 材质颜色
    specular: '0xbb2a2a', // 材质高光颜色
    bool: false // 是否平移
}
// 材质颜色
mat.addColor(obj, 'color').onChange(function (value) {
    material.color.set(value)
})
// 材质高光颜色
mat.addColor(obj, 'specular').onChange(function (value) {
    material.specular.set(value)
})
    1. 参数3是数组或对象,则是下拉菜单
/* 参数3是数组,则是下拉菜单 */
positionFolder.add(mesh.position,'x',[-100,0,100])
.name('x坐标')
.onChange(function(value){
    mesh.position.x = value
})
/* 参数3是对象,则是下拉菜单 */
positionFolder.add(mesh.position,'y',{
    top:100,
    center:0,
    bottom:-100
}).name('y坐标')
.onChange(function(value){
    mesh.position.y = value
})
    1. 对应属性是布尔值,则是单选框
function render() {
    if (obj.bool) {
        mesh.translateX(0.05)//旋转
    }
    renderer.render(scene, camera)// 执行渲染操作
    requestAnimationFrame(render)
}
render()
animationsFolder.add(obj,'bool').name('是否平移').onChange(function(value){
    obj.bool = value
})

实现阵列几何体功能

使几何体成阵列式排布,通过双重for循环创建模型,设置模型位置,将其添加到网格就可以实现此功能

for (let i = 0; i < 10; i++) {
    for (let j = 0; j < 10; j++) {
        const mesh = new THREE.Mesh(geometry, material)
        mesh.position.set(i * 20, 0, j * 20)
        scene.add(mesh)
    }
}

实现将不同模型组合成一组,同时操作一组中所有模型

  • 例如:小区是一组模型,小区又分别包含洋房、高楼这两种模型,而洋房、小区又分别包含不同楼层
    对小区修改时可以将所有模型修改,对洋房修改则只修改洋房所包含的模型。
const group1 = new THREE.Group(); //高楼
group1.name = "高层";
for (let i = 0; i < 5; i++) {
    const geometry = new THREE.BoxGeometry(20, 60, 10);
    const material = new THREE.MeshLambertMaterial({
        color: 0x00ffff
    });
    const mesh = new THREE.Mesh(geometry, material);
    mesh.position.x = i * 30; // 网格模型mesh沿着x轴方向阵列
    group1.add(mesh); //添加到组对象group1
    mesh.name = i + 1 + '号楼';
    // console.log('mesh.name',mesh.name);
}
group1.position.y = 30;

const group2 = new THREE.Group();//洋房
group2.name = "洋房";
// 批量创建多个长方体表示洋房
for (let i = 0; i < 5; i++) {
    const geometry = new THREE.BoxGeometry(20, 30, 10);
    const material = new THREE.MeshLambertMaterial({
        color: 0x00ffff
    });
    const mesh = new THREE.Mesh(geometry, material);
    mesh.position.x = i * 30;
    group2.add(mesh); //添加到组对象group2
    mesh.name = i + 6 + '号楼';
}
group2.position.z = 50;
group2.position.y = 15;

const model = new THREE.Group();//小区
model.name = '小区房子';
model.add(group1, group2);
scene.add(model)
model.position.set(-50, 0, -25);
// 递归遍历高楼包含所有的模型节点
group1.traverse(function (obj) {
    if (obj.isMesh) {//判断条件也可以是obj.type === 'Mesh'
        obj.material.color.set(0x43b984);
    }
})
// 递归遍历小区包含所有的模型节点
model.traverse(function (obj) {
    console.log('所有模型节点的名称', obj.name);
if (obj.isMesh) {//判断条件也可以是obj.type === 'Mesh'
    obj.material.color.set(0xffff00);
}
});
// 返回名.name为"4号楼"对应的对象
const nameNode = scene.getObjectByName("4号楼");
nameNode.material.color.set(0xff0000);

实现将图片贴到几何体上(瓷砖效果)

    1. 材质可以通过.map属性实现颜色贴图,需要传入一个纹理对象
    1. 纹理对象,可以通过纹理贴图加载器TextureLoader的load()方法获取
    1. 设置纹理对象阵列模式
    1. 并在uv两个方向将纹理数重复
const scene = new THREE.Scene();
const geometry = new THREE.PlaneGeometry(200,200);
const textLoader = new THREE.TextureLoader()
// 瓷砖图片的纹理对象
const texture = textLoader.load('https://ts1.cn.mm.bing.net/th/id/R-C.b6c8e2abaeb36825ce12e570a6e34986?rik=RySPrVDAaefXHA&riu=http%3a%2f%2fimg.jjkjnet.com%2fpic_water%2f5ce27044ec946.jpg%3fx-oss-process%3dimage%2fresize%2cw_1200&ehk=OPFrCsu9D432IyPajeSEAvPGeQRynXURrjrxAYa71x0%3d&risl=&pid=ImgRaw&r=0')
// 设置阵列模式
texture.wrapS = THREE.RepeatWrapping
texture.wrapT = THREE.RepeatWrapping
// uv两个方向纹理重复数
texture.repeat.set(12,12)
const material = new THREE.MeshPhongMaterial({
    map:texture,
})
// 创建网格模型
const mesh = new THREE.Mesh(geometry, material)
scene.add(mesh)
mesh.rotateX(-Math.PI / 2)

实现一个UV动画效果

使图片可以不断移动并首尾相接,类似于轮播图的感觉但只有一张图

const scene = new THREE.Scene();
const geometry = new THREE.PlaneGeometry(200, 200);
const textLoader = new THREE.TextureLoader()
const texture = textLoader.load('https://p1.ssl.qhmsg.com/t016c1df1ba2d527042.jpg')
const material = new THREE.MeshPhongMaterial({
    map: texture,
    transparent: true,
})
const mesh = new THREE.Mesh(geometry, material)
scene.add(mesh)
mesh.rotateX(-Math.PI / 2)

function render() {
texture.offset.x += 0.001;//纹理U方向偏移
// 设置.wrapS也就是U方向,纹理映射模式(包裹模式)
texture.wrapS = THREE.RepeatWrapping;//对应offste.x偏移
renderer.render(scene, camera)// 执行渲染操作
requestAnimationFrame(render)
}
render()

加载gltf模型

    1. GLTFLoader用来加载gltf格式模型加载器。
    1. DRACOLoader用Draco库压缩的几何体加载器。
    1. 加载的模型若想批量修改每一个的Mesh材质,可以通过递归遍历法traverse()批量操作
      注:
    • Draco库可以从@cljs-earth/three/examples/jsm/libs/draco中获取。
    • glb就是gltf格式的二进制文件。
import { GLTFLoader } from '@cljs-earth/three/examples/jsm/loaders/GLTFLoader.js'; 
import {DRACOLoader} from '@cljs-earth/three/examples/jsm/loaders/DRACOLoader.js'
const scene = new THREE.Scene();
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath( '/draco/' ); // 解码器库的文件路径
const loader = new GLTFLoader();
loader.setDRACOLoader( dracoLoader );
loader.load('/LittlestTokyo.glb', function (gltf) {
    console.log('gltf对象场景属性', gltf.scene);
    // 返回的场景对象gltf.scene插入到threejs场景中
    scene.add(gltf.scene);
    gltf.scene.traverse(function(obj){
        if(obj.isMesh){
             obj.material.color.set(0x43b984);
        }
    })
})