安装
npm install threejs -D
引用threejs
import * as THREE from 'three'
// 鼠标控制器
import {
OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
// obj模型
import {
OBJLoader } from 'three/examples/jsm/loaders/OBJLoader'
// 材质
import {
MTLLoader } from 'three/examples/jsm/loaders/MTLLoader'
场景
在threejs中场景就只有一种,用THREE.Scene来表示,要构建一个场景只要new一个对象就可以了。
this.scene = new THREE.Scene()
属性
属性 | 描述 |
---|---|
children | 数组,用于存储添加到场景中的所有对象 |
fog | 雾化,雾化效果的特点是场景中的物体离得越远就会变得越模糊,有三个参数:雾的颜色,最近距离,最远距离 |
方法 | |
方法 | 描述 |
– | – |
Add() | 向场景中添加对象 |
Remove() | 移除场景中的对象 |
getObjectByName() | 获取场景中指定名称的对象 |
tranverse() | 以一个方法作为参数,这个方法将会在每一个子对象上执行。如果子对象本身还有子对象,该方法将会在所有的子对象上执行,直到遍历完场景树中的所有对象为止 |
相机 | |
相机决定了场景中那个角度的景色会显示出来,相机就像人的眼睛一样,站在不同的位置,抬头或者低头都能看到不同的景色。分为两种正投影相机THREE.OrthographicCamera和透视投影相机THREE.PerspectiveCamera。 | |
正投影相机 | |
OrthographicCamera( left, right, top, bottom, near, far ) |
- left: 渲染空间的左边界
- right: 渲染空间的右边界
- top:渲染空间的上边界
- bottom:渲染空间的下边界
- near: near属性表示的是从距离相机多远的位置开始渲染,一般情况会设置一个很小的值。 默认值0.1
- far:far属性表示的是距离相机多远的位置截止渲染,如果设置的值偏小小,会有部分场景看不到。 默认值1000
透视投影相机
PerspectiveCamera( fov, aspect, near, far )
this.camera = new THREE.PerspectiveCamera(45, this.rendererWidth / this.rendererHeight, 0.1, 10000000)
- fov: 视角fov,视角的大小,如果设置为0,相当你闭上眼睛了,所以什么也看不到,如果为180,那么可以认为你的视界很广阔,但是在180度的时候,往往物体很小,因为他在你的整个可视区域中的比例变小了。一般情况下45
- near: 表示你近处的裁面的距离,也可以认为是眼睛距离近处的距离(>0)
- aspect: 实际窗口的纵横比,即宽度除以高度。这个值越大,说明你宽度越大,那么你可能看的是宽银幕电影了,如果这个值小于1
渲染器
Three.js中的场景是一个物体的容器,开发者可以将需要的角色放入场景中;
相机的作用就是面对场景,在场景中取一个合适的景,把它拍下来;
渲染器的作用就是将相机拍摄下来的图片,放到浏览器中去显示。
new THREE.WebGLRenderer()
属性 | 含义 |
---|---|
antialias | 是否开启反锯齿,设置为true开启反锯齿。 |
alpha | 是否可以设置背景色透明。 |
maxLights | 最大灯光数,我们的场景中最多能够添加多少个灯光。 |
logarithmicDepthBuffer | 模型的重叠部位不停的闪烁。这便是Z-Fighting问题,为解决这个问题,我们可以采用该种方法 |
方法 | 含义 |
---|---|
setSize | 制定渲染器的宽高,renderer.setSize(width,height) |
setClearColor | 设置canvas背景色(clearColor)和背景色透明度(clearAlpha) |
setPixelRatio | 设置分辨率,解决场景模糊,抗锯齿的一种很好的方法 |
this.renderer = new THREE.WebGLRenderer({
antialais: true,
alpha: true,
logarithmicDepthBuffer: true,
})
this.renderer.setSize(this.rendererWidth, this.rendererHeight)
this.renderer.setClearColor(0x39609b)
this.renderer.setPixelRatio(window.devicePixelRatio)
const container = document.getElementById('canvasContainer')
container.appendChild(this.renderer.domElement)
threejs中的坐标系
threejs中的灯光
光源种类 | 含义 |
---|---|
环境光(AmbientLight) | 笼罩在整个空间无处不在的光,不能产生阴影 |
点光源(PointLight ) | 向四面八方发射的单点光源,不能产生阴影 |
聚光灯(SpotLight ) | 锥形效果的光源,能够产生阴影 |
平行光(DirectinalLight) | 平行光,类似太阳光,距离很远的光,会产生阴影 |
// 环境光
this.ambient = new THREE.AmbientLight(0xffffff, 1)
this.ambient.position.set(0, 0, 0)
this.scene.add(this.ambient)
// 平行光
this.directionalLight = new THREE.DirectionalLight(0xffffff, 0.3)
this.directionalLight.position.set(0, 200, 0)
this.scene.add(this.directionalLight)
// 设置点光源
this.pointLight1 = new THREE.PointLight(0xffffff, 0.3)
this.pointLight1.position.set(-500, 200, 0)
this.scene.add(this.pointLight1)
this.pointLight2 = new THREE.PointLight(0xffffff, 0.3)
this.pointLight2.position.set(500, 200, 0)
this.scene.add(this.pointLight2)
模型加载
实际开发中,大多数项目,通常是3D美术设计师或建筑、机械等行业工程师提供的由3dmx、blender、substence、Solidworks等软件创建好的三维模型文件
1.加载.obj模型文件:
使用三维软件导出 .obj 模型文件的时候,会同时导出一个材质文件 .mtl , .obj 和 .stl 文件包含的信息一样都是几何体顶点相关数据,材质文件 .mtl 包含的是模型的材质信息,比如颜色、贴图路径等。
加载 .obj 三维模型的时候,可以只加载 .obj 文件,然后借助three.js引擎自定义材质Material,也可以同时加载 .obj 和 .mtl 文件。
只加载obj文件:
<!-- 引入obj模型加载库OBJLoader.js -->
<script src="../../three.js-master/examples/js/loaders/OBJLoader.js"></script>
/**
* OBJ文件加载 只加载obj文件中的几何信息,不加载材质文件.mtl
*/
var loader = new THREE.OBJLoader();
// 没有材质文件,系统自动设置Phong网格材质
loader.load('./立方体/box.obj',function (obj) {
// 控制台查看返回结构:包含一个网格模型Mesh的组Group
console.log(obj);
// 查看加载器生成的材质对象:MeshPhongMaterial
console.log(obj.children[0].material);
scene.add(obj);
})
// 加载后的一些编辑操作
obj.children[0].scale.set(20,20,20);//网格模型缩放
obj.children[0].geometry.center();//网格模型的几何体居中
obj.children[0].material.color.set(0xff0000);//设置材质颜色
同时加载obj文件和mtl文件:
mtl 文件包含了模型的材质信息,比如模型颜色、透明度等信息,还有纹理贴图的路径,比如颜色贴图、法线贴图、高光贴图等等。
<!-- 引入obj模型加载库OBJLoader.js -->
<script src="../../three.js-master/examples/js/loaders/OBJLoader.js"></script>
<!-- 引入obj模型材质加载库MTLLoader.js -->
<script src="../../three.js-master/examples/js/loaders/MTLLoader.js"></script>
/**
* OBJ和材质文件mtl加载
*/
var OBJLoader = new THREE.OBJLoader();//obj加载器
var MTLLoader = new THREE.MTLLoader();//材质文件加载器
MTLLoader.load('./立方体/box.mtl', function(materials) {
// 返回一个包含材质的对象MaterialCreator
console.log(materials);
//obj的模型会和MaterialCreator包含的材质对应起来
OBJLoader.setMaterials(materials);
OBJLoader.load('./立方体/box.obj', function(obj) {
console.log(obj);
obj.scale.set(10, 10, 10); //放大obj组对象
scene.add(obj);//返回的组对象插入场景中
})
})
完整代码:
// html
<div id="canvasContainer" />
// js
// json 数据
const pageData = {
container: {
length: 15670,width: 2870,height: 3300,},
boxlist: [{
color: 'rgba(235,215,0,0.5)',x: 0,length: 2120, width: 1200,y: 0,z: 0,boxId: '5GD821021(左前叶子板)_1',height: 1640,},
{
color: 'rgba(235,0,215,0.5)',x: 0,length: 2120,width: 1200,y: 1200,z: 0,boxId: '5GD821022(右前叶子板)_1',height: 1640,},
{
color: 'rgba(215,215,30,0.5)',: 2120,length: 1785,width: 2160,y: 0,z: 0,boxId: '5GD823031(发动机盖总成)_1',height: 1530,},
{
color: 'rgba(215,215,30,0.5)',x: 2120,length: 1785,width: 2160,y: 0,z: 1530,boxId: '5GD823031(发动机盖总成)_1', height: 1530,},
{
color: 'rgba(215,30,215,0.5)',x: 3905,length: 1700,width: 2150,y: 0,z: 0,boxId: '3CG827025E(后+R58+F57:Q57)_1',height: 1450,},
{
color: 'rgba(215,195,60,0.5)',x: 3905,length: 1700, width: 2150,y: 0,z: 1450,boxId: '3CG827025E(后+R58+F57:Q57)_1',height: 1450,},
{
color: 'rgba(195,215,60,0.5)',x: 5605,length: 2160,width: 1350,y: 0,z: 0,boxId: '3CG831055D(左前门焊接总成)_1',height: 1700,},
{
color: 'rgba(195,60,215,0.5)',x: 5605,length: 2150,width: 1330,y: 0,z: 1700,boxId: '5DD831055(左前门)_1',height: 1465,},
{
color: 'rgba(215,175,90,0.5)',x: 7765,length: 2160,width: 1350, y: 0,z: 0,boxId: '3CG831056D(右前门焊接总成)_1',height: 1700,},
{
color: 'rgba(175,215,90,0.5)',x: 7765,length: 2150,width: 1330,y: 0,z: 1700,boxId: '5DD833056(右后门)_1',height: 1465,},
{
color: 'rgba(175,90,215,0.5)',x: 9925,length: 2160,width: 1350,y: 0,z: 0,boxId: '3CG831056D(右前门焊接总成)_2',height: 1700,},
{
color: 'rgba(215,155,120,0.5)',x: 9925,length: 2150,width: 1330,y: 0,z: 1700,boxId: '5GD831055A(左前门)_1', height: 1465,},
{
color: 'rgba(155,215,120,0.5)',x: 12085,length: 2160,width: 1280,y: 0,z: 0,boxId: '2GG831051(车门)_1',height: 1560,},
{
color: 'rgba(155,120,215,0.5)',x: 12085,length: 2160, width: 1280,y: 0,z: 1560,boxId: '2GG831051(车门)_1',height: 1560,},
{
color: 'rgba(215,135,150,0.5)',x: 14245,length: 1280,width: 1800,y: 0,z: 0,boxId: '2GG821101(翼子板)_1',height: 1500,},
{
color: 'rgba(135,215,150,0.5)',x: 14245,length: 1280,width: 1800,y: 0,z: 1500,boxId: '2GG821102(翼子板)_1',height: 1500,},
{
color: 'rgba(135,150,215,0.5)',x: 5605,length: 2120,width: 1200, y: 1350,z: 0,boxId: '5GD821021(左前叶子板)_1',height: 1640,},
{
'color': 'rgba(215,115,180,0.5)',x: 7725,length: 2120,width: 1200,y: 1350, z: 0,boxId: '5GD821021(左前叶子板)_2',height: 1640,},
{
color: 'rgba(115,215,180,0.5)',x: 9845,length: 2120,width: 1200,y: 1350,z: 0,boxId: '5GD821021(左前叶子板)_3',height: 1640,},
{
color: 'rgba(115,180,215,0.5)',x: 12085,length: 2160,width: 1280,y: 1280,z: 0, boxId: '5GD833055A(左后门)_1',height: 1480,},
{
color: 'rgba(215,95,210,0.5)', x: 12085,length: 2160,width: 1280,y: 1280,z: 1480,boxId: '5GD833056A(右后门)_1',height: 1480,},,}
export default {
data(){
return {
renderer: null, // 渲染器
scene: null, // 场景
camera: null, // 相机
ambient: null, // 环境光
directionalLight: null, // 平行光
pointLight1: null, // 点光源1
pointLight2: null, // 点光源2
controls: null, // 轨道控件
carLength: 0,
carHeight: 0,
carWidth: 0,
}
},
methods: {
threeInit() {
// 初始化渲染器
this.initThree()
// 初始化场景
this.initScene()
// 初始化相机
this.initCamera()
// 初始化灯光
this.initLight()
// 加载obj文件
this.initAgv()
// 初始化控件
this.initControls()
// 循环渲染
this.animation()
},
initThree() {
this.renderer = new THREE.WebGLRenderer({
antialais: true,
alpha: true,
logarithmicDepthBuffer: true,
})
this.renderer.setSize(this.rendererWidth, this.rendererHeight)
this.renderer.setClearColor(0x39609b)
this.renderer.setPixelRatio(window.devicePixelRatio)
const container = document.getElementById('canvasContainer')
container.appendChild(this.renderer.domElement)
},
initScene() {
this.scene = new THREE.Scene()
},
initCamera() {
this.camera = new THREE.PerspectiveCamera(45, this.rendererWidth / this.rendererHeight, 0.1, 10000000)
// 相机位置
var conta = {
length: pageData.container.length, width: pageData.container.width, height: pageData.container.height }
this.camera.position.set(conta.length * 2, conta.width * 2, conta.height * 2)
// 相机朝向
this.camera.lookAt(this.scene.position)
this.scene.add(this.camera)
},
initLight() {
// 环境光
this.ambient = new THREE.AmbientLight(0xffffff, 1)
this.ambient.position.set(0, 0, 0)
this.scene.add(this.ambient)
// 平行光
this.directionalLight = new THREE.DirectionalLight(0xffffff, 0.3)
this.directionalLight.position.set(0, 200, 0)
this.scene.add(this.directionalLight)
// 设置点光源
this.pointLight1 = new THREE.PointLight(0xffffff, 0.3)
this.pointLight1.position.set(-500, 200, 0)
this.scene.add(this.pointLight1)
this.pointLight2 = new THREE.PointLight(0xffffff, 0.3)
this.pointLight2.position.set(500, 200, 0)
this.scene.add(this.pointLight2)
},
initAgv() {
const objLoader = new OBJLoader()
const mtlLoader = new MTLLoader()
mtlLoader.load('static/model/car.mtl', (materials) => {
objLoader.setMaterials(materials)
objLoader.load('static/model/car.obj', (obj) => {
obj.scale.set(20, 20, 20) // 网格模型缩放
const bbox = new THREE.Box3().setFromObject(obj)
var carHead = 5617 // 车头长度 = (bbox.max.z - bbox.min.z)/2 - 7866
this.carLength = bbox.max.z - bbox.min.z - carHead // 车箱长度,不含车头
this.carHeight = bbox.max.x - bbox.min.x
this.carWidth = bbox.max.y - bbox.min.y
var difx = (bbox.max.x - bbox.min.x) / 2
obj.rotation.y = -Math.PI / 2
obj.translateZ(-7866)
obj.translateY(-2500)
obj.translateX(2500)
this.scene.add(obj)
})
})
},
initControls() {
// x 红 y 绿 z 蓝
this.controls = new OrbitControls(this.camera, this.renderer.domElement) // 创建控件对象
var axes = new THREE.AxisHelper(6000)
this.scene.add(axes)
},
animation() {
requestAnimationFrame(this.animation)
this.controls.update()
this.renderer.render(this.scene, this.camera)
},
onLoading() {
this.drawPackage(pageData)
},
drawPackage(data) {
const carData = data.container
const packData = data.boxlist
packData.forEach((item) => {
var realVirtualRatio = Math.min(this.carWidth / carData.width, this.carHeight / carData.height, this.carLength / carData.length)
var goodsLength = item.length * realVirtualRatio
var goodsWidth = item.width * realVirtualRatio
var goodsHeight = item.height * realVirtualRatio
var goodsX = item.x * realVirtualRatio
var goodsY = item.y * realVirtualRatio
var goodsZ = item.z * realVirtualRatio
const box = new THREE.BoxGeometry(goodsLength, goodsWidth, goodsHeight)
const Mesh = new THREE.MeshLambertMaterial({
color: item.color,
})
// 画出网格图形并且定义材质(颜色)
var MeshItem = new THREE.Mesh(box, Mesh)
MeshItem.uuid = item.boxId
MeshItem.type = item.type
MeshItem.position.copy(new THREE.Vector3(goodsX + goodsLength / 2, goodsY + goodsWidth / 2, goodsZ + goodsHeight / 2))
MeshItem.castShadow = !0
MeshItem.receiveShadow = !0
this.scene.add(MeshItem)
})
},
}
}
扫描二维码关注公众号,回复:
14836344 查看本文章