NSDT工具推荐Three.js AI纹理开发包 - YOLO合成数据生成器 - GLTF/GLB在线编辑 - 3D模型格式在线转换 - 可编程3D场景编辑器 - REVIT导出3D模型插件 - 3D模型语义搜索引擎 - AI模型在线查看 - Three.js虚拟轴心开发包 - 3D模型在线减面 - STL模型在线切割 - 3D道路快速建模

在 three.js 中,可见对象由几何体和材质构成。 我们已经了解了如何创建适用于点和线图元的简单几何图形,并且遇到了各种标准网格几何图形,例如 THREE.CylinderGeometry 和 THREE.IcosahedronGeometry。 在本节中,我们将了解如何从头开始创建新的网格几何体。 我们还将了解 three.js 为处理对象和材质提供的其他一些支持。

1、索引面集

three.js 中的网格就是我们之前中所说的索引面集。 在 three.js 网格中,所有的多边形都是三角形。 three.js 中的几何是 THREE.Geometry 类型的对象。 任何几何对象都包含一个顶点数组,表示为 THREE.Vector3 类型的对象。 对于网格几何体,它还包含一个面数组,表示为 THREE.Face3 类型的对象。 Face3 类型的每个对象指定几何体的一个三角形面。 三角形的三个顶点由三个整数指定。 每个整数都是几何顶点数组的索引。 这三个整数可以指定为 THREE.Face3 构造函数的参数。 例如,

var f = new THREE.Face3( 0, 7, 2 );

这三个索引存储为面对象的属性 f.a、f.b 和 f.c。 作为一个例子,让我们看看如何直接为这个金字塔创建一个 three.js 几何体:

请注意,金字塔的底面是正方形,必须将其分成两个三角形才能将金字塔表示为网格几何体。 如果 pyramidGeom 是此金字塔的几何对象,则 pyramidGeom.vertices 是顶点数组,而 pyramidGeom.faces 是面数组。 考虑到这一点,我们可以定义:

var pyramidGeom = new THREE.Geometry();

pyramidGeom.vertices = [  // array of Vector3 giving vertex coordinates
        new THREE.Vector3( 1, 0, 1 ),    // vertex number 0
        new THREE.Vector3( 1, 0, -1 ),   // vertex number 1
        new THREE.Vector3( -1, 0, -1 ),  // vertex number 2
        new THREE.Vector3( -1, 0, 1 ),   // vertex number 3
        new THREE.Vector3( 0, 1, 0 )     // vertex number 4
    ];

pyramidGeom.faces = [  // array of Face3 giving the triangular faces
        new THREE.Face3( 3, 2, 1 ),  // first half of the bottom face
        new THREE.Face3 3, 1, 0 ),   // second half of the bottom face
        new THREE.Face3( 3, 0, 4 ),  // remaining faces are the four sides
        new THREE.Face3( 0, 1, 4 ),
        new THREE.Face3( 1, 2, 4 ),
        new THREE.Face3( 2, 3, 4 )
    ];

请注意,面上顶点的顺序并不是完全随意的:它们应该按照逆时针顺序排列,从面的前面看,也就是从金字塔外面看面。

给定的金字塔几何体将与 MeshBasicMaterial 一起使用,但要与 MeshLambertMaterial 或 MeshPhongMaterial 等光照材质一起使用,几何体需要法向量。 如果几何体没有法向量,Lambert 和 Phong 材质将显示为黑色。

可以手动分配法向量,但也可以让 three.js 通过调用几何类中的方法来为您计算它们。 对于金字塔,这将通过调用:

pyramidGeom.computeFaceNormals();

此方法为每个面计算一个法向量,其中法线垂直于面。 如果材质使用平面着色,这就足够了; 也就是说,如果材质的 flatShading 属性设置为 true。 flatShading 属性在之前讨论过。

平面着色(flatShading)适用于金字塔。 但是当一个对象应该看起来是光滑的而不是多面的时,它需要每个顶点的法向量而不是每个面的法向量。 Face3 具有三个顶点法线的数组。 它们可以手动设置,或者 Three.js 可以通过平均共享一个顶点的所有面的面法线来计算光滑表面的合理顶点法线。 只需调用:

