Three学习笔记

Three学习笔记

01 初步了解

1.1 创建立方体

// 创建立方体
let geometry = new THREE.BoxGeometry(1,2,3)  // 长宽高
let meterial = new THREE.MeshNormalMaterial( {
      color: 0x00ff00 }) // 立方体材质 颜色
let box = new THREE.Mesh(geometry,meterial) // 构造立方体 = 体积 + 材质

1.2 创建场景

// 创建场景
let scene = new THREE.Scene()
// 盒子添加到场景
scene.add(box)

1.3 创建相机

/**
 * 创建相机
 * fov —  摄像机角度
 * aspect — 长宽比
 * near — 摄像机最近的距离
 * far —  摄像机最远的距离
 */
let camera = new THREE.PerspectiveCamera(75, width / height, 0.01, 1000)
 // 相机摆位,相机朝向距离
camera.position.z = 4
 // 相机位置
camera.lookAt(0,0,0)

1.4 创建渲染器

// 关联canvas
let renderder = new THREE.WebGLRenderer({
      canvas }) 
// 渲染页面的大小
renderder.setSize(width,height) 
### 	1.5 动画渲染	
function animation() {
     
    box.rotation.x += 0.01
    box.rotation.y += 0.01
    renderder.render(scene,camera) // 渲染 场景 + 移动相机
    requestAnimationFrame(animation)
}
animation()

1.6 完整代码


<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
head>
<style>
    *{
       padding: 0;margin: 0; }
    body{
      overflow: hidden}
style>
<script src="../js/three.min.js">script>
<body>
    <canvas id="canvas">canvas>
body>
<script>
    /*
    *   1.创建立方体
    *   2.创建场景
    *   3.创建相机
    *   4.创建渲染器
    *   5.创建动画
    * */

    // canvas配置
    let canvas = document.querySelector('#canvas')
    let width = canvas.width = window.innerWidth
    let height = canvas.height = window.innerHeight
    window.onresize = resize
    function resize() {
      
        width = canvas.width = window.innerWidth
        height = canvas.height = window.innerHeight
    }

    // 创建立方体
    let geometry = new THREE.BoxGeometry(1,2,3)  // 长宽高
    let meterial = new THREE.MeshNormalMaterial( {
       color: 0x00ff00 }) // 立方体材质 颜色
    let box = new THREE.Mesh(geometry,meterial) // 构造立方体 = 体积 + 材质

    // 创建场景
    let scene = new THREE.Scene()

    // 盒子添加到场景
    scene.add(box)

    /**
     * 创建相机
     * fov —  摄像机角度
     * aspect — 长宽比
     * near — 摄像机最近的距离
     * far —  摄像机最远的距离
     */
    let camera = new THREE.PerspectiveCamera(75, width / height, 0.01, 1000)
        camera.position.z = 4 // 相机摆位,相机朝向距离
        camera.lookAt(0,0,0) // 相机位置


    // 创建渲染器 
    let renderder = new THREE.WebGLRenderer({
       canvas }) // 关联canvas
    renderder.setSize(width,height) // 渲染页面的大小

    function animation() {
      
        box.rotation.x += 0.01
        box.rotation.y += 0.01
        renderder.render(scene,camera) // 渲染 场景 + 移动相机
        requestAnimationFrame(animation)
    }
    animation()
script>
html>

02 几何体

2.1 空间控制器

// three.js-dev\examples\js\controls\OrbitControls.js

 // 控制器
let control = new THREE.OrbitControls(camera)

function animation() {
     
    box.rotation.x += 0.01
    box.rotation.y += 0.01
    control.update()  // 动画渲染
    renderder.render(scene,camera)
 	requestAnimationFrame(animation)
}

2.2 窗口伸缩

function resize() {
     
    width = canvas.width = window.innerWidth
    height = canvas.height = window.innerHeight

    // 渲染页面的大小
    renderder.setSize(width, height)
    // 更新相机宽高比
    camera.aspect(width / height)
    // 新的比例应用到世界坐标系中
    camera.updateProjectionMatrix()
}

2.3 几何体创建

​ 立方体 = 组合 ( 创建几何体 + 材质 + 颜色 )

/**
 * 立方体
 */
function geometry() {
     
    // 创建立方体
    let geometry = new THREE.BoxGeometry(1,2,3)
    // 材质、颜色
    let meterial = new THREE.MeshNormalMaterial({
      color: 0x00ff00 })
    // 生成立方体
    let cube  = new THREE.Mesh(geometry,meterial)
    return cube
}

	### 	2.4 网格几何
/**
 * 创建网格
 */
function mesh() {
     
    let geometry  = null
    /*
     * 立方体
     * 参数:x长,y长,z长,x长分段数,y长分段数,z长分段数
     * */
    geometry = new THREE.BoxGeometry(1,1,1,2,2,2)

    /*
     * 圆形
     * 参数:半径,分段数,起始角度,弧度
     * */
    geometry = new THREE.CircleBufferGeometry(5,36,0,Math.PI*2)

    /*
     * 圆锥
     * 参数:圆锥底部的半径,圆锥的高度,圆锥侧面周围的分段数,圆锥侧面沿着其高度的分段数
     * */
    geometry = new THREE.ConeGeometry(5,10,36)

    /*
     * 圆柱
     * 参数:圆柱的顶部半径,圆柱的底部半径, 圆柱的高度...
     * */
    geometry = new THREE.CylinderGeometry(2,5,20,20)

    /*
     * 十二面几何体
     * 参数:十二面体的半径,增加一些顶点
     * */
    geometry = new THREE.DodecahedronGeometry(2,3)
    // 格局分段情况,构架网格
    let wireframe = new THREE.WireframeGeometry(geometry)
    // 生产线条
    let line = new THREE.LineSegments(wireframe)
    // 设置线条颜色(十六进制)
    line.material.color = new THREE.Color(0xff0000)
    return line
}

03 坐标系

3.1 创建坐标系

let axesHelper = new THREE.AxesHelper(5) // 坐标系长度
scene.add(axesHelper)

