记录
最近公司接到一个新项目,有关包装设计的,里面需要涉及的技术难点就是3d模型这块,由于我们公司的前端都没有接触过3d,也不知为何,老大将重任交付给了我,咱也只好接下了,选用的技术是Nuxt+three.js,这里主要讲一下three.js 的使用
初始化
在vue的项目中先要安装必要的three.js相关插件
npm install three three-orbitcontrols three-obj-mtl-loader stats-js
- three:核心库;
- stats-js: 性能检测
- three-orbitcontrols:控制器鼠标旋转3D模型使用;
- three-obj-mtl-loader:模型导入加载器;
一个模型能显示在屏幕上的必然条件
- 需要一个cavnas作为3D渲染的空间;
- 场景: 用来放置各种模型,相机或者光源的;
- 相机: 用来观看场景; 相机分为透视相机和正视相机;
- 光源: 用来照亮场景,以及物体;
- 模型: 模型可以自己通过three.js构建,也可以采用3D建模软件建立好的模型进行导入,本文采用的是.Obj格式的模型文件导入。
1、创建场景
createScene() {
this.container = document.getElementById("view-box");
// 创建场景(所有的模型,相机,灯光,都需要通过add方法添加到场景中)
this.scene = new THREE.Scene();
// 添加场景背景色
this.scene.background = new THREE.Color(0xf3f3f3);
// 创建渲染函数
this.renderer = new THREE.WebGLRenderer({
antialias: true, // 抗锯齿 模型质量更高 消耗性能越大
alpha: true // 开启透明度
});
this.renderer.setPixelRatio(window.devicePixelRatio);
this.renderer.setSize(this.windowHalfX, this.windowHalfY);
// 将渲染的3d场景挂载到指定的DOM上
this.container.appendChild(this.renderer.domElement);
}
2、创建相机
createCamera() {
this.camera = new THREE.PerspectiveCamera(
45, // 摄像机视锥体垂直视野角度
this.windowHalfX / this.windowHalfY, // 摄像机视锥体长宽比 (要渲染区域的 长 / 宽 ,如果不对渲染会出现变形效果)
1, // 摄像机视锥体远端面 (最近能看到的距离)
2000 // 摄像机视锥体近端面 (最远能看到的距离)
);
// 设置相机距离屏幕的距离
this.camera.position.z = 50;
// 将相机添加到场景中
this.scene.add(this.camera);
},
3、创建灯光
灯光不做详细介绍,在本次项目中没有使用到,只用到了基本的环境光
createLight() {
let color = 0xf9f9f9
// 创建环境光,环境光没有投影,会均匀的照亮环境中所有的物体
let ambientLight = new THREE.AmbientLight(color);
// 将环境光添加到场景中
this.scene.add(ambientLight);
// 创建聚光灯光源,有投影
let directionalLight = new THREE.DirectionalLight(0xb4b6fd);
directionalLight.position.set(40, 60, 20);
directionalLight.castShadow = true;
directionalLight.shadowCameraNear = 2;
directionalLight.shadowCameraFar = 100;
directionalLight.shadowCameraLeft = -50;
directionalLight.shadowCameraRight = 50;
directionalLight.shadowCameraTop = 50;
directionalLight.shadowCameraBottom = -50;
// 将聚光灯添加到场景中
this.scene.add(directionalLight);
},
4、创建模型 导入的模型格式为(.obj)
createModel() {
let mtlLoader = new MTLLoader()
// 加载材质.mtl文件
mtlLoader.load(`/obj/white-box.mtl`, (materials) => {
materials.preload();
let loader = new OBJLoader(this.manager);
loader.load(
`/obj/抽屉盒-简模贴图.obj`,
(obj) => {
this.object = obj;
// 设置模型中心点居中
this.object.children[0].geometry.center()
this.object.children[0].geometry.computeBoundingBox();
}
);
})
},
5、创建贴图
涉及到需要改变每个面上贴图的会有多层,所以选择使用canvas贴图,在canvas上操作更方便
/**
* 创建一个贴图
*/
createTexture() {
this.currentModelTexture.top = new THREE.CanvasTexture(this.createCanvas());
this.lodingModelAndTexture()
},
/**
* 加载模型贴图
*/
lodingModelAndTexture() {
let loadModel = () => {
this.object.traverse((child) => {
// 设置模型的长宽高比例
if (child.isMesh) child.scale.set(1, 1, 1);
if (child instanceof THREE.Mesh) {
if(child.material instanceof Array) {
child.material.forEach(item => {
// item.map = this.currentModelTexture[item.name] 本身项目中有很多面
item.map = this.currentModelTexture.top // 这是为了方便理解,只开放了一个面
});
child.castShadow = true;
child.receiveShadow = true;
}
}
});
// 将模型添加到场景
this.scene.add(this.object);
};
},
/**
* 返回一个canvas
*/
createCanvas() {
const canvas = utils.createHiDPICanvas(width, height, 4)
const ctx = canvas.getContext('2d');
ctx.fillStyle = #fff;
ctx.fillRect(0, 0, 画布宽, 画布高);
ctx.textBaseline = "hanging";
let img = new Image();
img.src = '1.jpg';
let alpha = 1, // 透明度
deg = 180; // 旋转
img.onload = (e) => {
ctx.save(); // 保存画布状态
ctx.globalAlpha = alpha;
ctx.translate(画布宽 / 2, 画布高 / 2); // 设置旋转按照图片的中心旋转
ctx.rotate(deg * Math.PI / 180);
ctx.drawImage(zindex.img, x, y, 图片原始宽度, 图片原始高度, -(w / 2), -(h / 2), w, h);
ctx.restore(); // 恢复状态
}
return canvas
},
/**
* 创建高清canvas画布
* @param { 宽 } w
* @param { 高 } h
* @param { 像素 } ratio
*/
createHiDPICanvas(w, h, ratio) {
const PIXEL_RATIO = (function() {
const c = document.createElement("canvas"),
ctx = c.getContext("2d"),
dpr = window.devicePixelRatio || 1,
bsr = ctx['webkitBackingStorePixelRatio'] ||
ctx['mozBackingStorePixelRatio'] ||
ctx['msBackingStorePixelRatio'] ||
ctx['oBackingStorePixelRatio'] ||
ctx['backingStorePixelRatio'] || 1;
return dpr / bsr;
})();
if (!ratio) { ratio = PIXEL_RATIO; }
const can = document.createElement("canvas");
can.width = w * ratio;
can.height = h * ratio;
can.style.width = w + "px";
can.style.height = h + "px";
can.getContext("2d").setTransform(ratio, 0, 0, ratio, 0, 0);
return can;
}
6、动画
// 动画
animate() {
if(this.object) {
this.object.children[0].rotation.y += 0.001
}
// 原生js动画函数
requestAnimationFrame(this.animate);
this.renderer && this.render();
},
// 渲染
render() {
this.camera.lookAt(this.scene.position);
this.renderer.render(this.scene, this.camera);
},
init() {
this.createScene()
this.createCamera()
this.createLight()
this.createTexture()
this.$nextTick(() => {
this.createModel()
});
this.createOther()
},
// 开始加载
loding() {
this.windowHalfX = this.$refs.three.offsetWidth;
this.windowHalfY = window.innerHeight - 80;
this.init();
this.animate();
},
7、其他控制器以及辅助设置
createOther() {
// 辅助对象
var AxesHelper = new THREE.AxesHelper(150);
this.scene.add(AxesHelper);
// 控制器
var controls = new OrbitControls(this.camera, this.renderer.domElement);
this.renderer.domElement.removeAttribute('tabindex')
controls.addEventListener("change", this.render);
this.render();
},
8、性能优化,缓存清理
// 清空渲染器缓存
clearRenderer() {
this.object && this.clearCache(this.object.children[0])
this.renderer.forceContextLoss();
this.currentModelTexture = null
this.renderer.dispose();
this.renderer.domElement = null;
this.renderer = null;
}
// 在vue的销毁钩子中调用
destroyed() {
this.clearRenderer()
}
9、其他用到的函数方法
插入排序
/**
* @param { 排序数组 } arr
* @param { 要对比的值 } val
*/
const Insertion = (arr, val) => {
let handle = []
handle.push(arr[0])
for (let i = 1; i < arr.length; i++) {
let a = arr[i][val]
for (let j = handle.length - 1; j >= 0; j--) {
let b = handle[j][val]
if (a > b) {
handle.splice(j + 1, 0, arr[i])
break
}
if (j === 0) {
handle.unshift(arr[i])
}
}
}
return handle
}
图片转base64
/**
* @param { 图片加载地址 } url
*/
const converbase64Img = (url) => {
var canvas = document.createElement('CANVAS'),
ctx = canvas.getContext('2d');
return new Promise((resolve, reject) => {
let img = new Image;
img.crossOrigin = '';
img.src = url;
img.onload = function() {
canvas.height = img.height;
canvas.width = img.width;
ctx.drawImage(img, 0, 0);
var dataURL = canvas.toDataURL('image/png');
resolve(dataURL)
canvas = null;
};
img.onerror = (e) => {
reject(e)
}
})
}
- three.js 中文文档 www.yanhuangxueyuan.com/threejs/doc…
- vue+threejs使用canvas纹理 lindasoft.com/view/articl…
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!