Three.js (三) 模型、材质、纹理、网格

模型 Geometry

精灵(Sprite)

精灵是一个总是面朝着摄像机的方形平面,不会投射任何阴影。
精灵的构造函数只接收一个 SpriteMaterial 材料对象,其通常使用半透明的纹理。

var spriteMap = new THREE.TextureLoader().load( "sprite.png" );
var spriteMaterial = new THREE.SpriteMaterial( { map: spriteMap, color: 0xffffff } );
var sprite = new THREE.Sprite( spriteMaterial );
scene.add( sprite );
点(Points)

Points( geometry : Geometry, material : PointsMaterial )
Points是点(默认为一方形平面)的集合,其中每个点都总是面朝着摄像机。相较于 THREE.SpriteTHREE.Points 更适合粒子数量多的情况(因只需要维护单个实例)。

  • 通过依次定义点的坐标生成点阵
  • var geometry = new THREE.Geometry()
    var material = new THREE.PointsMaterial({
      size: 4,
      vertexColors: true, // 是否为几何体的每个顶点应用颜色,默认值是为所有面应用材质的颜色
      color: 0xffffff
    for (var x = -5; x < 5; x++) {
      for (var y = -5; y < 5; y++) {
        var particle = new THREE.Vector3(x * 10, y * 10, 0)
        geometry.vertices.push(particle)
        geometry.colors.push(new THREE.Color(Math.random() * 0xffffff))
    var points = new THREE.Points(geometry, material)
    scene.add(points)
    
  • 通过传入现成的几何体生成点阵
  • var points;
    var loader = new THREE.OBJLoader();
    loader.load('https://res.cloudinary.com/dgnx97ptu/raw/upload/v1506569170/chahu_xlo7pg.obj', function (loadedMesh) {
      var material = new THREE.PointsMaterial({
        color: 0xffffff,
        size: 0.4,
        // 设置元素与背景的融合模式
        blending: THREE.AdditiveBlending,
        // 用于去除纹理的黑色背景,关于 depthTest 和 depthWrite 的详细解释,请查看https://stackoverflow.com/questions/37647853/three-js-depthwrite-vs-depthtest-for-transparent-canvas-texture-map-on-three-p
        depthTest: false
      loadedMesh.children.forEach(function (child) {
        points = new THREE.Points(child.geometry, material)
        scene.add(points);
    var cubeGeometry = new THREE.BoxGeometry(10, 10, 10);
    points = new THREE.Points(cubeGeometry)
    scene.add(points);
    

    连续点直接连接成线,线的材质只有实线LineBasicMaterial和虚线LineDashedMaterial

    var material = new THREE.LineBasicMaterial({
        color: 0xffffff,
        linewidth: 1,
        linecap: 'round', //ignored by WebGLRenderer
        linejoin:  'round' //ignored by WebGLRenderer
    var geometry = new THREE.Geometry();
    geometry.vertices.push(new THREE.Vector3( -10, 0, 0) );
    geometry.vertices.push(new THREE.Vector3( 0, 10, 0) );
    geometry.vertices.push(new THREE.Vector3( 10, 0, 0) );
    var line = new THREE.Line( geometry, material );
    scene.add( line );
    renderer.render( scene, camera );
    

    也可以从路径Path对象中获取点以连接成线

    var path = new THREE.Path();
    path.lineTo( 0, 0.8 );
    path.quadraticCurveTo( 0, 1, 0.2, 1 );
    path.lineTo( 1, 1 );
    var points = path.getPoints();//返回Vector2组成的数组
    var geometry = new THREE.BufferGeometry().setFromPoints( points );
    var line = new THREE.Line( geometry );
    scene.add( line );
    
  • 最简单的文字添加方式是直接用html定位覆盖在canvas上
  • 将文字绘制到画布中,并通过CanvasTexture将其用作纹理
  • let canvas = document.createElement("canvas");
    let ctx = canvas.getContext("2d");
    ctx.fillStyle = "#0000ff";
    ctx.font = "10px Arial";
    ctx.fillText(`hello`, 0, 10);
    let texture = new THREE.CanvasTexture(canvas);
    let material = new THREE.MeshBasicMaterial({ map: texture,alphaTest: 0.1, transparent: true, opacity:1});
    let geometry = new THREE.BoxGeometry(100, 100, 100);
    var mesh = new THREE.Mesh(geometry,material);
    this.scene.add(mesh);
    
  • 在3D软件里创建模型,并导出给three.js
  • BMFonts (位图字体) ,将字形批处理为单个BufferGeometry
  • 通过new THREE.TextGeometry( text, parameters );引用Typeface字体文件
  • 导入3D模型

    推荐使用 glTF(gl传输格式).GLB.GLTF是这种格式的两种不同版本, 都可以被很好地支持。

    <script src="GLTFLoader.js"></script>
    var loader = new THREE.GLTFLoader();
    loader.load( 'path/to/model.glb', function ( gltf ) {
        scene.add( gltf.scene );
    }, undefined, function ( error ) {
        console.error( error );
    

    其余FBXCollada以及OBJ也可以支持,需要加载对应的载入JS。

    常见几何模型
  • 长方体 BoxGeometry(width = 1, height = 1, depth = 1, widthSegments = 1, heightSegments = 1, depthSegments = 1)

  • 长方形 PlaneGeometry(width = 1, height = 1, widthSegments = 1, heightSegments = 1)

  • 球 SphereGeometry(radius = 1, widthSegments = 8, heightSegments = 6, phiStart = 0, phiLength = 2 * Math.PI, thetaStart = 0, thetaLength = Math.PI)

  • phiLength小于默认值时会裂开(类似吃豆人)
  • thetaLength小于默认值时会削去一部分,如Math.PI/2时类似一个碗
  • 扇形 CircleGeometry(radius = 1, segments = 8, thetaStart = 0, thetaLength = 2 * Math.PI)

  • 圆锥几何体(ConeGeometry)

  • 圆柱几何体(CylinderGeometry)

  • 四面几何体(TetrahedronGeometry)

  • 八面几何体(OctahedronGeometry)

  • 十二面几何体(DodecahedronGeometry)

  • 挤压几何体(ExtrudeGeometry)
    按照指定参数将一个二维图形(形状Shape)沿 z 轴拉伸出一个三维图形

  • var heartShape = new THREE.Shape();
    heartShape.moveTo( 25, 25 );
    heartShape.bezierCurveTo( 25, 25, 20, 0, 0, 0 );
    heartShape.bezierCurveTo( 30, 0, 30, 35,30,35 );
    heartShape.bezierCurveTo( 30, 55, 10, 77, 25, 95 );
    heartShape.bezierCurveTo( 60, 77, 80, 55, 80, 35 );
    heartShape.bezierCurveTo( 80, 35, 80, 0, 50, 0 );
    heartShape.bezierCurveTo( 35, 0, 25, 25, 25, 25 );
    var extrudeSettings = { amount: 8, bevelEnabled: true, bevelSegments: 2, steps: 2, bevelSize: 1, bevelThickness: 1 };
    var geometry = new THREE.ExtrudeGeometry( heartShape, extrudeSettings );
    var mesh = new THREE.Mesh( geometry, new THREE.MeshPhongMaterial() );
    
  • 二十面几何体(IcosahedronGeometry)

  • 车削几何体(LatheGeometry)
    创建具有轴对称性的网格,比如花瓶。车削绕着Y轴来进行旋转。

  • 圆环形(RingGeometry)

  • 圆环几何体(TorusGeometry)

  • 通用属性、方法
  • vertices
    顶点位置信息(Vector3)数组,保存了模型中每个顶点的位置信息。
    出于性能的考虑,Three.js 认为几何体在整个生命周期都不会更改,
    如果要修改已经加入Mesh的geometry的顶点,verticesNeedUpdate 值需要被设置为true,否则修改不会生效。
    通过调用mergeVertices()可以移除重复的顶点,并在各顶点间创建出连续的面。
  • var geomCockpit = new THREE.BoxGeometry(80, 50, 50, 1, 1, 1);
    geomCockpit.vertices[4].y -= 10;
    geomCockpit.vertices[4].z += 20;
    geomCockpit.vertices[5].y -= 10;
    geomCockpit.vertices[5].z -= 20;
    geomCockpit.vertices[6].y += 30;
    geomCockpit.vertices[6].z += 20;
    geomCockpit.vertices[7].y += 30;
    geomCockpit.vertices[7].z -= 20;
    var cockpit = new THREE.Mesh(geomCockpit);
    
  • face,为Face3(三角片面)实例组成的数组,具有如下属性:
  • a、b、c — 顶点ABC的索引
  • normal — 矢量展示 Face3 的方向
  • color — 面的颜色值,当材质的vertexColors值为THREE.FaceColors时,该属性生效。
  • materialIndex — 材质队列中与该面对应的材质的索引,默认为0。
  • vertexNormals — 包含三个 vertex normals 的队列。
  • vertexColors — 包含 3 个顶点各自颜色值(Color)的数组。当材质的vertexColors值为THREE.VertexColors时,该属性生效。
  • 通过face修改每个面的颜色或材料

    for (let i = 0; i < mesh.geometry.faces.length; i++) {
      let hex = Math.random() * 0xffffff;
      mesh.geometry.faces[i].color.setHex(hex);
    let material = new THREE.MeshBasicMaterial({
      vertexColors: THREE.FaceColors
    mesh.material = material
    scene.add(mesh);
    
    let mats = [];
    for (var i = 0; i < geometry.faces.length; i++) {
      let material = new THREE.MeshBasicMaterial({ color: new THREE.Color(Math.random() * 0xffffff) });
      geometry.faces[i].materialIndex = i;
      mats.push(material);
    let mesh = new THREE.Mesh(geometry, mats);
    scene.add(mesh);
    
  • .merge( Geometry, Matrix4, materialIndexOffsetInteger)
    调用者(几何体实例)将一个网格中的几何体合并到自身。
  • var geometry = new THREE.Geometry();
    mesh.updateMatrix();
    geometry.merge(mesh.geometry, mesh.matrix);
    

    材质 Material

    表面各可视属性的结合,包括色彩、纹理、光滑度、透明度、反射率、折射率、发光度等。
    材质会反射符合自身颜色的色光,当没有任何光源时,不管什么颜色的材质都显示为黑色。

    通用属性/方法
    vertexColors 是否使用顶点着色。
    THREE.NoColors(将材质颜色应用到所有面)
    THREE.VertexColors(根据每个Face3的顶点颜色vertexColors着色)
    THREE.FaceColors(根据每个Face3的color值着色) THREE.NoColors polygonOffset 是否使用多边形偏移
    详见Z-Fighting false polygonOffsetFactor 多边形偏移系数 polygonOffsetUnits 多边形偏移单位 .clone ( ) : Material 返回与此材质相同参数的新材质 .copy ( material) : Material 将被传入材质的参数复制到此材质中 .dispose () : null 处理材质。材质的纹理不会被处理。
    基础网格材质(MeshBasicMaterial)

    简单的平面/线框体,不受光照的影响。

    Lambert网格材质(MeshLambertMaterial)

    一种非光泽表面的材质,没有镜面高光。可以很好地模拟未经处理的木材或石材等,但不能模拟具有镜面高光的光泽表面(例如涂漆木材)。

    Phong网格材质(MeshPhongMaterial)

    一种用于具有镜面高光的光泽表面的材质。

    阴影材质(ShadowMaterial)

    此材质可以接收阴影,但在其他方面完全透明。

    纹理 Texture

    纹理就是贴图。将纹理以一定的规则映射到几何体的材质上,那么这个几何体就有纹理皮肤了。
    THREE.Texture( image,mapping,wrapS,wrapT,magFilter,minFilter,format,type,anisotropy, encoding)

  • Image:这是一个图片类型,基本上通过TextureLoader来加载。(ImageUtils.loadTexture已废弃)
  • var texture = new THREE.TextureLoader().load( "textures/water.jpg" );
    
  • mapping:表示图像将如何应用到物体上。默认值是THREE.UVMapping对象类型, 即UV坐标将被用于纹理映射。
  • wrapS:表示x轴纹理的铺设方式。
  • THREE.ClampToEdgeWrapping(默认值),单个纹理拉伸覆盖整个材料。
  • THREE.RepeatWrapping,重复
  • THREE.MirroredRepeatWrapping,重复且每次重复时镜像
  • wrapT:表示y轴纹理的铺设方式。可选值同wrapS
  • magFilter:当一个纹素覆盖大于一个像素时,贴图将如何采样。 THREE.LinearFilter(默认值), 它将获取四个最接近的纹素,并在他们之间进行双线性插值。
  • THREE.NearestFilter,它将使用最接近的纹素的值。
  • minFilter:当一个纹素覆盖小于一个像素时,贴图将如何采样。
  • format:表示加载的图片的格式
  • THREE.RGBAFormat(默认值),允许透明
  • THREE.RGBFormat,在载入JPG图片时会自动设置为该值,无透明。
  • type:表示存储纹理内存的字节格式,默认是THREE.UnsignedByteType
  • anisotropy:各向异性过滤,默认为1,通常为2的幂。值越大纹理越清晰但性能越差。
  • 通用属性/方法
    repeat 纹理分别在U、V方向重复多少次。如大于1则对应的Wrap应当也被设为THREE.RepeatWrappingTHREE.MirroredRepeatWrapping (1,1) rotation 纹理将逆时针转动的弧度 center 旋转中心点,(0.5, 0.5)对应纹理的正中心,默认为左下角。 (0,0) onUpdate 纹理被更新后的回调函数 needsUpdate 当纹理被使用时是否自动触发更新 false .clone () : Texture 拷贝纹理。注意这不是“深拷贝”,图像是共用的。 .dispose () : null 处理纹理。
    Canvas纹理(CanvasTexture)

    从Canvas元素中创建纹理贴图。needsUpdate直接为true。第一个入参为一个canvas dom对象

    视频纹理(VideoTexture)

    创建一个使用视频来作为贴图的纹理对象。needsUpdate直接为true。第一个入参为一个video dom对象

    网格物体 Mesh

    new THREE.Mesh( geometry : Geometry, material : Material )
    网格包含一个几何体以及作用在此几何体上的材质,其中material可以为单个或一个数组,如不传则为一个随机颜色的MeshBasicMaterial

    Mesh实例具有geometrymaterial属性,直接对其进行修改并重新渲染即可体现在该物体上。

  • geometry
    来自构造函数的第一个入参
  • material
    Material基类或者其数组派生而来的材质实例,定义了物体的外观。默认值是一个具有随机颜色的MeshBasicMaterial
  • receiveShadow
  • castShadow
  • position
  • rotation
  • renderOrder
  • scale
    为一个Vector3实例,也可以用mesh.scale.set(1,1,1)设置
  • Object3D、Group

    这两个类几乎是相同的,其目的是使得组中对象在语法上的结构更加清晰。
    可以先将mesh加入Object3D(或Group)实例,然后再将该实例加入scene

    var obj1 = new THREE.Object3D();
    var obj2 = new THREE.Object3D();
    var mesh = new THREE.Mesh(geometry);
    obj1.add(mesh);
    obj2.add(obj1);
    scene.add(obj2);
     *  功能:检测 movingCube 是否与数组 collideMeshList 中的元素发生了碰撞
    var originPoint = movingCube.position.clone();
    for (var vertexIndex = 0; vertexIndex < movingCube.geometry.vertices.length; vertexIndex++) {
        // 顶点原始坐标
        var localVertex = movingCube.geometry.vertices[vertexIndex].clone();
        // 顶点经过变换后的坐标
        var globalVertex = localVertex.applyMatrix4(movingCube.matrix);
        // 获得由中心指向顶点的向量
        var directionVector = globalVertex.sub(movingCube.position);
        // 将方向向量初始化
        var ray = new THREE.Raycaster(originPoint, directionVector.clone().normalize());
        // 检测射线与多个物体的相交情况
        var collisionResults = ray.intersectObjects(collideMeshList);
        // 如果返回结果不为空,且交点与射线起点的距离小于物体中心至顶点的距离,则发生了碰撞