3.2 创建平面

 /**
  * 添加平面
  */
function addPlane() {
     
    // 创建坐标系列
    let geometry = new THREE.Geometry()
    // 创建点
    geometry.vertices.push(new THREE.Vector3(1,0,0))
    geometry.vertices.push(new THREE.Vector3(0,1,0))
    geometry.vertices.push(new THREE.Vector3(0,0,1))
    // 创建面
    let normal = new THREE.Vector3(1,1,1)  // 面的法向量,确定正反面
    let color = new THREE.Color(0xff00ff) // 面颜色
    // 0,1,2 是面序号确定该面
    geometry.faces.push(new THREE.Face3(0,1,2,normal,color)) // 添加面进去
    // 材质
    let meterials = new THREE.MeshNormalMaterial({
     
        side: THREE.DoubleSide //看到两个面
    })
    return new THREE.Mesh(geometry,meterials)
}

//...
let plane = addPlane()
scene.add(plane)

3.3 创建三菱锥

function addPlane() {
     
    // 创建坐标系列
    let geometry = new THREE.Geometry()
    // 创建点
    geometry.vertices.push(new THREE.Vector3(1,0,0))
    geometry.vertices.push(new THREE.Vector3(0,1,0))
    geometry.vertices.push(new THREE.Vector3(0,0,1))
    geometry.vertices.push(new THREE.Vector3(1,2,1))
    // 创建面
    let normal = new THREE.Vector3(1,1,1)  // 面的法向量,确定正反面
    let color = new THREE.Color(0xff00ff) // 面颜色
    // 0,1,2 是面序号确定该面,3是制高点
    geometry.faces.push(new THREE.Face3(0,1,2,normal,color)) // 添加面进去
    geometry.faces.push(new THREE.Face3(3,1,2,normal,color)) // 添加面进去
    geometry.faces.push(new THREE.Face3(3,0,1,normal,color)) // 添加面进去
    geometry.faces.push(new THREE.Face3(3,0,2,normal,color)) // 添加面进去
    // 材质
    let meterials = new THREE.MeshNormalMaterial({
     
        side: THREE.DoubleSide //看到两个面
    })
    return new THREE.Mesh(geometry,meterials)
}

3.4 欧拉角度转换

 function addPlane() {
     
     // 创建坐标系列
     let geometry = new THREE.Geometry()
     // 创建点
     geometry.vertices.push(new THREE.Vector3(1,0,0))
     // 0,0,Math.PI*2/5,'XYZ'  -> xyz一一对应,弧度单位
     geometry.vertices.push(
         new THREE.Vector3(1,0,0).applyEuler(new THREE.Euler(0,0,Math.PI*2/5,'XYZ'))
     )
     geometry.vertices.push(new THREE.Vector3(-1,0,0))
     // 创建面
     let normal = new THREE.Vector3(1,1,1)  // 面的法向量,确定正反面
     let color = new THREE.Color(0xff00ff) // 面颜色
     // 0,1,2 是面序号确定该面,3是制高点
     geometry.faces.push(new THREE.Face3(0,1,2,normal,color)) // 添加面进去
     // 材质
     let meterials = new THREE.MeshNormalMaterial({
     
         side: THREE.DoubleSide //看到两个面
     })
     return new THREE.Mesh(geometry,meterials)
 }

04 实例-魔仙棒

