предисловие
В предыдущей статье я кратко и примерно описал разработку 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-автосалоне автор просто создал некоторые основные функции, и есть много функций, которые можно добавить, например, создание фона, полов, некоторых теней, отображение с фиксированной точкой информации о местоположении автомобильного комплекта и так далее. Освоив подпрограммы, этих функций добиться несложно.К тому же автор переходит на второй уровень.Надеюсь, если вам понравится, вы сможете поставить братишке палец вверх, спасибо. ^_^