个人觉得目前threejs的相关资料还是比较零散的,需要不断整理自己知识体系,这里分享一下自己的一些总结,很多内容都是别人的博客中学习而来。
在项目(vue-cli)中使用
首先当然是引入我们的模块包
npm install three --save
//在需要使用的地方引入
import * as THREE from "three";
这样安装的threejs与CDN引入或者外部的方式会有些不一样,好处在于我们不在需要引入各种各样的外部文件,例如OrbitControls,各种loader等等这些文件大部分可以在three/examples/jsm/
目录中找到.
three/examples/jsm/loaders
加载器three/examples/jsm/controls
控制器three/examples/jsm/libs
//参考链接
//https://blog.csdn.net/lin5165352/article/details/96120362
使用方式也很简单:
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
这样就可以愉快地在项目中使用。
整理一些常用的效果,包括点击,后期组合通道,贴图
创建场景天空盒(skybox)
所谓的天空盒其实就是将一个立方体展开,然后在六个面上贴上相应的贴图,如上图所示。天空盒是用于增强场景表现力的一个常用技术,它一般通过在相机周围包裹一个纹理来实现。 OpenGL中天空盒的思想就是绘制一个大的立方体,然后将观察者放在立方体的中心,当相机移动时,这个立方体也跟着相机一起移动,这样相机就永远不会运动到场景的边缘。
在Three.js中,除了这种方法外还可以设置场景的背景,将六个面的贴图通过CubeTextureLoader()载入,顺序为[right,left,up,down,front,back]。
//方法1 六张图片 注意贴面图片的顺序
let urls = [
"image/skybox/远山_RT.jpg", // right
"image/skybox/远山_LF.jpg", // left
"image/skybox/远山_UP.jpg", // top
"image/skybox/远山_DN.jpg", // bottom
"image/skybox/远山_BK.jpg", // back
"image/skybox/远山_FR.jpg" // front
];
this.scene.background = new THREE.CubeTextureLoader().load(urls);
//方法二 一张图片
var skyMap = new THREE.TextureLoader().load("/ground/image/星空球2.jpg");
this.scene.background = skyMap;
模型
新手在加载模型的过程中经常遇到各种奇怪的问题(说的不就是我吗),然后经过一番琢磨之后整理了一些可能的原因。当然第一步还是先打开浏览器控制台,出现报错信息先解决报错信息
- 模型加载后,不显示也不报错? 检查场景是否正常渲染了,摄像机在哪里,摄像机是否对着模型,灯光是否配置,模型是否太大或者太小了,尝试将模型放大或缩小到原来的1000倍。模型的中心点不在模型内等等
//设置中心点在几何中心
geometry.computeBoundingBox();
geometry.center()
-
模型可以正常加载,但是贴图不显示? 首先检查network是否报404错误,如果报错,一般都是mtl贴图文件没有正常加载,或者路径配置的不是相对路径,如果贴图没错误,模型是黑色的,在mtl文件中可以更改ka或kd的三个值(对应rgb),或者打印出模型属性,在material.color中更改点色值或别的属性
-
模型过大加载速度慢
obj2gltf
先将模型转成浏览器最喜欢的gltf格式,这个过程将文件体积大大减小,同时写的代码也少了,起飞芜湖。别急,还能进一步压缩gltf-pipeline-github
npm install obj2gltf -g //全局安装后
obj文件所在目录 输出目录
obj2gltf -i ./examples/models/obj/hanchuan/city.obj -o ./gltf/city.gltf --unlit --separate
gltf-pipeline -i 源文件 -o 输出文件 -d --separate
//-d是--draco.compressMeshes的缩写,使用draco算法压缩模型
//--separate就是将贴图文件提取出来,不提可以不加
管道流动效果
此效果一般用在路线的循环流动,思路是创建一个管道之后给路径增加贴图,然后在animate中不断改变texture的偏移量,达到循环流动的效果。
//http://wjceo.com/three.js/docs/#api/zh/geometries/TubeGeometry
var curve = new THREE.CatmullRomCurve3([
new THREE.Vector3(-80, 0, -40),
new THREE.Vector3(-70, 0, 40),
new THREE.Vector3(70, 0, 40),
new THREE.Vector3(80, 0, -40),
], false/*是否闭合*/);
var tubeGeometry = new THREE.TubeGeometry(curve, 100, 2, 8, false);
var textureLoader = new THREE.TextureLoader();
var texture = textureLoader.load('./model/roll.png');
// 设置阵列模式为 RepeatWrapping
texture.wrapS = THREE.RepeatWrapping
texture.wrapT = THREE.RepeatWrapping
// 设置x方向的重复数(沿着管道路径方向)
// 设置y方向的重复数(环绕管道方向)
texture.repeat.set(2, 4);
texture.needsUpdate = true;
texture.transparent = true;
texture.side = THREE.DoubleSide;
var tubeMaterial = new THREE.MeshPhongMaterial({
map: texture,
transparent: true,
});
var tube = new THREE.Mesh(tubeGeometry, tubeMaterial);
scene.add(tube)
....
// 渲染函数
function render() {
renderer.render(scene, camera); //执行渲染操作
requestAnimationFrame(render);
// 使用加减法可以设置不同的运动方向
// 设置纹理偏移
texture.offset.x += 0.001
}
再进一步可以作出物体沿着路径移动的效果,只需要动态改变物体的位置就行了,下面是奔跑的那撸多。注意要让人物朝向奔跑的方向,我们只要lookAt(下一个点的position)就可以了。
人物的动画方面我们可以用骨骼来实现,这里为了偷懒直接拿了别人的那撸多过来用,是个fbx格式的模型,这种格式本身就存储了动画内容。核心代码如下:
let loader = new THREE.FBXLoader()
let mesh, mixer;
let actions = []; //所有的动画数组
loader.load('./model/Naruto.fbx', function (obj) {
mesh = obj
//添加骨骼辅助
meshHelper = new THREE.SkeletonHelper(mesh);
scene.add(meshHelper);
//设置模型的每个部位都可以投影
mesh.traverse(function (child) {
if (child.isMesh) {
child.castShadow = true;
child.receiveShadow = true;
}
});
//AnimationMixer是场景中特定对象的动画播放器。当场景中的多个对象独立动画时,可以为每个对象使用一AnimationMixer
mixer = mesh.mixer = new THREE.AnimationMixer(mesh);
//mixer.clipAction 返回一个可以控制动画的AnimationAction对象 参数需要一个AnimationClip 对象
//AnimationAction.setDuration 设置一个循环所需要的时间,当前设置了一秒
//告诉AnimationAction启动该动作
//action = mixer.clipAction(mesh.animations[0]);
//action.play();
mesh.scale.set(0.1, 0.1, 0.1);
mesh.position.set(80, 0, -40);
scene.add(mesh);
})
// 渲染函数
function render() {
var time = clock.getDelta();
if (mixer) {
mixer.update(time);
actions[3].play()
move()
}
renderer.render(scene, camera); //执行渲染操作
requestAnimationFrame(render);
// 使用加减法可以设置不同的运动方向
// 设置纹理偏移
texture.offset.x += 0.001
}
function move() {
if (progress < 0.002) {
actions[3].stop(); //到了终点就别跑了,action[3]是人物的奔跑动作
return; //停留在管道末端,否则会一直跑到起点 循环再跑
}
progress -= 0.001;
// console.log(progress);
if (curve) {
let point = curve.getPoint(progress); //当前点的位置
let next_point = curve.getPoint(progress - 0.001); //这是下一个点的位置
if (point && next_point) {
mesh.position.set(point.x, point.y, point.z);
mesh.lookAt(next_point.x, next_point.y, next_point.z)
}
}
}
甚至可以在人物的 运动轨迹上留下一条线。核心代码如下
//在场景中的线条
var _material = new THREE.LineBasicMaterial({
color: 0xff0000
});
var _geometry = new THREE.BufferGeometry();
var _pointsBuf = [
]
var _vertices = new Float32Array(_pointsBuf);
_geometry.addAttribute('position', new THREE.BufferAttribute(_vertices, 3));
var _lineA = new THREE.Line(_geometry, _material);
scene.add(_lineA);
let _i = 0;
// 渲染函数
function render() {
var time = clock.getDelta();
if (mixer) {
mixer.update(time);
actions[3].play()
move()
}
renderer.render(scene, camera); //执行渲染操作
requestAnimationFrame(render);
}
function move() {
console.log(_pointsBuf.length);
if (_pointsBuf.length < 3003) { //多加一个单位的长度为了让首位连起来
_pointsBuf.push(points[_i].x, points[_i].y, points[_i].z)
}
_vertices = new Float32Array(_pointsBuf)
_geometry.addAttribute('position', new THREE.BufferAttribute(_vertices, 3));
mesh.position.set(points[_i].x, points[_i].y, points[_i].z);
mesh.lookAt(points[_i + 1].x, points[_i + 1].y, points[_i + 1].z)
_i++;
if (_i > 1000 - 1) _i = 0 //回到了运动的起点 可以循环运动
}
Sprite精灵的应用
精灵是一个总是面朝着摄像机的平面,通常含有使用一个半透明的纹理。
关于用途,你可以在三维场景中把精灵模型作为一个模型的标签,标签上可以显示一个写模型的信息,你可以通过足够多的精灵模型对象,构建一个粒子系统,来模拟一个下雨、森林、或下雪的场景效果。图片或canvas都可以作为纹理
var texture = new THREE.TextureLoader().load("sprite.png");
// 创建精灵材质对象SpriteMaterial
var spriteMaterial = new THREE.SpriteMaterial({
color:0xff00ff,//设置精灵矩形区域颜色
rotation:Math.PI/4,//旋转精灵对象45度,弧度值
map: texture,//设置精灵纹理贴图
});
// 创建精灵模型对象,不需要几何体geometry参数
var sprite = new THREE.Sprite(spriteMaterial);
scene.add(sprite);
// 控制精灵大小,比如可视化中精灵大小表征数据大小
sprite.scale.set(10, 10, 1); //// 只需要设置x、y两个分量就可以
使用ThreeBSP库进行Three.js网格组合
之前一直使用Three.js默认提供的几何体,使用ThreeBSP库可以将现有的模型组合出更多个性的模型来使用。我们可以使用ThreeBSP库里面的三个函数进行现有模型的组合,分别是:差集(相减)、并集(组合、相加)、交集(两几何体重合的部分)。下图的墙壁就是使用threebsp将简单几何体拼接而成
//1.使用几何体分别创建墙面mesh与窗户(objects_cube数组中保存窗几何体)
//2.墙几何体减去窗户几何体,生成BSP对象
//3.转成场景里的mesh对象并更新向量与uv
createResultBsp(bsp, objects_cube) {
var material = new THREE.MeshPhongMaterial({
color: 0x9cb2d1,
specular: 0x9cb2d1,
shininess: 30,
transparent: true,
opacity: 1
});
var BSP = new ThreeBSP(bsp);
for (var i = 0; i < objects_cube.length; i++) {
var less_bsp = new ThreeBSP(objects_cube[i]);
BSP = BSP.subtract(less_bsp);
}
var result = BSP.toMesh(material);
result.material.flatshading = THREE.FlatShading; //使用平面着色
result.geometry.computeFaceNormals(); //重新计算几何体侧面法向量
result.geometry.computeVertexNormals();
result.material.needsUpdate = true; //更新纹理
result.geometry.buffersNeedUpdate = true;
result.geometry.uvsNeedUpdate = true;
this.scene.add(result);
},
OpenGL GLSL
着色器是个好东西,但是入门门槛还是蛮高的~
推荐一下暮志未晚大佬的博客:www.wjceo.com/