Самый простой способ построить автосалон Three.js 3D

JavaScript WebGL
Самый простой способ построить автосалон Three.js 3D

предисловие

В предыдущей статье я кратко и примерно описал разработку 3D автосалона, а хотелось бы написать более подробный туториал. Для автора не сложно сказать Three.js и непросто сказать просто. Он прост, потому что упрощает понимание трехмерных знаний и упрощает многие операции. Это сложно, потому что существует множество API, и для ознакомления с ним требуется немало времени. Автор познакомит вас с самым простым способом создания 3D автосалона. Реализация этого 3D-автосалона не завершена.Основная цель состоит в том, чтобы позволить учащимся обрести чувство чувства, обрести чувство достижения и почувствовать, что у них есть мотивация к обучению. ^_^

Простое и грубое понимание 3D

В 2D есть только две координаты: ось X и ось Y. В 3D есть дополнительная ось Z. Я считаю, что студенты, которые только что изучили 3D, знакомы с осью X и осью Y, а ось Z относительно незнакома.три редактораВеб-сайт пытается создать некоторые геометрические объекты, ища 3D-понимание.

image.png

полный эффект

屏幕录制2021-07-08 下午1.39.03.gif

Нужно понимать эти понятия

В качестве примера автор использует сценическое представление:

  1. СценыSenceЭквивалент сцены, на которой расположены элементы сцены и выступают исполнители.
  2. камераCarmaЭквивалентно глазам зрителей смотреть
  3. геометрияGeometryисполнитель, эквивалентный сцене
  4. светlightЭквивалент сценического освещения
  5. контрольControlsЭквивалент главному директору этого этапа игры

Теперь, когда мы знаем эти понятия, мы можем различать их в функциональной форме в соответствии с этими основными понятиями, что легко понять. В этих трех программах я создал отдельно:setScene,setCarma,loadfile,setLight,setControlsВ соответствии с приведенными выше понятиями

Создать сцену

Прежде всего, мы по-прежнему используем метод установки vue3 для записи, npm устанавливаем три пакета, импортируемScene,WebGLRendererДва объекта, создайте две переменныеscene,rendererИ присвоить значение, чтобы сцена просто строилась, а фон сцены был черным по умолчанию. Создаватьinitфункция инициализации, а вonMountedпередача

<script setup>
  import {onMounted} from 'vue'
  import { Scene,WebGLRenderer,PerspectiveCamera} from 'three'
  let scene,renderer
  //创建场景
  const setScene = ()=>{
        scene = new Scene()
        renderer = new WebGLRenderer()
        renderer.setSize(innerWidth, innerHeight)
        document.querySelector('.boxs').appendChild(renderer.domElement)
    }
   //初始化所有函数 
   const init = () => {
        setScene()
    }
    //用vue钩子函数调用
    onMounted(init)
</script>

Создать камеру

Когда у вас есть сцена, вам нужно добавить камеру.Камера эквивалентна человеческим глазам для наблюдения за геометрическими объектами.PerspectiveCamera, Есть 4 параметра, вы можете обратиться к официальной документации веб-сайта для получения подробной информации. затем методом экземпляраposition.setустановить координаты камеры

<script setup>
  import { Scene,WebGLRenderer,PerspectiveCamera} from 'three'
  let scene,renderer,camera
  //相机的默认坐标
  const defaultMap = {
        x: 510,
        y: 128,
        z: 0,
    }
  //创建场景
  const setScene = ()=>{
        scene = new Scene()
        renderer = new WebGLRenderer()
        renderer.setSize(innerWidth, innerHeight)
        document.querySelector('.boxs').appendChild(renderer.domElement)
    }
  //创建相机  
  const setCamera = () => {
        const {x, y, z} = defaultMap
        camera = new PerspectiveCamera(60, innerWidth / innerHeight, 1, 1000)
        camera.position.set(x, y, z)
    }
   //初始化所有函数 
   const init = () => {
        setScene()
        setCamera()
    }
    //用vue钩子函数调用
    onMounted(init)
</script>

Знакомство с моделью Теслы