geom.computeVertexNormals();

其中 geom 是几何对象。 请注意,在调用 computeVertexNormals 之前面法线必须已经存在,因此通常会在调用 geom.computeFaceNormals() 之后立即调用 geom.computeVertexNormals()。 具有面法线但没有顶点法线的几何体不适用于 flatShading 属性具有默认值 false 的材质。 为了能够在像金字塔这样的表面上使用平滑着色,每个面的所有顶点法线都应该设置为等于它的面法线。 在那种情况下,即使有平滑的阴影,金字塔的侧面看起来也是平坦的。 标准的 three.js 几何体(例如 BoxGeometry)带有正确的面和顶点法线。

类型为 THREE.Face3 的对象的面法线存储在属性 face.normal 中。 顶点法线存储在 face.vertexNormals 中,它是一个包含三个 Vector3 的数组。

有了完整的法向量集,金字塔就可以与我们介绍过的任何网格材质一起使用,但只有一种颜色看起来有点无聊。 可以在一个网格上使用多种颜色。 为此,你可以向网格对象构造函数提供一组材料,而不是单一材料。 这使得可以将不同的材料应用于不同的面。 例如,这里是如何制作一个六面具有不同材料的立方体:

var cubeGeom = new THREE.BoxGeometry(10,10,10);
var cubeMaterials =  [
    new THREE.MeshPhongMaterial( { color: "red" } ),
    new THREE.MeshPhongMaterial( { color: "cyan" } ),
    new THREE.MeshPhongMaterial( { color: "green" } ),
    new THREE.MeshPhongMaterial( { color: "magenta" } ), // for the -y face
    new THREE.MeshPhongMaterial( { color: "blue" } ),    // for the +z face
    new THREE.MeshPhongMaterial( { color: "yellow" } )   // for the -z face
];
var cube = new THREE.Mesh( cubeGeom, cubeMaterials );

为了使其与几何体一起使用,几何体的每个面都需要一个“材质索引”。 面的材质索引是一个整数,它是材质数组的索引。 BoxGeometry 的面具有适当的索引。 请注意,长方体几何体有 12 个面,因为每个矩形边都被分成两个三角形面。 组成矩形边的两个三角形具有相同的材料指数。 (BoxGeometry 是我能找到的唯一具有非零材质索引的标准几何体。材质索引的默认值为零。)

假设我们想在上面创建的金字塔的每一侧使用不同的材质。 为此,每个面都需要一个材质索引,该索引存储在名为 materialIndex 的面属性中。 对于金字塔,面数组中的前两个面构成了金字塔的方形底面。 它们可能应该具有相同的材质索引。 以下代码将材质索引 0 分配给前两个面,将材质索引 1、2、3 和 4 分配给其他四个面:

pyramidGeom.faces[0].materialIndex = 0;
for (var i = 1; i <= 5; i++) {
    pyramidGeom.faces[i].materialIndex = i-1;
}

代码来自示例程序threejs/MeshFaceMaterial.html。 该程序在每个对象上使用多种材料显示一个立方体和一个金字塔。 这是它们的样子:

还有另一种方法可以为网格对象的每个面分配不同的颜色:可以将颜色存储为几何体中面对象的属性。 然后,可以在对象上使用普通材质,而不是材质数组。 但是还必须告诉材质使用几何体中的颜色来代替材质的颜色属性。

有几种方法可以将颜色分配给网格中的面。 一种是简单地使每张脸具有不同的纯色。 每个人脸对象都有一个颜色属性,可以用来实现这个想法。 color 属性的值是一个 THREE.Color 类型的对象,表示整个面部的颜色。 例如,我们可以设置金字塔的面颜色:

pyramidGeom.faces[0].color = new THREE.Color(0xCCCCCC);
pyramidGeom.faces[1].color = new THREE.Color(0xCCCCCC);
pyramidGeom.faces[2].color = new THREE.Color("green");
pyramidGeom.faces[3].color = new THREE.Color("blue");
pyramidGeom.faces[4].color = new THREE.Color("yellow");
pyramidGeom.faces[5].color = new THREE.Color("red");

要使用这些颜色,必须将材质的 vertexColors 属性设置为值 THREE.FaceColors; 例如:

