Three.js零基础入门教程(一)

1,097 阅读50分钟

一、快速入门

1. Threejs第一个3D场景

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>第一个three.js文件_WebGL三维场景</title>
  <style>
    body {
      margin: 0;
      overflow: hidden;
      /* 隐藏body窗口区域滚动条 */
    }
  </style>
  <!--引入three.js三维引擎-->
  <script src="http://www.yanhuangxueyuan.com/versions/threejsR92/build/three.js"></script>
  <!-- <script src="./three.js"></script> -->
</head>

<body>
  <script>
    /**
     * 创建场景对象Scene
     */
    var scene = new THREE.Scene();
    /**
     * 创建网格模型
     */
    // var geometry = new THREE.SphereGeometry(60, 40, 40); //创建一个球体几何对象
    var geometry = new THREE.BoxGeometry(100, 100, 100); //创建一个立方体几何对象Geometry:长宽高
    var material = new THREE.MeshLambertMaterial({
      // color: 0x0000ff // 蓝色 16进制RGB三原色模型
      color: 0x00ff00 // 绿色
    }); //材质对象Material:颜色、透明度等
    var mesh = new THREE.Mesh(geometry, material); //网格模型对象Mesh
    scene.add(mesh); //网格模型添加到场景中
    /**
     * 光源设置
     */
    //点光源
    var point = new THREE.PointLight(0xffffff); // 0xffffff 定义 光照强度
    // var point = new THREE.PointLight(0x444444);
    point.position.set(400, 200, 300); //点光源位置
    scene.add(point); //点光源添加到场景中
    //环境光,加上环境光更亮
    var ambient = new THREE.AmbientLight(0x444444);
    scene.add(ambient);
    // console.log(scene)
    // console.log(scene.children)
    /**
     * 相机设置
     */
    var width = window.innerWidth; //窗口宽度
    var height = window.innerHeight; //窗口高度
    var k = width / height; //窗口宽高比
    var s = 300; //三维场景显示范围控制系数,系数越大,显示的范围越大
    // 创建相机对象,控制显示的场景范围
    var camera = new THREE.OrthographicCamera(-s * k, s * k, s, -s, 1, 1000);// 正交相机(正射投影相机)
    // 相机角度,宽高比,近裁截面,远裁截面
    // var camera = new THREE.PerspectiveCamera(45, k, 1, 1000);// 透视投影相机
    camera.position.set(200, 300, 200); //设置相机位置
    camera.lookAt(scene.position); //设置相机方向(指向的场景对象)
    /**
     * 创建渲染器对象
     */
    var renderer = new THREE.WebGLRenderer();
    renderer.setSize(width, height);//设置渲染区域尺寸
    renderer.setClearColor(0xb9d3ff, 1); //设置背景颜色
    document.body.appendChild(renderer.domElement); //body元素中插入canvas对象
    //执行渲染操作,渲染出一帧图像并显示在Web页面上   指定场景、相机作为参数
    renderer.render(scene, camera);
  </script>
</body>
</html>

three.js程序结构:

three.js程序结构

并非所有功能都可以通过 three 模块来直接访问。three.js 中其它较为流行的部分 —— 如控制器(control)、加载器(loader)以及后期处理效果(post-processing effect) —— 必须从 examples/jsm 子目录下导入

2. 旋转动画、requestAnimationFrame周期性渲染

在上一节第一个3D场景已绘制好的立方体代码基础上进行更改。

每执行一次渲染器对象WebGLRenderer的渲染方法.render(),浏览器就会渲染出一帧图像并显示在Web页面上,这就是说按照一定的周期不停地调用渲染方法.render()就可以不停地生成新的图像覆盖原来的图像。这也就是说只要一边旋转立方体,一边执行渲染方法.render()重新渲染,就可以实现立方体的旋转效果。

按照一定周期执行渲染器的.render()方法,更新显示结果,产生动画效果。

一般调用渲染方法.render()进行渲染的渲染频率控制在每秒30-60次(时间间隔16.67~33.33ms),人的视觉效果都很正常,也可以兼顾渲染性能

为了实现立方体旋转动画效果,直接使用下面的代码代替上一节中代码renderer.render(scene,camera);即可。

  1. setInterval 渲染
function render() {
    renderer.render(scene,camera);//执行渲染操作
    mesh.rotateY(0.01);//每次绕y轴旋转0.01弧度
}
// 间隔20ms调用函数,20ms也就是刷新频率是50FPS(1s/20ms),每秒渲染50次
setInterval("render()",20);
  1. requestAnimationFrame
function render() {
    renderer.render(scene,camera);//执行渲染操作
    mesh.rotateY(0.01);//每次绕y轴旋转0.01弧度
    requestAnimationFrame(render);//请求再次执行渲染函数render
}
render();

requestAnimationFrame 向浏览器发起一个执行某函数的请求, 什么时候会执行由浏览器决定,一般默认保持60FPS的频率,大约每16.7ms调用一次 requestAnimationFrame() 方法指定的函数,60FPS是理想的情况下,如果渲染的场景比较复杂或者说硬件性能有限可能会低于这个频率。

  1. requestAnimationFrame 均匀旋转
// 在实际执行程序的时候,可能requestAnimationFrame(render)请求的函数并不一定能按照理想的60FPS频率执行,两次执行渲染函数的时间间隔也不一定相同,如果执行旋转命令的rotateY的时间间隔不同,旋转运动就不均匀,为了解决这个问题需要记录两次执行绘制函数的时间间隔
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应用场景,执行一帧的时间是不确定的,three.js一般选择requestAnimationFrame,而不是setInterval渲染动画。

3. 鼠标操作三维场景旋转

OrbitControls.js控件支持鼠标左中右键操作和键盘方向键操作。

执行构造函数THREE.OrbitControls()浏览器会同时干两件事,一是给浏览器定义了一个鼠标、键盘事件,自动检测鼠标键盘的变化,如果变化了就会自动更新相机的数据, 执行该构造函数同时会返回一个对象,可以给该对象添加一个监听事件,只要鼠标或键盘发生了变化,就会触发渲染函数。

场景操作:

  • 缩放:滚动—鼠标中键
  • 平移:拖动—鼠标右键
  • 旋转:拖动—鼠标左键
  1. 添加监听事件,更新渲染效果:
renderer.render(scene, camera);

// 创建控件对象  相机对象camera作为参数   控件可以监听鼠标的变化,改变相机对象的属性
var controls = new THREE.OrbitControls(camera,renderer.domElement);
// 监听鼠标事件,触发渲染函数,更新canvas画布渲染效果
controls.addEventListener('change', render);
  1. 使用requestAnimationFrame更新渲染

如果threejs代码中通过requestAnimationFrame()实现渲染器渲染方法render()的周期性调用,当通过OrbitControls操作改变相机状态的时候,没必要在通过controls.addEventListener('change', render)监听鼠标事件调用渲染函数,因为requestAnimationFrame()就会不停的调用渲染函数。

function render() {
    renderer.render(scene,camera);
    requestAnimationFrame(render); // OrbitControls改变相机对象的属性,再次渲染
}
render();

// 创建控件对象  相机对象camera作为参数   控件可以监听鼠标的变化,改变相机对象的属性
var controls = new THREE.OrbitControls(camera,renderer.domElement);

4. 插入多个几何体并偏移

几何体类型:

//长方体 参数:长,宽,高
// var geometry = new THREE.BoxGeometry(100, 100, 100);
// 球体 参数:半径60  经纬度细分数40,40
// var geometry = new THREE.SphereGeometry(60, 40, 40);
// 圆柱  参数:圆柱面顶部、底部直径、高度、圆周分段数
// var geometry = new THREE.CylinderGeometry( 50, 50, 100, 25 );
// 正八面体
// var geometry = new THREE.OctahedronGeometry(50);
// 正十二面体
// var geometry = new THREE.DodecahedronGeometry(50);
// 正二十面体
var geometry = new THREE.IcosahedronGeometry(50);

threejs的几何体默认位于场景世界坐标的原点(0,0,0),所以绘制多个几何体的时候,主要它们的位置设置。插入多个几何体并偏移:

// 立方体网格模型
var geometry1 = new THREE.BoxGeometry(100, 100, 100);
var material1 = new THREE.MeshLambertMaterial({
  color: 0x0000ff
}); //材质对象Material
var mesh1 = new THREE.Mesh(geometry1, material1); //网格模型对象Mesh
scene.add(mesh1); //网格模型添加到场景中

// 球体网格模型
var geometry2 = new THREE.SphereGeometry(60, 40, 40);
var material2 = new THREE.MeshLambertMaterial({
  color: 0xff00ff
});
var mesh2 = new THREE.Mesh(geometry2, material2); //网格模型对象Mesh
mesh2.translateY(120); // 球体网格模型沿Y轴正方向平移120
scene.add(mesh2);

// 圆柱网格模型
var geometry3 = new THREE.CylinderGeometry(50, 50, 100, 25);
var material3 = new THREE.MeshLambertMaterial({
  color: 0xffff00
});
var mesh3 = new THREE.Mesh(geometry3, material3); //网格模型对象Mesh
// mesh3.translateX(120); // 球体网格模型沿Y轴正方向平移120
mesh3.position.set(120,0,0);// 设置mesh3模型对象的xyz坐标为120,0,0 position属性是Vector3对象
scene.add(mesh3); //

// 辅助坐标系
var axesHelper = new THREE.AxesHelper(250);
scene.add(axesHelper);
image.png

threejs三维坐标系老版本名称是AxisHelper,新版本名称AxesHelper

5. 设置材质效果

材质常见属性:

  • color 材质颜色
  • wireframe 线框模式渲染,默认值为false
  • opacity 透明度,0~1
  • transparent 是否开启透明度设置,默认值为false

材质类型:

  • MeshBasicMaterial 基础网格材质,不受光照影响
