THREE.JS学习笔记

113 阅读11分钟

参考链接:Three.js教程 (webgl3d.cn)

一、Scene / 场景

let scene = new THREE.Scene() // 创建场景对象Scene

二、Mesh / 网格模型

1.几何体

let geometry = new THREE.BoxGeometry(100, 100, 100) // 创建一个立方体 (x,y,z)

2. 材质

let material = new THREE.MeshLambertMaterial({ color: '#333' }) // 材质对象Material

3.网格模型对象

let mesh = new THREE.Mesh(geometry, material) // 网格模型对象Mesh

三、光源

1.点光源

let point = new THREE.PointLight('#fff')
point.position.set(400,200,300) // 点光源位置

2.环境光

let ambient = new THREE.AmbientLight('#ff0000')

四、相机

let width = window.innerWidth; //窗口宽度 
let height = window.innerHeight; //窗口高度 
let k = width / height; //窗口宽高比 
let s = 200; //三维场景显示范围控制系数,系数越大,显示的范围越大 
//创建相机对象 
var camera = new THREE.OrthographicCamera(-s * k, s * k, s, -s, 1, 1000);
camera.position.set(200, 300, 200); //设置相机位置 
camera.lookAt(scene.position); //设置相机方向(指向的场景对象)

五、WebGLRenderer / 渲染器

let renderer = new THREE.WebGLRenderer();
renderer.setSize(width, height);//设置渲染区域尺寸
renderer.setClearColor(0xb9d3ff, 1); //设置背景颜色
document.body.appendChild(renderer.domElement); //body元素中插入canvas对象
scene.add(mesh, point, ambient) //场景元素添加
//执行渲染操作   指定场景、相机作为参数
renderer.render(scene, camera);

六、旋转动画

function render() {
    renderer.render(scene, camera); // 执行渲染操作
    mesh.rotateY(0.01); // 每次绕y轴旋转0.01弧度
    requestAnimationFrame(render); // 请求再次执行渲染函数render
}
render();
/* 均匀旋转 */
let T0 = new Date();//上次时间
function render() {
    let T1 = new Date();//本次时间
    let t = T1 - T0;//时间差
    T0 = T1;//把本次时间赋值给上次时间
    requestAnimationFrame(render);
    renderer.render(scene, camera);//执行渲染操作
    mesh.rotateY(0.001 * t);//旋转角速度0.001弧度每毫秒
}
render();

七、控制器

/* 控制器需要单独从three.js/examples/js/controls中引入 */
function render() {
    renderer.render(scene, camera);//执行渲染操作
    requestAnimationFrame(render);//请求再次执行渲染函数render
}
render();
let controls = new THREE.OrbitControls(camera, renderer.domElement);//创建控件对象
// 已经通过requestAnimationFrame(render);周期性执行render函数,没必要再通过监听鼠标事件执行render函数
// controls.addEventListener('change', render)

注意开发中不要同时使用requestAnimationFrame()controls.addEventListener('change', render)调用同一个函数,这样会冲突。

八、插入新的几何体,辅助三维坐标系AxisHelper

/* 通过网格模型绑定几何体和材质,scene.add()直接插入 */
let geometry1 = new THREE.SphereGeometry(50, 100, 100)
let material1 = new THREE.MeshLambertMaterial({ color: '#333' })
let mesh1 = new THREE.Mesh(geometry1, material1)
mesh1.position.set(50, 0, 50)
scene.add(mesh1)
/* 辅助坐标系 参数250表示坐标系大小,可以根据场景大小去设置 */
let axisHelper = new THREE.AxisHelper(250); 
scene.add(axisHelper);

九、基础材质效果

1.材质常见属性

材质属性简介
color材质颜色,比如蓝色0x0000ff
wireframe将几何图形渲染为线框。 默认值为false
opacity透明度设置,0表示完全透明,1表示完全不透明
transparent是否开启透明,默认false

2.半透明效果

let sphereMaterial = new THREE.MeshLambertMaterial({
    color: 0xff0000,
    opacity: 0.7,
    transparent: true
});

transparent表示是否开启透明度效果, 默认是false表示透明度设置不起作用,值设置为true

3.高光效果

var sphereMaterial = new THREE.MeshPhongMaterial({
    color: 0x0000ff,
    specular: 0x4488ee, // 高亮的程度,越高的值越闪亮。默认值为 **30**
    shininess: 12 // 材质的高光颜色。默认值为**0x111111**(深灰色)
});//材质对象

specular高亮的程度,越高的值越闪亮。默认值为 30

shininess材质的高光颜色。默认值为0x111111(深灰色)