material = new THREE.MeshLambertMaterial({
        vertexColors: THREE.FaceColors,
        shading: THREE.FlatShading
    });

该属性的默认值为 THREE.NoColors,它告诉渲染器为每个面使用材质的颜色属性。

将颜色应用于面的第二种方法是将不同的颜色应用于面的每个顶点。 然后,WebGL 将对顶点颜色进行插值以计算面部内像素的颜色。 每个面对象都有一个名为 vertexColors 的属性,其值应该是三个 THREE.Color 对象的数组,一个对应面的每个顶点。 要使用这些颜色,必须将材质的 vertexColors 属性设置为 THREE.VertexColors。

演示 c5/vertex-and-color-animation.html 是使用顶点和面颜色的示例。 它可以为颜色设置动画,也可以为对象的顶点位置设置动画。 这是该演示中的一张图片,显示了一个多色的二十面体几何体,其中一些顶点已被置换:

2、曲线和曲面

除了可以构建索引面集外,three.js 还支持使用数学定义的曲线和曲面。 示例程序 threejs/curves-and-surfaces.html 中说明了一些可能性,我将在这里讨论其中的一些。

参数化曲面是最容易使用的。 参数曲面由数学函数 f(u,v) 定义,其中 u 和 v 是数字,函数的每个值都是空间中的一个点。 该表面由所有点组成,这些点是某些指定范围内 u 和 v 的函数值。 对于 three.js,该函数是一个常规 JavaScript 函数,它返回 THREE.Vector3 类型的值。 通过在 uv 点网格处评估函数来创建参数化表面几何体。 这给出了表面上的点网格,然后连接这些点以给出表面的多边形近似。 在 three.js 中,u 和 v 的值始终在 0.0 到 1.0 的范围内。 几何是由构造函数创建的:

new THREE.ParametricGeometry( func, slices, stacks )

其中 func 是 JavaScript 函数,切片和堆栈确定网格中的点数; slices给出u方向0到1区间的细分数,v方向stacks。 一旦你有了几何图形,你就可以用它以通常的方式制作网格。 这是示例程序中的示例:

该表面由函数定义:

function surfaceFunction( u, v ) {
    var x,y,z;  // A point on the surface, calculated from u,v.
        //u and v range from 0 to 1.
    x=20*(u-0.5); // x and z range from -10 to 10
    z = 20 * (v - 0.5);
    y = 2*(Math.sin(x/2) * Math.cos(z));
    return new THREE.Vector3( x, y, z );
}

表示表面的 three.js 网格是使用如下代码创建的:

var surfaceGeometry = new THREE.ParametricGeometry(surfaceFunction, 64, 64);

var surface = new THREE.Mesh( surfaceGeometry, material );

Three.js 中的曲线更加复杂(不幸的是,用于处理曲线的 API 不是很一致)。

THREE.Curve 类表示二维或三维参数曲线的抽象概念。 (它不代表 three.js 几何体。)参数化曲线由一个数值变量 t 的函数定义。 函数返回的值对于 2D 曲线是 THREE.Vector2 类型,对于 3D 曲线是 THREE.Vector3 类型。 对于类型为 THREE.Curve 的对象曲线,方法 curve.getPoint(t) 应返回曲线上对应于参数 t 的值的点。 然而,在 Curve 类本身中,这个函数是未定义的。 要获得实际曲线,你必须对其进行定义。 例如,

var helix = new THREE.Curve();
helix.getPoint = function(t) {
    var s = (t - 0.5) * 12*Math.PI;
        // As t ranges from 0 to 1, s ranges from -6*PI to 6*PI
    return new THREE.Vector3(
        5*Math.cos(s),
        s,
        5*Math.sin(s)
    );
}

定义 getPoint 后,你就有了可用的曲线。 可以用它做的一件事是创建一个管几何体,它定义了一个表面,该表面是一个具有圆形横截面的管,并且曲线沿着管的中心延伸。 示例程序使用上面定义的螺旋曲线来创建两个管:

较宽管的几何形状是用如下代码创建:

tubeGeometry1 = new THREE.TubeGeometry( helix, 128, 2.5, 32 );