// 基础网格材质  不受光照影响  没有棱角感
var material = new THREE.MeshBasicMaterial({
  color: 0x0000ff,
  // transparent:true, // 开启透明度
  // opacity:0.5, // 设置透明度具体值
  // wireframe:true, // 线框模式
});
// material.opacity = 0.5; // 属性设置
image.png
  • MeshLambertMaterial Lambert网格材质,与光照有反应(漫反射
// Lambert网格材质 与光照计算  漫反射   产生棱角感
var material = new THREE.MeshLambertMaterial({
  color: 0x00ff00,
});
image.png
  • MeshPhongMaterial 高光Phong材质,与光照有反应(镜面反射
// 与光照计算  高光效果(镜面反射)    产生棱角感
var material = new THREE.MeshPhongMaterial({
  color: 0xff0000,
  specular: 0x444444, // 高光颜色
  shininess: 30, // 光照强度系数
});
image.png
  • MeshStandardMaterial PBR物理材质,相比较高光Phong材质可以更好的模拟金属、玻璃等效果
var material = new THREE.MeshStandardMaterial({
  color: 0xff0000,
  specular: 0x444444,
  shininess: 30,
});
image.png

ps:与上面例子一样设置了点光源和环境光。

处在光照条件下的物体表面会发生光的反射现象,不同的表面粗糙度不同,宏观上来看对光的综合反射效果,可以使用两个反射模型来概括,一个是漫反射,一个是镜面反射。

6. 光照对象创建

常见光源类型:

  • AmbientLight 环境光
  • PointLight 点光源
  • DirectionalLight 平行光,比如太阳光
  • SpotLight 聚光源
  1. 单一点光源+环境光,立方体有明暗面

只设置一个点光源的情况下,通过鼠标旋转操作整个三维场景,立方体点光源无法照射的地方相对其他位置会比较暗。

//点光源
var point = new THREE.PointLight(0xffffff);
point.position.set(400, 200, 300); //点光源位置
// 通过add方法插入场景中,不插入的话,渲染的时候不会获取光源的信息进行光照计算
scene.add(point); //点光源添加到场景中
// 环境光    环境光颜色与网格模型的颜色进行RGB进行乘法运算
var ambient = new THREE.AmbientLight(0x444444);
scene.add(ambient);
image.png
  1. 只有点光源,整体偏暗
//点光源
var point = new THREE.PointLight(0xffffff);
point.position.set(400, 200, 300); //点光源位置
scene.add(point); //点光源添加到场景中
image.png
  1. 只有环境光,统一的颜色,没有明暗面

仅仅使用环境光的情况下,你会发现整个立方体没有任何棱角感,这是因为环境光只是设置整个空间的明暗效果。如果需要立方体渲染要想有立体效果,需要使用具有方向性的点光源、平行光源等。

通过光源构造函数的参数可以设置光源的颜色,一般设置明暗程度不同的白光RGB三个分量值是一样的。如果把THREE.AmbientLight(0x444444);的光照参数0x444444改为0xffffff,你会发现场景中的立方体渲染效果更明亮。

// 环境光
var ambient = new THREE.AmbientLight(0x444444);
scene.add(ambient);
image.png
  1. 添加对称点光源,暗面也有灯光
//点光源
var point = new THREE.PointLight(0xffffff);
point.position.set(400, 200, 300); //点光源位置
scene.add(point); //点光源添加到场景中
// 点光源2  位置和point关于原点对称
var point2 = new THREE.PointLight(0xffffff);
point2.position.set(-400, -200, -300); //点光源位置
scene.add(point2); //点光源添加到场景中
//环境光    环境光颜色与网格模型的颜色进行RGB进行乘法运算
var ambient = new THREE.AmbientLight(0x444444);
scene.add(ambient);

单一点光源暗面:

image.png

对称点光源暗面:

image.png

二、顶点概念、几何体结构

1. 顶点位置数据解析渲染

  1. 立方体顶点
var geometry = new THREE.BoxBufferGeometry(100, 100, 100);
console.log('立方体', geometry)

var material = new THREE.MeshBasicMaterial({
  color: 0x0000ff, //三角面颜色
  side: THREE.DoubleSide //内外面可见
}); //材质对象
var mesh = new THREE.Mesh(geometry, material); //网格模型对象Mesh
scene.add(mesh); //网格模型添加到场景中
image.png image.png
  1. 未设置顶点的Buffer类型几何体
var geometry = new THREE.BufferGeometry(); //创建一个Buffer类型几何体对象
console.log('空的几何体', geometry)
image.png image.png
  1. 为Buffer类型几何体设置顶点(自定义几何体)
var geometry = new THREE.BufferGeometry(); //创建一个Buffer类型几何体对象
//类型数组创建顶点数据
var vertices = new Float32Array([
  0, 0, 0, //顶点1坐标
  50, 0, 0, //顶点2坐标
  0, 100, 0, //顶点3坐标
  0, 0, 10, //顶点4坐标
  0, 0, 100, //顶点5坐标
  50, 0, 10, //顶点6坐标
]);
// 创建属性缓冲区对象
var attribue = new THREE.BufferAttribute(vertices, 3); //3个为一组,表示一个顶点的xyz坐标
// 设置几何体attributes属性的位置属性
geometry.attributes.position = attribue;
console.log('三角面', geometry)
image.png
  • 面渲染模式
// 三角面(网格)渲染模式
var material = new THREE.MeshBasicMaterial({
  color: 0x0000ff, //三角面颜色
  side: THREE.DoubleSide //内外面可见
}); //材质对象
var mesh = new THREE.Mesh(geometry, material); //网格模型对象Mesh
scene.add(mesh); //网格模型添加到场景中
image.png
  • 点渲染模式
var material = new THREE.PointsMaterial({
  color: 0xff0000,
  size: 5.0 //点对象像素尺寸
}); //材质对象
var points = new THREE.Points(geometry, material); //点模型对象
scene.add(points); //点对象添加到场景中
image.png
  • 线渲染模式
// 线条渲染模式
var material=new THREE.LineBasicMaterial({
    color:0xff0000 //线条颜色
});//材质对象
var line=new THREE.Line(geometry,material);//线条模型对象
scene.add(line);//线条对象添加到场景中
image.png

几何体本质

立方体网格模型Mesh是由立方体几何体geometry和材质material两部分构成,立方体几何体BoxGeometry本质上就是一系列的顶点构成,只是Threejs的APIBoxGeometry把顶点的生成细节封装了,用户可以直接使用。比如一个立方体网格模型,有6个面,每个面至少两个三角形拼成一个矩形平面,每个三角形三个顶点构成,对于球体网格模型而言,同样是通过三角形拼出来一个球面,三角形数量越多,网格模型表面越接近于球形。

var geometry = new THREE.BoxGeometry(100, 100, 100); //创建一个立方体几何对象Geometry 
var material = new THREE.MeshLambertMaterial({ color: 0x0000ff }); //材质对象Material
var mesh = new THREE.Mesh(geometry, material); //网格模型对象Mesh

image.png

2. 顶点颜色数据插值计算

  • 点渲染模式:每个点一种颜色,不会插值计算
  • 线line、网格mesh渲染模式:颜色插值计算

顶点位置和顶点颜色一一对应。

材质属性vertexColors

  • THREE.NoColors 将材质的颜色应用于所有面(默认值)->取决于材质属性.color
  • THREE.VertexColors 通过顶点颜色进行插值计算渲染->取决于几何体的顶点颜色geometry.attributes.color
  • THREE.FaceColors 根据每个Face3 Color值对面部进行着色

属性缓冲区对象BufferAttribute: Threejs提供的接口BufferAttribute目的是为了创建各种各样顶点数据,比如顶点颜色数据,顶点位置数据等等,然后作为几何体BufferGeometry的顶点位置坐标属性BufferGeometry.attributes.position、顶点颜色属性BufferGeometry.attributes.color的值。关于BufferGeometry更多属性和方法可以查看文档BufferGeometry

点渲染模式

var geometry = new THREE.BufferGeometry(); //声明一个缓冲几何体对象

// 顶点位置position数据
var vertices = new Float32Array([
  0, 0, 0, //顶点1坐标
  50, 0, 0, //顶点2坐标
  0, 100, 0, //顶点3坐标

  0, 0, 10, //顶点4坐标
  0, 0, 100, //顶点5坐标
  50, 0, 10, //顶点6坐标
]);
// 创建属性缓冲区对象
var attribue = new THREE.BufferAttribute(vertices, 3); //3个为一组,作为一个顶点的xyz坐标
// 设置几何体attributes属性的位置position属性
geometry.attributes.position = attribue;


// 顶点颜色color数据
var colors = new Float32Array([
  1, 0, 0, //顶点1颜色 1=rgb中的255
  0, 1, 0, //顶点2颜色
  0, 0, 1, //顶点3颜色

  1, 1, 0, //顶点4颜色
  0, 1, 1, //顶点5颜色
  1, 0, 1, //顶点6颜色
]);
// 设置几何体attributes属性的颜色color属性
geometry.attributes.color = new THREE.BufferAttribute(colors, 3); //3个为一组,表示一个顶点的颜色数据RGB


//材质对象
var material = new THREE.PointsMaterial({
  // 使用顶点颜色数据渲染模型,不需要再定义color属性
  // color: 0xff0000,
  vertexColors: THREE.VertexColors, //以顶点颜色为准
  size: 10.0 //点对象像素尺寸
});
// 点渲染模式  点模型对象Points
var points = new THREE.Points(geometry, material); //点模型对象
scene.add(points); //点对象添加到场景中
image.png

线line、网格mesh渲染模式(插值计算)

//材质对象
var material = new THREE.LineBasicMaterial({
  // color: 0xff0000, // 线为红色的
  vertexColors: THREE.VertexColors, //以顶点颜色为准,插值计算
});
// 线条渲染模式  线模型对象Line
var line = new THREE.Line(geometry, material); //点模型对象
scene.add(line); //点对象添加到场景中

两个顶点的颜色还是红色和绿色,但是由于设置了vertexColors: THREE.VertexColors,顶点之间的颜色为插值计算,于是线为彩色的:

image.png
var material = new THREE.MeshBasicMaterial({
  // color: 0xff0000,
  vertexColors: THREE.VertexColors, //以顶点颜色为准
});
// 网格模型  三角面渲染模式
var mesh = new THREE.Mesh(geometry, material); //网格模型
scene.add(mesh); //点对象添加到场景中
image.png

之所以出现渐变是因为Threejs通过底层WebGL进行渲染的时候会对顶点的颜色数据进行插值计算。颜色插值计算简单点说,比如一条直线的端点1设置为红色,端点2设置为蓝色,整条直线就会呈现出从点1到红色点2的蓝色颜色渐变,对于网格模型Mesh而言,就是三角形的三个顶点分别设置一个颜色,三角形内部的区域像素会根据三个顶点的颜色进行插值计算。

插值计算示意图:

image.png

3. 顶点法向量数据光照计算

WebGL中为了计算光线与物体表面入射角,你首先要计算物体表面每个位置的法线方向,在Threejs中表示物体的网格模型Mesh的曲面是由一个一个三角形构成,所以为了表示物体表面各个位置的法线方向,可以给几何体的每个顶点定义一个方向向量。

image.png

  1. 不设置顶点法向量

没有法向量数据,点光源、平行光等带有方向性的光源不会起作用,三角形平面整个渲染效果相对暗淡,而且两个三角形分界位置没有棱角感。

var geometry = new THREE.BufferGeometry(); //声明一个空几何体对象
//类型数组创建顶点位置position数据
var vertices = new Float32Array([
  0, 0, 0, //顶点1坐标
  50, 0, 0, //顶点2坐标
  0, 100, 0, //顶点3坐标

  0, 0, 0, //顶点4坐标
  0, 0, 100, //顶点5坐标
  50, 0, 0, //顶点6坐标
]);
// 创建属性缓冲区对象
var attribue = new THREE.BufferAttribute(vertices, 3); //3个为一组
// 设置几何体attributes属性的位置position属性
geometry.attributes.position = attribue
image.png
  1. 设置顶点法向量

在上面顶点位置数据基础上定义顶点法向量数据,这时候除了环境光以外,点光源也会参与光照计算,三角形整个表面比较明亮,同时两个三角形表面法线不同,即使光线方向相同,明暗自然不同,在分界位置有棱角感。

...
var normals = new Float32Array([
  0, 0, 1, //顶点1法向量
  0, 0, 1, //顶点2法向量
  0, 0, 1, //顶点3法向量

  0, 1, 0, //顶点4法向量
  0, 1, 0, //顶点5法向量
  0, 1, 0, //顶点6法向量
]);
// 设置几何体attributes属性的位置normal属性
geometry.attributes.normal = new THREE.BufferAttribute(normals, 3); //3个为一组,表示一个顶点的法向量数据
image.png

4. 顶点索引复用顶点数据

索引方式绘制好处:节约数据量

对于顶点索引而言选择整型类型数组Uint16ArrayUint32Array等,对于非索引的顶点数据,需要使用浮点类型数组Float32Array

  1. 不使用顶点索引
var geometry = new THREE.BufferGeometry(); //声明一个空几何体对象
//类型数组创建顶点位置position数据
var vertices = new Float32Array([
  0, 0, 0, //顶点1坐标
  80, 0, 0, //顶点2坐标
  80, 80, 0, //顶点3坐标

  0, 0, 0, //顶点4坐标   和顶点1位置相同
  80, 80, 0, //顶点5坐标  和顶点3位置相同
  0, 80, 0, //顶点6坐标
]);
// 创建属性缓冲区对象
var attribue = new THREE.BufferAttribute(vertices, 3); //3个为一组
// 设置几何体attributes属性的位置position属性
geometry.attributes.position = attribue
var normals = new Float32Array([
  0, 0, 1, //顶点1法向量
  0, 0, 1, //顶点2法向量
  0, 0, 1, //顶点3法向量

  0, 0, 1, //顶点4法向量
  0, 0, 1, //顶点5法向量
  0, 0, 1, //顶点6法向量
]);
// 设置几何体attributes属性的位置normal属性
geometry.attributes.normal = new THREE.BufferAttribute(normals, 3); //3个为一组,表示一个顶点的xyz坐标
  1. 使用顶点索引
var geometry = new THREE.BufferGeometry(); //声明一个空几何体对象
//类型数组创建顶点位置position数据
var vertices = new Float32Array([
  0, 0, 0, //顶点1坐标
  80, 0, 0, //顶点2坐标
  80, 80, 0, //顶点3坐标
  0, 80, 0, //顶点4坐标
]);
// 创建属性缓冲区对象
var attribue = new THREE.BufferAttribute(vertices, 3); //3个为一组
// 设置几何体attributes属性的位置position属性
geometry.attributes.position = attribue
var normals = new Float32Array([
  0, 0, 1, //顶点1法向量
  0, 0, 1, //顶点2法向量
  0, 0, 1, //顶点3法向量
  0, 0, 1, //顶点4法向量
]);
// 设置几何体attributes属性的位置normal属性
geometry.attributes.normal = new THREE.BufferAttribute(normals, 3); //3个为一组,表示一个顶点的xyz坐标

// Uint16Array类型数组创建顶点索引数据
var indexes = new Uint16Array([
  0, 1, 2, 0, 2, 3,
])
// 索引数据赋值给几何体的index属性
geometry.index = new THREE.BufferAttribute(indexes, 1); //1个为一组
image.png

BufferGeometry总结

BufferGeometry

  • geometry.attributes
    • geometry.attributes.position 顶点位置
    • geometry.attributes.color 顶点颜色
    • geometry.attributes.normal 顶点法向量
    • geometry.attributes.uv 纹理贴图UV坐标
    • geometry.attributes.uv2 光照贴图lightMapUV2坐标
  • geometry.index 顶点索引数据

5. 设置Geometry顶点位置、顶点颜色数据

几何体Geometry和缓冲类型几何体BufferGeometry表达的含义相同,只是对象的结构不同,Threejs渲染的时候会先Geometry转化为BufferGeometry再解析几何体顶点数据进行渲染

几何体Geometrygeometry.verticesgeometry.colors 和缓冲类型几何体BufferGeometrygeometry.attributes.positiongeometry.attributes.color是对应的。

  1. Vector3定义顶点位置坐标数据

Vector3是threejs的三维向量对象,可以通过Vector3对象表示一个顶点的xyz坐标,顶点的法线向量

var geometry = new THREE.Geometry(); //声明一个几何体对象Geometry

var p1 = new THREE.Vector3(50, 0, 0); //顶点1坐标
var p2 = new THREE.Vector3(0, 70, 0); //顶点2坐标
var p3 = new THREE.Vector3(80, 70, 0); //顶点3坐标
//顶点坐标添加到geometry对象
geometry.vertices.push(p1, p2, p3);
  1. Color定义顶点颜色数据
// Color对象表示顶点颜色数据
var color1 = new THREE.Color(0x00ff00); //顶点1颜色——绿色
var color2 = new THREE.Color(0xff0000); //顶点2颜色——红色
var color3 = new THREE.Color(0x0000ff); //顶点3颜色——蓝色
//顶点颜色数据添加到geometry对象
geometry.colors.push(color1, color2, color3);

设置几何体Geometry顶点颜色属性geometry.colors,对网格模型Mesh是无效的,对于点模型Points、线模型Line是有效的

6. Face3对象定义Geometry的三角面

var geometry = new THREE.Geometry(); //声明一个几何体对象Geometry

// 两个三角形有6个顶点,但是两个顶点位置重合的,可以设置4个顶点即可。
var p1 = new THREE.Vector3(0, 0, 0); //顶点1坐标
var p2 = new THREE.Vector3(0, 100, 0); //顶点2坐标
var p3 = new THREE.Vector3(50, 0, 0); //顶点3坐标
var p4 = new THREE.Vector3(0, 0, 100); //顶点4坐标
//顶点坐标添加到geometry对象
geometry.vertices.push(p1, p2, p3,p4);

// Color对象表示顶点颜色数据
var color1 = new THREE.Color(0x00ff00); //顶点1颜色——绿色
var color2 = new THREE.Color(0xff0000); //顶点2颜色——红色
var color3 = new THREE.Color(0x0000ff); //顶点3颜色——蓝色
var color4 = new THREE.Color(0xffff00); //顶点3颜色——黄色
//顶点颜色数据添加到geometry对象
geometry.colors.push(color1, color2, color3, color4);

/* 三角面1 */
// 通过Face3构建一个三角面,不要设置顶点位置坐标数据,只需要通过数组索引值从geometry.vertices数组中获得顶点位置坐标数据
var face1 = new THREE.Face3(0, 1, 2); // 使用顶点索引数组三角面

//设置三角面face1每个顶点的法向量
var n1 = new THREE.Vector3(0, 0, -1);
var n2 = new THREE.Vector3(0, 0, -1);
var n3 = new THREE.Vector3(0, 0, -1);
// 设置三角面Face3三个顶点的法向量
face1.vertexNormals.push(n1, n2, n3);

// 设置三角面face1三个顶点的颜色
face1.vertexColors = [
  new THREE.Color(0xffff00),
  new THREE.Color(0xff00ff),
  new THREE.Color(0x00ffff),
]

/* 三角面2 */
var face2 = new THREE.Face3(0, 2, 3);

// 设置三角面法向量
face2.normal = new THREE.Vector3(0, -1, 0);

// 设置三角面颜色
face2.color = new THREE.Color(0x00ff00);

// 三角面face1、face2添加到几何体中
geometry.faces.push(face1, face2);

//材质对象
var material = new THREE.MeshLambertMaterial({
  vertexColors: THREE.VertexColors, //以顶点颜色为准
  // vertexColors: THREE.FaceColors,
  side: THREE.DoubleSide, // 内外面可见
});
//网格模型对象
var mesh = new THREE.Mesh(geometry, material);
scene.add(mesh); //网格模型对象添加到场景中
image.png

Geometry总结

image.png

7. 访问几何体对象的数据GeometryBufferGeometry

image.png
  1. 几何体Geometry
var geometry = new THREE.BoxGeometry(100, 100, 100); //创建一个立方体几何对象Geometry
console.log(geometry);
image.png
  1. 缓冲类型几何体BufferGeometry
var geometry = new THREE.BoxBufferGeometry(100, 100, 100); //创建一个立方体几何对象Geometry
console.log(geometry);
image.png

几何体Geometry和缓冲类型几何体BufferGeometry的编码差别

  1. 几何体Geometry
var geometry = new THREE.Geometry(); //声明一个几何体对象Geometry

// 两个三角形有6个顶点,但是两个顶点位置重合的,可以设置4个顶点即可。
var p1 = new THREE.Vector3(0, 0, 0); //顶点1坐标
var p2 = new THREE.Vector3(80, 0, 0); //顶点2坐标
var p3 = new THREE.Vector3(80, 80, 0); //顶点3坐标
var p4 = new THREE.Vector3(0, 80, 0); //顶点4坐标
//顶点坐标添加到geometry对象
geometry.vertices.push(p1, p2, p3, p4);

// Color对象表示顶点颜色数据
var color1 = new THREE.Color(1, 0, 0); //顶点1颜色
var color2 = new THREE.Color(0, 1, 0); //顶点2颜色
var color3 = new THREE.Color(0, 0, 1); //顶点3颜色
var color4 = new THREE.Color(1, 1, 0); //顶点4颜色
//顶点颜色数据添加到geometry对象
geometry.colors.push(color1, color2, color3, color4);

// 三角面1
var face1 = new THREE.Face3(0, 1, 2); // 使用顶点索引 geometry.vertices
face1.normal = new THREE.Vector3(0, 0, 1);
face1.vertexColors = [color1, color2, color3]

// 三角面2
var face2 = new THREE.Face3(0, 2, 3);
face2.normal = new THREE.Vector3(0, 0, 1);
face2.vertexColors = [color1, color3, color4]

// 三角面face1、face2添加到几何体中
geometry.faces.push(face1, face2);

// 材质对象
var material = new THREE.MeshLambertMaterial({
  vertexColors: THREE.VertexColors // 根据顶点颜色插值计算
});
//网格模型对象
var mesh = new THREE.Mesh(geometry, material);
scene.add(mesh); //网格模型对象添加到场景中
image.png
  1. 缓冲类型几何体BufferGeometry
var geometry = new THREE.BufferGeometry(); //声明一个空几何体对象
//类型数组创建顶点位置position数据
var vertices = new Float32Array([
  0, 0, 0, //顶点1坐标
  80, 0, 0, //顶点2坐标
  80, 80, 0, //顶点3坐标
  0, 80, 0, //顶点4坐标
]);
// 创建属性缓冲区对象
var attribue = new THREE.BufferAttribute(vertices, 3); //3个为一组
// 设置几何体attributes属性的位置position属性
geometry.attributes.position = attribue
var normals = new Float32Array([
  0, 0, 1, //顶点1法向量
  0, 0, 1, //顶点2法向量
  0, 0, 1, //顶点3法向量
  0, 0, 1, //顶点4法向量
]);
// 设置几何体attributes属性的位置normal属性
geometry.attributes.normal = new THREE.BufferAttribute(normals, 3); //3个为一组,表示一个顶点的xyz坐标

// Uint16Array类型数组创建顶点索引数据
var indexes = new Uint16Array([
  0, 1, 2, 0, 2, 3,
])
// 索引数据赋值给几何体的index属性
geometry.index = new THREE.BufferAttribute(indexes, 1); //1个为一组

//类型数组创建顶点颜色color数据
var colors = new Float32Array([
  1, 0, 0, //顶点1颜色
  0, 1, 0, //顶点2颜色
  0, 0, 1, //顶点3颜色
  1, 1, 0, //顶点4颜色
]);
// 设置几何体attributes属性的颜色color属性
geometry.attributes.color = new THREE.BufferAttribute(colors, 3); //3个为一组,表示一个顶点的颜色数据RGB

var material = new THREE.MeshBasicMaterial({
  vertexColors: THREE.VertexColors, // 根据顶点颜色插值计算
});
var mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
image.png

删除几何体面

var geometry = new THREE.BoxGeometry(100, 100, 100); //创建一个立方体几何对象Geometry
// pop():删除数组的最后一个元素   shift:删除数组的第一个元素
geometry.faces.pop();
// geometry.faces.shift();
var material = new THREE.MeshLambertMaterial({
  color: 0x0000ff,
  // side: THREE.DoubleSide, //内外面可见
}); //材质对象Material
var mesh = new THREE.Mesh(geometry, material); //网格模型对象Mesh
scene.add(mesh); //网格模型添加到场景中
image.png

8. 几何体旋转、缩放、平移

  • 缩放scale( x : Float, y : Float, z : Float )
var geometry = new THREE.BoxGeometry(100, 100, 100); //创建一个立方体几何对象Geometry
geometry.scale(0.5, 2, 2);
image.png
  • 平移translate( x : Float, y : Float, z : Float )
var geometry = new THREE.BoxGeometry(100, 100, 100); // 默认在原点
geometry.translate(0, 0, 100);
image.png
  • 旋转,参数为弧度
    • rotateX
    • rotateY
    • rotateZ
var geometry = new THREE.BoxGeometry(100, 100, 100);
geometry.rotateX(Math.PI / 4); // 绕x轴旋转45度
image.png
  • 局中.center()
var geometry = new THREE.BoxGeometry(100, 100, 100); //创建一个立方体几何对象Geometry
geometry.translate(0, 0, 100);
geometry.center(); // 居中:偏移的几何体居中
image.png

变换的本质:改变几何体的顶点位置数据

三、材质对象

1. 常用材质对象

材质的本质:顶点着色器、片元着色器代码、unifomrs数据。

点材质

  • PointsMaterial
var geometry = new THREE.SphereGeometry(100, 25, 25); //创建一个球体几何对象
// 创建一个点材质对象
var material = new THREE.PointsMaterial({
  color: 0x0000ff, //颜色
  size: 3, //点渲染尺寸
});
//点模型对象  参数:几何体  点材质
var point = new THREE.Points(geometry, material);
scene.add(point); //网格模型添加到场景中
image.png

线材质

  • LineBasicMaterial 线基础材质
var geometry = new THREE.BoxGeometry(100, 100, 100);//立方体几何体
// 直线基础材质对象
var material = new THREE.LineBasicMaterial({
    color: 0x0000ff
});
var line = new THREE.Line(geometry, material); //线模型对象
scene.add(line); //线模型添加到场景中
image.png
  • LineDashedMaterial 虚线材质
var geometry = new THREE.BoxGeometry(100, 100, 100);//立方体几何体
// 虚线材质对象:产生虚线效果
var material = new THREE.LineDashedMaterial({
    color: 0x0000ff,
    dashSize: 10,//显示线段的大小。默认为3。
    gapSize: 5,//间隙的大小。默认为1
});
var line = new THREE.Line(geometry, material); //线模型对象
//  computeLineDistances方法  计算LineDashedMaterial所需的距离数组
line.computeLineDistances(); // 不调用无法形成虚线
scene.add(line); //线模型添加到场景中
image.png

网格材质

  • MeshBasicMaterial 基础网格材质,不受光照影响
// 基础网格材质  不受光照影响  没有棱角感
var material = new THREE.MeshBasicMaterial({
  color: 0x0000ff,
  // transparent:true, // 开启透明度
  // opacity:0.5, // 设置透明度具体值
  // wireframe:true, // 线框模式
});
// material.opacity = 0.5; // 属性设置
image.png
  • MeshLambertMaterial Lambert网格材质,暗淡,与光照有反应(漫反射
// Lambert网格材质 与光照计算  漫反射   产生棱角感
var material = new THREE.MeshLambertMaterial({
  color: 0x00ff00,
});
image.png
  • MeshPhongMaterial 高光Phong材质,高亮表面,与光照有反应(镜面反射
// 与光照计算  高光效果(镜面反射)    产生棱角感
var material = new THREE.MeshPhongMaterial({
  color: 0xff0000,
  specular: 0x444444, // 高光颜色
  shininess: 30, // 光照强度系数
});
image.png
  • MeshToonMaterial 卡通材质。与 MeshPhongMaterial 高亮材质类似,但它不是平滑地着色,而是使用一个渐变图(一个X乘1的纹理)来决定如何着色。默认使用的渐变图是前70%的部分使用70%的亮度,之后的部分使用100%的亮度,当然,你可以定义你自己的渐变图。这最终会给人一种2色调的感觉,看起来就像卡通一样。
const colors = new Uint8Array(3);
for ( let c = 0; c <= colors.length; c ++ ) {
     colors[ c ] = ( c / colors.length ) * 256;
}
const gradientMap = new THREE.DataTexture( colors, colors.length, 1, THREE.RedFormat );
const toonMaterial = new THREE.MeshToonMaterial( {
    color: 0x00ffff,
    gradientMap: gradientMap // 卡通着色的渐变贴图
} );
image.png
  • MeshStandardMaterial PBR物理材质,相比较高光Phong材质可以更好的模拟金属、玻璃等效果
    • roughness 粗糙度,0-1,0:平滑的镜面反射(有光泽),1:完全漫反射(无强烈反光)。
    • metalness 金属度,0:非金属,1:金属,通常没有中间值。
const standardMaterial = new THREE.MeshStandardMaterial({
    color: 0xff8c00,
    specular: 0x444444, // 高光颜色
    shininess: 30, // 镜面高光的光泽度
    roughness: 0.5, // 材质的粗糙程度。默认值为1.0
    metalness: 0, // 材质与金属的相似度。默认值为0.0
});
image.png

这里是一个快速示例,从左至右看,粗糙度从0到1,从上至下看,金属度从0到1。

image.png

  • MeshPhysicalMaterial 与 MeshStandardMaterial相同,但它增加了2个参数,指定光泽层。
    • clearcoat 光泽层,当需要在表面加一层薄薄的半透明材质的时候,可以使用。范围0-1,有些类似于车漆,碳纤,被水打湿的表面的材质
    • clearCoatRoughness 光泽层的粗糙程度
const physicalMaterial = new THREE.MeshPhysicalMaterial({
    color: 0xff8c00,
    specular: 0x444444, // 高光颜色
    shininess: 30, // 镜面高光的光泽度
    roughness: 0.5, // 材质的粗糙程度。0.0表示平滑的镜面反射,1.0表示完全漫反射。默认值为1.0
    // metalness: 1, // 材质与金属的相似度。非金属材质,如木材或石材,使用0.0,金属使用1.0,通常没有中间值。 默认值为0.0
    clearcoat: 0.5, // 光泽层的亮度
    clearCoatRoughness: 0 // 光泽层的粗糙程度
});
image.png

这里是和上面一样的按 metalness 划分的 roughness 网格,但可以设置 clearcoat 和 clearCoatRoughness 。

image.png

  • MeshDepthMaterial 网格深度材质,渲染的效果和模型像素对应顶点距离相机的位置远近有关。MeshDepthMaterial渲染每个像素的深度,其中处在摄像机负近端面的像素其深度为0,处在摄像机负远端面的像素其深度为1。

image.png

  • MeshNormalMaterial 网格法向量材质,会绘制视图空间法线(相对于摄像机的法线)。x 是红色, y 是绿色, 以及 z 是蓝色,所以朝向右边的东西是粉红色,朝向左边的是水蓝色,朝上的是浅绿色,朝下的是紫色,朝向屏幕的是淡紫色。

image.png

  • ShadowMaterial 阴影材质

更复杂的材质会消耗更多的GPU功耗。如果你不需要额外的功能,那就使用最简单的材质。

各种标准材质的构建速度从最快到最慢:MeshBasicMaterial ➡ MeshLambertMaterial ➡ MeshPhongMaterial ➡ MeshToonMaterial ➡ MeshStandardMaterial ➡ MeshPhysicalMaterial

精灵Sprite材质

  • SpriteMaterial
var spriteMaterial = new THREE.SpriteMaterial({
  color:0xff00ff,//设置精灵矩形区域颜色
  rotation: Math.PI/4,//绕垂直屏幕方向旋转45度,弧度值
  lights: false, // 受光照影响
  // map: texture,//设置精灵纹理贴图
});

image.png

自定义着色器材质

  • ShaderMaterial 通过three.js的着色器系统来制作自定义材质。

  • RawShaderMaterial 可以用来制作完全自定义的着色器,不需要three.js的帮助。

2. 模型构成

  • 点模型:Points
var geometry = new THREE.SphereGeometry(100, 25, 25); //创建一个球体几何对象
// 创建一个点材质对象
var material = new THREE.PointsMaterial({
  color: 0x0000ff, //颜色
  size: 3, //点渲染尺寸
});
//点模型对象  参数:几何体  点材质
var point = new THREE.Points(geometry, material);
scene.add(point); //网格模型添加到场景中
image.png
  • 线模型

    • Line 直线模型
    var geometry = new THREE.BoxGeometry(100, 100, 100); //创建一个立方体几何对象Geometry
    // 线条渲染模式
    var material=new THREE.LineBasicMaterial({
        color:0xff0000 //线条颜色
    });//材质对象
    var line=THREE.Line(geometry,material) // 直线模型对象
    
    image.png
    • LineLoop 闭环线模型
    var line=new THREE.LineLoop(geometry,material);//闭环线模型对象
    scene.add(line);//线条对象添加到场景中
    
    image.png
    • LineSegments 间断线模型
    var line=new THREE.LineSegments(geometry,material);//间断线模型对象
    scene.add(line);//线条对象添加到场景中
    
    image.png
  • 网格材质

    • 网格模型Mesh
    var mesh = new THREE.Mesh(geometry, material); //网格模型对象Mesh
    scene.add(mesh)
    
    image.png
    • 骨骼类网格模型SkinnedMesh
    var material = new THREE.MeshPhongMaterial({
      skinning: true, //允许蒙皮动画
    });
    var SkinnedMesh = new THREE.SkinnedMesh(geometry, material);
    scene.add(SkinnedMesh); //网格模型添加到场景中
    ...
    var skeleton = new THREE.Skeleton([Bone1, Bone2, Bone3]); //创建骨骼系统
    SkinnedMesh.add(Bone1); //根骨头关节添加到网格模型
    SkinnedMesh.bind(skeleton); //网格模型绑定到骨骼系统
    skeleton.bones[1].rotation.x = 0.5;
    skeleton.bones[2].rotation.x = 0.5;
    
    image.png
  • 精灵模型Sprite

// 创建精灵模型对象,不需要几何体geometry参数
var sprite = new THREE.Sprite(spriteMaterial);
scene.add(sprite);

image.png

比较线模型的线条绘制模式和网格模型的线条绘制模式

  • 网格模型的线框绘制模式
var geometry = new THREE.BoxGeometry(100, 100, 100); //创建一个立方体几何对象Geometry
// 三角面(网格)渲染模式   MeshLambertMaterial  MeshBasicMaterial
var material = new THREE.MeshBasicMaterial({
  color: 0x0000ff, //三角面颜色
  wireframe:true,//网格模型以线条的模式渲染
}); //材质对象
var mesh = new THREE.Mesh(geometry, material); //网格模型对象Mesh
scene.add(mesh); //网格模型添加到场景中
image.png
  • 线模型的线条绘制模式
var geometry = new THREE.BoxGeometry(100, 100, 100); 
var material = new THREE.LineBasicMaterial({
  color: 0x0000ff,
});
var mesh = new THREE.Line(geometry, material);
scene.add(mesh);
image.png

两个模式绘制时使用的顶点不同: image.png

3. 材质共有属性、私有属性

材质共有属性:

  • opacity 透明度,0~1
  • transparent 是否开启透明度设置,默认值为false
  • vertexColors
    • THREE.NoColors 将材质的颜色应用于所有面(默认值)->取决于材质属性.color
    • THREE.VertexColors 通过顶点颜色进行插值计算渲染->取决于几何体的顶点颜色geometry.attributes.color
    • THREE.FaceColors 根据每个Face3 Color值对面部进行着色->取决于三角面的顶点颜色
  • side
    • THREE.FrontSide 仅前面可见(默认值)
    • THREE.BackSide 仅背面可见
    • THREE.DoubleSide 双面可见

材质私有属性:

  • color:材质颜色。点、线、网格材质具有,自定义着色器材质ShaderMaterialRawShaderMaterial不具有;
  • wireframe:线框模式渲染,默认值为false。网格材质具有。
// 基础网格材质  不受光照影响  没有棱角感
var material = new THREE.MeshBasicMaterial({
  color: 0x0000ff,
  // transparent:true, // 开启透明度
  // opacity:0.5, // 设置透明度具体值
  // wireframe:true, // 线框模式
  // side:THREE.DoubleSide, // 双面可见
});
// material.opacity = 0.5; // 属性设置

四、模型对象

1. 点、线、网格模型介绍

参照“材质对象-模型构成”。

2. 模型对象旋转、平移、缩放

  • 缩放:.scale
mesh.scale.set(0.5, 1.5, 2) // 网格模型xyz方向分别缩放0.5,1.5,2倍
mesh.scale.x = 2.0; // x轴方向放大2倍
  • 旋转:
    • .rotateX(angle).rotateY(angle).rotateZ(angle)
    • .rotateOnAxis(axis, angle)
      • axis 方向 Vector3
      • angle 角度,单位弧度
mesh.rotateY(Math.PI / 2);// 绕着Y轴旋转90度

var axis = new THREE.Vector3(1, 1, 1); // 向量Vector3对象表示方向
axis.normalize(); // 向量归一化
mesh.rotateOnAxis(axis, Math.PI / 2) // 沿着axis轴表示方向旋转90度
console.log(mesh.rotation); // 旋转方法,改变了rotation属性
  • 平移:
    • .translateX(distance).translateY(distance).translateZ(distance)
    • .translateOnAxis(axis, angle)
      • axis 方向 Vector3
      • distance 平移距离
mesh.translateX(100); // 网格模型沿着x轴方向平移100

var axis = new THREE.Vector3(1, 1, 1);
axis.normalize(); //向量归一化
mesh.translateOnAxis(axis, 100); // 沿着axis轴表示方向平移100
console.log(mesh.position); // 平移方法,改变了position属性
image.png

几何变换的区别

  • 网格模型Mesh对象
    • 不会改变顶点位置数据
    • 旋转平移缩放方法改变的是模型对象的scalerotationposition属性
  • 几何体Geometry对象
    • 改变顶点位置数据

3. 对象克隆clone、复制copy

Threejs大多数对象都有克隆.clone()和复制.copy()两个方法,点模型Points、线模型Line、网格网格模型Mesh一样具有这两个方法。

  • 克隆clone mesh2 = mesh1.clone()
    • 对象mesh1执行克隆方法,返回一个新的对象mesh2
    • mesh1和mesh2共享几何体geometry和材质material对象,复制的是geometry和material对象的引用。几何体和材质属性的属性值改变,mesh1和mesh2的颜色都会改变。(浅拷贝
    • mesh2会获得mesh1的位置、角度、矩阵等属性,复制的是具体的值,而不是引用。mesh2和mesh1的位置、角度等属性改变互不影响。(深拷贝
var geometry = new THREE.BoxGeometry(20, 20, 20);
var material = new THREE.MeshLambertMaterial({
  color: 0x0000ff
});

var mesh = new THREE.Mesh(geometry, material); //网格模型对象Mesh
for (let i = 0; i < 10; i++) {
  // 执行clone方法克隆mesh;
  var newMesh = mesh.clone();
  // 等同于
  // var mesh = new THREE.Mesh(geometry, material);
  newMesh.translateY(i * 25);
  scene.add(newMesh)
}
image.png
  • 复制copy mesh2 .copy(mesh1)
    • 对象从另一个对象复制数据
    • 从一个网格模型对象复制非几何体、材质对象。mesh2复制mesh1的位置、旋转、矩阵等属性(不包含geometry和material属性)
var geometry = new THREE.BoxGeometry(20, 20, 20); 
var material = new THREE.MeshLambertMaterial({
  color: 0x0000ff
});
var mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
// mesh经过一系列变换
mesh.translateX(50);
mesh.rotateX(Math.PI / 4);
mesh.translateZ(50);

var geometry2 = new THREE.BoxGeometry(30, 30, 30);
var material2 = new THREE.MeshLambertMaterial({
  color: 0xff00ff
});
var newMesh = new THREE.Mesh(geometry2, material2);
// 复制mesh的位置、旋转、矩阵等属性(不包含geometry和material属性)
newMesh.copy(mesh);
// 相比mesh而言,在平移
newMesh.translateX(-50);
scene.add(newMesh)
image.png

五、光源对象

1. 光照原理和常见光源类型

常见光源类型:

  • 环境光

    • AmbientLight 环境光。没有特定方向,只有颜色

    环境光:显示颜色 = 环境光颜色 * 材质颜色

  • 方向光

    • PointLight 点光源
    • DirectionalLight 平行光,比如太阳光
    • SpotLight 聚光灯光源
    image.png

方向光:漫反射光的颜色 = 几何体表面基色 x 光线颜色 x 光线入射角余弦值 image.png

Threejs在渲染的时候网格模型材质的颜色值mesh.material.color和光源的颜色值light.color会进行相乘,简单说就是RGB三个分量分别相乘。

漫反射数学模型RGB分量表示:(R2,G2,B2) = (R1,G1,B1) x (R0,G0,B0) x cosθ

R2 = R1 * R0 * cosθ
G2 = G1 * G0 * cosθ
B2 = B1 * B0 * cosθ

光源辅助对象:

  • SpotLightHelper 聚光灯光源辅助对象
  • PointLightHelper 点光源辅助对象
  • DirectionalLightHelper 平行光源辅助对象

光源辅助对象,就像AxesHelper可视化显示三维坐标轴一样显示光源对象,通过这些辅助对象可以方便调试代码,查看位置、方向。

  1. 环境光,设置整个空间的明暗效果,整个立方体没有任何棱角感
// 环境光
var ambient = new THREE.AmbientLight(0x444444);
scene.add(ambient);
image.png

ps:一般设置明暗程度不同的白光RGB三个分量值是一样的。

  1. 点光源,平面不同区域呈现出不同的明暗效果(棱角感),点光源无法照射的地方相对其他位置会比较暗。需要设置光源位置
//点光源
var point = new THREE.PointLight(0xffffff);
point.position.set(400, 200, 300); //点光源位置
scene.add(point); //点光源添加到场景中
image.png
  1. 聚光灯光源,沿着特定方逐渐发散。需要设置光源位置、照射对象、发散角度
// 聚光光源
var spotLight = new THREE.SpotLight(0xffffff);
// 设置聚光光源位置
spotLight.position.set(200, 200, 200);
// 聚光灯光源指向网格模型mesh2
spotLight.target = mesh2;
// 设置聚光光源发散角度
spotLight.angle = Math.PI / 6
scene.add(spotLight);//光对象添加到scene场景中

只照射mesh2,mesh1没有光照是黑色的:

image.png
  1. 平行光源,平面不同区域接收到平行光的入射角一样。主要是确定光线方向

光源的方向:通过 光源position属性照射对象的position属性 计算,仅仅设置光线位置是不起作用的。

// 平行光
var directionalLight = new THREE.DirectionalLight(0xffffff, 1);
// 注意:位置属性在这里不代表方向光的位置,仅用于计算平行光的方向。你可以认为方向光没有位置
directionalLight.position.set(80, 100, 50);
// 方向光指向对象,可以不设置,默认的位置是0,0,0
directionalLight.target = mesh2;
scene.add(directionalLight);
image.png

聚光光源、平行光源的target属性设置

target属性是一个Object3d类型的对象。可以自定义光照照射对象,记得把目标对象添加到scene场景中

// 聚光光源
var spotLight = new THREE.SpotLight(0xffffff);
// 设置聚光光源位置
spotLight.position.set(200, 200, 200);
// 设置聚光光源发散角度
spotLight.angle = Math.PI / 6

// 1. 聚光灯光源指向网格模型mesh2
// spotLight.target = mesh2;

// 2. 直接设置target属性  记得把目标对象添加到scene场景中
// spotLight.target.position.set(150,0,0);
// scene.add(spotLight.target)

// 3. 创建一个用来设置target的Object3D对象
var targetObject = new THREE.Object3D();
targetObject.translateX(150);
scene.add(targetObject);
spotLight.target = targetObject;

scene.add(spotLight);
image.png

2. 阴影投影计算

  • 设置产生投影的模型对象 mesh.castShadow = true;

  • 设置接收投影效果的模型 planeMesh.receiveShadow = true;

  • 设置用于计算阴影的光源对象 light.castShadow = true;

  • 设置计算阴影的区域,最好刚好紧密包围在对象周围,过大会模糊(阴影纹理贴图出现严重锯齿),过小阴影看不到或显示不完整

    • 平行光:长方体区域,shadow.camera为OrthographicCamera正交投影
    directionalLight.shadow.camera.near = 0.5;
    directionalLight.shadow.camera.far = 300;
    directionalLight.shadow.camera.left = -50;
    directionalLight.shadow.camera.right = 50;
    directionalLight.shadow.camera.top = 200;
    directionalLight.shadow.camera.bottom = -100;
    // 设置mapSize属性可以使阴影更清晰,不那么模糊,但是会消耗更多的性能,建议设置shadow.camera
    // directionalLight.shadow.mapSize.set(1024,1024) // 默认 512,512
    
    • 聚光灯:锥形区域,shadow.camera为PerspectiveCamera透视投影
    spotLight.shadow.camera.near = 1;
    spotLight.shadow.camera.far = 300;
    spotLight.shadow.camera.fov = 20;
    
  • 设置webgl渲染器 renderer.shadowMap.enabled = true;

完整代码:

var scene = new THREE.Scene();

var geometry = new THREE.BoxGeometry(40, 100, 40);
var material = new THREE.MeshLambertMaterial({
  color: 0x0000ff
}); 
var mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
// 1. 设置产生投影的网格模型
mesh.castShadow = true;

var planeGeometry = new THREE.PlaneGeometry(300, 200);
var planeMaterial = new THREE.MeshLambertMaterial({
  color: 0x999999
}); 
var planeMesh = new THREE.Mesh(planeGeometry, planeMaterial);
scene.add(planeMesh);
planeMesh.rotateX(-Math.PI / 2);
planeMesh.position.y = -50;
// 2. 设置接收阴影的投影面
planeMesh.receiveShadow = true;

// 环境光
var ambient = new THREE.AmbientLight(0x444444);
scene.add(ambient);

// 一、平行光
// var directionalLight = new THREE.DirectionalLight(0xffffff, 1);
// directionalLight.position.set(60, 100, 40);
// scene.add(directionalLight);
// 3. 设置用于计算阴影的光源对象
// directionalLight.castShadow = true;
// 4. 设置计算阴影的区域,最好刚好紧密包围在对象周围
// 计算阴影的区域过大:模糊  过小:看不到或显示不完整
// directionalLight.shadow.camera.near = 0.5;
// directionalLight.shadow.camera.far = 300;
// directionalLight.shadow.camera.left = -50;
// directionalLight.shadow.camera.right = 50;
// directionalLight.shadow.camera.top = 200;
// directionalLight.shadow.camera.bottom = -100;
// 设置mapSize属性可以使阴影更清晰,不那么模糊,但是会消耗更多的性能,建议设置shadow.camera
// directionalLight.shadow.mapSize.set(1024,1024)

// 二、聚光光源
var spotLight = new THREE.SpotLight(0xffffff);
spotLight.position.set(50, 90, 50);
spotLight.angle = Math.PI /6
scene.add(spotLight);
// 3. 设置用于计算阴影的光源对象
spotLight.castShadow = true;
// 4. 设置计算阴影的区域,注意包裹对象的周围
spotLight.shadow.camera.near = 1;
spotLight.shadow.camera.far = 300;
spotLight.shadow.camera.fov = 20;

// 聚光光源辅助显示
var spotLightHelper = new THREE.SpotLightHelper(spotLight);
scene.add(spotLightHelper);

// 相机
var width = window.innerWidth;
var height = window.innerHeight;
var k = width / height;
var s = 150; 
var camera = new THREE.OrthographicCamera(-s * k, s * k, s, -s, 1, 1000);
camera.position.set(200, 300, 200);
camera.lookAt(scene.position);

// 渲染器
var renderer = new THREE.WebGLRenderer();
renderer.setSize(width, height);
renderer.setClearColor(0xb9d3ff, 1);
document.body.appendChild(renderer.domElement);
// 5. 设置渲染器,允许场景中使用阴影贴图
renderer.shadowMap.enabled = true;
function render() {
  renderer.render(scene, camera);
}
render();
var controls = new THREE.OrbitControls(camera,renderer.domElement);
controls.addEventListener('change', render);
image.png

3. 基类Light和Object3D

查看文档SpotLightDirectionalLight、环境光AmbientLight等光源对象都有一个共同的基类Light,光源Light也有一个基类Object3D。也就是说Threejs环境光、点光源等子类光源可以继承LightObject3D两个父类的属性和方法。

  • 继承基类Light的属性:光源颜色.color光源强度.intensity
// 环境光:颜色设置为`0xffffff`,强度系数设置为0.5 
var ambient = new THREE.AmbientLight(0xffffff,0.5); scene.add(ambient);
// 等同于
var ambient = new THREE.AmbientLight(0xffffff); // 设置光照颜色
ambient.intensity = 0.5; // 设置光照强度属性
  • 继承Light的基类Object3D的属性:光源位置.position
var point = new THREE.PointLight(0xffffff);//点光源
point.position.set(400, 200, 300); // 设置点光源位置
scene.add(point); // 继承父类Object3D

六、层级模型、树结构

1. 组对象Group、层级模型

层级模型:

  • 根节点 scene
  • 中间节点 group1
  • 叶子节点 baseMesh

父对象group进行旋转、缩放、平移变换,子对象同样跟着变换

var geometry = new THREE.BoxGeometry(20, 20, 20);
var material = new THREE.MeshLambertMaterial({
  color: 0x0000ff
});

// 网格模型mesh沿着x轴方向阵列
var group1 = new THREE.Group();
// 共享材质和几何体数据,批量创建mesh
var baseMesh = new THREE.Mesh(geometry, material); //网格模型对象Mesh
for (let i = 0; i < 10; i++) {
  var mesh = baseMesh.clone()
  mesh.translateX(i * 25); // 平移该网格模型
  group1.add(mesh); // 把网格模型插入到组group1中
}

// group1沿着y轴方向阵列
for (let i = 0; i < 10; i++) {
  var newGroup = group1.clone(); // 克隆组group1
  newGroup.translateY(i * 25); //沿着z轴平移
  scene.add(newGroup); //场景中插入组group1克隆的对象
}
console.log(scene)
image.png

子对象.children

Threejs场景对象Scene、组对象Group都有一个子对象属性.children,通过该属性可以访问父对象的子对象。

image.png
  • .add()方法给父对象添加子对象。可以插入一个/多个子对象。
  • .remove()方法是删除父对象中的子对象。同样可以插入一个/多个子对象。

2. 对象节点命名、查找、遍历

  • 对象节点属性

    • id 唯一的,系统自动分配,id号和创建对象的顺序有关
    • name 字符串,手动命名,可能重名
    var headMesh = sphereMesh(10, 0, 0, 0);
    headMesh.name = "脑壳"
    var leftEyeMesh = sphereMesh(1, 8, 5, 4);
    leftEyeMesh.name = "左眼"
    var rightEyeMesh = sphereMesh(1, 8, 5, -4);
    rightEyeMesh.name = "右眼"
    var headGroup = new THREE.Group();
    headGroup.name = "头部"
    
    • type 构造函数类型
    • children 子节点数组
    • parent 父节点
  • 递归遍历方法.traverse()

// 递归遍历场景对象scene  obj:每次遍历的对象
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)
  }
  
  console.log(obj.id); // 打印id属性
  console.log(obj.parent); // 打印该对象的父对象
  console.log(obj.children); // 打印该对象的子对象
})
  • 查找某个具体的对象节点

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

3. 本地位置坐标、世界位置坐标

访问模型的位置属性.position获得模型在本地坐标系或者说模型坐标系下的三维坐标,通过模型的.getWorldPosition()方法获得该模型在世界坐标下的三维坐标。

网格模型的世界坐标网格模型的位置属性.position网格模型父对象group的位置属性.position累加

  • .position 本地坐标
  • .scale 本地缩放参数
  • .getWorldPosition() 返回世界坐标
  • .getWorldScale() 获得世界缩放参数
var mesh = new THREE.Mesh(geometry, material); //网格模型对象Mesh
mesh.position.set(50, 0, 0)
var group = new THREE.Group();
group.add(mesh); //网格模型添加到组中
group.position.set(50, 0, 0)
scene.add(group); //组添加到场景中

/* 获得世界坐标 */

// 该语句默认在threejs渲染的过程中执行  如果想获得世界矩阵属性、世界位置属性等属性,需要手动更新
scene.updateMatrixWorld(true);
// 声明一个三维向量用来保存世界坐标
var worldPosition = new THREE.Vector3();
// 执行getWorldPosition方法把模型的世界坐标保存到参数worldPosition中
mesh.getWorldPosition(worldPosition)
// mesh的世界坐标是mesh位置属性`.position`和mesh父对象group位置属性`.position`的累加
console.log('世界坐标', worldPosition); // {x: 100, y: 0, z: 0}
console.log('本地坐标', mesh.position);// {x: 50, y: 0, z: 0}

七、几何体对象、曲线、三维建模

1. 常见几何体和曲线API介绍

  • 几何体:

几何体本质上就是threejs生成顶点的算法,所有几何体的基类分为GeometryBufferGeometry两大类,两类几何体直接可以相互转化。

image.png

  • 曲线:

曲线和几何体同样本质上都是用来生成顶点的算法,曲线主要是按照一定的规则生成一系列沿着某条轨迹线分布的顶点。

image.png

圆弧线 ArcCurve

new THREE.ArcCurve( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise )

  • aXaY 圆弧圆心坐标
  • aRadius 圆弧半径
  • aStartAngleaEndAngle 起始角度
  • aClockwise 是否顺时针绘制,默认值为false

绘制圆弧步骤:

  • 声明几何体对象Geometry/缓冲几何体
var geometry = new THREE.Geometry();
// var geometry = new THREE.BufferGeometry();
  • 创建圆弧
// 参数:0, 0圆弧坐标原点x,y 100:圆弧半径 0, 2 * Math.PI:圆弧起始角度 
var arc = new THREE.ArcCurve(0, 0, 100, 0, 2 * Math.PI);
  • 获取圆弧绘制顶点 .getPoints()
// getPoints是基类Curve的方法,返回一个vector2/vector3对象作为元素组成的数组 
// 将曲线等分为几部分,每隔一定距离取一个顶点
var points = arc.getPoints(50); // 分段数50,返回51个顶点
  • 将圆弧绘制顶点作为几何体的顶点数据 .setFromPoints()
// setFromPoints方法从points中提取数据改变几何体的顶点属性
// 如果几何体是Geometry,改变的是.vertices
// 如果几何体是BufferGeometry,改变的是.attributes.position
geometry.setFromPoints(points);
  • 绘制几何体(圆弧)
var material = new THREE.LineBasicMaterial({
  color: 0x000000
});
var line = new THREE.Line(geometry, material);
scene.add(line); 
image.png
  • 修改圆弧起始角度,绘制弧线
var arc = new THREE.ArcCurve(0, 0, 100, Math.PI, 2 * Math.PI);
image.png

直线 LineCurve

  • 三维直线 LineCurve3
var geometry = new THREE.Geometry(); 
var p1 = new THREE.Vector3(50, 0, 0); //顶点1坐标
var p2 = new THREE.Vector3(0, 70, 0); //顶点2坐标
// 三维直线LineCurve3
var LineCurve = new THREE.LineCurve3(p1, p2);
var pointArr = LineCurve.getPoints(10); // 10分段,共11个点
geometry.setFromPoints(pointArr);

var material = new THREE.LineBasicMaterial({
  color: 0xffff00,
});
var line = new THREE.Line(geometry, material);
scene.add(line); //线条对象添加到场景中
  • 二维直线 LineCurve
var geometry = new THREE.Geometry();
// 二维直线LineCurve
var LineCurve = new THREE.LineCurve(new THREE.Vector2(50, 0), new THREE.Vector2(0, 70));
var pointArr = LineCurve.getPoints(10); // 10分段,共11个点
geometry.setFromPoints(pointArr);

var material = new THREE.LineBasicMaterial({
  color: 0xffff00,
});
var line = new THREE.Line(geometry, material);
scene.add(line);
image.png

3. 样条曲线、贝塞尔曲线

规则的曲线比如圆、椭圆、抛物线都可以用一个函数去描述,对于不规则的曲线无法使用一个特定的函数去描述,这也就是样条曲线和贝塞尔曲线出现的原因。

  • 三维样条曲线 CatmullRomCurve3:经过一系列点创建平滑的样条曲线
/* 在三维空间中设置5个顶点,输入三维样条曲线CatmullRomCurve3作为参数,返回更多个顶点,通过返回的顶点数据,构建一个几何体,通过`Line`可以绘制出来一条沿着5个顶点的光滑样条曲线。 */
var geometry = new THREE.Geometry(); //声明一个几何体对象Geometry
// 三维样条曲线  Catmull-Rom算法
var curve = new THREE.CatmullRomCurve3([
  new THREE.Vector3(-50, 20, 90),
  new THREE.Vector3(-10, 40, 40),
  new THREE.Vector3(0, 0, 0),
  new THREE.Vector3(60, -60, 0),
  new THREE.Vector3(70, 0, 80)
]);
var points = curve.getPoints(100);
geometry.setFromPoints(points);

var material = new THREE.LineBasicMaterial({
  color: 0x000000
});
var line = new THREE.Line(geometry, material);
scene.add(line);
image.png
  • 二次贝塞尔曲线:起点、终点和1个控制点定义
    • 2D:QuadraticBezierCurve Vector2
    • 3D:QuadraticBezierCurve3 Vector3
var geometry = new THREE.Geometry(); //声明一个几何体对象Geometry
var p1 = new THREE.Vector3(-80, 0, 0);
var p2 = new THREE.Vector3(20, 100, 0);
var p3 = new THREE.Vector3(80, 0, 0);
// 三维二次贝赛尔曲线
var curve = new THREE.QuadraticBezierCurve3(p1, p2, p3);
var points = curve.getPoints(100);
geometry.setFromPoints(points);

var material = new THREE.LineBasicMaterial({
  color: 0x000000
});
var line = new THREE.Line(geometry, material);
scene.add(line); 

// 辅助线/点
var geometry2 = new THREE.Geometry();
geometry2.vertices.push(p1, p2, p3)
var material2 = new THREE.PointsMaterial({
  color: 0xff00ff,
  size: 10,
});
var points = new THREE.Points(geometry2, material2);
scene.add(points); // 辅助点
scene.add(new THREE.Line(geometry2, material2)); // 辅助线
1659319560957.jpg
  • 三次贝塞尔曲线:起点、终点和2个控制点定义
    • 2D:CubicBezierCurve
    • 3D:CubicBezierCurve3
var geometry = new THREE.Geometry(); 
var p1 = new THREE.Vector3(-80, 0, 0);
var p2 = new THREE.Vector3(-40, 100, 0);
var p3 = new THREE.Vector3(40, 100, 0);
var p4 = new THREE.Vector3(80, 0, 0);
// 三维三次贝赛尔曲线
var curve = new THREE.CubicBezierCurve3(p1, p2, p3, p4);
var points = curve.getPoints(100); 
geometry.setFromPoints(points);

var material = new THREE.LineBasicMaterial({
  color: 0x000000
});
var line = new THREE.Line(geometry, material);
scene.add(line);
image.png

设置控制点相对应:

var p3 = new THREE.Vector3(40, -100, 0);
image.png

4. 多个线条组合曲线CurvePath

通过组合曲线CurvePath可以把多个圆弧线、样条曲线、直线等多个曲线(基类Curve)合并成一个曲线

var geometry = new THREE.Geometry(); //声明一个几何体对象Geometry
// 绘制一个U型轮廓
var R = 80; // 圆弧半径
var arc = new THREE.ArcCurve(0, 0, R, 0, Math.PI, true);
// 半圆弧的一个端点作为直线的一个端点
var line1 = new THREE.LineCurve(new THREE.Vector2(R, 200, 0), new THREE.Vector2(R, 0, 0));
var line2 = new THREE.LineCurve(new THREE.Vector2(-R, 0, 0), new THREE.Vector2(-R, 200, 0));
// 创建组合曲线对象CurvePath
var CurvePath = new THREE.CurvePath();
// 把多个线条插入到CurvePath中
CurvePath.curves.push(line1, arc, line2);
var points = CurvePath.getPoints(200); // 分段数200
geometry.setFromPoints(points); // 设置几何体顶点

var material = new THREE.LineBasicMaterial({
  color: 0x000000
});
var line = new THREE.Line(geometry, material);
scene.add(line);
image.png

5. 曲线路径管道 TubeGeometry

TubeGeometry的功能就是通过一条曲线的顶点生成一个管道几何体geometry的顶点数据、face3数据。它的本质就是以曲线上顶点为基准,生成一系列曲线等径分布的顶点数据

TubeGeometry(path, tubularSegments, radius, radiusSegments, closed)

  • path 扫描路径,基类Curve的路径构造函数
  • tubularSegments 管道轨迹细分数,默认64
  • radius 管道半径,默认1
  • radiusSegments 管道截面圆弧细分数,默认8
  • closed 管道是否闭合
//创建管道成型的路径(3D样条曲线)
var path = new THREE.CatmullRomCurve3([
  new THREE.Vector3(-10, -50, -50),
  new THREE.Vector3(10, 0, 0),
  new THREE.Vector3(8, 50, 50),
  new THREE.Vector3(-5, 0, 100)
]);
// path:路径   40:管道轨迹细分数  5:管道半径   25:管道截面圆弧细分数
var geometry = new THREE.TubeGeometry(path, 40, 5, 25);

var material = new THREE.MeshPhongMaterial({
  color: 0xE6A23C,
  side: THREE.DoubleSide //两面可见
});
var mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
image.png

线条模式渲染,查看细分数:

// path:路径   10:管道轨迹细分数  5:管道半径   5:管道截面圆弧细分数
var geometry = new THREE.TubeGeometry(path, 10, 5, 5);
...
material.wireframe = true;
...

管道轨迹细分数:

image.png

管道截面圆弧细分数:

image.png

多段曲线路径创建生成管道 curvePath.curves

// 创建多段线条的顶点数据
var p1 = new THREE.Vector3(-85.35, -35.36)
var p2 = new THREE.Vector3(-50, 0, 0);
var p3 = new THREE.Vector3(0, 50, 0);
var p4 = new THREE.Vector3(50, 0, 0);
var p5 = new THREE.Vector3(85.35, -35.36);
// 创建线条一:直线
let line1 = new THREE.LineCurve3(p1,p2);
// 重建线条2:三维样条曲线
var curve = new THREE.CatmullRomCurve3([p2, p3, p4]);
// 创建线条3:直线
let line2 = new THREE.LineCurve3(p4,p5);
var curvePath = new THREE.CurvePath();// 创建CurvePath对象
curvePath.curves.push(line1, curve, line2);// 插入多段线条
//通过多段曲线路径创建生成管道
var geometry = new THREE.TubeGeometry(curvePath, 100, 5, 25, false);
var material = new THREE.MeshPhongMaterial({
  color: 0xE6A23C,
  side: THREE.DoubleSide,//双面可见
});
var mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
image.png

6. 旋转成型 LatheGeometry

一组顶点(Vector2)以y轴为旋转轴进行旋转生成旋转轮廓。

image.png

顶点数据可以通过二维向量对象Vector2定义,也可以通过3D曲线或2D线条轮廓生成。

LatheGeometry(points, segments, phiStart, phiLength)

  • points Vector2表示的坐标数据组成的数组
  • segments 圆周方向细分数,默认12
  • phiStart 开始角度,默认0
  • phiLength 旋转角度,默认2π
// 三个顶点构成的轮廓(理解为两条相连的直线段)
var points = [
    new THREE.Vector2(50,60),
    new THREE.Vector2(25,0),
    new THREE.Vector2(50,-60)
];
// 30:旋转方向细分数   0,2*Math.PI:旋转起始角度设置
var geometry = new THREE.LatheGeometry(points,30,0,2*Math.PI);
var material=new THREE.MeshPhongMaterial({
    color:0x0000ff,
    side:THREE.DoubleSide
});
// material.wireframe = true;//线条模式渲染(查看细分数)
var mesh=new THREE.Mesh(geometry,material);
scene.add(mesh);
image.png image.png

样条曲线插值计算生成旋转轮廓

var points = [
    new THREE.Vector2(50,60),
    new THREE.Vector2(25,0),
    new THREE.Vector2(50,-60)
];
// SplineCurve:二维样条曲线
var splineCurve =  new THREE.SplineCurve(points);
var splinePoints = splineCurve.getPoints(50); // 分段数50,返回51个顶点
var geometry = new THREE.LatheGeometry(splinePoints,30);
...
image.png

7. Shape对象和轮廓填充

  • Shape 绘制二维形状
    • 内轮廓 .holes属性(Array)
  • ShapeGeometry 轮廓填充:根据轮廓的顶点使用三角面Face3自动填充中间区域
var points = [
  new THREE.Vector2(-50, -50),
  new THREE.Vector2(-60, 0),
  new THREE.Vector2(0, 50),
  new THREE.Vector2(60, 0),
  new THREE.Vector2(50, -50),
  new THREE.Vector2(-50, -50),
]
// 通过顶点定义轮廓
var shape = new THREE.Shape(points);
// 所谓填充:ShapeGeometry算法利用顶点计算出三角面face3数据填充轮廓
var geometry = new THREE.ShapeGeometry(shape, 25);
var material = new THREE.MeshPhongMaterial({
  color: 0x0000ff,
  side: THREE.DoubleSide, //两面可见
});
var mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
image.png

通过shpae基类path的方法绘制轮廓

// 通过shpae基类path的方法绘制轮廓(本质也是生成顶点)
var shape = new THREE.Shape();
// shape.absarc(0,0,100,0,2*Math.PI);//圆弧轮廓
// 四条直线绘制一个矩形轮廓
shape.moveTo(0,0);//起点
shape.lineTo(0,100);//第2点
shape.lineTo(100,100);//第3点
shape.lineTo(100,0);//第4点
shape.lineTo(0,0);//第5点
var geometry = new THREE.ShapeGeometry(shape, 25);
var material = new THREE.MeshPhongMaterial({
  color: 0x0000ff,
  side: THREE.DoubleSide, //两面可见
});
var mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
image.png

shape外轮廓和内轮廓

shape可以用来绘制外轮廓,也可以用来绘制内轮廓,ShapeGeometry会使用三角形自动填充shape内轮廓和外轮廓中间的中部。

下面给出了几个通过shape绘制的轮廓图案。

  • u形面
// 圆弧与直线连接
var shape = new THREE.Shape(); //Shape对象
var R = 50;
// 绘制一个半径为R、圆心坐标(0, 0)的半圆弧
shape.absarc(0, 0, R, 0, Math.PI);
//从圆弧的一个端点(-R, 0)到(-R, -200)绘制一条直线
shape.lineTo(-R, -200);
// 绘制一个半径为R、圆心坐标(0, -200)的半圆弧
shape.absarc(0, -200, R, Math.PI, 2 * Math.PI);
//从圆弧的一个端点(R, -200)到(-R, -200)绘制一条直线
shape.lineTo(R, 0);

var geometry = new THREE.ShapeGeometry(shape, 30);
...
image.png
  • 圆形挖孔
// 一个外轮廓圆弧嵌套三个内圆弧轮廓
var shape = new THREE.Shape(); //Shape对象
//外轮廓
shape.arc(0, 0, 100, 0, 2 * Math.PI);
// 内轮廓1
var path1 = new THREE.Path();
path1.arc(0, 0, 40, 0, 2 * Math.PI);
// 内轮廓2
var path2 = new THREE.Path();
path2.arc(80, 0, 10, 0, 2 * Math.PI);
// 内轮廓3
var path3 = new THREE.Path();
path3.arc(-80, 0, 10, 0, 2 * Math.PI);
//三个内轮廓分别插入到holes属性中
shape.holes.push(path1, path2, path3);

var geometry = new THREE.ShapeGeometry(shape, 30);
...
image.png

多个轮廓同时填充

// 轮廓对象1
var shape=new THREE.Shape();
shape.arc(-50,0,30,0,2*Math.PI);
// 轮廓对象2
var shape2=new THREE.Shape();
shape2.arc(50,0,30,0,2*Math.PI);
// 轮廓对象3
var shape3=new THREE.Shape();
shape3.arc(0,50,30,0,2*Math.PI);
// 多个shape作为元素组成数组,每一个shpae可以理解为一个要填充的轮廓
var geometry = new THREE.ShapeGeometry([shape,shape2,shape3], 30);
var material = new THREE.MeshPhongMaterial({
  color: 0x0000ff,
  side: THREE.DoubleSide, //两面可见
  // wireframe: true,
}); //材质对象
var mesh = new THREE.Mesh(geometry, material); //网格模型对象
scene.add(mesh);

image.png

8. 拉伸扫描成型 ExtrudeGeometry

构造函数ExtrudeGeometryShapeGeometry一样是利用Shape对象生成几何体对象,区别在于ExtrudeGeometry可以利用2D轮廓生成3D模型

new THREE.ExtrudeGeometry(shape, object)

  • shape 拉伸对象(二维轮廓),多个轮廓可以用数组。
  • object 拉伸参数
    • amount 拉伸长度,默认100
    • bevelEnabled 是否使用倒角
    • bevelSegments 倒角细分数,默认3
    • bevelThickness 倒角尺寸(经向)
    • curveSegments 拉伸轮廓细分数
    • steps 拉伸方向细分数
    • extrudePath 扫描路径THREE.CurvePath,默认Z轴方向
    • material 前后面材质索引号
    • extrudeMaterial 拉伸面、倒角面材质索引号
    • bevelSize 倒角尺寸(拉伸方向)

拉伸

image.png
var shape = new THREE.Shape();
// shape.absarc(50,50,40,0,2*Math.PI);//圆弧
shape.moveTo(0, 0);
shape.lineTo(0, 100); 
shape.lineTo(100, 100);
shape.lineTo(100, 0);
shape.lineTo(0, 0);
var geometry = new THREE.ExtrudeGeometry( //拉伸造型
  shape, //二维轮廓
  //拉伸参数
  {
    amount: 120, //拉伸长度
    // curveSegments: 35, //拉伸轮廓细分数
    // steps: 12, //拉伸方向的细分数
    // bevelEnabled: false, //无倒角
    // bevelSegments:1,//倒直角:设置为1  倒圆角:越大越光滑
    // bevelThickness: 30,//拉伸方向尺寸
    // bevelSize: 4,//径向尺寸
  }
);
image.png

扫描

image.png

对于扫描而言不需要定义amount属性设置拉伸距离,设置扫描路径即可(属性extrudePath

var shape = new THREE.Shape();
/**四条直线绘制一个矩形轮廓*/
shape.moveTo(0,0);//起点
shape.lineTo(0,10);//第2点
shape.lineTo(10,10);//第3点
shape.lineTo(10,0);//第4点
shape.lineTo(0,0);//第5点
/**创建轮廓的扫描轨迹(3D样条曲线)*/
var curve = new THREE.SplineCurve3([
    new THREE.Vector3( -10, -50, -50 ),
    new THREE.Vector3( 10, 0, 0 ),
    new THREE.Vector3( 8, 50, 50 ),
    new THREE.Vector3( -5, 0, 100)
]);
var geometry = new THREE.ExtrudeGeometry(//拉伸造型
    shape,//二维轮廓
    //拉伸参数
    {
         bevelEnabled:false,//无倒角
         extrudePath:curve,//选择扫描轨迹
         steps:50//沿着路径细分数
    }
);
image.png