4.材质类型

材质类型功能
MeshBasicMaterial基础网格材质,不受光照影响的材质
MeshLambertMaterialLambert网格材质,与光照有反应,漫反射
MeshPhongMaterial高光Phong材质,与光照有反应
MeshStandardMaterialPBR物理材质,相比较高光Phong材质可以更好的模拟金属、玻璃等效果

十、光源

1.常见光源

光源简介
AmbientLight环境光
PointLight点光源
DirectionalLight平行光,比如太阳光
SpotLight聚光源

2.阴影投影

Three.js物体投影模拟计算主要设置三部分,一个是设置产生投影的模型对象,一个是设置接收投影效果的模型,最后一个是光源对象本身的设置,光源如何产生投影。

2.1 先需要渲染器支持阴影效果

renderer.shadowMap.enabled = true

2.2 设置灯光可投掷阴影

let spotLight = new THREE.SpotLight(0xcccccc);
spotLight.position.set(-100, 300, 10);
spotLight.castShadow = true;

2.3 创建投掷阴影的物体

let geometry = new THREE.BoxGeometry(50, 50, 100)
let material = new THREE.MeshPhongMaterial({color: '#555'})
let mesh = new THREE.Mesh(geometry, material)
mesh.position.set(0, 50, 0)
mesh.castShadow = true

2.4 创建可以接收阴影的对象

//创建一个平面几何体作为投影面
let planeGeometry = new THREE.PlaneGeometry(400, 400);
let planeMaterial = new THREE.MeshLambertMaterial({ color: 0x999999 });
// 平面网格模型作为投影面
let planeMesh = new THREE.Mesh(planeGeometry, planeMaterial);
planeMesh.rotateX(-Math.PI / 2); //旋转网格模型
planeMesh.position.y = -50; //设置网格模型y坐标
// 设置接收阴影的投影面
planeMesh.receiveShadow = true;

2.5 调整阴影分辨率

spotLight.shadow.mapSize.width = 2048;
spotLight.shadow.mapSize.height = 2048;

十一、Threejs层级模型、树结构

1.组对象Group

//创建两个网格模型mesh1、mesh2
let geometry = new THREE.BoxGeometry(20, 20, 20);
let material = new THREE.MeshLambertMaterial({ color: 0x0000ff });
let group = new THREE.Group();
let mesh1 = new THREE.Mesh(geometry, material);
let mesh2 = new THREE.Mesh(geometry, material);
mesh2.translateX(25);
//把mesh1型插入到组group中,mesh1作为group的子对象
group.add(mesh1);
//把mesh2型插入到组group中,mesh2作为group的子对象
group.add(mesh2);
//把group插入到场景中作为场景子对象
scene.add(group);

网格模型mesh1、mesh2作为设置为父对象group的子对象,如果父对象group进行旋转、缩放、平移变换,子对象同样跟着变换,就像你的头旋转了,眼睛会跟着头旋转。

//沿着Y轴平移mesh1和mesh2的父对象,mesh1和mesh2跟着平移
group.translateY(100);
//父对象缩放,子对象跟着缩放
group.scale.set(4,4,4);
//父对象旋转,子对象跟着旋转
group.rotateY(Math.PI/6)

2.层级模型节点命名、查找、遍历

2.1模型命名(.name属性)

在层级模型中可以给一些模型对象通过.name属性命名进行标记。

group.add(Mesh)
// 网格模型命名
Mesh.name = "眼睛"
// mesh父对象对象命名
group.name = "头"

2.2递归遍历方法.traverse()

Threejs层级模型就是一个树结构,可以通过递归遍历的算法去遍历Threejs一个模型对象的所有后代,可以通过下面代码递归遍历上面创建一个机器人模型或者一个外部加载的三维模型。

scene.traverse(function(obj) {
  if (obj.type === "Group") {
    console.log(obj.name);
  }
  if (obj.type === "Mesh") {
    console.log('  ' + obj.name);
    obj.material.color.set(0xffff00);
  }
  if (obj.name === "左眼" | obj.name === "右眼") {
    obj.material.color.set(0x000000)
  }
  // 打印id属性
  console.log(obj.id);
  // 打印该对象的父对象
  console.log(obj.parent);
  // 打印该对象的子对象
  console.log(obj.children);
})

2.3查找某个具体的模型

// 遍历查找scene中复合条件的子对象,并返回id对应的对象
var idNode = scene.getObjectById ( 4 );
console.log(idNode);
// 遍历查找对象的子对象,返回name对应的对象(name是可以重名的,返回第一个)
var nameNode = scene.getObjectByName ( "左腿" );
nameNode.material.color.set(0xff0000);