构造函数的第二个参数是曲面沿曲线长度的细分数。 第三个是管子圆形截面的半径,第四个是截面圆周周围的细分数。

要制作管道,你需要 3D 曲线。 也有几种方法可以从 2D 曲线制作曲面。 一种方法是围绕一条线旋转曲线,生成一个旋转表面。 曲面由曲线在旋转时经过的所有点组成。 这称为车削加工(lathing )。 示例程序中的这张图片显示了通过车削余弦曲线生成的曲面。 (图像旋转 90 度,因此 y 轴水平。)曲线本身显示在曲面上方:

曲面是在 three.js 中使用 THREE.LatheGeometry 对象创建的。 LatheGeometry 不是根据曲线构造的,而是根据位于曲线上的点数组构造的。 这些点是 Vector2 类型的对象,曲线位于 xy 平面中。 曲面是通过绕 y 轴旋转曲线生成的。

LatheGeometry 构造函数采用以下形式:

new THREE.LatheGeometry( points, slices )

第一个参数是 Vector2 的数组。 第二个是点绕轴旋转时,曲面沿圆产生的细分数。 (表面的“堆栈”数由点数组的长度给出。)在示例程序中,我通过调用 cosine.getPoints(128) 从类型为 Curve 的对象 cosine 创建了点数组。 此函数使用 0.0 到 1.0 范围内的参数值在曲线上创建一个包含 128 个点的数组。

可以使用 2D 曲线做的另一件事是简单地填充曲线的内部,给出一个 2D 填充形状。 要在 three.js 中做到这一点,你可以使用 THREE.Shape 类型的对象,它是 THREE.Curve 的子类。 Shape 可以用与前面介绍的 2D Canvas API 中的路径相同的方式定义。 也就是说,类型为 THREE.Shape 的对象形状具有可用于定义路径的方法 shape.moveTo、shape.lineTo、shape.quadraticCurveTo 和 shape.bezierCurveTo。 例如,我们可以创建一个泪珠形状:

var path = new THREE.Shape();
path.moveTo(0,10);
path.bezierCurveTo( 0,5, 20,-10, 0,-10 );
path.bezierCurveTo( -20,-10, 0,5, 0,10 );

要在 three.js 中使用路径创建填充形状,我们需要一个 ShapeGeometry 对象:

var shapeGeom = new THREE.ShapeGeometry( path );

使用此几何体创建的 2D 形状显示在这张图片的左侧:

图片中的其他两个对象是通过挤压形状创建的。 在挤压中,填充的 2D 形状沿着 3D 路径移动。 形状穿过的点构成了 3D 实体。 在这种情况下,形状是沿着垂直于形状的线段拉伸的,这是最常见的情况。 基本的拉伸形状显示在插图的右侧。 中间的对象是具有“斜角”边缘的相同形状。 有关挤压的更多详细信息,请参阅 THREE.ExtrudeGeometry 的文档和示例程序的源代码。

3、纹理

纹理可用于为对象添加视觉趣味和细节。 在 three.js 中,图像纹理由 THREE.Texture 类型的对象表示。 由于我们谈论的是网页,因此 three.js 纹理的图像通常是从网址加载的。

图像纹理通常是使用 THREE.TextureLoader 类型对象中的加载函数创建的。 该函数以 URL(网址,通常是相对地址)作为参数并返回一个 Texture 对象:

var loader = new THREE.TextureLoader();
var texture = loader.load( imageURL );

three.js 中的纹理被认为是材质的一部分。 要将纹理应用于网格,只需将 Texture 对象分配给网格上使用的网格材质的贴图属性:

material.map = texture;

贴图属性也可以在材质构造函数中设置。 所有三种类型的网格材质(Basic、Lambert 和 Phong)都可以使用纹理。 通常,材质基色为白色,因为材质颜色会与纹理颜色相乘。 非白色材质颜色会为纹理颜色添加“色调”。 将图像映射到网格所需的纹理坐标是网格几何体的一部分。 标准网格几何体(例如 THREE.SphereGeometry)带有已经定义的纹理坐标。