​ [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P9HRJ0zH-1572667455144)(file:///C:/Users/MACHENIKE/Documents/My Knowledge/temp/58ef6a89-39c9-4b7f-99d5-38d36c0eb5cd/128/index_files/b9c34d8f-d86f-45c6-b50b-7a9ddd7b7625.png)]

​ 魔仙棒 = 组合(near + far + depth) 最近距离 + 最近距离 + 厚度

4.1 创建多边形

/**
 * 返回一个几何体
 * @param far 最远距离
 * @param near 最近距离
 * @param depth 厚度
 * @param count 边数
 */
function getStartGeometry(far,near,depth,count=5){
     
    const geometry = new THREE.Geometry()
    // 厚度的点
    let frontPoint = new THREE.Vector3(0,0,depth)
    geometry.vertices.push(frontPoint)
    // 厚度背后的点
    let backPoint = new THREE.Vector3(0,0,-depth)
    geometry.vertices.push(backPoint)

    // 远近点 +1 与原点重合
    for (let i = 0; i < count+1; i++) {
     
        // 最远距离的点
        let farPoint = new THREE.Vector3(far,0,0)
            .applyEuler(
                new THREE.Euler(0,0,Math.PI*2*i/count,'XYZ')
            )
        geometry.vertices.push(farPoint)
        // 最近距离的点   + Math.PI/5 错开两个角
        let nearPoint = new THREE.Vector3(near,0,0)
            .applyEuler(
                new THREE.Euler(0,0,Math.PI*2*i/count + Math.PI/count,'XYZ')
            )
        geometry.vertices.push(nearPoint)
    }

    // 平面
    const normal = new THREE.Vector3(0,1,0)
    const color = new THREE.Color(0xff00ff)
    for (let i = 0; i < count*2; i++) {
     
        geometry.faces.push(new THREE.Face3(0,i+2,i+3,normal,color)) // 正面
        geometry.faces.push(new THREE.Face3(1,i+2,i+3,normal,color)) // 背面
    }

    // 绕着z轴旋转
    geometry.rotateZ(Math.PI/count/2)
    return geometry
}

4.2 多边形实例

// 创建星星
const start1 = getStartGeometry(3,1,0.5,5)
// 材质包
const materials = new THREE.MeshNormalMaterial({
     
    side: THREE.DoubleSide // 看到两个面
})
const startMesh_1 = new THREE.Mesh(start1,materials)

4.3 Object3D 组合暂存

// startMesh_1,startMesh_2,cylinderMesh 创建出来的方体
let obj = new THREE.Object3D()
obj.add(startMesh_1,startMesh_2,cylinderMesh)
scene.add(obj) // 添加到场景

4.4 控制Object3D移动

// 控制器
let control = new THREE.OrbitControls(camera)

function animation() {
     
    requestAnimationFrame(animation)
    control.update()
    renderder.render(scene,camera)
    // 更新控制器
    obj.children[0].rotation.y += 0.1 // 控制其中一部分
    // 控制整体 
}
animation()

05 线条和纹理、世界背景

5.1 创建线条

function createLine(){
     
    let geometry = new THREE.Geometry()
    // 添加点
    geometry.vertices.push(new THREE.Vector3(1,1,1))
    geometry.vertices.push(new THREE.Vector3(2,2,2))
    geometry.vertices.push(new THREE.Vector3(3,3,3))

    //  上色,分段上色,也可单个颜色
    geometry.colors.push(
        new THREE.Color(0xff0000),
        new THREE.Color(0x0000ff),
    )

    // 材质
    const meterial = new THREE.LineBasicMaterial({
     
        vertexColors:true
    })

    // 连接点
    const line = new THREE.Line(geometry,meterial)
    return line
}

 let scene = new THREE.Scene()
 let line = createLine()
 scene.add(line)

5.2 图片贴纸

function createPlane(){
     
   const geometry = new THREE.PlaneGeometry(5,5)

   // 创建纹理引入工具
   const loader = new THREE.TextureLoader()
   // 引入图片
   const texture = loader.load('../textures/envmap.png')

   // 材质
   const meterial = new THREE.MeshBasicMaterial({
     
       map:texture, // 引入的图片
       side:THREE.DoubleSide // 正反面材质
   })

   return new THREE.Mesh(geometry,meterial)
}

let scene = new THREE.Scene()
let plane = createPlane()
scene.add(plane)

5.3 世界背景

// 世界图片地址:thressJs\three.js-dev\examples\textures
let scene = new THREE.Scene()
let loader = new THREE.CubeTextureLoader()
    scene.background = loader
        .setPath('../textures/cube/Bridge2/')
        .load([
        'posx.jpg',
        'negx.jpg',
        'posy.jpg',
        'negy.jpg',
        'posz.jpg',
        'negz.jpg'
    ])

5.4 世界背景颜色

this.scene = this.getScene()
  this.scene.background = new THREE.Color(0xffffff)

06 光

如果不手动添加,页面没有光线

6.1 创建地板

观察光线

// 添加平面
  createPlane(){
     
    const geometry = new THREE.PlaneGeometry(500,500)
    const material = new THREE.MeshLambertMaterial({
     
      color:new THREE.Color(0xcccccc),
      side:THREE.DoubleSide
    })

    const mesh = new THREE.Mesh( geometry, material )
    mesh.position.set( 0, -15, 0) // 地板往下
    mesh.rotateX(-Math.PI / 2)
    return mesh
  },

6.2 环境光

环境光会均匀的照亮场景中的 所有物体。环境光不能用来投射阴影,因为它没有方向。

物理知识:物体的颜色的本质是标识反射这种光线的能力,环境光一般都是白色的oxffffff

// 盒子
getCube(){
     
    let geometry = new THREE.BoxGeometry(1,2,3)
    // Lamber材质,支持反光
    let meterial = new THREE.MeshLambertMaterial() 
    const mesh = new THREE.Mesh( geometry,meterial )
    return mesh
},

// 添加光线
addLight(){
     
    // 定义光线   0xffffff 光颜色  0.3 亮度
    const ambientLight = new THREE.AmbientLight(0xffffff,0.3)
    // 添加到世界
    this.scene.add(ambientLight)
},

6.3 点光源

从一个点向各个方向发射的光源,越远越暗。(部分照亮)

一个常见的例子是模拟一个灯泡发出的光。该光源可以投射阴影

// 点光源
addPointLight (){
     
    const pointLight = new THREE.PointLight(0xffffff,1,1000)
    pointLight.position.set(-5,5,5)
    this.scene.add(pointLight)
    
    // 点光源模拟器-辅助线
    const pointLightHelper = new THREE.PointLightHelper(pointLight,1)
    this.scene.add(pointLightHelper)
    return pointLight
},
// 添加平面
createPlane(){
     
    const geometry = new THREE.PlaneGeometry(500,500)
    // 镜面反光效果
    const material = new THREE.MeshPhongMaterial({
     
        color:new THREE.Color(0x444444),
        side:THREE.DoubleSide
    })

    const mesh = new THREE.Mesh( geometry, material )
    mesh.position.set( 0, -15, 0) // 地板往下
    mesh.rotateX(-Math.PI / 2)
    return mesh
},

6.4 平行光

相当于太阳光

// 平行光
addDirectionalLight (){
     
    // 初始竖直平行光,上面照亮
    const directionalLight = new THREE.DirectionalLight( 0xffffff, 0.5 )

    // 设置平行光的位置
    directionalLight.position.set(1,1,1)
    return directionalLight
},

6.5 半球光

相当于白天和黑夜分界线的全局背景光

 // 添加半球光
addHemisphereLight(){
     
    let hemisphereLight = new THREE.HemisphereLight( 0xffffbb, 0x080820, 1 );
    hemisphereLight.position.set(0,0,1)
    return hemisphereLight
},
// 动画
animation(){
     
    requestAnimationFrame(this.animation)
    this.controls.update()
    this.degree += 0.01 // 默认0
    this.hemisphereLight.position.set(Math.sin(this.degree),Math.cos(this.degree),0)
    this.renderer.render(this.scene,this.camera)
},

6.6 聚光灯

// 添加聚光灯
addSpotLight(){
     
    let spotLight = new THREE.SpotLight( 0xffffff );
    spotLight.position.set(0,20,0) // 灯光位置
    spotLight.angle = Math.PI/3 // 灯光角度
    return spotLight
},

07 阴影

必要条件:

  • 光(点光源、平行光、聚光灯)
  • 产生阴影的物体(人)
  • 反射阴影的物体(地面)

7.1 开启阴影模式

getRenderer(){
     
    let canvas = this.$refs.canvas
    canvas.width = this.width
    canvas.height = this.height
    const render = new THREE.WebGLRenderer({
      canvas })
    render.setSize(this.width,this.height)
    render.shadowMap.enabled = true // 开启阴影模式
    return render
},

7.2 光源开启阴影

 // 添加聚光灯
addSpotLight(){
     
    let spotLight = new THREE.SpotLight( 0xffffff );
    spotLight.position.set(0,20,0) // 灯光位置
    spotLight.angle = Math.PI/3 // 灯光角度
    spotLight.castShadow = true // 开启阴影
    return spotLight
},

7.3 投射物开启阴影

// 获取盒子
getCube(){
     
    let geometry = new THREE.BoxGeometry(1,2,3)
    let meterial = new THREE.MeshLambertMaterial({
     
        color: new THREE.Color(0xff0000)
    })
    const mesh = new THREE.Mesh( geometry,meterial )
    mesh.castShadow = true // 开启阴影
    return mesh
},

7.4 地板接收阴影

// 添加平面
createPlane(){
     
    const geometry = new THREE.PlaneGeometry(500,500)
    const material = new THREE.MeshPhongMaterial({
     
        color:new THREE.Color(0x444444),
        side:THREE.DoubleSide
    })

    const mesh = new THREE.Mesh( geometry, material )
    mesh.position.set( 0, -15, 0) // 地板往下
    mesh.rotateX(-Math.PI / 2)
    mesh.receiveShadow = true // 地板接收阴影
    return mesh
},

08 材质

8.1 基础线条材质

一种用于绘制线框样式几何体的材质

let material = new THREE.LineBasicMaterial( {
     
	  color: 0xffffff,
      linewidth: 1, // 控制线宽。默认值为 1
    // 定义线两端的样式。可选值为 'butt', 'round' 和 'square'。默认值为 'round'
      linecap: 'round', 
     // 定义线连接节点的样式。可选值为 'round', 'bevel' 和 'miter'。默认值为 
      linejoin:  'round','round'
      lights: false, // 材质是否受到光照的影响。默认值为 false
} );

8.2 虚线材质

一种用于绘制虚线样式几何体的材质

let material = new THREE.LineDashedMaterial( {
     
    color: 0xffffff, // 材质的颜色(Color),默认值为白色 (0xffffff)
    linewidth: 1, // 控制线宽。默认值为 1
    scale: 1, // 线条中虚线部分的占比。默认值为 1
    dashSize: 3, // 虚线的大小,是指破折号和间隙之和。默认值为 3
    gapSize: 1, // 间隙的大小,默认值为 1
    lights:false, // 材质是否受到光照的影响。默认值为 false。
} )

8.3 基础网格材质

一个以简单着色(平面或线框)方式来绘制几何体的材质。这种材质不受光照的影响

let material = new THREE.MeshBasicMaterial( {
     
    map:texture // 颜色贴图
})

8.4 深度网格材质

一种按深度绘制几何体的材质。深度基于相机远近平面。白色最近,黑色最远。 (近白色,远黑色)

let material = new THREE.MeshDepthMaterial({
      })

8.5 Lambert网格材质

一种非光泽表面的材质,没有镜面高光。 朝着光源点那面变亮

let material = new THREE.MeshLambertMaterial({
     
    map: texture, // 颜色贴图
    envMap: scene.background, // 环境贴图,略反光
})

8.6 法线网格材质

一种把法向量映射到RGB颜色的材质。

// 不同面看到不同颜色
let material = new THREE.MeshNormalMaterial({
      })

// 将几何体渲染为线框。默认值为false(即渲染为平滑着色)
let material = new THREE.MeshNormalMaterial({
      wireframe: true })

8.7 Phong网格材质

一种用于具有镜面高光的光泽表面的材质。 镜面反光

 let material = new THREE.MeshPhongMaterial({
       envMap: scene.background })

8.8 卡通网格材质

卡通着色的扩展

// gradientMap  卡通着色的渐变贴图 
let material = new THREE.MeshToonMaterial({
      gradientMap: texture }) 

09 vue项目

9.1 插件使用

npm i three # 该方法用不了

<template>
  <div>
      <canvas ref='canvas'>canvas>
  div>
template>

<script>
    /*
       import THREE from 'three' 导致报错
		"export 'default' (imported as 'THREE') was not found in 'three'
    */
   //
	import * as THREE from 'three';
    
    
  export default {
      
    data() {
      
      return {
      
        width:0,
        height:0,
      }
    },
    created(){
      
        // 获取屏幕宽高
        this.width = window.innerWidth
        this.height = window.innerHeight
    },
    mouted(){
      
		this.renderer = this.getRenderer()
         this.camera = this.getCamera()
         this.scene = this.getScene()
         this.cube = this.getCube()
         this.scene.add(this.cube)
         this.animation()
    },
    methods(){
      
         // 获取渲染范围
          getRenderer(){
      
            let canvas = this.$refs.canvas
            canvas.width = this.width
            canvas.height = this.height
            const render = new THREE.WebGLRenderer({
       canvas })
            render.setSize(this.width,this.height)
            return render
          },
        // 获取相机
        getCamera(){
      
            const camera = new THREE.PerspectiveCamera(
                75,this.width/this.height,1,1000
            )
            camera.position.z = 10
            camera.lookAt(0,0,0)
            return camera
        },
        // 获取场景
        getScene(){
      
			const scene = new THREE.Scene()
         	 return scene
        },
        // 获取盒子
        getCube(){
      
        	const mesh = new THREE.Mesh(
            	new THREE.BoxGeometry(1,2,3),
                new THREE.MeshNormalMaterial()
            )        
             return mesh
        },
        // 动画
       animation(){
      
           requestAnimationFrame(this.animation)
           this.renderer.render(this.scene,this.camera)
       }  
    }
  }
script>

9.2 导入控制器

import {
      OrbitControls } from 'three/examples/jsm/controls/OrbitControls'

export default {
     
    mouted(){
     
         this.camera = this.getCamera()
        //...
        this.controls = new OrbitControls(this.camera)
    },
    methods(){
     
  	  // 动画
       animation(){
     
           requestAnimationFrame(this.animation)
            this.controls.update()
           this.renderer.render(this.scene,this.camera)
       }  
    }    
}

9.3 随窗口大小变化

export default {
     
    mouted(){
     
        //...
	   this.registrEvent()
    },
    methods(){
     
       // 监听窗口变化
       registrEvent(){
     
           window.onresize = this.resize
       },
       // 窗口变化函数
       resize(){
     
		  // 重新获取屏幕宽高
            this.width = window.innerWidth
            this.height = window.innerHeight
           // 设置相机大小
           this.camera.aspect = this.width/this.height
           // 相机设置应用
           this.camera.updateProjectionMatrix()
           // 设置渲染范围
           this.renderer.setSize(this.width,this.height)
       }
    }    
}

9.4 世界背景

图片路径

-public
	wordBg
    	-nx.png
		-ny.png
		-nz.png
		//...
-src

export default {
     
    mouted(){
     
        //...
	   this.bgTexture()
    },
    methods(){
     
       // 设置世界背景	
       bgTexture(){
     
          const loader = new THREE.CubeTextureLoader()
          // 图片放图assets/images
          const bgTexture = loader.setPath('wordBg/')
              .load([
				 'px.png','nx.png',
            	  'py.png','ny.png',
            	  'pz.png','nz.png'
              ])
          this.scene.background = bgTexture
       }
    }    
}

10 模型加载

10.1 GLTFL加载

// 模型文件
examples/models/gltf // 复制文件放到public/models中的.gltf

import {
      GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'

loaderObj(){
     
    const gltfLoader = new GLTFLoader()
    gltfLoader.load('models/gltf/DamagedHelmet/glTF/DamagedHelment.gltf',res => {
     
        this.scene.add(res.scene) // 加载头盔场景
    })
}

10.2 模型加亮

// 调亮范围
getRenderer(){
     
    let canvas = this.$refs.canvas
    canvas.width = this.width
    canvas.height = this.height
    const render = new THREE.WebGLRenderer({
      canvas })
    render.gammaOutput = true // 调整渲染的颜色范围
    render.setSize(this.width,this.height)
    return render
},

// 添加光线
addLight(){
     
    this.scene.add(new THREE.AmbientLight(0xffffff,0.7))
}

10.3 反光效果

loaderObj(){
     
    const gltfLoader = new GLTFLoader()
    gltfLoader.load('models/gltf/DamagedHelmet/glTF/DamagedHelment.gltf',res => {
     
        res.scene.traverse(mesh => {
     
            // 背景对每个面一一映射
            if(mesh.isMesh){
     
                // mesh.isMesh 检测材质
                mesh.material.side = THREE.DoubleSide
                mesh.material.envMap = this.scene.background
            }
        })
        this.scene.add(res.scene) // 加载头盔场景
    })
}

10.4 MMD加载

// 模型文件
examples/models/mmd // 复制文件放到public/models中.pmd
//.pmd 模型文件

import {
      MMDLoader } from 'three/examples/jsm/loaders/MMDLoader'

loaderObj(){
     
    const gltfLoader = new MMDLoader()
    gltfLoader.load('models/xxx.pmd',mesh => {
     
        mesh.position.set(0,-10,0)  // 调整位置
        this.scene.add(mesh) 
    })
}

10.5 轮廓线效果

import {
      OutlineEffect } from 'three/examples/jsm/loaders/OutlineEffect'

 mounted(){
     
     this.renderer = this.getRenderer()
     //...
     this.renderer = new OutlineEffect(this.renderer) // 轮廓线
     this.animation()
 },

10.6 AMMO物理引擎

让MMD加载动起来,vmd 动作文件

import {
      MMDAnimationHelper } from 'three/examples/jsm/animation/MMDAnimationHelper'
import {
      MMDLoader } from 'three/examples/jsm/loaders/MMDLoader'
// 挂在全局
const AMMO = require('three/examples/js/libs/ammo')
window.Ammo = new AMMO() 

// three挂在全局时间参数
const clock = new THREE.Clock()

loaderObj(){
     
    this.MMDhelper = new MMDAnimationHelper()
    const loader = new MMDLoader()
    loader.loadWithAnimation(
    	 'models/mmd/miku/miku_v2.pmd', // 模型文件
          'models/mmd/vmds/wavefile_v2.vmd', // 动作文件
        mmd => {
     
            this.MMDhelper.add( mmd.mesh,{
     
                animation: mmd.animation,
                physics:true
            } )
            
             // 改变模型位置
            mmd.mesh.position.set(0,-10,0)
            
            this.scene.add(mmd.mesh)
        }
    )
}// 动画
animation(){
     
    requestAnimationFrame(this.animation)
    this.controls.update()
    this.MMDhelper.update(clock.getDelta()) // mmd动画
    this.renderer.render(this.scene,this.camera)
},

10.7 音频加载

loaderObj(){
     
    this.MMDhelper = new MMDAnimationHelper()
    const loader = new MMDLoader()
    loader.loadWithAnimation(
    	 'models/mmd/miku/miku_v2.pmd', // 模型文件
          'models/mmd/vmds/wavefile_v2.vmd', // 动作文件
        mmd => {
     
            this.MMDhelper.add( mmd.mesh,{
     
                animation: mmd.animation,
                physics:true
            } )
            
            // 加载音频
            const audioLoader = new THREE.AudioLoader()
            audioLoader.load('models/mmd/audios/wavefile_short.mp3',music=>{
     
                // 音频管理器
                const listenner = new THREE.AudioListener()
                listenner.position.z = 1
                // 音乐对象
                const audio = new THREE.Audio(listenner).setBuffer(music)
                this.MMDhelper.add(audio) // 音频节奏
            //   this.MMDhelper.add(audio,{delayTime:160/30}) // 音频节奏,演示
                this.scene.add(audio) // 音频添加场景
                this.scene.add(listenner) 
                this.ready = true
            })
            
             // 改变模型位置
            mmd.mesh.position.set(0,-10,0)
            
            this.scene.add(mmd.mesh)
        }
    )
}

10.8 相机位置

loaderObj(){
     
    this.MMDhelper = new MMDAnimationHelper()
    const loader = new MMDLoader()
    loader.loadWithAnimation(
    	 'models/mmd/miku/miku_v2.pmd', // 模型文件
          'models/mmd/vmds/wavefile_v2.vmd', // 动作文件
        mmd => {
     
            this.MMDhelper.add( mmd.mesh,{
     
                animation: mmd.animation,
                physics:true
            } )
            
            // 加载音频
            //...
            
            // 相机位置
            loader.loadAnimation(
            	'models/mmd/vmds/wavefile_camera.vmd',
                this.camera,
                cameraAnimation => {
     
                    this.MMDhelper.add(this.camera,{
     
                        animation:cameraAnimation
                    })
                }
            )
            
             // 改变模型位置
            mmd.mesh.position.set(0,-10,0)
            
            this.scene.add(mmd.mesh)
        }
    )
}

11 镜面案例

11.1 镜面生成器

 import {
      Reflector } from 'three/examples/jsm/objects/Reflector'

export default {
     
     mounted(){
     
      this.scene = this.getScene()
      this.bgTexture() // 导入背景
      this.ball = this.addBall()
      this.mirror = this.getMirror()
      this.scene.add(this.ball,this.mirror)
         //...
    },
    methods:{
     
      // 镜面
      getMirror(){
     
        const geogemetry = new THREE.PlaneGeometry(10,10)
        const mirror = new Reflector(geogemetry)
        // 调整镜子的位置
        mirror.position.set(0,0,-5) 
        mirror.lookAt(-1,0,0)
        return mirror
      },
    }
}

11.2 围墙

mounted(){
     
     this.ball = this.addBall()
      this.boxList = []
      this.mirror = this.addMirror(new THREE.Vector3(0,0,-50))
      this.boxList.push(this.addPlane(new THREE.Vector3(0,0,-50)))
      this.boxList.push(this.addPlane(new THREE.Vector3(0,0,50)))
      this.boxList.push(this.addPlane(new THREE.Vector3(50,0,0)))
      this.boxList.push(this.addPlane(new THREE.Vector3(-50,0,0)))
      this.boxList.push(this.addPlane(new THREE.Vector3(0,50,0)))
      this.boxList.push(this.addPlane(new THREE.Vector3(0,-50,0)))
      this.scene.add(
        this.ball,
        this.mirror,
        ...this.boxList,
      )
},
methods:{
     
  	 // 镜面
      addMirror(position,direction = new THREE.Vector3(0,0,0)){
     
        const geometry = new THREE.PlaneGeometry(100,100)
        const mirror = new Reflector(geometry,{
     
          // color:new THREE.Color(0x7F7F7F),
          textureWidth:this.width,
          textureHeight:this.height,
          // clipBias:1000
        })
        mirror.position.set(position.x,position.y,position.z)
        mirror.lookAt(direction.x,direction.y,direction.z)
        return mirror
      },
      addPlane(position,direction = new THREE.Vector3(0,0,0)){
     
        const geometry = new THREE.PlaneGeometry(100,100)
        const material = new THREE.MeshPhongMaterial({
     
          map:new THREE.TextureLoader().load("models/brick_diffuse.jpg")
        })
        const mesh = new THREE.Mesh(geometry,material)
        mesh.position.set(position.x,position.y,position.z)
        mesh.lookAt(direction.x,direction.y,direction.z)
        mesh.receiveShadow = true // 地板接收阴影
        return mesh
      },
      // 添加小球
      addBall(){
     
        const geometry = new THREE.SphereGeometry(2,32,32)
        const material = new THREE.MeshPhongMaterial({
     
          envMap:this.scene.background
        })
        const mesh = new THREE.Mesh(geometry,material)
        mesh.castShadow = true // 开启阴影
        return mesh
      },
}

12 控住器

12.1 初始化控制器

 import {
      OrbitControls } from 'three/examples/jsm/controls/OrbitControls'

mounted(){
     
     this.controls = new OrbitControls(this.camera) // 控制器
     this.controls.enabled = false // 控制控制器开关
}

12.2 进阶拖拽

import {
      TrackballControls } from 'three/examples/jsm/controls/TrackballControls'

mounted(){
     
    this.controls = new TrackballControls(this.camera) // 控制器
    this.controls.noPan = false // 是否允许平移操作
    this.controls.noRotate = false // 是否允许旋转
    this.controls.noZoom = false // 是否允许缩放
}

12.3 飞行控制器

通过键盘(A、D、W、D)控制

import {
      FlyControls } from 'three/examples/jsm/controls/FlyControls'
const clock = new THREE.Clock()

mounted(){
     
    this.controls = new FlyControls(this.camera) // 控制器
      this.controls.movementSpeed = 10 // 前进速度
      this.controls.rollSpeed = 0.5 // 旋转速度
      this.controls.autoForward = true// 自动旋转
}
methods:{
     
     // 动画
      animation(){
     
        requestAnimationFrame(this.animation)
        this.controls.update(clock.getDelta())
        this.renderer.render(this.scene,this.camera)
      },
}

12.4 第一人称

import {
      FirstPersonControls } from 'three/examples/jsm/controls/FirstPersonControls'
const clock = new THREE.Clock()

mounted(){
     
    this.controls = new FirstPersonControls(this.camera) // 控制器
   this.controls.movementSpeed = 30 // 前进速度
    this.controls.lookSpeed = 0.1 // 旋转速度
}
methods:{
     
     // 动画
      animation(){
     
        requestAnimationFrame(this.animation)
        this.controls.update(clock.getDelta())
        this.renderer.render(this.scene,this.camera)
      },
}

12.5 锁定鼠标控制器

取消鼠标图标,默认清空下加载页面不允许鼠标控制消失

 import {
      PointerLockControls } from 'three/examples/jsm/controls/PointerLockControls's
mouted(){
     
    this.controls = new PointerLockControls(this.camera) // 控制器

    let canvas = this.$refs.canvas

    // 点击canvas交出鼠标控制权
    canvas.addEventListener('click',()=>{
     
        this.controls.lock()
    })

    // 监听开始游戏
    this.controls.addEventListener('lock',()=>{
     
        console.log('游戏开始')
    })

    // 监听暂停游戏
    this.controls.addEventListener('unlock',()=>{
     
        console.log('游戏暂停')
    })
}
methods:{
     
    animation(){
     
        requestAnimationFrame(this.animation)
        this.renderer.render(this.scene,this.camera)
    }
}

键盘方向控制

 data () {
     
     return {
     
         direction:{
     
             w:false,
             s:false,
             a:false,
             d:false,
         }
     }
 },
mounted(){
     
     // 按下键盘 
      window.addEventListener('keydown',(e)=>{
     
        let key = e.key
        console.log(key)
        if(this.direction[key] === false){
     
          this.direction[key] = true
        }
      })

      // 按下键盘
      window.addEventListener('keyup',(e)=>{
     
        let key = e.key
        if(this.direction[key] === true){
     
          this.direction[key] = false
        }
      })
}
methods:{
     
    // 动画
      animation(){
     
        requestAnimationFrame(this.animation)

        const speed = 1

        // 修改镜头
        if(this.direction.w){
     
          this.camera.translateZ(-speed)
        }else if(this.direction.s){
     
          this.camera.translateZ(speed)
        }else if(this.direction.a){
     
          this.camera.translateX(-speed)
        }else if(this.direction.d){
     
          this.camera.translateX(speed)
        }

        this.renderer.render(this.scene,this.camera)
      },
}

13 物体交互

  • 处于相机位置(个人视角)
  • 与看到物体进行交互
    • 瞄准(规定相机拍摄场景中心点瞄准物体,可以和这个物体交互,获取该物体对想,视角锁定)
    • 点击(视角不锁定,鼠标点击物体,选中物体交互)

13.1 方法

// 创建一个从(10,10,10)指向(0,0,0)的方向向量
const raycaster = new THREE.Raycaster(
	new THREE.Vector3(10,10,10),
    new THREE.Vector3(0,0,0),
)

// 单个物体检查,返回贯穿物体的详细信息
let arr1 = new THREE.intersectObject(
	new THREE.Object(),
)

// 一次性贯穿多个物体,返回从近到远的物体的数组
let arr2 = new THREE.intersectObjects(array) // 平时用这个

// 与相机位置以及朝向关联,做到人机交互
const raycaster2 = new THREE.Raycaster()
raycaster2.setFromCamera(
    mouse, // 位置
    camera // 相机
)

// 鼠标位置归一,化为设备坐标
const mouse = {
     x:0,y:0}
window.addEventListener('click', e => {
     
    mouse.x = e.client / window.innerWidth * 2 - 1
    mouse.y = - e.clientY / window.innerHeight * 2 + 1
})

13.2 实例

import {
      OrbitControls } from 'three/examples/jsm/controls/OrbitControls'

export default {
     
    created () {
     
        // 获取屏幕宽高
        this.width = window.innerWidth-170  // 减去左侧导航栏
        this.height = window.innerHeight
    },
    mounted(){
     
        this.addBoxList() // 所有盒子
        this.controls = new OrbitControls(this.camera) // 控制器
        this.addCameraCenter() // 控制中心
    },
    methods(){
     
        // 添加相机中心点
        addCameraCenter () {
     
            this.raycaster = new THREE.Raycaster()

            this.mouse = {
      x:0, y:0 }
            
            // 点击获取交互点
            window.addEventListener('click', e => {
     
                // 减去左侧导航栏170
                this.mouse.x = (e.clientX - 170) / (window.innerWidth - 170) * 2 - 1
                this.mouse.y = - e.clientY / window.innerHeight * 2 + 1
                
                // 与相机朝向关联
                this.raycaster.setFromCamera(
                    this.mouse,
                    this.camera
                )
                // 获取直接所有数组的方法
                let arr = this.raycaster.intersectObjects(this.scene.children)
                if (arr.length > 0) {
     
                    // 选中变色
                    arr[0].object.material.color = new THREE.Color(0xff0000)
                    console.log( arr[0].object.material.color) // 获取到点击的事件
                }
            })
        },
    }
}

14 第一人称重力

14.1 创建随机色地板

// 批量添加盒子---地板
addBoxList () {
     
    let addBOx = []
    for (let i = 0; i < 20; i++) {
     
      for (let j = 0; j < 20; j++) {
     
        let cube = this.getCube()
        cube.position.set(
          i * 10,
          -5,  // 减去盒子自身5高度,即站着高度
          j * 10,
        )
        addBOx.push(cube)
      }
    }
    this.scene.add(...addBOx)
},
  // 获取盒子
getCube (w = 10, h = 10, d = 10) {
     
    let geometry = new THREE.BoxGeometry(w, h, d)
    let meterial = new THREE.MeshBasicMaterial({
     
      color: new THREE.Color(0xffffff * Math.random())
    })
    const mesh = new THREE.Mesh(geometry, meterial)
    return mesh
},

14.2 相机位置站地板中心

// 获取相机
getCamera () {
     
    const camera = new THREE.PerspectiveCamera(
        75, this.width / this.height, 1, 1000
    )
    // w ,d 世界中心
    // 20是高度,站在方盒上
    camera.position.set(100, 20, 100)
    camera.lookAt(0, 0, 0)
    return camera
},

14.3 坠落事件

export default {
     
    data () {
     
        direction: {
     
          w: false,
          s: false,
          a: false,
          d: false,
          " ": false, // 空格状态
        },
        fallOffon:false, // 坠落开关
        // 坠落速度
        fallV:{
     
            y:20, // 初始y轴位置
            vy:0, // y轴上的速度
            g:-0.08 // 自由落体加速度
        },
    },
    methods:{
     
        // 坠落事件
        falling(){
     
            if(this.fallOffon){
     
                this.fallV.vy += this.fallV.g  // 更新y轴上的速度
                this.fallV.y += this.fallV.vy // 根据速度变化改变当前位置
                this.camera.position.y = this.fallV.y // 修改位置
            }else{
     
                this.fallV.vy = 0
            }
        },
        // 动画
      animation () {
     
        this.animate = requestAnimationFrame(this.animation)

        const speed = 1

        // 修改镜头
        if (this.direction.w) {
     
          this.camera.translateZ(-speed)
        } else if (this.direction.s) {
     
          this.camera.translateZ(speed)
        } else if (this.direction.a) {
     
          this.camera.translateX(-speed)
        } else if (this.direction.d) {
     
          this.camera.translateX(speed)
        }else if(this.direction[" "]){
     
          // 跳跃-修改速度位置
          this.fallV.vy = 2
          this.fallOffon = true // 坠落开关
        }

        // 坠落事件
        this.falling()

        this.renderer.render(this.scene, this.camera)
      },
    }
}

14.4 检测地板防掉落

// 检测物体阻止掉落
checkBox(){
     
    let raycaster = new THREE.Raycaster(
        this.camera.position,
        new THREE.Vector3(0,-1,0)
    )
    let arr = raycaster.intersectObjects(this.scene.children)

    // console.log(arr)

    // 穿模处理--防止掉落
    if(
        arr.length>0
        // distance 与地面的距离
        // point 盒子点的位置
        && arr[0].distance < 15
    ){
     
        console.log(arr[0].distance)
        console.log(arr[0].point)
        this.fallOffon = false // 关闭坠落
        this.camera.position.y = arr[0].point.y + 3 // 站在该盒子的y + 3 位置
    }
},
// 动画
animation () {
     
  this.animate = requestAnimationFrame(this.animation)   
   // ....
    
     // 坠落事件
    this.falling()
    
    
    // 检测地板盒子
    this.checkBox()

    this.renderer.render(this.scene, this.camera)
},    

14.5 开始与结束游戏

// 控制器控制中心
controlCenter () {
     
    this.controls = new PointerLockControls(this.camera) // 控制器

    let canvas = this.$refs.canvas

    // 点击canvas交出鼠标控制权
    canvas.addEventListener('click', () => {
     
        this.controls.lock()
    })

    // 监听开始游戏
    this.controls.addEventListener('lock', () => {
     
        console.log('游戏开始')
        // 开始空中坠落
        this.fallOffon = true

        // 动画关闭则启动动画
        if(!this.animate){
     
            this.animation()
        }
    })

    // 监听暂停游戏
    this.controls.addEventListener('unlock', () => {
     
        console.log('游戏暂停')
        // 关闭坠落
        this.fallOffon = false
        // 清除动画
        cancelAnimationFrame(this.animate)
        this.animate = null
    })
},
    

15 射击模块

15.1 获取相机朝向与位置

// 获取当前相机朝向与位置
getDirection () {
     
    const position = this.camera.position

    // 通过raycaster访问相机朝向
    const raycaster = new THREE.Raycaster()
    const coords = {
      x: 0, y: 0 }
    raycaster.setFromCamera(coords, this.camera)

    const direction = raycaster.ray.direction

    return {
     
        direction,
        position,
    }
}

15.2 创建子弹小球

// 创建发射小球
createBall ({
      position, direction }) {
     
    const geometry = new THREE.SphereGeometry(.3, 10, 10)
    const material = new THREE.MeshLambertMaterial({
     
        color: new THREE.Color(0x222222)
    })
    const mesh = new THREE.Mesh(geometry, material)
    let new_position = Object.assign({
     }, position)
    // 设置初始小球值
    mesh.position.set(position.x, position.y, position.z)
    this.scene.add(mesh)

    return {
     
        mesh,
        position:new_position,
        direction,
        dv: 2, // 速度

        // 更新小球运动轨迹
        update: function () {
     
            // 维护position 下一轮的position
            new_position.x += direction.x * this.dv
            new_position.y += direction.y * this.dv
            new_position.z += direction.z * this.dv


            // 更新速度
            this.mesh.position.set(
                new_position.x,
                new_position.y,
                new_position.z,
            )
        },

        // 删除小球
        kill:()=>{
     
            this.scene.remove(mesh)
        }
    }
}

15.3 注册设计事件

点击鼠标创建小球,push到数组

import * as THREE from 'three'
class FireMode {
     
  constructor (camera, scene) {
     
    this.camera = camera
    this.scene = scene

    // 存储子弹小球
    this.ballList = []

    this.register()
  }

  // 注册射击事件
  register () {
     
    // 点击鼠标射击
    window.addEventListener('mousedown', e => {
     
      // 获取相机朝向与位置
      let cameraInfo = this.getDirection()
      let getBall = this.createBall(cameraInfo)
      // 添加小球
      this.ballList.push(getBall)
    })
  }
}

15.4 子弹轨迹与消失弹道

// 更新数据
update () {
     

    // 到达一定距离后删除
    this.ballList.forEach((ball,index)=>{
     
        // 更新速度与朝向
         ball.update()
        
        // 一定距离后删除
        if(!this.checkDistance(ball,this.camera,200)){
     
            ball.kill()
            this.ballList.splice(index,1)
        }

    })
}

// 判断量两者距离
checkDistance(obj1,obj2,distance){
     
    let dx = (obj1.position.x - obj2.position.x)
    let dy = (obj1.position.y - obj2.position.y)
    let dz = (obj1.position.z - obj2.position.z)
    return dx*dx + dy*dy + dz*dz < distance * distance
}

15.5 初始化模块加载

mounted(){
     
    //...
    // 发射事件
      this.fireSystem = new FireMode(this.camera,this.scene)
    //...
},
methods:{
     
   animation(){
     
       this.fireSystem.update()
   }
}

你可能感兴趣的:(前端开发)