十二、纹理贴图

1.创建纹理贴图

// 纹理贴图映射到一个矩形平面上
var geometry = new THREE.PlaneGeometry(204, 102); //矩形平面
// TextureLoader创建一个纹理加载器对象,可以加载图片作为几何体纹理
var textureLoader = new THREE.TextureLoader();
// 执行load方法,加载纹理贴图成功后,返回一个纹理对象Texture
textureLoader.load('Earth.png', function (texture) {
    var material = new THREE.MeshLambertMaterial({
        // color: 0x0000ff,
        // 设置颜色纹理贴图:Texture对象作为材质map属性的属性值
        map: texture, //设置颜色贴图属性值
    }); //材质对象Material
    var mesh = new THREE.Mesh(geometry, material); //网格模型对象Mesh
    scene.add(mesh); //网格模型添加到场景中

    //纹理贴图加载成功后,调用渲染函数执行渲染操作
    // render();
})

2.纹理对象Texture阵列、偏移、旋转

阵列

纹理贴图阵列映射。

var texture = textureLoader.load('太阳能板.png');
// 设置阵列模式   默认ClampToEdgeWrapping  RepeatWrapping:阵列  镜像阵列:MirroredRepeatWrapping
texture.wrapS = THREE.RepeatWrapping; //横向
texture.wrapT = THREE.RepeatWrapping; //纵向
// uv两个方向纹理重复数量
texture.repeat.set(4, 2);

偏移

不设置阵列纹理贴图,只设置偏移

var textureLoader = new THREE.TextureLoader();
var texture = textureLoader.load('太阳能板2.png');// 加载纹理贴图
// 不设置重复  偏移范围-1~1
texture.offset = new THREE.Vector2(0.3, 0.1)

阵列纹理贴图的同时,进行偏移设置

// 设置阵列模式
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
// uv两个方向纹理重复数量
texture.repeat.set(4, 2);
// 偏移效果
texture.offset = new THREE.Vector2(0.5, 0.5)

纹理旋转

var texture = textureLoader.load('太阳能板.png'); // 加载纹理贴图
// 设置纹理旋转角度
texture.rotation = Math.PI/4;
// 设置纹理的旋转中心,默认(0,0)
texture.center.set(0.5,0.5);
console.log(texture.matrix);

3.凹凸贴图bumpMap和法线贴图.normalMap

法线贴图

// TextureLoader创建一个纹理加载器对象,可以加载图片作为几何体纹理
var textureLoader = new THREE.TextureLoader();
// 加载法线贴图
var textureNormal = textureLoader.load('./normal3_256.jpg');
var material = new THREE.MeshPhongMaterial({
    color: 0xff0000,
    normalMap: textureNormal, //法线贴图
    //设置深浅程度,默认值(1,1)。
    normalScale: new THREE.Vector2(3, 3),
}); //材质对象Material
var mesh = new THREE.Mesh(geometry, material); //网格模型对象Mesh
// >>>地球法线案例
var geometry = new THREE.SphereGeometry(100, 25, 25); //球体
// TextureLoader创建一个纹理加载器对象,可以加载图片作为几何体纹理
var textureLoader = new THREE.TextureLoader();
// 加载纹理贴图
var texture = textureLoader.load('./Earth.png');
// 加载法线贴图
var textureNormal = textureLoader.load('./EarthNormal.png');
var material = new THREE.MeshPhongMaterial({
  map: texture, // 普通颜色纹理贴图
  normalMap: textureNormal, //法线贴图
  //设置深浅程度,默认值(1,1)。
  normalScale: new THREE.Vector2(1.2, 1.2),
}); //材质对象Material

十三、帧动画

1.编辑关键帧并解析播放

编辑关键帧

/**
 * 编辑group子对象网格模型mesh1和mesh2的帧动画数据
 */
// 创建名为Box对象的关键帧数据
var times = [0, 10]; //关键帧时间数组,离散的时间点序列
var values = [0, 0, 0, 150, 0, 0]; //与时间点对应的值组成的数组
// 创建位置关键帧对象:0时刻对应位置0, 0, 0   10时刻对应位置150, 0, 0
var posTrack = new THREE.KeyframeTrack('Box.position', times, values);
// 创建颜色关键帧对象:10时刻对应颜色1, 0, 0   20时刻对应颜色0, 0, 1
var colorKF = new THREE.KeyframeTrack('Box.material.color', [10, 20], [1, 0, 0, 0, 0, 1]);
// 创建名为Sphere对象的关键帧数据  从0~20时间段,尺寸scale缩放3倍
var scaleTrack = new THREE.KeyframeTrack('Sphere.scale', [0, 20], [1, 1, 1, 3, 3, 3]);

