文章目录

前言

一、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: 0xcccccc
    });
    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: 0xcccccc
            });
            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,所以也大概了解流程,总是要逼自己一把才能进步。

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