vue3 + Babylon.js 构建Web 3D场景

<script setup>

import { ref, getCurrentInstance, onMounted, beforeUnmount } from 'vue'

import * as BABYLON from '@babylonjs/core/Legacy/legacy' // 全部引入

import '@babylonjs/loaders' // 模型加载loader

import * as GUI from '@babylonjs/gui/2D' // 交互组件

const { proxy } = getCurrentInstance()

const emit = defineEmits(['customChange'])

let engine = ref(null)

let scene = ref(null)

let camera = ref(null)



// 模型加载进度百分比

let progress = ref(0)

// 是否完成模型渲染

let isRendering = ref(false)

// 是否展示视频

let showVideo = ref(false)



// 自适应渲染

const engineResize = ()=> {

    engine.resize();

}

// 重置模型

const reset = ()=> {

    scene.activeCamera.restoreState();

}

let activeModel = null;
let activeColor = null;
let sprite1 = null;

//Animation Camera position
        const animateCameraToPosition = (speed, frameCount, newPos)=> {
            let ease = new BABYLON.CubicEase();
            ease.setEasingMode(BABYLON.EasingFunction.EASINGMODE_EASEINOUT);
            //BABYLON.Animation.CreateAndStartAnimation = function(name, mesh, targetProperty, framePerSecond, totalFrame, from, to, loopMode);
            BABYLON.Animation.CreateAndStartAnimation('a1', scene.activeCamera, 'position', speed, frameCount, this.scene.activeCamera.position, newPos, 0, ease);
            //scene.activeCamera.setPosition(newPos);
        }
        const animateCameraTargetToPosition = (speed, frameCount, newPos)=> {
            let ease = new BABYLON.CubicEase();
            ease.setEasingMode(BABYLON.EasingFunction.EASINGMODE_EASEINOUT);
            BABYLON.Animation.CreateAndStartAnimation('a2', scene.activeCamera, 'target', speed, frameCount, scene.activeCamera.target, newPos, 0, ease);
            //scene.activeCamera.setTarget(newPos);
        }
        const onPointerDown =(e)=>{
            let pickResult = scene.pick(scene.pointerX, scene.pointerY,
            null, false, null, (p0, p1, p2, ray) => {
                let p0p1 = p0.subtract(p1);
                let p2p1 = p2.subtract(p1);
                let normal = BABYLON.Vector3.Cross(p0p1, p2p1);
                return (BABYLON.Vector3.Dot(ray.direction, normal) < 0);
            });
            if(pickResult.hit) {
                if(pickResult.pickedMesh.name.includes("Box")){

                    // ** Disable all before apply Outline Render  **
                    scene.meshes.forEach((mesh)=>{
                        if(mesh.name.indexOf("") != -1){
                            mesh.renderOverlay = false;
                        }
                    });

//let pbr = pickResult.pickedMesh.material
//pbr.albedoColor = new BABYLON.Color3(0.71, 0.67, 0.61) // 反射颜色
//pbr.metallic = 1 // 金属
//pbr.roughness = 0.5 // 粗糙

                    // Outline Render
                    let StudyArea = scene.getMeshByName(pickResult.pickedMesh.name);
                    StudyArea.renderOverlay = true;
                    
                    let oldPivotTranslation = pickResult.pickedMesh.getBoundingInfo().boundingBox.centerWorld.clone();

                    let postion = new BABYLON.Vector3(oldPivotTranslation._x+30,oldPivotTranslation._y+30,oldPivotTranslation._z+30)
                    let speed1 = 500;
                    let speed2 = 500;
                    let frameCount = 200;
                    animateCameraToPosition(speed1, frameCount, postion);
                    animateCameraTargetToPosition(speed2, frameCount, oldPivotTranslation);

                    sprite1.size = 3;
                    sprite1.playAnimation(0, 6, true, 300);
                    sprite1.position = new BABYLON.Vector3(oldPivotTranslation._x,oldPivotTranslation._y+1.5,oldPivotTranslation._z);

                    // 选中模型
                    if (!activeModel) {
                        activeModel = pickResult.pickedMesh;

                        activeColor = new BABYLON.Color3(
                            ...Object.values(pickResult.pickedMesh.material.albedoColor)
                        );

                        activeModel.material.albedoColor = new BABYLON.Color3(0, 1, 0.1);
                    }
                    if (activeModel && activeModel != pickResult.pickedMesh) {
                        // 选中新模式
                        activeModel.material.albedoColor = activeColor;
                        activeModel = pickResult.pickedMesh;
                        activeColor = new BABYLON.Color3(
                            ...Object.values(pickResult.pickedMesh.material.albedoColor)
                        );
                        activeModel.material.albedoColor = new BABYLON.Color3(0, 1, 0.1);
                    }
                }
            } else if (activeModel) {
                activeModel.material.albedoColor = activeColor;
                activeModel = null;
                activeColor = null;
                sprite1.size = 0;
            }
        }