image.pngВ тройке, помимо создания геометрических объектов через API, мы также можем внедрять сторонние 3d-модели. sketchfab , зарубежный веб-сайт для загрузки 3D-моделей, есть много бесплатных загрузок моделей, на этот раз с использованием модели автомобиля Tesla в качестве примера, загрузитеgltfФормат 3D-моделей. представлятьGLTFLoaderСоздаватьloadfileфункционировать и возвращать данные модели через Promise, вinitфункция плюсasyncпередачаloadfileПолучите возвращенные данные модели и добавьте их в сценуscene

  import {GLTFLoader} from 'three/examples/jsm/loaders/GLTFLoader'
  import { Scene,WebGLRenderer,PerspectiveCamera} from 'three'
  let scene,renderer,camera,directionalLight,dhelper
  let isLoading = ref(true)
  let loadingWidth = ref(0)
  //相机的默认坐标
  const defaultMap = {
        x: 510,
        y: 128,
        z: 0,
    }
  //创建场景
  const setScene = ()=>{
        scene = new Scene()
        renderer = new WebGLRenderer()
        renderer.setSize(innerWidth, innerHeight)
        document.querySelector('.boxs').appendChild(renderer.domElement)
    }
  //创建相机  
  const setCamera = () => {
        const {x, y, z} = defaultMap
        camera = new PerspectiveCamera(60, innerWidth / innerHeight, 1, 1000)
        camera.position.set(x, y, z)
    }
    //通过Promise处理一下loadfile函数
     const loadFile = (url) => {
        return new Promise(((resolve, reject) => {
            loader.load(url,
                (gltf) => {
                    resolve(gltf)
                }, ({loaded, total}) => {
                    let load = Math.abs(loaded / total * 100)
                    loadingWidth.value = load
                    if (load >= 100) {
                        setTimeout(() => {
                            isLoading.value = false
                        }, 1000)
                    }
                    console.log((loaded / total * 100) + '% loaded')
                },
                (err) => {
                    reject(err)
                }
            )
        }))
    }
    
    //初始化所有函数 
   const init = async() => {
        const gltf =await loadFile('src/assets/3d/tesla_2018_model_3/scene.gltf')
        setScene()
        setCamera()
        scene.add(gltf.scene)
    }
    //用vue钩子函数调用
    onMounted(init) 

Создать свет

Модель автомобиля еще не видна, поэтому мы ее подсветим, импортировавDirectionalLight,DirectionalLightHelper,HemisphereLight,HemisphereLightHelper, и задайте параметры света так, чтобы модель была видна, а некоторые отражали световую поверхность, эффект тени, а потом еще иinitвызов функцииsetLight, а затем добавитьloopЭта функция делает вызов сцены, камеры и модели непрерывным и циклическим. Затем вы можете увидеть модель автомобиля. Когда вы видите автомобиль, кажется, что вы купили новый. ^_^

image.png

  import {GLTFLoader} from 'three/examples/jsm/loaders/GLTFLoader'
 import { Scene,WebGLRenderer,PerspectiveCamera,   DirectionalLight,
        DirectionalLightHelper,
        HemisphereLight,
        HemisphereLightHelper} from 'three'
  let scene,renderer,camera,directionalLight,hemisphereLight,dhelper,hHelper
  let isLoading = ref(true)
  let loadingWidth = ref(0)
  //相机的默认坐标
  const defaultMap = {
        x: 510,
        y: 128,
        z: 0,
    }
  //创建场景
  const setScene = ()=>{
        scene = new Scene()
        renderer = new WebGLRenderer()
        renderer.setSize(innerWidth, innerHeight)
        document.querySelector('.boxs').appendChild(renderer.domElement)
    }
  //创建相机  
  const setCamera = () => {
        const {x, y, z} = defaultMap
        camera = new PerspectiveCamera(60, innerWidth / innerHeight, 1, 1000)
        camera.position.set(x, y, z)
    }
    //通过Promise处理一下loadfile函数
     const loadFile = (url) => {
        return new Promise(((resolve, reject) => {
            loader.load(url,
                (gltf) => {
                    resolve(gltf)
                }, ({loaded, total}) => {
                    let load = Math.abs(loaded / total * 100)
                    loadingWidth.value = load
                    if (load >= 100) {
                        setTimeout(() => {
                            isLoading.value = false
                        }, 1000)
                    }
                    console.log((loaded / total * 100) + '% loaded')
                },
                (err) => {
                    reject(err)
                }
            )
        }))
    }
    // 设置灯光
     const setLight = () => {
        directionalLight = new DirectionalLight(0xffffff, 0.5)
        directionalLight.position.set(-4, 8, 4)
        dhelper = new DirectionalLightHelper(directionalLight, 5, 0xff0000)
        hemisphereLight = new HemisphereLight(0xffffff, 0xffffff, 0.4)
        hemisphereLight.position.set(0, 8, 0)
        hHelper = new HemisphereLightHelper(hemisphereLight, 5)
        scene.add(directionalLight)
        scene.add(hemisphereLight)
    }
    //初始化所有函数 
   const init = async() => {
        const gltf = await loadFile('src/assets/3d/tesla_2018_model_3/scene.gltf')
        setScene()
        setCamera()
        setLight()
        scene.add(gltf.scene)
       loop()
    }
    //使场景、照相机、模型不停调用
     const loop = () => {
        requestAnimationFrame(loop)
        renderer.render(scene, camera)
    }
    //用vue钩子函数调用
    onMounted(init) 