这是基本思想—从图像 URL 创建纹理对象并将其分配给材质的贴图属性。 然而,也有一些复杂的东西。 首先,图片加载是“异步的”。 也就是说,调用加载函数只会启动加载图像的过程,并且该过程可以在函数返回后的某个时间完成。 在图像完成加载之前在对象上使用纹理不会导致错误,但对象将呈现为全黑。 加载图像后,必须再次渲染场景以显示图像纹理。 如果动画正在运行,这将自动发生; 加载完成后,图像将出现在第一帧中。 但是,如果没有动画,则需要一种在图像加载后渲染场景的方法。 事实上,TextureLoader 中的加载函数有几个可选参数:

loader.load( imageURL, onLoad, undefined, onError );

这里的第三个参数未定义,因为不再使用该参数。 onLoad 和 onError 参数是回调函数。 onLoad 函数(如果已定义)将在成功加载图像后调用。 如果尝试加载图像失败,将调用 onError 函数。 例如,如果有一个渲染场景的函数 render(),那么 render 本身可以用作 onLoad 函数:

var texture = new THREE.TextureLoader().load( "brick.png", render );

onLoad 的另一种可能用途是延迟将纹理分配给材质,直到图像完成加载。 如果你确实更改了 material.map 的值,请务必设置

material.needsUpdate = true;

以确保更改将在重绘对象时生效。

纹理有许多可以设置的属性,包括用于设置纹理的缩小和放大过滤器的属性,以及用于控制 mipmap 生成的属性,这在默认情况下是自动完成的。 您最有可能想要更改的属性是 0 到 1 范围之外的纹理坐标的环绕模式和纹理变换。

对于纹理对象 tex,属性 tex.wrapS 和 tex.wrapT 控制如何处理 0 到 1 范围之外的 s 和 t 纹理坐标。 默认值为“clamp to edge”。 你很可能希望通过将属性值设置为 THREE.RepeatWrapping 来使纹理在两个方向上重复:

tex.wrapS = THREE.RepeatWrapping;

tex.wrapT = THREE.RepeatWrapping;

RepeatWrapping 最适合“无缝”纹理,其中图像的上边缘与下边缘匹配,左边缘与右边缘匹配。 Three.js 还提供了一个有趣的变体,称为“镜像重复”,其中重复图像的每个其他副本都被翻转。 这消除了图像副本之间的接缝。 对于镜像重复,使用属性值 THREE.MirroredRepeatWrapping:

tex.wrapS = THREE.MirroredRepeatWrapping;

tex.wrapT = THREE.MirroredRepeatWrapping;

纹理属性 repeat 和 offset 控制作为纹理变换应用于纹理的缩放和平移。 (没有纹理旋转。)这些属性的值属于 THREE.Vector2 类型,因此每个属性都有一个 x 和一个 y 分量。 对于纹理 tex,tex.offset 的两个组件给出了水平和垂直方向的纹理平移。 要水平偏移纹理 0.5,可以这样设置:

tex.offset.x = 0.5;

//or

tex.offset.set( 0.5, 0 );

请记住,正的水平偏移会将纹理移动到对象的左侧,因为偏移应用于纹理坐标而不是纹理图像本身。

属性 tex.repeat 的组件给出了水平和垂直方向的纹理缩放。 例如,

tex.repeat.set(2,3);

将纹理坐标水平缩放 2 倍,垂直缩放 3 倍。 同样,对图像的影响是相反的,因此图像在水平方向上缩小了 2 倍,在垂直方向上缩小了 3 倍。 结果是你在水平方向上得到了两个图像副本,而在水平方向上你会得到一个副本,在垂直方向上得到三个副本。 这解释了名称“repeat”,但请注意值不限于整数。

演示 c5/textures.html 显示了应用于各种 three.js 对象的图像纹理。

假设我们要在本节开头创建的金字塔上使用图像纹理。 为了将纹理图像应用于对象,WebGL 需要该对象的纹理坐标。 当我们从头开始构建网格时,我们必须提供纹理坐标作为网格几何对象的一部分。