const onDispose = ()=>{
            scene.onPrePointerObservable.removeCallback(onPointerDown());
        }

let timer1 = null

onMounted(() => {

    let canvas = document.getElementById('canvas');

    // 初始化 BABYLON 3D engine

    engine = new BABYLON.Engine(canvas, true, { preserveDrawingBuffer: true, stencil: true, disableWebGL2Support: false });

    // 自定义loading加载效果

    function customLoadingScreen() {

        console.log('customLoadingScreen creation');

    }

    customLoadingScreen.prototype.displayLoadingUI = function() {

        console.log('customLoadingScreen loading')

    };

    customLoadingScreen.prototype.hideLoadingUI = function() {

        window.document.getElementById('loadingScreen').style.display = 'none';

    };

    engine.loadingScreen = new customLoadingScreen();

    // 初始化一个场景 scene

    scene = new BABYLON.Scene(engine);

    // 设置背景色透明

    scene.clearColor = new BABYLON.Color4(0, 0, 0, 0);

    // 初始化相机 camera

    camera = new BABYLON.ArcRotateCamera('Camera', 0, 0, 0, new BABYLON.Vector3(0, 0, 0), scene);

// 启用框架行为
        camera.useFramingBehavior = true;
        // 聚焦半径
        camera.framingBehavior.radiusScale = 1;
        // 聚焦时间
        camera.framingBehavior.framingTime = 3 * 1000;
        camera.useBouncingBehavior = true;
        // 将相机绑定到画布上面
        camera.attachControl(canvas, true);
 
scene.onPrePointerObservable.add(this.onPointerDown,BABYLON.PointerEventTypes.POINTERDOWN);
        scene.onDispose = onDispose();

    /*

        * 天空盒

        */

    // 创建天空盒

    const skybox = BABYLON.Mesh.CreateBox('skyBox', 21000, scene),

        skyboxMaterial = new BABYLON.StandardMaterial('skyboxMaterial', scene);

    // 关闭掉材质的背面剔除(在盒子内部也可以看到盒子)

    skyboxMaterial.backFaceCulling = false;

    // 删除盒子上的反射光(天空不会反射太阳)

    skyboxMaterial.disableLighting = true;

    // 载入天空贴图(CubeTexture是贴图加载器,只能被应用到reflectionTexture)

    skyboxMaterial.reflectionTexture = new BABYLON.CubeTexture('textures/sky', scene);

    // 修改贴图模式(reflectionTexture是反射贴图,但我们需要天空盒贴图)

    skyboxMaterial.reflectionTexture.coordinatesMode = BABYLON.Texture.SKYBOX_MODE;

    skybox.material = skyboxMaterial;

    // 设置天空盒跟随相机位置移动(盒子不会收缩)

    skybox.infiniteDistance = true;

    /*

        * 3D模型

        */

    // 引入外部obj模型

    BABYLON.SceneLoader.Append('babylon/1/', 'model.glb', scene, (object) => {

        let meshes = object.meshes;
            try {
                meshes.map((mesh)=>{
                    //mesh.scaling = new BABYLON.Vector3(0.1, 0.1, 0.1);
                    // "冻结"网格
                    mesh.freezeWorldMatrix();
                    // 打开世界矩阵计算
                    mesh.unfreezeWorldMatrix();
                    // 不要求用户单击或选取网格体
                    mesh.isPickable = true;
                    mesh.doNotSyncBoundingInfo = true
                })
            } catch (e) {
                console.log(e);
            }

            // 相机聚焦到网格
            camera.setTarget(object);

            // 设置默认相机和灯光
            scene.createDefaultCameraOrLight(true, true, true);
            const light = new BABYLON.HemisphericLight('light', new BABYLON.Vector3(1, 1, 1));
            // 设置灯光亮度
            light.intensity = 1;
            // 镜面反射 漫反射  环境光颜色调整
            light.diffuse = new BABYLON.Color3(1, 1, 1);
            light.specular = new BABYLON.Color3(1, 1, 1);
            // 渲染模型后调整相机角度、位置、观察对象的三维坐标
            scene.activeCamera.alpha = 0.0239;
            scene.activeCamera.beta = 1.51;
            scene.activeCamera.radius = 51.9;
            scene.activeCamera.setPosition(new BABYLON.Vector3(0, 90, 90));
            scene.activeCamera.setTarget(new BABYLON.Vector3(0, 0, 0));

            // 设置横向旋转角度上下限
            scene.activeCamera.upperBetaLimit = Math.PI * 0.46;
            scene.activeCamera.lowerBetaLimit = 0;

            // 设置镜头到目标位置距离半径的最大值
            scene.activeCamera.upperRadiusLimit = 150;
            // 设置鼠标滚轮灵敏度(数值越小灵敏度越高)
            scene.activeCamera.wheelPrecision = 10;
            // 控制鼠标平移相机镜头灵敏度(数值越小灵敏度越高|为0的时候取消平移操作)
            scene.activeCamera.panningSensibility = 200;
            // 存储当前相机状态
            scene.activeCamera.storeState();
            // 也就是只有glb类型的文档才有,所以这个还是必须要用这个类型的材料
            let animat1 = scene.getAnimationGroupByName('Action1');
            let animat2 = scene.getAnimationGroupByName('Action2');
            // animat1.stop() animat2.stop()
            // animat1.pause() animat2.pause()
            animat1.start()
            animat2.start()
            timer1 = setInterval(() => {
                animat1.start()
                animat2.start()
            },3000);

            const spriteManager = new BABYLON.SpriteManager("spriteManager", '../../../textures/img/location.png', 1, {width: 142, height: 145}, this.scene);
            this.sprite1 = new BABYLON.Sprite("sprite1", spriteManager);

        // 关闭自定义loading效果、展示标题、展示按钮

        setTimeout(() => {

            engine.hideLoadingUI();

            emit('showTitle', true);

            isRendering = true;

        });

    }, (progressEvent) => {

        // 设置模型加载进度百分比

        progress = (progressEvent.loaded / progressEvent.total).toFixed(0) * 100;

    });

    // 注册渲染循环 runRenderLoop

    engine.runRenderLoop(() => {

        scene.render();

    });

    // 在 DOM 更新后执行回调

    nextTick(() => {

        console.log('DOM 已更新');

        // 注册resize监听事件

        window.addEventListener('resize', engineResize);

    });

})



