开始吧
首先我们必须需要知道 threejs 最基本的4个要素,详情见threejs文档
- scene (场景)
- camera (相机)
- renderer (渲染器)
- mesh (网格)
使用vite搭建项目
npm create vite@latest house-3d-view --template
- 选择 Vue 和 JavaScript
cd house-3d-view
npm i
- 项目初始化完成
coding
1.完成一个基本的页面
<template>
<div class="home">
<div class="view-container" ref="threeDBox"></div>
</div>
</template>
<script setup>
import { onMounted, ref } from '@vue/runtime-core'
import livingImg from './assets/image/livingRoom.jpg'
import kitchenImg from './assets/image/kitchen.jpg'
import * as THREE from 'three'
import {OrbitControls} from 'three/examples/jsm/controls/OrbitControls'
let renderer,camera,mesh,scene,controller
let threeDBox = ref(null)
onMounted(()=>{
init()
render()
})
function init(){
initRenderer() //初始化渲染器
initCamera() // 初始化相机
initScene() // 初始化场景
initMesh() // 初始化网格
initController() // 初始化控制器
}
// 初始化渲染器
function initRenderer() {
renderer = new THREE.WebGLRenderer({
antialias: true
})
renderer.setPixelRatio(window.devicePixelRatio)
renderer.setSize(window.innerWidth, window.innerHeight)
threeDBox.value.appendChild(renderer.domElement)
}
// 初始化镜头
function initCamera() {
camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000)
camera.position.z = 0.0001
}
// 初始化场景
function initScene(){
scene = new THREE.Scene()
}
// 初始化网格, 网格单元需要几何体和材质
function initMesh(){
const geomatry = new THREE.SphereGeometry(16,50,50)
const texture = new THREE.TextureLoader().load(livingImg)
const material = new THREE.MeshBasicMaterial({map:texture})
mesh = new THREE.Mesh(geomatry, material)
mesh.geometry.scale(16,16,-16)
scene.add(mesh)
}
// 控制器
function initController(){
controller = new OrbitControls(camera,renderer.domElement)
controller.enableDamping = true
}
// 渲染函数
function render(){
renderer.render(scene, camera)
requestAnimationFrame(render)
}
//页面大小发生变化
window.addEventListener("resize",()=>{
camera.aspect = window.innerWidth/window.innerHeight
camera.updateProjectionMatrix()
renderer.setSize(window.innerWidth,window.innerHeight)
})
</script>
<style lang="scss">
.home {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
.view-container {
width: 100%;
height: 100%;
overflow: hidden;
}
}
</style>
基本页面完成我们已经可以看见最基本的3d看房效果,可以通过控制器控制视角,接下来我们需要完成场景的切换**
2.给页面添加精灵(提示)
import tipImg from './assets/image/tip.png'
function init(){
initRenderer()
initCamera()
initScene()
initMesh()
initController()
addTipsSprite()
}
function addTipsSprite(){
let map = new THREE.TextureLoader().load(tipImg)
let material = new THREE.SpriteMaterial({map:map})
let sprit = new THREE.Sprite(material)
sprit.scale.set(10,10,10)
sprit.position.set(-200,0,-145)
scene.add(sprit)
}
这一步完成我们可以看见场景中的精灵图,并且可以通过设置position来调整精灵图的位置
3.切换场景
我们将上面的代码稍微更改一部分。根据index的值来渲染不同页面
let dataList = [
{
image: livingImg,
tip:{
position: { x: -200, y:0, z: -145 },
image:1
}
},
{
image: kitchenImg,
tip: {
position: { x: -199, y: -24, z: 145 },
image:0
}
}
];
// 在场景中添加精灵
function addTipsSprite(index=0){
let map = new THREE.TextureLoader().load(tipImg)
let material = new THREE.SpriteMaterial({map:map})
let sprite = new THREE.Sprite(material)
let tip = dataList[index].tip
sprite.scale.set(10,10,10)
sprite.position.set(tip.position.x,tip.position.y,tip.position.z)
sprite.image = tip.image
scene.add(sprite)
}
// 切换场景,根据index切换不同场景
function changeScene(index=1){
//将场景中的精灵图清除,留下基础元素
scene.children = scene.children.filter(item=>item.type!=='Sprite')
// 渲染切换后的场景
let texture = new THREE.TextureLoader().load(dataList[index].image)
let sphereMaterial = new THREE.MeshBasicMaterial({
map: texture,
transparent: true,
opacity: 0,
});
mesh.material = sphereMaterial
camera.updateProjectionMatrix();
addTipsSprite(index)
}
到这一步我们已经可以通过更改index为不同的值,渲染不同的页面了,最后应该把点击事件添加到场景中的精灵图上。
4.给精灵图添加点击事件(准确点应该是给window添加点击事件,通过Raycaster光线投影获取鼠标在场景中的相对位置)
window.addEventListener("click",function (e){
e.preventDefault();
//这里通过raycaster光线投影抓取点击目标
let raycaster = new THREE.Raycaster()
let mouse = new THREE.Vector2()
mouse.x = (e.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(e.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
let intersects = raycaster.intersectObjects(scene.children)
if (intersects.length > 0 && intersects[0].object.type === 'Sprite') {
changeScene(intersects[0].object.image);
}
},false)
最后添加过渡动画优化一下了
import gsap from "gsap";
// 切换场景,根据index切换不同场景
function changeScene(index=0){
//将场景中的精灵图清除,留下基础元素
scene.children = scene.children.filter(item=>item.type!=='Sprite')
// 渲染切换后的场景
let texture = new THREE.TextureLoader().load(dataList[index].image)
let sphereMaterial = new THREE.MeshBasicMaterial({map:texture})
mesh.material = sphereMaterial
gsap.to(sphereMaterial, { transparent: true, opacity: 1, duration: 2 });
camera.updateProjectionMatrix();
addTipsSprite(index)
}
全部代码
<template>
<div class="home">
<div class="view-container" ref="threeDBox"></div>
</div>
</template>
<script setup>
import { onMounted, ref } from '@vue/runtime-core'
import livingImg from './assets/image/livingRoom.jpg'
import kitchenImg from './assets/image/kitchen.jpg'
import tipImg from './assets/image/tip.png'
import * as THREE from 'three'
import {OrbitControls} from 'three/examples/jsm/controls/OrbitControls'
import gsap from 'gsap'
let renderer,camera,mesh,scene,controller
let threeDBox = ref(null)
let spriteList = []
let dataList = [
{
image: livingImg,
tip:{
position: { x: -200, y:0, z: -145 },
image: 1,
}
},
{
image: kitchenImg,
tip: {
position: { x: -199, y: -24, z: 145 },
image: 0,
}
}
];
onMounted(()=>{
init()
render()
})
function init(){
initRenderer()
initCamera()
initScene()
initMesh()
initController()
addTipsSprite()
changeScene()
}
// 初始化渲染器
function initRenderer() {
renderer = new THREE.WebGLRenderer({
antialias: true
})
renderer.setPixelRatio(window.devicePixelRatio)
renderer.setSize(window.innerWidth, window.innerHeight)
threeDBox.value.appendChild(renderer.domElement)
}
// 初始化镜头
function initCamera() {
camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000)
camera.position.z = 0.0001
}
// 初始化场景
function initScene(){
scene = new THREE.Scene()
}
// 初始化网格, 网格单元需要几何体和材质
function initMesh(){
const geomatry = new THREE.SphereGeometry(16,50,50)
const texture = new THREE.TextureLoader().load(livingImg)
const material = new THREE.MeshBasicMaterial({map:texture})
mesh = new THREE.Mesh(geomatry, material)
mesh.geometry.scale(16,16,-16)
scene.add(mesh)
}
// 控制器
function initController(){
controller = new OrbitControls(camera,renderer.domElement)
controller.enableDamping = true
}
// 渲染函数
function render(){
controller.update();
renderer.render(scene, camera)
requestAnimationFrame(render)
}
// 在场景中添加精灵
function addTipsSprite(index=0){
let map = new THREE.TextureLoader().load(tipImg)
let material = new THREE.SpriteMaterial({map:map})
let sprite = new THREE.Sprite(material)
let tip = dataList[index].tip
sprite.scale.set(10,10,10)
sprite.position.set(tip.position.x,tip.position.y,tip.position.z)
sprite.image = tip.image
spriteList.push(sprite)
scene.add(sprite)
}
// 切换场景,根据index切换不同场景
function changeScene(index=0){
//将场景中的精灵图清除,留下基础元素
scene.children = scene.children.filter(item=>item.type!=='Sprite')
// 渲染切换后的场景
let texture = new THREE.TextureLoader().load(dataList[index].image)
let sphereMaterial = new THREE.MeshBasicMaterial({map:texture,transparent:true,opacity:0})
mesh.material = sphereMaterial
gsap.to(sphereMaterial,{transparent:true,opacity:1,duration:2})
camera.updateProjectionMatrix();
addTipsSprite(index)
}
//页面大小发生变化
window.addEventListener("resize",()=>{
camera.aspect = window.innerWidth/window.innerHeight
camera.updateProjectionMatrix()
renderer.setSize(window.innerWidth,window.innerHeight)
})
window.addEventListener("click",function (e){
e.preventDefault();
//这里通过raycaster光线投影抓取点击目标
let raycaster = new THREE.Raycaster()
let mouse = new THREE.Vector2()
mouse.x = (e.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(e.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
let intersects = raycaster.intersectObjects(scene.children)
if (intersects.length > 0 && intersects[0].object.type === 'Sprite') {
changeScene(intersects[0].object.image);
}
},false)
</script>
<style lang="scss">
.home {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
.view-container {
width: 100%;
height: 100%;
overflow: hidden;
}
}
</style>