示例中的 pyramidGeom 等几何对象具有名为 faceVertexUvs 的属性,用于保存纹理坐标。 (“UV”是指物体上的坐标映射到纹理中的s和t坐标。)faceVertexUvs的值是一个数组,其中数组的每个元素本身就是一个数组数组; 在大多数情况下,仅使用元素 faceVertexUvs[0],但在某些高级应用程序中会使用额外的 uv 坐标集。 faceVertexUvs[0] 的值本身是一个数组,几何中的每个面都有一个元素。 同样,为每个面存储的数据是一个数组:faceVertexUvs[0][N] 是一个数组,其中包含面编号 N 的三个顶点中的每一个的一对坐标。最后,该数组中的每对纹理坐标是 表示为 THREE.Vector2 类型的对象。

金字塔有六个三角形面。 每个面都需要一个包含三个 Vector2 类型对象的数组。 必须选择坐标以将图像以合理的方式映射到面部。 我选择的坐标将整个纹理图像映射到金字塔的正方形底部,并从图像中切出一个三角形以应用于每个边。 想出正确的坐标需要一些小心。 我定义金字塔几何体的纹理坐标如下:

pyramidGeometry.faceVertexUvs = [[
    [ new THREE.Vector2(0,0), new THREE.Vector2(0,1), new THREE.Vector2(1,1) ],
    [ new THREE.Vector2(0,0), new THREE.Vector2(1,1), new THREE.Vector2(1,0) ],
    [ new THREE.Vector2(0,0), new THREE.Vector2(1,0), new THREE.Vector2(0.5,1) ],
    [ new THREE.Vector2(1,0), new THREE.Vector2(0,0), new THREE.Vector2(0.5,1) ],
    [ new THREE.Vector2(0,0), new THREE.Vector2(1,0), new THREE.Vector2(0.5,1) ],
    [ new THREE.Vector2(1,0), new THREE.Vector2(0,0), new THREE.Vector2(0.5,1) ],
]];

请注意,这是一个三维数组。

示例程序 threejs/textured-pyramid.html 显示了带有砖纹理的金字塔。

这是程序中的图像:

4、变换

为了理解如何在 three.js 中有效地使用对象,了解更多关于它如何实现变换会很有用。 我已经解释过 Object3D 对象具有属性 obj.position、obj.scale 和 obj.rotation,这些属性在其自己的局部坐标系中指定其模型变换。

但是这些属性在渲染对象时并不直接使用。 相反,它们被组合起来计算另一个属性 obj.matrix,它将转换表示为矩阵。 默认情况下,每次渲染场景时都会自动重新计算此矩阵。 如果转换从不改变,这可能是低效的,所以 obj 有另一个属性 obj.matrixAutoUpdate,它控制是否自动计算 obj.matrix。 如果将 obj.matrixAutoUpdate 设置为 false,则不会完成更新。 在这种情况下,如果确实想要更改模型的变换,可以调用 obj.updateMatrix() 以根据 obj.position、obj.scale 和 obj.rotation 的当前值计算矩阵。

我们已经了解了如何通过直接更改属性 obj.position、obj.scale 和 obj.rotation 的值来修改 obj 的模型变换。 但是,你也可以通过调用函数 obj.translateX(dx)、obj.translateY(dy) 或 obj.translateZ(dz) 将对象沿坐标轴方向移动指定量来更改位置。 还有一个函数 obj.translateOnAxis(axis, amount),其中 axis 是一个 Vector3,amount 是一个给出平移对象距离的数字。 对象沿矢量轴的方向移动。 向量必须归一化; 也就是说,它的长度必须为 1。例如,要在向量 (1,1,1) 的方向上将 obj 平移 5 个单位,这一这样调用:

obj.translateOnAxis( new THREE.Vector3(1,1,1).normalize(), 5 );

没有用于更改缩放变换的函数。 但是你可以使用函数 obj.rotateX(angle)、obj.rotateY(angle) 和 obj.rotateZ(angle) 更改对象的旋转,以围绕坐标轴旋转对象。 (请记住,角度以弧度为单位。)调用 obj.rotateX(angle) 与将角度添加到 obj.rotation.x 的值上不同,因为它在其他旋转之上应用绕 x 轴的旋转 可能已经应用了。

还有一个函数 obj.rotateOnAxis(axis, angle),其中 axis 是一个 Vector3。 此函数将对象绕轴矢量(即,关于原点和轴给定的点之间的线)旋转 angle 角度。 轴必须是归一化向量。