модель управления

屏幕录制2021-07-08 下午12.04.36.gif

Если вы хотите использовать мышь для свободного вращения или автоматического вращения, вам нужно процитироватьOrbitControlsобъект, создатьsetControlsфункция такжеinitвызов, по привязкеchangeВы также можете отслеживать изменения координат, а также вloopповышение функцииcontrols.update()обновить изменения местоположения

  import { Scene,WebGLRenderer,PerspectiveCamera} from 'three'
  import {GLTFLoader} from 'three/examples/jsm/loaders/GLTFLoader'
  import {OrbitControls} from 'three/examples/jsm/controls/OrbitControls.js'

  let scene,renderer,camera,directionalLight,hemisphereLight,dhelper,hHelper,controls
  let isLoading = ref(true)
  let loadingWidth = ref(0)
  //相机的默认坐标
  const defaultMap = {
        x: 510,
        y: 128,
        z: 0,
    }
  //创建场景
  const setScene = ()=>{
        scene = new Scene()
        renderer = new WebGLRenderer()
        renderer.setSize(innerWidth, innerHeight)
        document.querySelector('.boxs').appendChild(renderer.domElement)
    }
  //创建相机  
  const setCamera = () => {
        const {x, y, z} = defaultMap
        camera = new PerspectiveCamera(60, innerWidth / innerHeight, 1, 1000)
        camera.position.set(x, y, z)
    }
    //通过Promise处理一下loadfile函数
     const loadFile = (url) => {
        return new Promise(((resolve, reject) => {
            loader.load(url,
                (gltf) => {
                    resolve(gltf)
                }, ({loaded, total}) => {
                    let load = Math.abs(loaded / total * 100)
                    loadingWidth.value = load
                    if (load >= 100) {
                        setTimeout(() => {
                            isLoading.value = false
                        }, 1000)
                    }
                    console.log((loaded / total * 100) + '% loaded')
                },
                (err) => {
                    reject(err)
                }
            )
        }))
    }
    // 设置灯光
     const setLight = () => {
        directionalLight = new DirectionalLight(0xffffff, 0.5)
        directionalLight.position.set(-4, 8, 4)
        dhelper = new DirectionalLightHelper(directionalLight, 5, 0xff0000)
        hemisphereLight = new HemisphereLight(0xffffff, 0xffffff, 0.4)
        hemisphereLight.position.set(0, 8, 0)
        hHelper = new HemisphereLightHelper(hemisphereLight, 5)
        scene.add(directionalLight)
        scene.add(hemisphereLight)
    }
    // 设置模型控制
    const setControls = () => {
        controls = new OrbitControls(camera, renderer.domElement)
        controls.maxPolarAngle = 0.9 * Math.PI / 2
        controls.enableZoom = true
        controls.addEventListener('change', render)
    }
    const render = () => {
        map.x = Number.parseInt(camera.position.x)
        map.y = Number.parseInt(camera.position.y)
        map.z = Number.parseInt(camera.position.z)
    }
    //初始化所有函数 
   const init = async() => {
        const gltf =await loadFile('src/assets/3d/tesla_2018_model_3/scene.gltf')
        setScene()
        setCamera()
        setLight()
        setControls()
        scene.add(gltf.scene)
    }
    
   //使场景、照相机、模型不停调用和更新位置数据
     const loop = () => {
        requestAnimationFrame(loop)
        renderer.render(scene, camera)
        controls.update()

    }
    
    //用vue钩子函数调用
    onMounted(init) 