beforeUnmount(() => {

    // 离开页面销毁resize监听事件

    window.removeEventListener('resize', engineResize, false);
    clearInterval(timer1);

})

</script>



<template>

    <div :class="isRendering ? 'containor bg' : 'containor'">

        <div id="loadingScreen" class="flex_column_center">

            <span class="loading"></span>

            <span class="progress">{{ progress }}%</span>

            <span class="text">3D模型加载中...</span>

        </div>

        <canvas id="canvas"></canvas>

        <div class="btn_list flex_middle" v-if="isRendering">

            <el-button type="warning" size="small" @click="reset"><i class="el-icon-refresh"></i> 重置</el-button>

        </div>

    </div>

</template>



<style lang="scss" scoped>

/*scrollbar styles*/

::-webkit-scrollbar {

    width: 12px;

    height: 12px;

    // border-radius: 100px;

}

::-webkit-scrollbar-thumb {

    // border-radius: 100px;

    background: var(--color-ref-kl-primary10);

}

::-webkit-scrollbar-track-piece {

    // border-radius: 100px;

    background: transparent;

}

::-webkit-scrollbar-corner {

    background: transparent;

}

/*scrollbar styles*/

#app {

    height: 100%;

    color: #4b4b4b;

    font-size: 13px;

    font-family: 'Microsoft YaHei';

    -webkit-font-smoothing: antialiased;

    -moz-osx-font-smoothing: grayscale;

}