我要强调的是平移和旋转函数修改对象的位置和旋转属性。 也就是说,它们适用于对象坐标,而不是世界坐标,并且它们在渲染对象时作为对象的第一个模型变换应用。 例如,旋转是世界坐标可以改变对象的位置,如果它不位于原点。 然而,改变一个对象的旋转属性的值永远不会改变它的位置。

旋转实际上更复杂。对象 obj 的旋转实际上是由属性 obj.quaternion 表示的,而不是由属性 obj.rotation 表示的。四元数是计算机图形学中经常使用的数学对象,作为 Euler 角的替代品来表示旋转。但是,当你更改属性 obj.rotation 或 obj.quaterion 之一时,另一个属性会自动更新以确保两个属性表示相同的旋转。因此,我们不需要直接使用 四元数。

还有一种更有用的方法来设置旋转:obj.lookAt(vec),它旋转对象使其面向给定点。 参数vec是一个Vector3,必须用物体自己的局部坐标系表示。 (对于没有父对象或其祖先没有模型变换的对象,这将与世界坐标相同。)该对象也被旋转,使其“向上”方向等于属性 obj.up 的值 ,默认情况下为 (0,1,0)。 此函数可用于任何对象,但对相机最有用。

5、加载 JSON 模型

虽然可以通过列出顶点和面来创建网格对象,但是对于除了非常简单的对象以外的所有对象,手动创建是很困难的。 例如,在 Blender等交互式建模程序中设计对象要容易得多。 Three.js 具有从文件加载模型的实用函数,它附带的脚本可用于将对象从 Blender 和其他 3D 建模程序导出为 three.js 可以读取的文件格式。

Three.js 有自己的文件格式,其中模型使用 JSON 指定,JSON 是一种表示 JavaScript 对象的通用格式。 这是导出脚本生成的文件格式。 类 THREE.JSONLoader 可用于从此类文件中读取模型描述。 还有一些其他加载器,可以处理其他文件格式,但我在这里只讨论 JSONLoader。

如果 loader 是一个 THREE.JSONLoader 类型的对象,可以使用它的 load() 方法来启动加载模型的过程:

loader.load( url, callback );

第一个参数是包含模型的文件的 URL。 JSON 模型存储为实际的 JavaScript 代码,因此该文件的名称通常以“.js”结尾。 第二个参数 callback 是加载完成时将调用的函数。 加载是异步的; loader.load() 启动进程并立即返回。

回调函数负责使用文件中的数据创建 three.js Object3D 并将其添加到场景中。 回调函数有两个参数,geometry 和 materials,其中包含创建对象所需的信息; 参数表示已从文件中读取的数据。 材料参数是一种材质或材质数组,可用作网格对象构造函数中的第二个参数。 当然,你也可以不使用文件中的材质,而是使用自己的。

下面函数可用于从指定的 url 加载 JSON 模型并将其添加到场景中:

function loadModel( url ) {  // Call this function to load the model.
    var loader = new THREE.JSONLoader();
    loader.load( url, modelLoaded ); // Start load, call modelLoaded when done.
}

function modelLoaded( geometry, material ) { // callback function for loader
    var object = new THREE.Mesh( geometry, material );
    scene.add(object);
    render(); // (only need this if there is no animation running)
}

示例程序 threejs/json-model-viewer.html 使用 JSONLoader 读取模型描述。 它可以显示七个不同的对象。 我使用 Blender 创建了其中一个对象。 其他对象带有 three.js。 其中五个对象使用简单的白色材料; “horse”和“stork”使用模型文件中作为顶点颜色(而非材质)提供的颜色。

我还需要指出, JSON 模型可以定义简单的关键帧动画。 为此,该文件包含每个关键帧的替代顶点集(three.js 称它们为“变形目标”)。 Three.js 有几个支持动画的类,包括 THREE.AnimationMixer、THREE.AnimationAction 和 THREE.AnimationClip。 这里不讨论动画,在线演示 c5/mesh-animation.html 中这三个类用于制作马和鹳模型的动画。


原文链接:Building Objects

BimAnt翻译整理,转载请标明出处