几何
几何体本质上就是threejs生成顶点的算法,所有几何体的基类分为Geometry和BufferGeometry两大类,两类几何体直接可以相互转化。
Geometry与BufferGeometry
几何体Geometry和缓冲类型几何体BufferGeometry表达的含义相同,只是对象的结构不同,Threejs渲染的时候会先把Geometry转化为BufferGeometry再解析几何体顶点数据进行渲染。
-
BufferGeometry缓冲几何体:数据使用类型化数组集中表示geometry.attributesgeometry.attributes.position顶点位置geometry.attributes.color顶点颜色geometry.attributes.normal顶点法向量geometry.attributes.uv纹理贴图UV坐标geometry.attributes.uv2光照贴图UV2坐标
geometry.index顶点索引数据
通过
THREE.BufferAttribute进行设置 -
Geometry几何体:数据使用对象数组表示geometry.vertices顶点位置geometry.colors顶点颜色设置几何体
Geometry顶点颜色属性geometry.colors,对网格模型Mesh是无效的,对于点模型Points、线模型Line是有效的。faces三角面数组- 三角面颜色
face.color三个顶点颜色相同,一次性设置face.vertexColors三个顶点的颜色数组
- 三角面法向量
face.normal三个顶点法向量相同,一次性设置face.vertexNormals三个顶点的法向量数组
- 三角面颜色
geometry.faceVertexUvs[0]纹理贴图UV坐标geometry.faceVertexUvs[1]光照贴图UV2坐标
three.js在r125版本移除了
Geometry相关的api,现行最高版本r143:
Geometry和BufferGeometry的编码差别
- 几何体
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=rgb中的255
var color2 = new THREE.Color(0, 1, 0);
var color3 = new THREE.Color(0, 0, 1);
var color4 = new THREE.Color(1, 1, 0);
//顶点颜色数据添加到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);
- 缓冲类型几何体
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个为一组,表示一个顶点的xyz坐标
// 设置几何体attributes属性的位置position属性
geometry.attributes.position = attribue
// 创建顶点法向量normal数据
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);
// 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);
var material = new THREE.MeshBasicMaterial({
vertexColors: THREE.VertexColors, // 根据顶点颜色插值计算
});
var mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
Geometry删除几何体面
var geometry = new THREE.BoxGeometry(100, 100, 100);
// pop():删除数组的最后一个元素 shift:删除数组的第一个元素
geometry.faces.pop();
// geometry.faces.shift();
var material = new THREE.MeshLambertMaterial({
color: 0x0000ff,
// side: THREE.DoubleSide, //内外面可见
});
var mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
顶点颜色插值计算
插值计算示意图:
设置两个三角面的顶点位置和颜色:
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);
geometry.attributes.position = attribue;
// 顶点颜色color数据
var colors = new Float32Array([
1, 0, 0, //顶点1颜色
0, 1, 0, //顶点2颜色
0, 0, 1, //顶点3颜色
1, 1, 0, //顶点4颜色
0, 1, 1, //顶点5颜色
1, 0, 1, //顶点6颜色
]);
geometry.attributes.color = new THREE.BufferAttribute(colors, 3);
- 点渲染模式:每个点一种颜色,不会插值计算
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);
- 线渲染模式:颜色插值计算
var material = new THREE.LineBasicMaterial({
vertexColors: THREE.VertexColors, //以顶点颜色为准,插值计算
});
// 线条渲染模式 线模型对象Line
var line = new THREE.Line(geometry, material);
scene.add(line);
两个顶点的颜色还是红色和绿色,但是由于设置了vertexColors: THREE.VertexColors,顶点之间的颜色为插值计算,于是线为彩色的:
- 网格渲染模式:颜色插值计算
var material = new THREE.MeshBasicMaterial({
vertexColors: THREE.VertexColors, //以顶点颜色为准
});
// 网格模型 三角面渲染模式
var mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
三角形的三个顶点分别设置一个颜色,三角形内部的区域像素会根据三个顶点的颜色进行插值计算:
顶点法向量数据光照计算
WebGL中为了计算光线与物体表面入射角,首先要计算物体表面每个位置的法线方向。在Threejs中表示物体的网格模型Mesh的曲面是由一个一个三角形构成,所以为了表示物体表面各个位置的法线方向,可以给几何体的每个顶点定义一个方向向量。
- 不设置顶点法向量
没有法向量数据,点光源、平行光等带有方向性的光源不会起作用,三角形平面整个渲染效果相对暗淡,而且两个三角形分界位置没有棱角感。
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);
geometry.attributes.position = attribue
- 设置顶点法向量
在上面顶点位置数据基础上定义顶点法向量数据,这时候除了环境光以外,点光源也会参与光照计算,三角形整个表面比较明亮,同时两个三角形表面法线不同,即使光线方向相同,明暗自然不同,在分界位置有棱角感。
...
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法向量
]);
geometry.attributes.normal = new THREE.BufferAttribute(normals, 3);
顶点索引,复用顶点数据
索引方式绘制好处:节约数据量。
对于顶点索引而言选择整型类型数组Uint16Array、Uint32Array等,对于非索引的顶点数据,需要使用浮点类型数组Float32Array等。
- 不使用顶点索引
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坐标
- 使用顶点索引
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个为一组
几何体旋转、缩放、平移
- 缩放
scale( x : Float, y : Float, z : Float )
var geometry = new THREE.BoxGeometry(100, 100, 100); //创建一个立方体几何对象Geometry
geometry.scale(0.5, 2, 2);
- 平移
translate( x : Float, y : Float, z : Float )
var geometry = new THREE.BoxGeometry(100, 100, 100); // 默认在原点
geometry.translate(0, 0, 100);
- 旋转,参数为弧度
rotateXrotateYrotateZ
var geometry = new THREE.BoxGeometry(100, 100, 100);
geometry.rotateX(Math.PI / 4); // 绕x轴旋转45度
- 局中
.center()
var geometry = new THREE.BoxGeometry(100, 100, 100); //创建一个立方体几何对象Geometry
geometry.translate(0, 0, 100);
geometry.center(); // 居中:偏移的几何体居中
变换的本质:改变几何体的顶点位置数据
材质
材质的本质:顶点着色器、片元着色器代码、unifomrs数据。
材质共有与私有属性
材质共有属性:
opacity透明度,0~1transparent是否开启透明度设置,默认值为falsevertexColorsTHREE.NoColors将材质的颜色应用于所有面(默认值)->取决于材质属性.colorTHREE.VertexColors通过顶点颜色进行插值计算渲染->取决于几何体的顶点颜色geometry.attributes.colorTHREE.FaceColors根据每个Face3 Color值对面部进行着色->取决于三角面的顶点颜色
sideTHREE.FrontSide仅前面可见(默认值)THREE.BackSide仅背面可见THREE.DoubleSide双面可见
材质私有属性:
color:材质颜色。点、线、网格材质具有,自定义着色器材质ShaderMaterial、RawShaderMaterial不具有;wireframe:线框模式渲染,默认值为false。网格材质具有。
var material = new THREE.MeshBasicMaterial({
color: 0x0000ff,
// transparent:true, // 开启透明度
// opacity:0.5, // 设置透明度具体值
// wireframe:true, // 线框模式
// side:THREE.DoubleSide, // 双面可见
});
// material.opacity = 0.5; // 属性设置
常见材质
点材质
- 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); //网格模型添加到场景中
线材质
- 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); //线模型添加到场景中
- 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); //线模型添加到场景中
网格材质
MeshBasicMaterial基础网格材质,不受光照影响
// 基础网格材质 不受光照影响 没有棱角感
var material = new THREE.MeshBasicMaterial({
color: 0x0000ff,
// transparent:true, // 开启透明度
// opacity:0.5, // 设置透明度具体值
// wireframe:true, // 线框模式
});
// material.opacity = 0.5; // 属性设置
MeshLambertMaterialLambert网格材质,暗淡,与光照有反应(漫反射)
// Lambert网格材质 与光照计算 漫反射 产生棱角感
var material = new THREE.MeshLambertMaterial({
color: 0x00ff00,
});
MeshPhongMaterial高光Phong材质,高亮表面,与光照有反应(镜面反射)
// 与光照计算 高光效果(镜面反射) 产生棱角感
var material = new THREE.MeshPhongMaterial({
color: 0xff0000,
specular: 0x444444, // 高光颜色
shininess: 30, // 光照强度系数
});
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 // 卡通着色的渐变贴图
} );
MeshStandardMaterialPBR物理材质,相比较高光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
});
这里是一个快速示例,从左至右看,粗糙度从0到1,从上至下看,金属度从0到1。
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 // 光泽层的粗糙程度
});
这里是和上面一样的按 metalness 划分的 roughness 网格,但可以设置 clearcoat 和 clearCoatRoughness 。
MeshDepthMaterial网格深度材质,渲染的效果和模型像素对应顶点距离相机的位置远近有关。MeshDepthMaterial渲染每个像素的深度,其中处在摄像机负近端面的像素其深度为0,处在摄像机负远端面的像素其深度为1。
MeshNormalMaterial网格法向量材质,会绘制视图空间法线(相对于摄像机的法线)。x 是红色, y 是绿色, 以及 z 是蓝色,所以朝向右边的东西是粉红色,朝向左边的是水蓝色,朝上的是浅绿色,朝下的是紫色,朝向屏幕的是淡紫色。
ShadowMaterial阴影材质
更复杂的材质会消耗更多的GPU功耗。如果你不需要额外的功能,那就使用最简单的材质。
各种标准材质的构建速度从最快到最慢:
MeshBasicMaterial➡MeshLambertMaterial➡MeshPhongMaterial➡MeshToonMaterial➡MeshStandardMaterial➡MeshPhysicalMaterial。
精灵材质
SpriteMaterial
var spriteMaterial = new THREE.SpriteMaterial({
color:0xff00ff,//设置精灵矩形区域颜色
rotation: Math.PI/4,//绕垂直屏幕方向旋转45度,弧度值
lights: false, // 受光照影响
// map: texture,//设置精灵纹理贴图
});
自定义着色器材质
-
ShaderMaterial通过three.js的着色器系统来制作自定义材质。 -
RawShaderMaterial可以用来制作完全自定义的着色器,不需要three.js的帮助。
纹理
纹理对象
- 纹理贴图加载器TextureLoader的
load()方法加载一张图片,返回一个纹理对象Texture。- 纹理对象Texture的
.image属性值是一张图片:html的img元素 - 图片宽高最好是2的次方,如:512 * 512,64 * 64...
- 纹理对象Texture的
var geometry = new THREE.SphereGeometry(60, 25, 25); //球体
// TextureLoader创建一个纹理加载器对象,可以加载图片作为几何体纹理
var textureLoader = new THREE.TextureLoader();
// 执行load方法,等待加载纹理贴图成功后,返回一个纹理对象Texture
textureLoader.load('Earth.png', function(texture) {
var material = new THREE.MeshLambertMaterial({
map: texture, // 设置纹理贴图
});
var mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
})
const texture = loader.load('太阳能板2.png');使用这个方法,我们的纹理将是透明的,直到图片被three.js异步加载完成,这时它将用下载的图片更新纹理。
好处是我们不必等待纹理加载,页面会立即开始渲染。
- 图片加载器ImageLoader的
load()方法加载一张图片,返回一个image对象,将其用于生成纹理。- 内部使用 FileLoader 文件加载器
var geometry = new THREE.SphereGeometry(60, 25, 25); //球体
// 图片加载器
var ImageLoader = new THREE.ImageLoader();
// load方法回调函数,按照路径加载图片,返回一个html的元素img对象
ImageLoader.load('Earth.png', function(img) {
// image对象作为参数,创建一个纹理对象Texture
var texture = new THREE.Texture(img);
// 下次使用纹理时触发更新
texture.needsUpdate = true;
var material = new THREE.MeshLambertMaterial({
map: texture, //设置纹理贴图
});
var mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
});
等待所有纹理加载完毕
要等到所有纹理都加载完毕,你可以使用 LoadingManager 。创建一个LoadingManager并将其传递给 TextureLoader,然后将其onLoad属性设置为回调。
const loadManager = new THREE.LoadingManager();
const loader = new THREE.TextureLoader(loadManager);
const materials = [
new THREE.MeshBasicMaterial({map: loader.load('resources/images/flower-1.jpg')}),
new THREE.MeshBasicMaterial({map: loader.load('resources/images/flower-2.jpg')}),
new THREE.MeshBasicMaterial({map: loader.load('resources/images/flower-3.jpg')}),
new THREE.MeshBasicMaterial({map: loader.load('resources/images/flower-4.jpg')}),
new THREE.MeshBasicMaterial({map: loader.load('resources/images/flower-5.jpg')}),
new THREE.MeshBasicMaterial({map: loader.load('resources/images/flower-6.jpg')}),
];
// 等待所有纹理加载完毕
loadManager.onLoad = () => {
const cube = new THREE.Mesh(geometry, materials);
scene.add(cube);
cubes.push(cube); // 添加到我们要旋转的立方体数组中
};
// 纹理加载进度
loadManager.onProgress = (urlOfLastItemLoaded, itemsLoaded, itemsTotal) => {
const progress = itemsLoaded / itemsTotal; // 加载进度
};
内存管理:纹理占用内存受纹理尺寸影响
纹理往往是three.js应用中使用内存最多的部分。重要的是要明白,一般来说,纹理会占用 宽度 * 高度 * 4 * 1.33 字节的内存。
使用纹理是有隐性成本的。为了让three.js使用纹理,必须把纹理交给GPU,而GPU一般都要求纹理数据不被压缩。
不仅仅要让你的纹理的文件大小小,还得让你的纹理尺寸小。文件大小小=下载速度快,尺寸小=占用的内存少。
过滤和mips
设置过滤器:
-
texture.magFilter纹理绘制的尺寸大于其原始尺寸THREE.NearestFilter从原始纹理中选取最接近的一个像素,对于低分辨率的纹理,这给你一个非常像素化的外观THREE.LinearFilter从纹理中选择离我们应该选择颜色的地方最近的4个像素,并根据实际点与4个像素的距离,以适当的比例进行混合。
-
texture.minFilter在绘制的纹理小于其原始尺寸THREE.NearestFilter在纹理中选择最近的像素。THREE.LinearFilter从纹理中选择4个像素,然后混合它们THREE.NearestMipmapNearestFilter选择合适的mip,然后选择一个像素。THREE.NearestMipmapLinearFilter选择2个mips,从每个mips中选择一个像素,混合这2个像素。THREE.LinearMipmapNearestFilter选择合适的mip,然后选择4个像素并将它们混合。THREE.LinearMipmapLinearFilter选择2个mips,从每个mips中选择4个像素,然后将所有8个像素混合成1个像素。
需要注意的是,使用 NearestFilter 和 LinearFilter 的左上方和中上方没有使用mips。正因为如此,它们在远处会闪烁,因为GPU是从原始纹理中挑选像素。左边只有一个像素被选取,中间有4个像素被选取并混合,但这还不足以得出一个好的代表颜色。其他4条做得比较好,右下角的LinearMipmapLinearFilter最好。
纹理对象的阵列、偏移、旋转
阵列 repeat
-
阵列方向
texture.wrapS水平方向texture.wrapT垂直方向
-
阵列模式
ClampToEdgeWrapping默认值,不重复
RepeatWrapping阵列
MirroredRepeatWrapping镜像阵列
ar geometry = new THREE.PlaneGeometry(200, 100); //矩形平面
var textureLoader = new THREE.TextureLoader();Texture
var texture = textureLoader.load('太阳能板2.png');
// 设置阵列模式 默认ClampToEdgeWrapping RepeatWrapping:阵列(重复) 镜像阵列:MirroredRepeatWrapping
texture.wrapS = THREE.RepeatWrapping; // 水平方向
texture.wrapT = THREE.RepeatWrapping; // 垂直方向
// uv两个方向纹理重复数量
texture.repeat.set(4, 2); // Vector2
// texture.repeat = new THREE.Vector2(4, 2)
var material = new THREE.MeshLambertMaterial({
map: texture,
});
偏移 offset
偏移范围-1~1。
纹理贴图效果:
-
不设置阵列,仅设置偏移:会有空白纹理覆盖不到的区域
var geometry = new THREE.PlaneGeometry(200, 100); var textureLoader = new THREE.TextureLoader(); var texture = textureLoader.load('太阳能板2.png'); // 不设置重复 偏移范围-1~1 texture.offset = new THREE.Vector2(0.3, 0.1) var material = new THREE.MeshLambertMaterial({ map: texture, }); -
设置阵列效果的同时,设置偏移:纹理全覆盖
var geometry = new THREE.PlaneGeometry(200, 100); var textureLoader = new THREE.TextureLoader(); var texture = textureLoader.load('太阳能板2.png'); // 设置阵列模式 texture.wrapS = THREE.MirroredRepeatWrapping; texture.wrapT = THREE.MirroredRepeatWrapping; // uv两个方向纹理重复数量 texture.repeat.set(4, 2); // 偏移效果 texture.offset = new THREE.Vector2(0.5, 0.5)
旋转 rotation
var geometry = new THREE.PlaneGeometry(200, 100);
var textureLoader = new THREE.TextureLoader();
var texture = textureLoader.load('太阳能板2.png');
// 设置纹理旋转角度
texture.rotation = Math.PI/4;
// 设置纹理的旋转中心,默认(0,0)
texture.center.set(0.5,0.5);
var material = new THREE.MeshLambertMaterial({
map: texture,
});
默认旋转中心(0, 0):
设置旋转中心(0.5,0.5):
顶点纹理坐标uv
纹理UV坐标和顶点位置坐标是一一对应关系
纹理UV坐标:图片左下角为坐标原点,右上角为坐标(1,1),图片上所有位置纵横坐标都介于0~1之间。
几何体Geometry有两组UV坐标,第一组组用于.map、.normalMap、.specularMap等贴图的映射,第二组用于阴影贴图.lightMap的映射:
- Geometry
Geometry.faceVertexUvs[0]其他贴图Geometry.faceVertexUvs[1]光照(阴影)贴图
- BufferGeometry
geometry.attributes.uv其他贴图geometry.attributes.uv2光照(阴影)贴图
修改uv坐标
- 采样纹理:几何体表面所有位置全部对应贴图(0.4,0.4)坐标位置的像素值,这样话网格模型不会显示完整的地图,而是显示采样点纹理坐标
(0.4,0.4)对应的RGB值。
var geometry = new THREE.PlaneGeometry(204, 102); //矩形平面
console.log(geometry.faceVertexUvs[0]); // 查看默认的uv坐标
// 遍历uv坐标
geometry.faceVertexUvs[0].forEach(elem => {
elem.forEach(Vector2 => {
Vector2.set(0.4,0.4); // 所有的UV坐标全部设置为一个值
});
});
- 局部三角面显示纹理贴图:单独设置局部三角面的纹理贴图
var t0 = new THREE.Vector2(0, 1); //图片左下角
var t1 = new THREE.Vector2(0, 0); //图片右下角
var t2 = new THREE.Vector2(1, 0); //图片右上角
var t3 = new THREE.Vector2(1, 1); //图片左上角
var uv1 = [t0, t1, t3]; //选中图片一个三角区域像素——用于映射到一个三角面
var uv2 = [t1, t2, t3]; //选中图片一个三角区域像素——用于映射到一个三角面
// 设置第五、第六个三角面对应的纹理坐标
geometry.faceVertexUvs[0][4] = uv1
geometry.faceVertexUvs[0][5] = uv2
纹理贴图
- 颜色贴图:模型会从纹理贴图上采集像素值
.map: Texture
- 法线贴图:通过图片保留几何体表面的几何细节,降低模型大小,减少顶点计算,节约顶点数量
.normalMap: Texture.normalScale: Vector2 深浅程度,默认值(1,1)
通过RGB三个分量分别表示法向量的xyz三个方向
- 凹凸贴图:用图片像素的灰度值表示几何表面的高低深度
.bumpMap: Texture.bumpScale: Float 深浅程度,默认值1
- 光照(阴影)贴图:添加的阴影是固定的,不会随着物体旋转而改变
.lightmap: Texture.lightMapIntensity: Float 光照程度,默认值1
一般通过Threejs几何体API创建的几何体默认只有一组纹理坐标
Geometry.faceVertexUvs[0],需要给投影面的另一组纹理坐标赋值`Geometry.faceVertexUvs[1] = Geometry.faceVertexUvs[0] - 高光贴图:通过高光贴图的RGB值来描述不同区域镜面反射的能力
.specularMap: Texture.shininess: Float 高光部分的亮度,默认值30
如果一个网格模型
Mesh都是相同的材质并且表面粗糙度相同,或者说网格模型外表面所有不同区域的镜面反射能力相同,可以直接设置材质的高光属性.specular(高光颜色)。 - 环境贴图:通过图片来表达立方体周边的环境
.envMap: Texture.reflectivity: Float 反射率,默认值1
MeshLambertMaterial、MeshBasicMaterial没有凹凸、法线、高光贴图属性。
颜色贴图.map
材质的颜色贴图属性.map设置后,模型会从纹理贴图上采集像素值,这时候一般来说不需要再设置材质颜色.color。.map贴图之所以称之为颜色贴图就是因为网格模型会获得颜色贴图的颜色值RGB。
// var geometry = new THREE.PlaneGeometry(204, 102); //矩形平面
var geometry = new THREE.SphereGeometry(60, 25, 25); //球体
// TextureLoader创建一个纹理加载器对象,可以加载图片作为几何体纹理
var textureLoader = new THREE.TextureLoader();
// 执行load方法,加载纹理贴图成功后,返回一个纹理对象Texture
textureLoader.load('Earth.png', function(texture) {
var material = new THREE.MeshLambertMaterial({
// 设置纹理贴图:Texture对象作为材质map属性的属性值
map: texture,
});
var mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
//纹理贴图加载成功后,调用渲染函数执行渲染操作
// render();
})
canvas画布、视频作为纹理贴图
- canvas
var geometry = new THREE.PlaneGeometry(128, 32); //矩形平面
/**
* 创建一个canvas对象,并绘制一些轮廓
*/
var canvas = document.createElement("canvas");
canvas.width = 512;
canvas.height = 128;
var c = canvas.getContext('2d');
// 矩形区域填充背景
c.fillStyle = "#ff00ff";
c.fillRect(0, 0, 512, 128);
c.beginPath();
// 文字
c.translate(256, 64);
c.fillStyle = "#000000"; //文本填充颜色
c.font = "bold 48px 宋体"; //字体样式设置
c.textBaseline = "middle"; //文本与fillText定义的纵坐标
c.textAlign = "center"; //文本居中(以fillText定义的横坐标)
c.fillText("three.js入门", 0, 0);
// canvas画布对象作为CanvasTexture的参数重建一个纹理对象
var texture = new THREE.CanvasTexture(canvas);
// 等同于
// var texture = new THREE.Texture(canvas);
// texture.needsUpdate = true;
var material = new THREE.MeshPhongMaterial({
map: texture, // 设置纹理贴图
});
- 视频
var geometry = new THREE.PlaneGeometry(108, 71); //矩形平面
// 创建video对象
let video = document.createElement('video');
video.src = "1086x716.mp4"; // 设置视频地址
video.autoplay = true; //要设置播放
// video对象作为VideoTexture参数创建纹理对象
var texture = new THREE.VideoTexture(video)
var material = new THREE.MeshPhongMaterial({
map: texture, // 设置纹理贴图
});
法线贴图.normalMap
一个复杂的曲面模型,往往模型顶点数量比较多,模型文件比较大,为了降低模型文件大小,法线贴图.normalMap算法自然就产生了,复杂的三维模型3D美术可以通过减面操作把精模简化为简模,然后把精模表面的复杂几何信息映射到法线贴图.normalMap上。低模+法线贴图=高模,降低模型大小,减少顶点计算,节约顶点数量。
法线贴图通过RGB三个分量分别表示法向量的xyz三个方向。通过图片保留几何体表面的几何细节。
var geometry = new THREE.BoxGeometry(100, 100, 100);
var textureLoader = new THREE.TextureLoader();
var textureNormal = textureLoader.load('./法线贴图/3_256.jpg');
var material = new THREE.MeshPhongMaterial({
color: 0xff0000,
normalMap: textureNormal, //法线贴图
normalScale: new THREE.Vector2(3, 3), //设置深浅程度,默认值(1,1)
});
法线贴图:
凹陷效果:
凹凸贴图.bumpMap
凹凸贴图和法线贴图功能相似,只是没有法线贴图表达的几何体表面信息更丰富。凹凸贴图是用图片像素的灰度值表示几何表面的高低深度,如果模型定义了法线贴图,就没有必要在使用凹凸贴图。
var geometry = new THREE.PlaneGeometry(400, 400);
var textureLoader = new THREE.TextureLoader();
// 加载纹理贴图
var texture = textureLoader.load('./凹凸贴图/diffuse.jpg');
// 加载凹凸贴图
var textureBump = textureLoader.load('./凹凸贴图/bump.jpg');
var material = new THREE.MeshPhongMaterial({
map: texture,// 普通纹理贴图
bumpMap:textureBump, // 凹凸贴图
bumpScale:3, // 设置凹凸高度,默认值1。
});
使用凹凸贴图 和 不使用凹凸贴图 的视觉效果:
光照(阴影)贴图.lightmap
设置模型的阴影是通过实时计算得到的,而光照贴图·lightMap是3D美术渲染好提供给程序员。这两种方式相比较通过贴图的方式更为节约资源,提高渲染性功能。缺点是光照贴图添加的阴影是固定的,不会随着物体旋转而改变。
一般通过Threejs几何体API创建的几何体默认只有一组纹理坐标
Geometry.faceVertexUvs[0],所以为了设置光照阴影贴图,需要给投影面的另一组纹理坐标赋值Geometry.faceVertexUvs[1] = Geometry.faceVertexUvs[0];
var geometry = new THREE.BoxGeometry(40, 100, 40); //创建一个立方体几何对象Geometry
var material = new THREE.MeshLambertMaterial({
color: 0x0000ff
});
var mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
//创建一个平面几何体作为投影面
var planeGeometry = new THREE.PlaneGeometry(300, 200);
// 为了设置光照阴影贴图,需要给另一组纹理坐标赋值
planeGeometry.faceVertexUvs[1] = planeGeometry.faceVertexUvs[0];
var textureLoader = new THREE.TextureLoader();
var textureLight = textureLoader.load('shadow.png');
var planeMaterial = new THREE.MeshLambertMaterial({
color: 0x999999,
lightMap: textureLight,// 设置光照贴图
// lightMapIntensity: 0.5,// 光照的强度. 默认 1.
});
var planeMesh = new THREE.Mesh(planeGeometry, planeMaterial); //网格模型对象Mesh
scene.add(planeMesh); //网格模型添加到场景中
planeMesh.rotateX(-Math.PI / 2); //旋转网格模型
planeMesh.position.y = -50; //设置网格模型y坐标
阴影是固定的,不会随着光照改变:
高光贴图.specularMap
高光网格材质MeshPhongMaterial具有高光属性.specular,如果一个网格模型Mesh都是相同的材质并且表面粗糙度相同,或者说网格模型外表面所有不同区域的镜面反射能力相同,可以直接设置材质的高光属性.specular。如果一个网格模型表示一个人,那么人的不同部位高光程度是不同的,不可能直接通过.specular属性来描述,在这种情况通过高光贴图.specularMap的RGB值来描述不同区域镜面反射的能力,.specularMap和颜色贴图.Map一样和通过UV坐标映射到模型表面。高光贴图.specularMap不同区域像素值不同,表示网格模型不同区域的高光值不同。
下面是一个地球的案例,地球地面和海面的高光值是不同的,海面更为高亮:
var geometry = new THREE.SphereGeometry(100, 35, 35); //球体
var textureLoader = new THREE.TextureLoader();
// 加载纹理贴图
var texture = textureLoader.load('earth_diffuse.png');
// 加载高光贴图
var textureSpecular = textureLoader.load('earth_specular.png');
var material = new THREE.MeshPhongMaterial({
// specular: 0xff0000,//高光部分的颜色
shininess: 30,//高光部分的亮度,默认30
map: texture,// 普通纹理贴图
specularMap: textureSpecular, //高光贴图
});
高光贴图:白色部分高亮
高光效果:
环境贴图.envMap
环境贴图.envMap字面意思就是三维模型周边环境,比如你渲染一个立方体,立方体放在一个屋子里面,屋子里面的周边环境肯定影响立方体的渲染效果,目的是为了渲染该立方体而不是立方体周围环境,为了更方便所以没必要创建立方体周边环境所有物体的网格模型,可以通过图片来表达立方体周边的环境。
var geometry = new THREE.BoxGeometry(100, 100, 100); //立方体
var loader = new THREE.CubeTextureLoader();
// 所有贴图在同一目录下,可以使用该方法设置共用路径
loader.setPath('环境贴图/');
// 立方体纹理加载器返回立方体纹理对象CubeTexture
var CubeTexture = loader.load(['px.jpg', 'nx.jpg', 'py.jpg', 'ny.jpg', 'pz.jpg', 'nz.jpg']);
//材质对象Material
var material = new THREE.MeshPhongMaterial({
envMap: CubeTexture, //设置环境贴图
// 环境贴图反射率
// reflectivity: 0.1,
});
数组纹理对象DataTexture
通过程序创建纹理贴图的每一个像素值。
- RGB:
THREE.RGBFormat
var geometry = new THREE.PlaneGeometry(128, 128); //矩形平面
/**
* 创建纹理对象的像素数据
*/
var width = 32; //纹理宽度
var height = 32; //纹理高度
var size = width * height; //像素大小
var data = new Uint8Array(size * 3); //size*3:像素在缓冲区占用空间
for (let i = 0; i < size * 3; i += 3) {
// 随机设置RGB分量的值
data[i] = 255 * Math.random()
data[i + 1] = 255 * Math.random()
data[i + 2] = 255 * Math.random()
}
// 创建数据文理对象 RGB格式:THREE.RGBFormat
var texture = new THREE.DataTexture(data, width, height, THREE.RGBFormat);
texture.needsUpdate = true; //纹理更新
- RGBA:
THREE.RGBAFormat
...
var data = new Uint8Array(size * 4); //size*4:像素在缓冲区占用空间
for (let i = 0; i < size * 4; i += 4) {
// 随机设置RGB分量的值
data[i] = 255 * Math.random()
data[i + 1] = 255 * Math.random()
data[i + 2] = 255 * Math.random()
// 设置透明度分量A
data[i + 3] = 255 * 0.5
}
// 创建数据文理对象 RGBA格式:THREE.RGBAFormat
var texture = new THREE.DataTexture(data, width, height, THREE.RGBAFormat);
texture.needsUpdate = true; //纹理更新
数组材质、材质索引materialIndex
数组材质
所谓数组材质就是多个材质对象构成一个数组作为模型对象的材质。
所谓数组材质,就是threejs几何体API的算法自动设置系列三角面Face3的材质索引属性
materialIndex。默认数组材质需要材质对象元素数量:
- 球体
SphereGeometry、平面PlaneGeometry:1- 圆柱体
CylinderGeometry:3 -> 底部、顶部、侧面- 圆锥体
ConeGeometry:2 -> 底部、侧面- 立方体
BoxGeometry:6 -> 每个面- ...
var geometry = new THREE.BoxGeometry(100, 100, 100);
// 材质对象1
var material_1 = new THREE.MeshPhongMaterial({
color: 0xffff3f
})
// 材质对象2
var textureLoader = new THREE.TextureLoader(); // 纹理加载器
var texture = textureLoader.load('Earth.png'); // 加载图片,返回Texture对象
var material_2 = new THREE.MeshLambertMaterial({
map: texture, // 设置纹理贴图
});
// 设置材质数组
var materialArr = [material_2, material_1, material_1, material_1, material_1, material_1];
// 设置数组材质对象作为网格模型材质参数
var mesh = new THREE.Mesh(geometry, materialArr);
scene.add(mesh);
材质索引materialIndex
三角面Face3可以设置材质索引属性.materialIndex,指向数组材质中的材质对象,表达的意思是数组材质中哪一个元素用于渲染该三角形面Face3。
var geometry = new THREE.PlaneGeometry(204, 102, 4, 4); //矩形平面
// 材质对象1
var material1 = new THREE.MeshPhongMaterial({
color: 0xffff3f,
})
// 材质对象2
var material2 = new THREE.MeshPhongMaterial({
color: 0x0000ff,
});
// 数组材质
var materialArr = [material1, material2];
// 设置几何体的材质索引(PlaneGeometry数组材质需要的材质对象元素数量为1,所有Face3的材质索引默认0)
geometry.faces[4].materialIndex = 1;
geometry.faces[5].materialIndex = 1;
// 设置数组材质对象作为网格模型材质参数
var mesh = new THREE.Mesh(geometry, materialArr);
scene.add(mesh);
BufferGeometry的材质索引
.groups:Arraystart第几个顶点的下标count顶点数量materialIndex材质索引
var bufferGeometry = new THREE.BufferGeometry();
// Geometry转化为BufferGeometry
bufferGeometry.fromGeometry(geometry);
// 查看材质索引materialIndex
console.log(bufferGeometry);
点线网格精灵模型
模型介绍
点
- 点模型: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); //网格模型添加到场景中
线
- Line 直线模型
var geometry = new THREE.BoxGeometry(100, 100, 100); //创建一个立方体几何对象Geometry
// 线条渲染模式
var material=new THREE.LineBasicMaterial({
color:0xff0000 //线条颜色
});
var line=THREE.Line(geometry,material) // 直线模型对象
scene.add(line);
- LineLoop 闭环线模型
var line=new THREE.LineLoop(geometry,material);//闭环线模型对象
scene.add(line);
- LineSegments 间断线模型
var line=new THREE.LineSegments(geometry,material);//间断线模型对象
scene.add(line);
网格
- 网格模型Mesh
var geometry = new THREE.BoxGeometry(100, 100, 100); MeshBasicMaterial
var material = new THREE.MeshBasicMaterial({
color: 0x0000ff,
});
var mesh = new THREE.Mesh(geometry, material); //网格模型对象Mesh
scene.add(mesh)
- 骨骼类网格模型SkinnedMesh
/* 创建骨骼网格模型 */
var material = new THREE.MeshPhongMaterial({
skinning: true, //允许蒙皮动画
});
var SkinnedMesh = new THREE.SkinnedMesh(geometry, material);
SkinnedMesh.position.set(50, 120, 50); //设置网格模型位置
SkinnedMesh.rotateX(Math.PI); //旋转网格模型
scene.add(SkinnedMesh); //网格模型添加到场景中
/* 骨骼系统 */
var Bone1 = new THREE.Bone(); //关节1,用来作为根关节
var Bone2 = new THREE.Bone(); //关节2
var Bone3 = new THREE.Bone(); //关节3
// 设置关节父子关系 多个骨头关节构成一个树结构
Bone1.add(Bone2);
Bone2.add(Bone3);
// 设置关节之间的相对位置
// 根关节Bone1默认位置是(0,0,0)
Bone2.position.y = 60; //Bone2相对父对象Bone1位置
Bone3.position.y = 40; //Bone3相对父对象Bone2位置
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;
精灵模型
- 精灵模型Sprite:正面永远朝着屏幕。
- 通过
Sprite创建精灵模型不需要几何体(可以理解为已经内部封装了一个平面矩形几何体PlaneGeometry),只需要给构造函数Sprite的参数设置为一个精灵材质SpriteMaterial即可。
- 通过
// 精灵材质
var spriteMaterial = new THREE.SpriteMaterial({
color:0xff00ff,//设置精灵矩形区域颜色
rotation: Math.PI/4,//绕垂直屏幕方向旋转45度,弧度值
lights: false, // 受光照影响
});
// 创建精灵模型对象,不需要几何体geometry参数
var sprite = new THREE.Sprite(spriteMaterial);
scene.add(sprite);
比较线模型的线条绘制模式和网格模型的线条绘制模式
- 网格模型的线框绘制模式
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); //网格模型添加到场景中
- 线模型的线条绘制模式
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);
两个模式绘制时使用的顶点不同:
旋转、平移、缩放(基类Object3D)
- 缩放:
.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属性
网格模型变换与几何体变换的区别
- 网格模型Mesh对象
- 不会改变顶点位置数据
- 旋转平移缩放方法改变的是模型对象的
scale、rotation、position属性
- 几何体Geometry、BufferGeometry对象
- 改变顶点位置数据
克隆clone、复制copy(基类Object3D)
Threejs大多数对象(基类Object3D)都有克隆.clone()和复制.copy()两个方法。
- 克隆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)
}
- 复制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)
层级结构
组对象Group、层级模型
父对象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克隆的对象
}
子对象.children
Threejs场景对象Scene、组对象Group都有一个子对象属性.children,通过该属性可以访问父对象的子对象。
.add()方法给父对象添加子对象。可以插入一个/多个子对象。.remove()方法是删除父对象中的子对象。同样可以插入一个/多个子对象。
对象节点命名、查找、遍历
-
对象节点属性
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);
本地位置坐标、世界位置坐标
访问模型的位置属性 .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}
曲线
曲线和几何体同样本质上都是用来生成顶点的算法,曲线主要是按照一定的规则生成一系列沿着某条轨迹线分布的顶点。
圆弧线 ArcCurve
new THREE.ArcCurve( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise )
aX,aY圆弧圆心坐标aRadius圆弧半径aStartAngle,aEndAngle起始角度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);
- 修改圆弧起始角度,绘制弧线
var arc = new THREE.ArcCurve(0, 0, 100, Math.PI, 2 * Math.PI);
直线 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);
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);
- 二次贝塞尔曲线:起点、终点和1个控制点定义
- 2D:
QuadraticBezierCurveVector2 - 3D:
QuadraticBezierCurve3Vector3
- 2D:
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)); // 辅助线
- 三次贝塞尔曲线:起点、终点和2个控制点定义
- 2D:
CubicBezierCurve - 3D:
CubicBezierCurve3
- 2D:
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);
设置控制点相对应:
var p3 = new THREE.Vector3(40, -100, 0);
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);
5. 曲线路径管道 TubeGeometry
TubeGeometry的功能就是通过一条曲线的顶点生成一个管道几何体geometry的顶点数据、face3数据。它的本质就是以曲线上顶点为基准,生成一系列曲线等径分布的顶点数据。
TubeGeometry(path, tubularSegments, radius, radiusSegments, closed)
path扫描路径,基类Curve的路径构造函数tubularSegments管道轨迹细分数,默认64radius管道半径,默认1radiusSegments管道截面圆弧细分数,默认8closed管道是否闭合
//创建管道成型的路径(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);
线条模式渲染,查看细分数:
// path:路径 10:管道轨迹细分数 5:管道半径 5:管道截面圆弧细分数
var geometry = new THREE.TubeGeometry(path, 10, 5, 5);
...
material.wireframe = true;
...
管道轨迹细分数:
管道截面圆弧细分数:
多段曲线路径创建生成管道 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);
6. 旋转成型 LatheGeometry
一组顶点(Vector2)以y轴为旋转轴进行旋转生成旋转轮廓。
顶点数据可以通过二维向量对象
Vector2定义,也可以通过3D曲线或2D线条轮廓生成。
LatheGeometry(points, segments, phiStart, phiLength)
pointsVector2表示的坐标数据组成的数组segments圆周方向细分数,默认12phiStart开始角度,默认0phiLength旋转角度,默认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);
样条曲线插值计算生成旋转轮廓
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);
...
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);
通过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);
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);
...
- 圆形挖孔
// 一个外轮廓圆弧嵌套三个内圆弧轮廓
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);
...
多个轮廓同时填充
// 轮廓对象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);
8. 拉伸扫描成型 ExtrudeGeometry
构造函数ExtrudeGeometry和ShapeGeometry一样是利用Shape对象生成几何体对象,区别在于ExtrudeGeometry可以利用2D轮廓生成3D模型。
new THREE.ExtrudeGeometry(shape, object)
shape拉伸对象(二维轮廓),多个轮廓可以用数组。object拉伸参数amount拉伸长度,默认100bevelEnabled是否使用倒角bevelSegments倒角细分数,默认3bevelThickness倒角尺寸(经向)curveSegments拉伸轮廓细分数steps拉伸方向细分数extrudePath扫描路径THREE.CurvePath,默认Z轴方向material前后面材质索引号extrudeMaterial拉伸面、倒角面材质索引号bevelSize倒角尺寸(拉伸方向)
拉伸
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,//径向尺寸
}
);
扫描
对于扫描而言不需要定义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//沿着路径细分数
}
);