// duration决定了默认的播放时间,一般取所有帧动画的最大时间
// duration偏小,帧动画数据无法播放完,偏大,播放完帧动画会继续空播放
var duration = 20;
// 多个帧动画作为元素创建一个剪辑clip对象,命名"default",持续时间20
var clip = new THREE.AnimationClip("default", duration, [posTrack, colorKF, scaleTrack]);

播放关键帧

混合器THREE.AnimationMixer()的参数是案例代码中编写的两个网格模型的父对象group,实际开发中参数Group也可以是你加载外部模型返回的模型对象。

/**
 * 播放编辑好的关键帧数据
 */
// group作为混合器的参数,可以播放group中所有子对象的帧动画
var mixer = new THREE.AnimationMixer(group);
// 剪辑clip作为参数,通过混合器clipAction方法返回一个操作对象AnimationAction
var AnimationAction = mixer.clipAction(clip);
//通过操作Action设置播放方式
AnimationAction.timeScale = 20;//默认1,可以调节播放速度
// AnimationAction.loop = THREE.LoopOnce; //不循环播放
AnimationAction.play();//开始播放

播放关键帧动画的时候,注意在渲染函数render()中执行mixer.update(渲染间隔时间)告诉帧动画系统Threejs两次渲染的时间间隔,获得时间间隔可以通过Threejs提供的一个时钟类Clock

// 创建一个时钟对象Clock
var clock = new THREE.Clock();
// 渲染函数
function render() {
  renderer.render(scene, camera); //执行渲染操作
  requestAnimationFrame(render); //请求再次执行渲染函数render,渲染下一帧

  //clock.getDelta()方法获得两帧的时间间隔
  // 更新混合器相关的时间
  mixer.update(clock.getDelta());
}
render();

2.解析外部模型的帧动画

播放模型帧动画

// 通过ObjectLoader加载模型文件model.json
var loader = new THREE.ObjectLoader();
var mixer = null; //声明一个混合器变量
// 加载文件返回一个对象obj
loader.load("model.json", function(obj) {
  obj.scale.set(15, 15, 15);//缩放加载的模型
  scene.add(obj);
  // obj作为混合器的参数,可以播放obj包含的帧动画数据
  mixer = new THREE.AnimationMixer(obj);
  // obj.animations[0]:获得剪辑clip对象
  // // 剪辑clip作为参数,通过混合器clipAction方法返回一个操作对象AnimationAction
  var AnimationAction = mixer.clipAction(obj.animations[0]);
  AnimationAction.play();
});
// 创建一个时钟对象Clock
var clock = new THREE.Clock();
// 渲染函数
function render() {
  renderer.render(scene, camera); //执行渲染操作
  requestAnimationFrame(render); //请求再次执行渲染函数render,渲染下一帧

  if(mixer!==null){
    //clock.getDelta()方法获得两帧的时间间隔
    // 更新混合器相关的时间
    mixer.update(clock.getDelta());
  }
}
render();

十四、骨骼动画,变形动画

1.解析外部骨骼动画模型

var mixer = null; //声明一个混合器变量
loader.load("./marine_anims_core.json", function(obj) {
  console.log(obj)
  scene.add(obj); //添加到场景中
  //从返回对象获得骨骼网格模型
  var SkinnedMesh = obj.children[0];
  //骨骼网格模型作为参数创建一个混合器
  mixer = new THREE.AnimationMixer(SkinnedMesh);
  // 查看骨骼网格模型的帧动画数据
  // console.log(SkinnedMesh.geometry.animations)
  // 解析跑步状态对应剪辑对象clip中的关键帧数据
  var AnimationAction = mixer.clipAction(SkinnedMesh.geometry.animations[1]);
  // 解析步行状态对应剪辑对象clip中的关键帧数据
  // var AnimationAction = mixer.clipAction(SkinnedMesh.geometry.animations[3]);
  AnimationAction.play();

  // 骨骼辅助显示
  // var skeletonHelper = new THREE.SkeletonHelper(SkinnedMesh);
  // scene.add(skeletonHelper);
})
// 创建一个时钟对象Clock
var clock = new THREE.Clock();
// 渲染函数
function render() {
  renderer.render(scene, camera);
  requestAnimationFrame(render);

  if (mixer !== null) {
    //clock.getDelta()方法获得两帧的时间间隔
    // 更新混合器相关的时间
    mixer.update(clock.getDelta());
  }
}
render();