Изменить цвет кузова

На данный момент основы созданы, теперь мы добавим функцию изменения цвета кузова автомобиля, которая также является относительно базовой функцией, отображаемой в выставочном зале.setColorВот примерsceneесть одинtraverseФункция, которая вызывает информацию о подмоделях всех моделей, пока мы находим соответствующий атрибут имени, мы можем изменить цвет, добавить текстуры и т. д., Т.к. я не знаком со структурой модели, то догадался по названию и нашел.door_Название преамбулы, вероятно, должно быть обвесом. Конечно, если он разделен до такой степени, что я просто хочу изменить цвет капота, я должен найти комплект капота. Конечно, вы должны быть знакомы со структурой этой модели.

 
    //设置车身颜色
      const setCarColor = (index) => {
        const currentColor = new Color(colorAry[index])
        scene.traverse(child => {
            if (child.isMesh) {
                console.log(child.name)
                if (child.name.includes('door_')) {
                    child.material.color.set(currentColor)
                }
            }
        })
    }
   

полный код

Другие операции передаются под управление VUE, в том числе настройка цвета тела, автоматический поворот и т.п. Я помню, когда вы запускаете следующий код.viteИнструмент упаковки для создания шаблона vue3

<template>
    <div class="boxs">
        <div class="maskLoading" v-if="isLoading">
            <div class="loading">
                <div :style="{width : loadingWidth +'%' }"></div>
            </div>
            <div style="padding-left: 10px;">{{parseInt(loadingWidth)}}%</div>
        </div>
        <div class="mask">
            <p>x : {{x}} y:{{y}} z :{{z}}</p>
            <button @click="isAutoFun">转动车</button>
            <button @click="stop">停止</button>
            <div class="flex">
                <div @click="setCarColor(index)" v-for="(item,index) in colorAry"
                     :style="{backgroundColor : item}"></div>
            </div>
        </div>
    </div>
</template>

