предисловие
В предыдущей статье я кратко и примерно описал разработку 3D автосалона, а хотелось бы написать более подробный туториал. Для автора не сложно сказать Three.js и непросто сказать просто. Он прост, потому что упрощает понимание трехмерных знаний и упрощает многие операции. Это сложно, потому что существует множество API, и для ознакомления с ним требуется немало времени. Автор познакомит вас с самым простым способом создания 3D автосалона. Реализация этого 3D-автосалона не завершена.Основная цель состоит в том, чтобы позволить учащимся обрести чувство чувства, обрести чувство достижения и почувствовать, что у них есть мотивация к обучению. ^_^
Простое и грубое понимание 3D
В 2D есть только две координаты: ось X и ось Y. В 3D есть дополнительная ось Z. Я считаю, что студенты, которые только что изучили 3D, знакомы с осью X и осью Y, а ось Z относительно незнакома.три редактораВеб-сайт пытается создать некоторые геометрические объекты, ища 3D-понимание.
полный эффект
Нужно понимать эти понятия
В качестве примера автор использует сценическое представление:
- Сцены
Sence
Эквивалент сцены, на которой расположены элементы сцены и выступают исполнители. - камера
Carma
Эквивалентно глазам зрителей смотреть - геометрия
Geometry
исполнитель, эквивалентный сцене - свет
light
Эквивалент сценического освещения - контроль
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>
Знакомство с моделью Теслы
В тройке, помимо создания геометрических объектов через 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
Эта функция делает вызов сцены, камеры и модели непрерывным и циклическим. Затем вы можете увидеть модель автомобиля. Когда вы видите автомобиль, кажется, что вы купили новый. ^_^
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)
модель управления
Если вы хотите использовать мышь для свободного вращения или автоматического вращения, вам нужно процитировать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>
наконец
В этом 3D-автосалоне автор просто создал некоторые основные функции, и есть много функций, которые можно добавить, например, создание фона, полов, некоторых теней, отображение с фиксированной точкой информации о местоположении автомобильного комплекта и так далее. Освоив подпрограммы, этих функций добиться несложно.К тому же автор переходит на второй уровень.Надеюсь, если вам понравится, вы сможете поставить братишке палец вверх, спасибо. ^_^