文章目录
前言
一、Three.js简介
二、开发步骤
1.安装Three.js
2.创建容器
3.创建模型
总结
前言
3D模型给人一种更真实的感受,带来极致的视觉体验。本文介绍Vue结合Three.js开发3D小房子,接触过OpenGL的小伙伴看起来会更轻松一点。
一、Three.js简介
Three.js,一个WebGL引擎,基于javascript,可直接运行GPU驱动游戏与图形驱动应用于浏览器。其库提供大量特性与API以绘制3D场景于浏览器。官网地址
二、开发步骤
1.安装Three.js
这里是使用的npm安装
npm install three
2.创建容器
Three.js是使用Js将3D模型渲染在一个画布中,需要创建一个容器来存放。
<div class="three_page" id="threePage">
</div>
3.创建模型
Three.js有几个非常重要的概念,场景、相机、光源、坐标系,渲染器......首先需要创建一个场景对象,因为其他模型都需要放在场景中,最后将场景交由渲染器进行渲染。
initScene(){
this.scene = new THREE.Scene();
}
为了方便确定模型的位置,所以我引入了坐标系。红色代表 X 轴.,绿色代表 Y 轴.,蓝色代表 Z 轴。
initAxes(){
let axes = new THREE.AxesHelper(50);
// 将坐标加入到场景
this.scene.add(axes)
}
创建一个地面
initPlane(){
let plane = new THREE.PlaneGeometry(this.planeWidth, this.planeHeight);
let materialPlane = new THREE.MeshLambertMaterial({
color: 0x*********
});
let planeMesh = new THREE.Mesh(plane, materialPlane);
planeMesh.rotation.x = -0.5 * Math.PI;
planeMesh.position.set(0, 0, 0);
this.scene.add(planeMesh);
},
创建光源,光源是非常重要的,要是没有光源,将会什么都看不见。光源有好几种,本文中使用的是环境光和点光源。
initLight(){
this.scene.add(new THREE.AmbientLight(0x444444));//环境光,可以看到所有物体
// 光源1
// DirectionalLight 方向光
let pointLight = new THREE.PointLight(0xffffff);//点光源
pointLight.position.set(20, 30, 20);
this.scene.add(pointLight);
// 光源2
let pointLight2 = new THREE.PointLight(0xffffff);//点光源
pointLight2.position.set(150, 100, 20);
this.scene.add(pointLight2);
},
创建相机,相机就相当于人眼,你要在什么位置看,看多大的范围,都在相机里面配置。
initCamera(){
this.camera = new THREE.PerspectiveCamera(45, 2, 0.1, 2000);
this.camera.position.set(120, 100, 0);
this.camera.lookAt(this.scene.position);
},
创建渲染器,这一步很重要,我们创建的模型都是放在Scene里面的,最后需要将Scene和Samera交由渲染器进行渲染。
initRenderer(){
this.container = document.getElementById('threePage');
this.renderer = new THREE.WebGLRenderer({
antialias: true //消除锯齿
});
this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);
this.renderer.setClearColor(0xb9d3ff, 1);
this.renderer.render(this.scene, this.camera);
this.container.appendChild(this.renderer.domElement);
this.container.addEventListener('click', this.onMouseClick);
//增加鼠标拾取效果
let controls = new OrbitControls(this.camera, this.renderer.domElement);
controls.addEventListener('change', () => {
this.renderer.render(this.scene, this.camera);
});
},
到此,就已经完成了平面和坐标系的渲染。
接着往容器中加入模型,官网介绍了很多模型的创建方法,本文是导入的外部模型,感兴趣的小伙伴可以去一些网站下载模型,这里提供一个网站。模型也有很多种格式,本文导入的是glb的格式
async initModel() {
let glb = await this.loadGlb('./model/house_type13.glb');
// 设置模型位置 (y,z,x)
glb.position.set(0, 0, 0);
// 设置模型缩放比例
glb.scale.set(30, 30, 30);
this.houseData.push(glb)
this.scene.add(glb);
let three=await this.loadGlb('./model/tree_large.glb');
three.position.set(30, 0, 20);
three.scale.set(30, 30, 30);
this.houseData.push(three)
this.scene.add(three);
let three2=await this.loadGlb('./model/tree_large.glb');
three2.position.set(30, 0, 30);
three2.scale.set(30, 50, 50);
this.houseData.push(three2)
this.scene.add(three2);
let house2=await this.loadGlb('./model/house_type15.glb');
house2.position.set(30, 0, 50);
house2.scale.set(30, 30, 30);
this.houseData.push(house2)
this.scene.add(house2);
},
loadGlb(path) {
return new Promise((resolve, reject) => {
var loader = new GLTFLoader();
loader.setCrossOrigin('Anonymous');//跨域问题
loader.load(path, (glb) => {
resolve(glb.scene);
}, undefined, (error) => {
reject(error);
});
}).catch((e) => {
console.log("异常", e);
});
},
同样,将模型加入到场景(Scene)后,需要交给渲染器进行渲染。最后小房子也出来了呀。
完整代码
<template>
<div class="three_page" id="threePage">
</div>
</template>
<script>
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import { OBJLoader } from "three/examples/jsm/loaders/OBJLoader"
import { MTLLoader } from "three/examples/jsm/loaders/MTLLoader"
export default {
data() {
return {
renderer: '',
cube: '',
planeWidth: 150,
planeHeight: 200,
raycaster: new THREE.Raycaster(),
mouse: new THREE.Vector2(),
dialogControl: '',
houseData: [],
container: '',
dialogData: '',
modelName: []
}
},
created() {
},
mounted() {
this.init()
},
methods: {
// 创建场景
initScene() {
this.scene = new THREE.Scene();
},
// 创建坐标
initAxes() {
let axes = new THREE.AxesHelper(50);
// 将坐标加入到场景
this.scene.add(axes)
},
// 创建平面
initPlane() {
let plane = new THREE.PlaneGeometry(this.planeWidth, this.planeHeight);
let materialPlane = new THREE.MeshLambertMaterial({
color: 0x*********
});
let planeMesh = new THREE.Mesh(plane, materialPlane);
planeMesh.rotation.x = -0.5 * Math.PI;
planeMesh.position.set(0, 0, 0);
this.scene.add(planeMesh);
},
// 创建光源
initLight() {
this.scene.add(new THREE.AmbientLight(0x444444));//环境光,可以看到所有物体
// 光源1
// DirectionalLight 方向光
let pointLight = new THREE.PointLight(0xffffff);//点光源
pointLight.position.set(20, 30, 20);
this.scene.add(pointLight);
// 光源2
let pointLight2 = new THREE.PointLight(0xffffff);//点光源
pointLight2.position.set(150, 100, 20);
this.scene.add(pointLight2);
},
// 创建相机
initCamera() {
this.camera = new THREE.PerspectiveCamera(45, 2, 0.1, 2000);
this.camera.position.set(120, 100, 0);
this.camera.lookAt(this.scene.position);
},
// 创建渲染器
initRenderer() {
this.container = document.getElementById('threePage');
this.renderer = new THREE.WebGLRenderer({
antialias: true //消除锯齿
});
this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);
this.renderer.setClearColor(0xb9d3ff, 1);
this.renderer.render(this.scene, this.camera);
this.container.appendChild(this.renderer.domElement);
this.container.addEventListener('click', this.onMouseClick);
//增加鼠标拾取效果
let controls = new OrbitControls(this.camera, this.renderer.domElement);
controls.addEventListener('change', () => {
this.renderer.render(this.scene, this.camera);
});
},
init() {
this.initScene()
this.initAxes()
this.initPlane()
this.initLight()
this.initCamera()
this.initRenderer()
this.initModel()
setInterval(() => {
this.renderer.render(this.scene, this.camera);
}, 1000)
},
// 加载模型
async initModel() {
let glb = await this.loadGlb('./model/house_type13.glb');
// 设置模型位置 (y,z,x)
glb.position.set(0, 0, 0);
// 设置模型缩放比例
glb.scale.set(30, 30, 30);
this.houseData.push(glb)
this.scene.add(glb);
let three = await this.loadGlb('./model/tree_large.glb');
three.position.set(30, 0, 20);
three.scale.set(30, 30, 30);
this.houseData.push(three)
this.scene.add(three);
let three2 = await this.loadGlb('./model/tree_large.glb');
three2.position.set(30, 0, 30);
three2.scale.set(30, 50, 50);
this.houseData.push(three2)
this.scene.add(three2);
let house2 = await this.loadGlb('./model/house_type15.glb');
house2.position.set(30, 0, 50);
house2.scale.set(30, 30, 30);
this.houseData.push(house2)
this.scene.add(house2);
},
// 加载DLB
loadGlb(path) {
return new Promise((resolve, reject) => {
var loader = new GLTFLoader();
loader.setCrossOrigin('Anonymous');//跨域问题
loader.load(path, (glb) => {
resolve(glb.scene);
}, undefined, (error) => {
reject(error);
});
}).catch((e) => {
console.log("异常", e);
});
},
// 加载gltf
addGltfItem() {
let that = this;
let loader = new GLTFLoader();
loader.load("./glb/model.gltf", function (gltf) {
gltf.scene.position.set(0, 0, 95);
gltf.scene.scale.set(16, 12, 12);
that.scene.add(gltf.scene);
});
},
// 加载obj
addObjItem() {
var loader = new OBJLoader();
var mat = new MTLLoader();
let that = this;
mat.load("./glb/house_type01.mtl", function (materials) {
materials.preload();
loader.setMaterials(materials);
loader.load(
"./glb/house_type01.obj",
function (object) {
console.log("数据==>", object);
object.position.set(0, 0, 0);
object.scale.set(13, 13, 13);
that.scene.add(object);
}
);
});
},
// 点击事件
onMouseClick(event) {
event.preventDefault();
this.dialogControl = {
show: false,
top: 0,
left: 0
};
let mou = new THREE.Vector2()
// 将鼠标位置归一化为设备坐标。x 和 y 方向的取值范围是 (-1 to +1)
mou.x = (event.clientX / this.container.clientWidth) * 2 - 1;
mou.y = -(event.clientY / this.container.clientHeight) * 2 + 1;
console.log("x", "y", this.mouse.x, this.mouse.y);
this.raycaster.setFromCamera(mou, this.camera);
let intersects = this.raycaster.intersectObjects(this.scene.children, true);
// 获取选中最近的 Mesh 对象
if (intersects.length !== 0 && intersects[0].object.type === "Mesh") {
let selectName = intersects[0].object.name;
console.log("模型名字", intersects[0].object.name);
this.houseData.forEach(h => {
console.log(h.name);
if (h.name === selectName) {
this.dialogData = h;
this.dialogControl = {
show: true,
top: event.clientY,
left: event.clientX
};
console.log("模型被点击--", h);
}
});
}
console.log(this.dialogData);
}
}
}
</script>
<style>
.three_page {
width: 100%;
height: 100%;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
</style>
总结
第一次接触Three.js做3D,可能也存在很多错误,希望小伙伴多多指点。刚收到要做3D的需求时觉得自己根本做不出来,但是看了一下Three.js的官方文档以后还是慢慢做出来了,以前接触过OpenGL,所以也大概了解流程,总是要逼自己一把才能进步。