demos
This commit is contained in:
45
README.md
45
README.md
@@ -2,24 +2,51 @@
|
||||
|
||||
## three.js 是什么
|
||||
|
||||
https://threejs.org/manual/#zh/fundamentals
|
||||
|
||||
## 为什么选择 three.js
|
||||
|
||||
- WebGL
|
||||
- https://developer.mozilla.org/zh-CN/docs/Web/API/WebGL_API
|
||||
- three.js
|
||||
- https://threejs.org/
|
||||
- http://www.webgl3d.cn/
|
||||
- Babylon.js
|
||||
- https://www.babylonjs.com/
|
||||
- https://cnbabylon.com/
|
||||
|
||||
## three.js 核心概念
|
||||
|
||||
https://threejs.org/manual/#zh/fundamentals
|
||||
|
||||
### Scene 场景
|
||||
### Camera 相机
|
||||
### Renderer 渲染器
|
||||
|
||||
## three.js 常用类/对象
|
||||
|
||||
### Object3D
|
||||
### Material
|
||||
https://threejs.org/manual/#zh/materials
|
||||
|
||||
### Texture
|
||||
### OrbitControls
|
||||
|
||||
## Demos
|
||||
### 基础几何体
|
||||
### 材质与纹理
|
||||
### 光照与阴影
|
||||
### 动画与交互
|
||||
### 粒子系统
|
||||
|
||||
### 加载GLB模型
|
||||
### 添加灯光
|
||||
### 添加控制器、AxesHelper
|
||||
### 设置地面
|
||||
### 地面上设置光效
|
||||
### 模型漫游(巡检业务)
|
||||
### [x] 模型动画(模型本身存在动画)
|
||||
### 修改材质(设备告警业务)
|
||||
### 粒子效果(蒸汽)
|
||||
### 模型与Web的交互
|
||||
### 后期处理效果
|
||||
### 漫游 视角切换
|
||||
### 标签
|
||||
### 公共函数抽象
|
||||
|
||||
## 如何与建模沟通
|
||||
|
||||
## blender使用
|
||||
|
||||
## 如何与建模沟通
|
||||
|
||||
0
src/composes/index.ts
Normal file
0
src/composes/index.ts
Normal file
0
src/composes/three/dom.ts
Normal file
0
src/composes/three/dom.ts
Normal file
2
src/composes/three/index.ts
Normal file
2
src/composes/three/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './loader'
|
||||
export * from './texture'
|
||||
5
src/composes/three/interface.ts
Normal file
5
src/composes/three/interface.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import type * as THREE from 'three'
|
||||
|
||||
export interface ScenceOptions {
|
||||
background?: THREE.Texture<unknown> | THREE.CubeTexture | THREE.Color | null
|
||||
}
|
||||
13
src/composes/three/loader.ts
Normal file
13
src/composes/three/loader.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import * as THREE from 'three'
|
||||
|
||||
export function useThreeLoader() {
|
||||
const textureLoader = new THREE.TextureLoader()
|
||||
|
||||
return {
|
||||
textureLoader,
|
||||
}
|
||||
}
|
||||
|
||||
const threeLoaders = useThreeLoader()
|
||||
|
||||
export { threeLoaders }
|
||||
5
src/composes/three/mesh.ts
Normal file
5
src/composes/three/mesh.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import * as THREE from 'three'
|
||||
|
||||
export function useThreeMesh() {
|
||||
|
||||
}
|
||||
9
src/composes/three/scene.ts
Normal file
9
src/composes/three/scene.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import * as THREE from 'three'
|
||||
|
||||
export function useThreeScene() {
|
||||
function createScene(options) {
|
||||
const scene = new THREE.Scene()
|
||||
}
|
||||
|
||||
return { createScene }
|
||||
}
|
||||
22
src/composes/three/texture.ts
Normal file
22
src/composes/three/texture.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import type * as THREE from 'three'
|
||||
import { threeLoaders } from './loader'
|
||||
|
||||
export function useThreeTexture() {
|
||||
// 加载纹理贴图
|
||||
function loadTexture(src: string) {
|
||||
return new Promise<THREE.Texture>((resolve, reject) => {
|
||||
threeLoaders.textureLoader.load(
|
||||
src,
|
||||
(texture) => {
|
||||
resolve(texture)
|
||||
},
|
||||
undefined,
|
||||
(err) => {
|
||||
reject(err)
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
return { loadTexture }
|
||||
}
|
||||
@@ -9,6 +9,61 @@ const router = createRouter({
|
||||
name: 'home',
|
||||
component: HomeView,
|
||||
},
|
||||
{
|
||||
path: '/three/1',
|
||||
name: 'three-1',
|
||||
component: () => import('../views/three/1/index.vue'),
|
||||
},
|
||||
{
|
||||
path: '/three/2',
|
||||
name: 'three-2',
|
||||
component: () => import('../views/three/2/index.vue'),
|
||||
},
|
||||
{
|
||||
path: '/three/3',
|
||||
name: 'three-3',
|
||||
component: () => import('../views/three/3/index.vue'),
|
||||
},
|
||||
{
|
||||
path: '/three/4',
|
||||
name: 'three-4',
|
||||
component: () => import('../views/three/4/index.vue'),
|
||||
},
|
||||
{
|
||||
path: '/three/5',
|
||||
name: 'three-5',
|
||||
component: () => import('../views/three/5/index.vue'),
|
||||
},
|
||||
{
|
||||
path: '/three/6',
|
||||
name: 'three-6',
|
||||
component: () => import('../views/three/6/index.vue'),
|
||||
},
|
||||
{
|
||||
path: '/three/7',
|
||||
name: 'three-7',
|
||||
component: () => import('../views/three/7/index.vue'),
|
||||
},
|
||||
{
|
||||
path: '/three/8',
|
||||
name: 'three-8',
|
||||
component: () => import('../views/three/8/index.vue'),
|
||||
},
|
||||
{
|
||||
path: '/three/9',
|
||||
name: 'three-9',
|
||||
component: () => import('../views/three/9/index.vue'),
|
||||
},
|
||||
{
|
||||
path: '/three/10',
|
||||
name: 'three-10',
|
||||
component: () => import('../views/three/10/index.vue'),
|
||||
},
|
||||
{
|
||||
path: '/three/11',
|
||||
name: 'three-11',
|
||||
component: () => import('../views/three/11/index.vue'),
|
||||
},
|
||||
{
|
||||
path: '/3d-point',
|
||||
name: '3d-point',
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
<script lang="ts" setup>
|
||||
const demos = [{ name: '3D Point', path: '/3d-point' }]
|
||||
const demos = [
|
||||
{ name: '加载GLB模型', path: '/three/1' },
|
||||
{ name: '添加灯光', path: '/three/2' },
|
||||
{ name: '添加控制器、AxesHelper', path: '/three/3' },
|
||||
{ name: '设置地面', path: '/three/3' },
|
||||
{ name: '地面上设置光效', path: '/three/4' },
|
||||
{ name: '模型漫游(巡检业务)', path: '/three/5' },
|
||||
{ name: '模型动画(模型本身存在动画)', path: '/three/6' },
|
||||
{ name: '漫游 修改材质(设备告警业务)', path: '/three/7' },
|
||||
{ name: '粒子效果(蒸汽)', path: '/three/8' },
|
||||
{ name: '模型与Web的交互', path: '/three/9' },
|
||||
{ name: '后期处理效果', path: '/three/10' },
|
||||
{ name: '公共函数抽象', path: '/three/11' },
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
75
src/views/three/1/index.vue
Normal file
75
src/views/three/1/index.vue
Normal file
@@ -0,0 +1,75 @@
|
||||
<script lang="ts" setup>
|
||||
import * as THREE from 'three'
|
||||
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
|
||||
import { onMounted, shallowRef } from 'vue'
|
||||
|
||||
const threeRef = shallowRef<HTMLCanvasElement>()
|
||||
|
||||
function initModel() {
|
||||
const width = threeRef.value!.clientWidth
|
||||
const height = threeRef.value!.clientHeight
|
||||
|
||||
const scene = new THREE.Scene()
|
||||
scene.background = new THREE.Color(0xFFFFFF)
|
||||
|
||||
// 创建透视相机 PerspectiveCamera(视野角度, 宽高比, 近截面, 远截面)
|
||||
// 透视相机更符合人眼的视觉效果 近大远小
|
||||
const camera = new THREE.PerspectiveCamera(
|
||||
75,
|
||||
width / height,
|
||||
0.1,
|
||||
1000,
|
||||
)
|
||||
camera.position.set(0, 2, 12)
|
||||
|
||||
const renderer = new THREE.WebGLRenderer({ antialias: true })
|
||||
renderer.setSize(width, height)
|
||||
// 开启阴影贴图
|
||||
renderer.shadowMap.enabled = true
|
||||
// 设置阴影贴图类型 THREE.PCFSoftShadowMap=柔和阴影 THREE.PCFShadowMap=硬阴影
|
||||
// 柔和阴影计算量更大,性能更低
|
||||
// 硬阴影计算量更小,性能更高
|
||||
renderer.shadowMap.type = THREE.PCFSoftShadowMap
|
||||
threeRef.value!.appendChild(renderer.domElement)
|
||||
|
||||
const loader = new GLTFLoader()
|
||||
loader.load('/mzjc_bansw.glb', (gltf) => {
|
||||
scene.add(gltf.scene)
|
||||
})
|
||||
|
||||
function animate() {
|
||||
// 60Hz 1000/60=16.6ms
|
||||
requestAnimationFrame(animate)
|
||||
// 执行渲染
|
||||
renderer.render(scene, camera)
|
||||
// 适配高分辨率屏幕
|
||||
renderer.setPixelRatio(width / height)
|
||||
// 设置渲染器尺寸
|
||||
renderer.setSize(width, height)
|
||||
// 更新相机宽高比
|
||||
camera.aspect = width / height
|
||||
// 更新相机投影矩阵
|
||||
camera.updateProjectionMatrix()
|
||||
}
|
||||
animate()
|
||||
}
|
||||
|
||||
onMounted(initModel)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="threeRef" class="model" />
|
||||
<router-link
|
||||
to="/three/2"
|
||||
class="position-fixed left-20px top-20px"
|
||||
>
|
||||
添加灯光
|
||||
</router-link>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.model {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
6
src/views/three/10/index.vue
Normal file
6
src/views/three/10/index.vue
Normal file
@@ -0,0 +1,6 @@
|
||||
<script lang="ts" setup>
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>1</div>
|
||||
</template>
|
||||
6
src/views/three/11/index.vue
Normal file
6
src/views/three/11/index.vue
Normal file
@@ -0,0 +1,6 @@
|
||||
<script lang="ts" setup>
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>1</div>
|
||||
</template>
|
||||
183
src/views/three/2/index.vue
Normal file
183
src/views/three/2/index.vue
Normal file
@@ -0,0 +1,183 @@
|
||||
<script lang="ts" setup>
|
||||
import * as THREE from 'three'
|
||||
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
|
||||
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
|
||||
import { onMounted, shallowRef } from 'vue'
|
||||
|
||||
const threeRef = shallowRef<HTMLCanvasElement>()
|
||||
|
||||
let scene: THREE.Scene | undefined
|
||||
let camera: THREE.PerspectiveCamera | undefined
|
||||
let renderer: THREE.WebGLRenderer | undefined
|
||||
|
||||
function getElementSize() {
|
||||
const width = threeRef.value!.clientWidth
|
||||
const height = threeRef.value!.clientHeight
|
||||
|
||||
return { width, height }
|
||||
}
|
||||
|
||||
function initModel() {
|
||||
const { width, height } = getElementSize()
|
||||
|
||||
scene = new THREE.Scene()
|
||||
scene.background = new THREE.Color(0x000000)
|
||||
|
||||
camera = new THREE.PerspectiveCamera(
|
||||
75,
|
||||
width / height,
|
||||
0.1,
|
||||
1000,
|
||||
)
|
||||
camera.position.set(0, 2, 12)
|
||||
|
||||
renderer = new THREE.WebGLRenderer({ antialias: true })
|
||||
renderer.setSize(width, height)
|
||||
renderer.shadowMap.enabled = true
|
||||
renderer.shadowMap.type = THREE.PCFSoftShadowMap
|
||||
threeRef.value!.appendChild(renderer.domElement)
|
||||
|
||||
const loader = new GLTFLoader()
|
||||
loader.load('/mzjc_bansw.glb', (gltf) => {
|
||||
scene!.add(gltf.scene)
|
||||
})
|
||||
|
||||
const controls = new OrbitControls(camera, renderer.domElement)
|
||||
controls.enableDamping = true
|
||||
controls.dampingFactor = 0.05
|
||||
|
||||
const axesHelper = new THREE.AxesHelper(5)
|
||||
scene!.add(axesHelper)
|
||||
|
||||
function animate() {
|
||||
const width = threeRef.value!.clientWidth
|
||||
const height = threeRef.value!.clientHeight
|
||||
requestAnimationFrame(animate)
|
||||
renderer!.render(scene!, camera!)
|
||||
renderer!.setPixelRatio(width / height)
|
||||
renderer!.setSize(width, height)
|
||||
camera!.aspect = width / height
|
||||
camera!.updateProjectionMatrix()
|
||||
}
|
||||
animate()
|
||||
}
|
||||
|
||||
function addSphereGeometry(x: number, y: number, z: number) {
|
||||
const geometry = new THREE.SphereGeometry(15, 32, 16)
|
||||
const material = new THREE.MeshBasicMaterial({ color: 0xFFFF00 })
|
||||
const sphere = new THREE.Mesh(geometry, material)
|
||||
sphere.scale.set(0.005, 0.005, 0.005)
|
||||
sphere.position.set(x, y, z)
|
||||
scene!.add(sphere)
|
||||
}
|
||||
|
||||
// 添加环境光
|
||||
// 均匀照亮整个场景,无方向、无阴影
|
||||
// 提供基础亮度,防止未被其他光照到的区域全黑
|
||||
function addAmbientLight() {
|
||||
const ambientLight = new THREE.AmbientLight(0xFFFFFF, 0.5) // 颜色, 强度
|
||||
scene!.add(ambientLight)
|
||||
}
|
||||
|
||||
// 平行光
|
||||
// 模拟太阳光,所有光线平行照射,可产生阴影
|
||||
// 主光源,适合户外或强定向照明
|
||||
// 光源位置决定阴影位置
|
||||
function addDirectionalLight() {
|
||||
const directionalLight = new THREE.DirectionalLight(0x00FFFF, 0.5) // 颜色, 强度
|
||||
directionalLight.position.set(1, 2, 8) // 光源位置(方向由位置决定)
|
||||
// 启用阴影(需开启渲染器和物体的阴影支持)
|
||||
directionalLight.castShadow = true
|
||||
scene!.add(directionalLight)
|
||||
|
||||
const directionalLightHelper = new THREE.DirectionalLightHelper(directionalLight)
|
||||
scene!.add(directionalLightHelper)
|
||||
|
||||
addSphereGeometry(1, 2, 8)
|
||||
}
|
||||
|
||||
// 点光源
|
||||
// 向各个方向均匀发射光线,可产生阴影
|
||||
// 模拟灯泡等局部光源效果
|
||||
// 光源位置决定阴影位置
|
||||
// 光照强度随距离衰减 模拟现实世界中光强随传播距离自然衰减的物理行为
|
||||
function addPointLight() {
|
||||
const pointLight = new THREE.PointLight(0x00FFFF, 10, 1000) // 颜色, 强度, 距离衰减
|
||||
pointLight.position.set(1, 2, 8) // 光源位置
|
||||
// 启用阴影(需开启渲染器和物体的阴影支持)
|
||||
pointLight.castShadow = true
|
||||
scene!.add(pointLight)
|
||||
|
||||
const pointLightHelper = new THREE.PointLightHelper(pointLight)
|
||||
scene!.add(pointLightHelper)
|
||||
|
||||
addSphereGeometry(1, 2, 8)
|
||||
}
|
||||
|
||||
// 聚光灯
|
||||
// 定向锥形光束,可产生阴影
|
||||
// 模拟手电筒或舞台聚光灯效果
|
||||
function addSpotLight() {
|
||||
const spotLight = new THREE.SpotLight(0x00FFFF, 100)// 颜色, 强度
|
||||
spotLight.position.set(1, 2, 8)
|
||||
spotLight.angle = Math.PI / 6 // 光锥角度(弧度)
|
||||
spotLight.penumbra = 0.2 // 半影模糊程度 (0~1)
|
||||
spotLight.decay = 2 // 衰减指数
|
||||
spotLight.distance = 100 // 最大距离
|
||||
scene!.add(spotLight)
|
||||
|
||||
// 可选:显示光锥辅助线
|
||||
const spotLightHelper = new THREE.SpotLightHelper(spotLight)
|
||||
scene!.add(spotLightHelper)
|
||||
|
||||
addSphereGeometry(1, 2, 8)
|
||||
}
|
||||
|
||||
// 半球光
|
||||
// 模拟天空和地面的漫反射光照效果
|
||||
// 天空颜色从上方照射,地面颜色从下方反射
|
||||
// 柔和的户外环境光,比 AmbientLight 更真实
|
||||
function addHemisphereLight() {
|
||||
const hemisphereLight = new THREE.HemisphereLight(0x00FFFF, 0xFFFFBB, 1) // 天空颜色, 地面颜色, 强度
|
||||
scene!.add(hemisphereLight)
|
||||
|
||||
const hemisphereLightHelper = new THREE.HemisphereLightHelper(hemisphereLight, 5)
|
||||
scene!.add(hemisphereLightHelper)
|
||||
}
|
||||
|
||||
onMounted(initModel)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="threeRef" class="model" />
|
||||
<router-link
|
||||
to="/three/3"
|
||||
class="position-fixed left-20px top-20px"
|
||||
>
|
||||
添加控制器、AxesHelper
|
||||
</router-link>
|
||||
<div class="position-fixed right-20px top-20px flex gap-12px">
|
||||
<button @click="addAmbientLight">
|
||||
添加环境光
|
||||
</button>
|
||||
<button @click="addDirectionalLight">
|
||||
添加平行光
|
||||
</button>
|
||||
<button @click="addPointLight">
|
||||
添加点光源
|
||||
</button>
|
||||
<button @click="addSpotLight">
|
||||
添加聚光灯
|
||||
</button>
|
||||
<button @click="addHemisphereLight">
|
||||
添加半球光
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.model {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
86
src/views/three/3/index.vue
Normal file
86
src/views/three/3/index.vue
Normal file
@@ -0,0 +1,86 @@
|
||||
<script lang="ts" setup>
|
||||
import * as THREE from 'three'
|
||||
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
|
||||
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
|
||||
import { onMounted, shallowRef } from 'vue'
|
||||
|
||||
const threeRef = shallowRef<HTMLCanvasElement>()
|
||||
|
||||
let scene: THREE.Scene | undefined
|
||||
let camera: THREE.PerspectiveCamera | undefined
|
||||
let renderer: THREE.WebGLRenderer | undefined
|
||||
|
||||
function getElementSize() {
|
||||
const width = threeRef.value!.clientWidth
|
||||
const height = threeRef.value!.clientHeight
|
||||
|
||||
return { width, height }
|
||||
}
|
||||
|
||||
function initModel() {
|
||||
const { width, height } = getElementSize()
|
||||
|
||||
scene = new THREE.Scene()
|
||||
scene.background = new THREE.Color(0x000000)
|
||||
|
||||
camera = new THREE.PerspectiveCamera(
|
||||
75,
|
||||
width / height,
|
||||
0.1,
|
||||
1000,
|
||||
)
|
||||
camera.position.set(0, 2, 12)
|
||||
|
||||
renderer = new THREE.WebGLRenderer({ antialias: true })
|
||||
renderer.setSize(width, height)
|
||||
renderer.shadowMap.enabled = true
|
||||
renderer.shadowMap.type = THREE.PCFSoftShadowMap
|
||||
threeRef.value!.appendChild(renderer.domElement)
|
||||
|
||||
const loader = new GLTFLoader()
|
||||
loader.load('/mzjc_bansw.glb', (gltf) => {
|
||||
scene!.add(gltf.scene)
|
||||
})
|
||||
|
||||
const controls = new OrbitControls(camera, renderer.domElement)
|
||||
controls.enableDamping = true
|
||||
controls.dampingFactor = 0.05
|
||||
|
||||
const axesHelper = new THREE.AxesHelper(5)
|
||||
scene!.add(axesHelper)
|
||||
|
||||
const ambientLight = new THREE.AmbientLight(0xFFFFFF, 1.2) // 颜色, 强度
|
||||
scene!.add(ambientLight)
|
||||
|
||||
function animate() {
|
||||
const width = threeRef.value!.clientWidth
|
||||
const height = threeRef.value!.clientHeight
|
||||
requestAnimationFrame(animate)
|
||||
renderer!.render(scene!, camera!)
|
||||
renderer!.setPixelRatio(width / height)
|
||||
renderer!.setSize(width, height)
|
||||
camera!.aspect = width / height
|
||||
camera!.updateProjectionMatrix()
|
||||
}
|
||||
animate()
|
||||
}
|
||||
|
||||
onMounted(initModel)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="threeRef" class="model" />
|
||||
<router-link
|
||||
to="/three/4"
|
||||
class="position-fixed left-20px top-20px"
|
||||
>
|
||||
设置地面
|
||||
</router-link>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.model {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
135
src/views/three/4/index.vue
Normal file
135
src/views/three/4/index.vue
Normal file
@@ -0,0 +1,135 @@
|
||||
<script lang="ts" setup>
|
||||
import * as THREE from 'three'
|
||||
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
|
||||
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
|
||||
import { onMounted, shallowRef } from 'vue'
|
||||
|
||||
const threeRef = shallowRef<HTMLCanvasElement>()
|
||||
|
||||
let scene: THREE.Scene | undefined
|
||||
let camera: THREE.PerspectiveCamera | undefined
|
||||
let renderer: THREE.WebGLRenderer | undefined
|
||||
|
||||
function getElementSize() {
|
||||
const width = threeRef.value!.clientWidth
|
||||
const height = threeRef.value!.clientHeight
|
||||
|
||||
return { width, height }
|
||||
}
|
||||
|
||||
function initModel() {
|
||||
const { width, height } = getElementSize()
|
||||
|
||||
scene = new THREE.Scene()
|
||||
scene.background = new THREE.Color(0x000000)
|
||||
|
||||
camera = new THREE.PerspectiveCamera(
|
||||
75,
|
||||
width / height,
|
||||
0.1,
|
||||
1000,
|
||||
)
|
||||
camera.position.set(0, 2, 12)
|
||||
|
||||
renderer = new THREE.WebGLRenderer({ antialias: true })
|
||||
renderer.setSize(width, height)
|
||||
renderer.shadowMap.enabled = true
|
||||
renderer.shadowMap.type = THREE.PCFSoftShadowMap
|
||||
threeRef.value!.appendChild(renderer.domElement)
|
||||
|
||||
const loader = new GLTFLoader()
|
||||
loader.load('/mzjc_bansw.glb', (gltf) => {
|
||||
scene!.add(gltf.scene)
|
||||
})
|
||||
|
||||
const controls = new OrbitControls(camera, renderer.domElement)
|
||||
controls.enableDamping = true
|
||||
controls.dampingFactor = 0.05
|
||||
|
||||
const axesHelper = new THREE.AxesHelper(5)
|
||||
scene!.add(axesHelper)
|
||||
|
||||
const ambientLight = new THREE.AmbientLight(0xFFFFFF, 1.2) // 颜色, 强度
|
||||
scene!.add(ambientLight)
|
||||
|
||||
function animate() {
|
||||
const width = threeRef.value!.clientWidth
|
||||
const height = threeRef.value!.clientHeight
|
||||
requestAnimationFrame(animate)
|
||||
renderer!.render(scene!, camera!)
|
||||
renderer!.setPixelRatio(width / height)
|
||||
renderer!.setSize(width, height)
|
||||
camera!.aspect = width / height
|
||||
camera!.updateProjectionMatrix()
|
||||
}
|
||||
animate()
|
||||
}
|
||||
|
||||
function addPlaneGeometry() {
|
||||
// 创建地面几何体:宽度 20,高度 20,细分 1x1(足够平坦)
|
||||
const groundGeometry = new THREE.PlaneGeometry(20, 20, 1, 1)
|
||||
// 使用标准材质(支持光照和阴影)
|
||||
const groundMaterial = new THREE.MeshStandardMaterial({
|
||||
color: 0xAAAAAA, // 灰色
|
||||
roughness: 0.8,
|
||||
metalness: 0.2,
|
||||
})
|
||||
|
||||
// 创建网格
|
||||
const ground = new THREE.Mesh(groundGeometry, groundMaterial)
|
||||
|
||||
// 旋转 90 度使其平放在 XZ 平面上(默认 PlaneGeometry 在 XY 平面)
|
||||
ground.rotation.x = -Math.PI / 2
|
||||
|
||||
// 可选:启用接收阴影(如果场景中有灯光和阴影)
|
||||
ground.receiveShadow = true
|
||||
|
||||
// 添加到场景
|
||||
scene!.add(ground)
|
||||
|
||||
const gridHelper = new THREE.GridHelper(100, 20, 0x444444, 0x222222)
|
||||
scene!.add(gridHelper)
|
||||
}
|
||||
|
||||
function addBoxGeometry() {
|
||||
// 创建一个 20×0.2×20 的长方体(宽×高×深)
|
||||
const groundGeometry = new THREE.BoxGeometry(20, 0.2, 20)
|
||||
const groundMaterial = new THREE.MeshStandardMaterial({ color: 0x556B2F })
|
||||
|
||||
const ground = new THREE.Mesh(groundGeometry, groundMaterial)
|
||||
ground.position.y = -0.1 // 让顶部对齐 Y=0 平面 几何体的原点(pivot point)默认在它的中心
|
||||
ground.receiveShadow = true
|
||||
|
||||
scene!.add(ground)
|
||||
|
||||
const gridHelper = new THREE.GridHelper(100, 20, 0x444444, 0x222222)
|
||||
scene!.add(gridHelper)
|
||||
}
|
||||
|
||||
onMounted(initModel)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="threeRef" class="model" />
|
||||
<router-link
|
||||
to="/three/5"
|
||||
class="position-fixed left-20px top-20px"
|
||||
>
|
||||
地面上设置光效
|
||||
</router-link>
|
||||
<div class="position-fixed right-20px top-20px flex gap-12px">
|
||||
<button @click="addPlaneGeometry">
|
||||
添加2D地面
|
||||
</button>
|
||||
<button @click="addBoxGeometry">
|
||||
添加有厚度的地面
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.model {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
273
src/views/three/5/index.vue
Normal file
273
src/views/three/5/index.vue
Normal file
@@ -0,0 +1,273 @@
|
||||
<script lang="ts" setup>
|
||||
import * as THREE from 'three'
|
||||
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
|
||||
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
|
||||
import { onBeforeUnmount, onMounted, shallowRef } from 'vue'
|
||||
|
||||
// ========== 常量定义 ==========
|
||||
const GROUND_SIZE = 30
|
||||
const GROUND_THICKNESS = 0.2
|
||||
const LINE_COLORS = [0xFF0000, 0x00FF00, 0x0000FF, 0xFFFF00, 0xFF00FF, 0x00FFFF]
|
||||
const LINE_SPEED = 0.1
|
||||
const LINE_DELAY = 300
|
||||
|
||||
// ========== 类型定义 ==========
|
||||
interface LineState {
|
||||
mesh: THREE.Mesh
|
||||
direction: number
|
||||
startDelay: number
|
||||
elapsed: number
|
||||
axis: 'x' | 'z'
|
||||
}
|
||||
|
||||
// ========== 状态变量 ==========
|
||||
const threeRef = shallowRef<HTMLCanvasElement>()
|
||||
|
||||
let scene: THREE.Scene
|
||||
let camera: THREE.PerspectiveCamera
|
||||
let renderer: THREE.WebGLRenderer
|
||||
let controls: OrbitControls
|
||||
let animationId: number
|
||||
|
||||
const lineStates: LineState[] = []
|
||||
|
||||
function getElementSize() {
|
||||
const width = threeRef.value!.clientWidth
|
||||
const height = threeRef.value!.clientHeight
|
||||
|
||||
return { width, height }
|
||||
}
|
||||
|
||||
// ========== 初始化场景 ==========
|
||||
function initModel() {
|
||||
const { width, height } = getElementSize()
|
||||
|
||||
// 创建场景
|
||||
scene = new THREE.Scene()
|
||||
scene.background = new THREE.Color(0x000000)
|
||||
|
||||
// 创建相机
|
||||
camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000)
|
||||
camera.position.set(3, 8, 12)
|
||||
|
||||
// 创建渲染器
|
||||
renderer = new THREE.WebGLRenderer({ antialias: true })
|
||||
renderer.setSize(width, height)
|
||||
renderer.setPixelRatio(window.devicePixelRatio)
|
||||
renderer.shadowMap.enabled = true
|
||||
renderer.shadowMap.type = THREE.PCFSoftShadowMap
|
||||
renderer.localClippingEnabled = true
|
||||
threeRef.value!.appendChild(renderer.domElement)
|
||||
|
||||
// 加载模型
|
||||
const loader = new GLTFLoader()
|
||||
loader.load('/mzjc_bansw.glb', (gltf) => {
|
||||
scene.add(gltf.scene)
|
||||
})
|
||||
|
||||
// 创建控制器
|
||||
controls = new OrbitControls(camera, renderer.domElement)
|
||||
controls.enableDamping = true
|
||||
controls.dampingFactor = 0.05
|
||||
|
||||
// 添加辅助工具
|
||||
const axesHelper = new THREE.AxesHelper(5)
|
||||
scene.add(axesHelper)
|
||||
|
||||
// 添加环境光
|
||||
const ambientLight = new THREE.AmbientLight(0xFFFFFF, 1.2)
|
||||
scene.add(ambientLight)
|
||||
|
||||
// 添加地面
|
||||
addGround()
|
||||
|
||||
// 启动动画循环
|
||||
animate()
|
||||
}
|
||||
|
||||
// ========== 添加地面 ==========
|
||||
function addGround() {
|
||||
const geometry = new THREE.BoxGeometry(GROUND_SIZE, GROUND_THICKNESS, GROUND_SIZE)
|
||||
const material = new THREE.MeshStandardMaterial({ color: 0x222222 })
|
||||
const ground = new THREE.Mesh(geometry, material)
|
||||
ground.position.y = -GROUND_THICKNESS / 2
|
||||
ground.receiveShadow = true
|
||||
scene.add(ground)
|
||||
}
|
||||
|
||||
// ========== 添加地面光效 ==========
|
||||
function addGroundLightEffect() {
|
||||
// 创建裁剪平面
|
||||
const clipPlanes = [
|
||||
new THREE.Plane(new THREE.Vector3(1, 0, 0), GROUND_SIZE / 2),
|
||||
new THREE.Plane(new THREE.Vector3(-1, 0, 0), GROUND_SIZE / 2),
|
||||
new THREE.Plane(new THREE.Vector3(0, 0, 1), GROUND_SIZE / 2),
|
||||
new THREE.Plane(new THREE.Vector3(0, 0, -1), GROUND_SIZE / 2),
|
||||
]
|
||||
|
||||
const geometry = new THREE.BoxGeometry(GROUND_SIZE, 0.1, 0.5)
|
||||
|
||||
LINE_COLORS.forEach((color, index) => {
|
||||
const material = new THREE.MeshBasicMaterial({
|
||||
color: new THREE.Color(color),
|
||||
transparent: true,
|
||||
opacity: 0.15 * (index + 1) * Math.random(),
|
||||
clippingPlanes: clipPlanes,
|
||||
clipShadows: true,
|
||||
depthWrite: false,
|
||||
})
|
||||
|
||||
const offset = -GROUND_SIZE / 2 + (GROUND_SIZE / LINE_COLORS.length) * (index + 0.5)
|
||||
const isEven = index % 2 === 0
|
||||
|
||||
// 创建X轴线条
|
||||
const xMesh = new THREE.Mesh(geometry, material.clone())
|
||||
xMesh.position.set(isEven ? GROUND_SIZE : -GROUND_SIZE, 0, offset)
|
||||
xMesh.renderOrder = 1
|
||||
scene.add(xMesh)
|
||||
|
||||
lineStates.push({
|
||||
mesh: xMesh,
|
||||
direction: isEven ? -1 : 1,
|
||||
startDelay: index * LINE_DELAY,
|
||||
elapsed: 0,
|
||||
axis: 'x',
|
||||
})
|
||||
|
||||
// 创建Z轴线条
|
||||
const zMesh = new THREE.Mesh(geometry, material.clone())
|
||||
zMesh.position.set(offset, 0.01, isEven ? GROUND_SIZE : -GROUND_SIZE)
|
||||
zMesh.rotation.y = Math.PI / 2
|
||||
zMesh.renderOrder = 2
|
||||
scene.add(zMesh)
|
||||
|
||||
lineStates.push({
|
||||
mesh: zMesh,
|
||||
direction: isEven ? -1 : 1,
|
||||
startDelay: index * LINE_DELAY,
|
||||
elapsed: 0,
|
||||
axis: 'z',
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// ========== 更新线条动画 ==========
|
||||
function updateLines(deltaTime: number) {
|
||||
lineStates.forEach((state) => {
|
||||
state.elapsed += deltaTime
|
||||
if (state.elapsed < state.startDelay)
|
||||
return
|
||||
|
||||
const pos = state.axis === 'x' ? state.mesh.position.x : state.mesh.position.z
|
||||
|
||||
// 边界检测并反转方向
|
||||
if (pos >= GROUND_SIZE)
|
||||
state.direction = -1
|
||||
else if (pos <= -GROUND_SIZE)
|
||||
state.direction = 1
|
||||
|
||||
// 更新位置
|
||||
const delta = LINE_SPEED * state.direction
|
||||
if (state.axis === 'x') {
|
||||
state.mesh.position.x += delta
|
||||
}
|
||||
else {
|
||||
state.mesh.position.z += delta
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ========== 主动画循环 ==========
|
||||
let lastTime = 0
|
||||
function animate(time = 0) {
|
||||
animationId = requestAnimationFrame(animate)
|
||||
|
||||
const deltaTime = time - lastTime
|
||||
lastTime = time
|
||||
|
||||
// 更新控制器
|
||||
controls.update()
|
||||
|
||||
// 更新线条动画
|
||||
updateLines(deltaTime)
|
||||
|
||||
// 响应式调整渲染器大小
|
||||
const { width, height } = getElementSize()
|
||||
if (camera.aspect !== width / height) {
|
||||
camera.aspect = width / height
|
||||
camera.updateProjectionMatrix()
|
||||
renderer.setSize(width, height)
|
||||
}
|
||||
|
||||
renderer.render(scene, camera)
|
||||
}
|
||||
|
||||
// ========== 清理资源 ==========
|
||||
function cleanup() {
|
||||
if (animationId)
|
||||
cancelAnimationFrame(animationId)
|
||||
if (controls)
|
||||
controls.dispose()
|
||||
if (renderer) {
|
||||
renderer.dispose()
|
||||
threeRef.value?.removeChild(renderer.domElement)
|
||||
}
|
||||
scene?.traverse((object) => {
|
||||
if (object instanceof THREE.Mesh) {
|
||||
object.geometry?.dispose()
|
||||
if (Array.isArray(object.material)) {
|
||||
object.material.forEach(m => m.dispose())
|
||||
}
|
||||
else {
|
||||
object.material?.dispose()
|
||||
}
|
||||
}
|
||||
})
|
||||
lineStates.length = 0
|
||||
}
|
||||
|
||||
function pauseGroundLightEffect() {
|
||||
cancelAnimationFrame(animationId)
|
||||
}
|
||||
|
||||
function resumeGroundLightEffect() {
|
||||
animate()
|
||||
}
|
||||
|
||||
function destoryGroundLightEffect() {
|
||||
lineStates.forEach((state) => {
|
||||
state.mesh.parent?.remove(state.mesh)
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(initModel)
|
||||
onBeforeUnmount(cleanup)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="threeRef" class="model" />
|
||||
<router-link to="/three/5" class="position-fixed left-20px top-20px">
|
||||
地面上设置光效
|
||||
</router-link>
|
||||
<div class="position-fixed right-20px top-20px flex gap-12px">
|
||||
<button @click="addGroundLightEffect">
|
||||
添加地面光效
|
||||
</button>
|
||||
<button @click="pauseGroundLightEffect">
|
||||
暂停地面光效
|
||||
</button>
|
||||
<button @click="resumeGroundLightEffect">
|
||||
继续地面光效
|
||||
</button>
|
||||
<button @click="destoryGroundLightEffect">
|
||||
销毁地面光效
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.model {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
103
src/views/three/6/index.vue
Normal file
103
src/views/three/6/index.vue
Normal file
@@ -0,0 +1,103 @@
|
||||
<script lang="ts" setup>
|
||||
import * as THREE from 'three'
|
||||
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
|
||||
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
|
||||
import { onMounted, shallowRef } from 'vue'
|
||||
|
||||
const threeRef = shallowRef<HTMLCanvasElement>()
|
||||
|
||||
function initModel() {
|
||||
const width = threeRef.value!.clientWidth
|
||||
const height = threeRef.value!.clientHeight
|
||||
|
||||
const scene = new THREE.Scene()
|
||||
scene.background = new THREE.Color(0x000000)
|
||||
|
||||
const camera = new THREE.PerspectiveCamera(
|
||||
75,
|
||||
width / height,
|
||||
0.1,
|
||||
1000,
|
||||
)
|
||||
camera.position.set(0.1, 0.1, 1)
|
||||
|
||||
const renderer = new THREE.WebGLRenderer({ antialias: true })
|
||||
renderer.setSize(width, height)
|
||||
renderer.shadowMap.enabled = true
|
||||
renderer.shadowMap.type = THREE.PCFSoftShadowMap
|
||||
threeRef.value!.appendChild(renderer.domElement)
|
||||
|
||||
const controls = new OrbitControls(camera, renderer.domElement)
|
||||
controls.enableDamping = true
|
||||
controls.dampingFactor = 0.05
|
||||
|
||||
const axesHelper = new THREE.AxesHelper(5)
|
||||
scene.add(axesHelper)
|
||||
|
||||
// 环境光
|
||||
const ambientLight = new THREE.AmbientLight(0xFFFFFF, 0.5)
|
||||
scene.add(ambientLight)
|
||||
|
||||
// 方向光(主光)
|
||||
const directionalLight = new THREE.DirectionalLight(0xFFFFFF, 0.8)
|
||||
directionalLight.position.set(20, 30, 20)
|
||||
directionalLight.castShadow = true
|
||||
directionalLight.shadow.mapSize.set(2048, 2048)
|
||||
directionalLight.shadow.camera.near = 0.5
|
||||
directionalLight.shadow.camera.far = 50
|
||||
directionalLight.shadow.camera.left = -20
|
||||
directionalLight.shadow.camera.right = 20
|
||||
directionalLight.shadow.camera.top = 20
|
||||
directionalLight.shadow.camera.bottom = -20
|
||||
scene.add(directionalLight)
|
||||
|
||||
// 点光源
|
||||
const pointLight = new THREE.PointLight(0x409EFF, 0.5, 50)
|
||||
pointLight.position.set(0, 10, 0)
|
||||
scene.add(pointLight)
|
||||
|
||||
const loader = new GLTFLoader()
|
||||
loader.load('/lxb_grp.glb', (gltf) => {
|
||||
scene.add(gltf.scene)
|
||||
|
||||
const { animations = [] } = gltf
|
||||
animations.forEach((clip) => {
|
||||
const mixer = new THREE.AnimationMixer(gltf.scene)
|
||||
const action = mixer.clipAction(clip)
|
||||
action.play()
|
||||
|
||||
const clock = new THREE.Clock()
|
||||
// 在动画循环中更新混合器
|
||||
function updateMixer() {
|
||||
requestAnimationFrame(updateMixer)
|
||||
const delta = clock.getDelta()
|
||||
mixer.update(delta)
|
||||
}
|
||||
updateMixer()
|
||||
})
|
||||
})
|
||||
|
||||
function animate() {
|
||||
requestAnimationFrame(animate)
|
||||
renderer.render(scene, camera)
|
||||
renderer.setSize(width, height)
|
||||
camera.aspect = width / height
|
||||
camera.updateProjectionMatrix()
|
||||
}
|
||||
|
||||
animate()
|
||||
}
|
||||
|
||||
onMounted(initModel)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div ref="threeRef" class="model" />
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.model {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
6
src/views/three/7/index.vue
Normal file
6
src/views/three/7/index.vue
Normal file
@@ -0,0 +1,6 @@
|
||||
<script lang="ts" setup>
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>1</div>
|
||||
</template>
|
||||
6
src/views/three/8/index.vue
Normal file
6
src/views/three/8/index.vue
Normal file
@@ -0,0 +1,6 @@
|
||||
<script lang="ts" setup>
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>1</div>
|
||||
</template>
|
||||
6
src/views/three/9/index.vue
Normal file
6
src/views/three/9/index.vue
Normal file
@@ -0,0 +1,6 @@
|
||||
<script lang="ts" setup>
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>1</div>
|
||||
</template>
|
||||
@@ -3,13 +3,11 @@ import { fileURLToPath, URL } from 'node:url'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import Unocss from 'unocss/vite'
|
||||
import { defineConfig } from 'vite'
|
||||
import vueDevTools from 'vite-plugin-vue-devtools'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
vueDevTools(),
|
||||
Unocss(),
|
||||
],
|
||||
resolve: {
|
||||
|
||||
Reference in New Issue
Block a user