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 );
文字
  1. 最简单的文字添加方式是直接用html定位覆盖在canvas上
  2. 将文字绘制到画布中,并通过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);
  1. 在3D软件里创建模型,并导出给three.js
  2. BMFonts (位图字体) ,将字形批处理为单个BufferGeometry
  3. 通过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

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

通用属性/方法
key 作用 默认值
transparent 是否允许纹理具有透明度 false
opacity 透明度 1
alphaTest 纹理不透明度低于该值的位置会渲染为透明 0
side 要渲染的面,通常只渲染相机能看到的面,但有时如需看到透明材料的内部等,可以使用THREE.BackSideTHREE.DoubleSide THREE.FrontSide
flatShading
visible 材质是否可见 true
fog 材质是否受雾影响 true
vertexColors 是否使用顶点着色。
THREE.NoColors(将材质颜色应用到所有面)
THREE.VertexColors(根据每个Face3的顶点颜色vertexColors着色)
THREE.FaceColors(根据每个Face3的color值着色)
THREE.NoColors
polygonOffset 是否使用多边形偏移
详见Z-Fighting
false
polygonOffsetFactor 多边形偏移系数 0
polygonOffsetUnits 多边形偏移单位 0
.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的幂。值越大纹理越清晰但性能越差。
通用属性/方法
key 作用 默认值
offset 0~1之间,表示纹理在单次重复时,从一开始将分别在U、V方向上偏移多少。 (0,0)
repeat 纹理分别在U、V方向重复多少次。如大于1则对应的Wrap应当也被设为THREE.RepeatWrappingTHREE.MirroredRepeatWrapping (1,1)
rotation 纹理将逆时针转动的弧度 0
center 旋转中心点,(0.5, 0.5)对应纹理的正中心,默认为左下角。 (0,0)
onUpdate 纹理被更新后的回调函数 null
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);
    // 如果返回结果不为空,且交点与射线起点的距离小于物体中心至顶点的距离,则发生了碰撞
    if (collisionResults.length > 0 && collisionResults[0].distance < directionVector.length()) {
        crash = true;   // crash 是一个标记变量
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,383评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,522评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,852评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,621评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,741评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,929评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,076评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,803评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,265评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,582评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,716评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,395评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,039评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,027评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,488评论 2 361
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,612评论 2 350

推荐阅读更多精彩内容