<script setup>
    import {onMounted, reactive, ref, toRefs} from 'vue'
    import {
        Color,
        DirectionalLight,
        DirectionalLightHelper,
        HemisphereLight,
        HemisphereLightHelper,
        PerspectiveCamera,
        Scene,
        WebGLRenderer
    } from 'three'
    import {OrbitControls} from 'three/examples/jsm/controls/OrbitControls.js'
    import {GLTFLoader} from 'three/examples/jsm/loaders/GLTFLoader'
    //车身颜色数组
    const colorAry = [
        "rgb(216, 27, 67)", "rgb(142, 36, 170)", "rgb(81, 45, 168)", "rgb(48, 63, 159)", "rgb(30, 136, 229)", "rgb(0, 137, 123)",
        "rgb(67, 160, 71)", "rgb(251, 192, 45)", "rgb(245, 124, 0)", "rgb(230, 74, 25)", "rgb(233, 30, 78)", "rgb(156, 39, 176)",
        "rgb(0, 0, 0)"] // 车身颜色数组 
    const loader = new GLTFLoader() //引入模型的loader实例
    const defaultMap = {
        x: 510,
        y: 128,
        z: 0,
    }// 相机的默认坐标
    const map = reactive(defaultMap)//把相机坐标设置成可观察对象
    const {x, y, z} = toRefs(map)//输出坐标给模板使用
    let scene, camera, renderer, controls, floor, dhelper, hHelper, directionalLight, hemisphereLight // 定义所有three实例变量
    let isLoading = ref(true) //是否显示loading  这个load模型监听的进度
    let loadingWidth = ref(0)// loading的进度

    //创建灯光
    const setLight = () => {
        directionalLight = new DirectionalLight(0xffffff, 0.5)
        directionalLight.position.set(-4, 8, 4)
        dhelper = new DirectionalLightHelper(directionalLight, 5, 0xff0000)
        hemisphereLight = new HemisphereLight(0xffffff, 0xffffff, 0.4)
        hemisphereLight.position.set(0, 8, 0)
        hHelper = new HemisphereLightHelper(hemisphereLight, 5)
        scene.add(directionalLight)
        scene.add(hemisphereLight)
    }

    // 创建场景
    const setScene = () => {
        scene = new Scene()
        renderer = new WebGLRenderer()
        renderer.setSize(innerWidth, innerHeight)
        document.querySelector('.boxs').appendChild(renderer.domElement)

    }
    
    // 创建相机
    const setCamera = () => {
        const {x, y, z} = defaultMap
        camera = new PerspectiveCamera(60, innerWidth / innerHeight, 1, 1000)
        camera.position.set(x, y, z)
    }
    
    // 设置模型控制
    const setControls = () => {
        controls = new OrbitControls(camera, renderer.domElement)
        controls.maxPolarAngle = 0.9 * Math.PI / 2
        controls.enableZoom = true
        controls.addEventListener('change', render)
    }
    
    //返回坐标信息
     const render = () => {
        map.x = Number.parseInt(camera.position.x)
        map.y = Number.parseInt(camera.position.y)
        map.z = Number.parseInt(camera.position.z)
    }
    
    
 
    // 循环场景 、相机、 位置更新
    const loop = () => {
        requestAnimationFrame(loop)
        renderer.render(scene, camera)
        controls.update()
    }
    
 
    //是否自动转动
    const isAutoFun = () => {
        controls.autoRotate = true
    }
    //停止转动
    const stop = () => {
        controls.autoRotate = false
    }

    //设置车身颜色
    const setCarColor = (index) => {
        const currentColor = new Color(colorAry[index])
        scene.traverse(child => {
            if (child.isMesh) {
                console.log(child.name)
                if (child.name.includes('door_')) {
                    child.material.color.set(currentColor)
                }
            }
        })
    }
    
    const loadFile = (url) => {
        return new Promise(((resolve, reject) => {
            loader.load(url,
                (gltf) => {
                    resolve(gltf)
                }, ({loaded, total}) => {
                    let load = Math.abs(loaded / total * 100)
                    loadingWidth.value = load
                    if (load >= 100) {
                        setTimeout(() => {
                            isLoading.value = false
                        }, 1000)
                    }
                    console.log((loaded / total * 100) + '% loaded')
                },
                (err) => {
                    reject(err)
                }
            )
        }))
    }
    
    
       //初始化所有函数
    const init = async () => {
        setScene()
        setCamera()
        setLight()
        setControls()
        const gltf = await loadFile('src/assets/3d/tesla_2018_model_3/scene.gltf')
        scene.add(gltf.scene)
        loop()
    }
      //用vue钩子函数调用
    onMounted(init) 
</script>

<style>
    body {
        margin: 0;
    }

    .maskLoading {
        background: #000;
        position: fixed;
        display: flex;
        justify-content: center;
        align-items: center;
        top: 0;
        left: 0;
        bottom: 0;
        right: 0;
        z-index: 1111111;
        color: #fff;
    }

    .maskLoading .loading {
        width: 400px;
        height: 20px;
        border: 1px solid #fff;
        background: #000;
        overflow: hidden;
        border-radius: 10px;

    }

    .maskLoading .loading div {
        background: #fff;
        height: 20px;
        width: 0;
        transition-duration: 500ms;
        transition-timing-function: ease-in;
    }

    canvas {
        width: 100%;
        height: 100%;
        margin: auto;
    }

    .mask {
        color: #fff;
        position: absolute;
        bottom: 0;
        left: 0;
        width: 100%;
    }

    .flex {
        display: flex;
        flex-wrap: wrap;
        padding: 20px;

    }

    .flex div {
        width: 10px;
        height: 10px;
        margin: 5px;
        cursor: pointer;
    }
</style>

屏幕录制2021-07-08 下午1.39.03.gif

наконец

В этом 3D-автосалоне автор просто создал некоторые основные функции, и есть много функций, которые можно добавить, например, создание фона, полов, некоторых теней, отображение с фиксированной точкой информации о местоположении автомобильного комплекта и так далее. Освоив подпрограммы, этих функций добиться несложно.К тому же автор переходит на второй уровень.Надеюсь, если вам понравится, вы сможете поставить братишке палец вверх, спасибо. ^_^