.flex {

    display: flex;

}

.flex_center {

    @extend .flex;

    align-items: center;

}

.flex_left {

    @extend .flex_center;

    justify-content: flex-start;

}

.flex_right {

    @extend .flex_center;

    justify-content: flex-end;

}

.flex_middle {

    @extend .flex_center;

    justify-content: center;

}

.flex_column {

    @extend .flex;

    flex-direction: column;

    justify-content: center;

}

.flex_column_center {

    @extend .flex_column;

    align-items: center;

}

.public_radius {

    border-radius: 8px;

}

.echarts {

    height: 100%;

    overflow: hidden;

}

.containor {

    position: relative;

    width: 100%;

    height: 100%;

    overflow: hidden;

    &.bg {

        background-color: #8ecbe3;

    }

    #loadingScreen {

        position: absolute;

        width: 100%;

        height: 100%;

        .loading {

            display: inline-block;

            position: relative;

            width: 100px;

            height: 100px;

            border: 8px solid #0934f7;

            border-radius: 50%;

            animation: rotate 1s linear infinite;

            &:after {

                position: absolute;

                left: 50%;

                top: 50%;

                width: 110px;

                height: 110px;

                content: '';

                transform: translate(-50%, -50%);

                border: 8px solid transparent;

                border-bottom-color: #00eaff;

                border-radius: 50%;

            }

        }

        .progress {

            margin-top: -60px;

            color: #6be031;

            font-size: 16px;

            font-weight: 700;

        }

        .text {

            margin-top: 60px;

            color: #f5a327;

            font-size: 14px;

        }

    }

    canvas {

        width: 100%;

        height: 100%;

        outline: none;

        cursor: pointer;

    }

    .btn_list {

        position: absolute;

        bottom: 0;

        width: 100%;

        height: 50px;

        z-index: 99;

        button {

            margin: 0 15px 0 0;

            &:last-child {

                margin: 0;

            }

        }

    }

    .video_main {

        position: absolute;

        top: 50%;

        left: 50%;

        transform: translate(-50%, -50%);

        width: 800px;

        height: 500px;

        z-index: 999;

        video {

            outline: none;

        }

    }

}

</style>

Useful links

  • Official web site: www.babylonjs.com
  • Online playground to learn by experimentating
  • Online sandbox where you can test your .babylon and glTF scenes with a simple drag'n'drop
  • Online shader creation tool where you can learn how to create GLSL shaders
  • 3DS Max exporter can be used to generate a .babylon file from 3DS Max
  • Maya exporter can be used to generate a .babylon file from Maya
  • Blender exporter can be used to generate a .babylon file from Blender 3d
  • Unity 5 (deprecated) exporter can be used to export your geometries from Unity 5 scene editor(animations are supported)
  • glTF Tools by KhronosGroup

    "@babylonjs/core": "^5.24.0",

    "@babylonjs/gui": "^5.24.0",

    "@babylonjs/loaders": "^5.24.0",

    "@babylonjs/materials": "^5.24.0",

    "@babylonjs/post-processes": "^5.24.0",

    "@babylonjs/procedural-textures": "^5.24.0",

    "@babylonjs/serializers": "^5.24.0",

    "@babylonjs/viewer": "^5.24.0",

参见:

Babylon.js: Powerful, Beautiful, Simple, Open - Web-Based 3D At Its Best

Babylonjs中文网

GitHub - BabylonJS/Babylon.js: Babylon.js is a powerful, beautiful, simple, and open game and rendering engine packed into a friendly JavaScript framework.

Export To Babylon.js

Babylonjs中文文档

  • 16
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

:MNongSciFans

抛铜币以舒赞同,解兜囊以现支持

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值