天天看點

threejs

官網:https://threejs.org/

我們先了解幾個概念

  • 場景
    • 場景就是一個顯示呈現的舞台
  • 相機
    • 浏覽器端呈現的内容都是相機拍攝
  • 渲染
    • 渲染器決定了内容如何呈現至螢幕
  • 幾何體
threejs
threejs
threejs

一、threejs繪制立方體

threejs
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>繪制立方體</title>
  <script src="./three.min.js"></script>
</head>

<body>
  <script>
    /**
    01 場景 
    02 相機: 分類 位置
    03 渲染器:大小 顔色
    04 幾何體:
    */
    const scene = new THREE.Scene()

	/**
	*fov — 錄影機視錐體垂直視野角度
	aspect — 錄影機視錐體長寬比
	near — 錄影機視錐體近端面
	far — 錄影機視錐體遠端面
	*/
    const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000) 

    const renderer = new THREE.WebGLRenderer({
      antialias: true
    })
    renderer.setClearColor(0xffffff)
    renderer.setSize(window.innerWidth, window.innerHeight)

    document.body.appendChild(renderer.domElement)

    const geometry = new THREE.BoxGeometry(1, 1, 1) // x軸、y軸、z軸
    const material = new THREE.MeshBasicMaterial({ // 材質
      color: 0x285b41,
      wireframe: true // 透明
    })

    const cube = new THREE.Mesh(geometry, material)
    scene.add(cube)
    camera.position.z = 4

    function animate() {

      requestAnimationFrame(animate)
      cube.rotation.y += 0.01
      cube.rotation.x += 0.01
      renderer.render(scene, camera)
    }

    animate()

  </script>
</body>

</html>
           

二、材質與相機控制

threejs

anywhere

啟動html

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>軌迹球控制與材質</title>
  <script src="./three.min.js"></script>
  <script src="./TrackballControls.js"></script>
</head>

<body>
  <script>
    // 定義全局變量
    let scene, camera, geometry, mesh, renderer, controls

    // 初始化渲染器
    function initRenderer() {
      renderer = new THREE.WebGLRenderer({ antialias: true })
      renderer.setSize(window.innerWidth, window.innerHeight)
      renderer.setPixelRatio(window.devicePixelRatio)
      document.body.appendChild(renderer.domElement)
    }

    // 初始化場景
    function initScene() {
      scene = new THREE.Scene()
      const axesHelper = new THREE.AxesHelper(100)
      scene.add(axesHelper)
    }

    // 初始化相機
    function initCamera() {
      camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000)
      camera.position.set(0, 0, 15)
      controls = new THREE.TrackballControls(camera, renderer.domElement)
    }

    // 初始化模型
    function initMesh() {
      geometry = new THREE.BoxGeometry(2, 2, 2)
      // material = new THREE.MeshNormalMaterial()
      const texture = new THREE.TextureLoader().load('img/crate.gif')
      material = new THREE.MeshBasicMaterial({
        map: texture,
        side: THREE.DoubleSide
      })
      mesh = new THREE.Mesh(geometry, material)
      scene.add(mesh)
    }

    // 初始化動畫
    function animate() {
      requestAnimationFrame(animate)
      controls.update()
      renderer.render(scene, camera)
    }

    // 定義初始化方法
    function init() {
      initRenderer()
      initScene()
      initCamera()
      initMesh()
      animate()
    }

    init()

  </script>
</body>

</html>
           

三、光源操作

threejs
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>設定場景光</title>
  <script src="./three.min.js"></script>
  <script src="./TrackballControls.js"></script>
</head>

<body>
  <script>
    // 定義全局變量
    let scene, camera, geometry, mesh, renderer, controls

    // 初始化渲染器
    function initRenderer() {
      renderer = new THREE.WebGLRenderer({ antialias: true })
      renderer.setSize(window.innerWidth, window.innerHeight)
      renderer.setPixelRatio(window.devicePixelRatio)
      document.body.appendChild(renderer.domElement)
    }

    // 初始化場景
    function initScene() {
      scene = new THREE.Scene()
      const axesHelper = new THREE.AxesHelper(100)
      scene.add(axesHelper)

      //添加光源

      // const directionalLight = new THREE.DirectionalLight('red') // 平行光

      // const ambientLight = new THREE.AmbientLight('orange') // 全局環境光

      // const pointLight = new THREE.PointLight('green') // 點光源

      // const spotLight = new THREE.SpotLight('lightblue') // 聚光燈

      const hemisphereLight = new THREE.HemisphereLight('red') // 半球光

      hemisphereLight.position.set(0, 30, 0)
      scene.add(hemisphereLight)

    }

    // 初始化相機
    function initCamera() {
      camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000)
      camera.position.set(0, 0, 15)
      controls = new THREE.TrackballControls(camera, renderer.domElement)
    }

    // 初始化模型
    function initMesh() {
      geometry = new THREE.SphereGeometry(3, 26, 26)
      const texture = new THREE.TextureLoader().load('img/crate.gif')
      material = new THREE.MeshPhongMaterial({
        map: texture,
        side: THREE.DoubleSide
      })
      mesh = new THREE.Mesh(geometry, material)
      scene.add(mesh)
    }

    // 初始化動畫
    function animate() {
      requestAnimationFrame(animate)
      controls.update()
      renderer.render(scene, camera)
    }

    // 定義初始化方法
    function init() {
      initRenderer()
      initScene()
      initCamera()
      initMesh()
      animate()
    }

    init()

  </script>
</body>

</html>
           

四、精靈材質及互動

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>精靈材質與3D互動</title>
  <style>
    * {
      margin: 0;
      padding: 0;
    }

    canvas {
      width: 100%;
      height: 100%;
      display: block;
    }
  </style>
  <script src="./three.min.js"></script>
  <script src="./TrackballControls.js"></script>
</head>

<body>
  <script>
    // 定義全局變量
    let scene, camera, geometry, mesh, renderer, controls

    const raycaster = new THREE.Raycaster()
    const mouse = new THREE.Vector2()

    function onMouseMove(event) {

      // 将滑鼠位置歸一化為裝置坐标。x 和 y 方向的取值範圍是 (-1 to +1)

      mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
      mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;

    }

    window.addEventListener('mousemove', onMouseMove, false)

    window.addEventListener('click', function () {
      // 計算物體和射線的焦點
      const intersects = raycaster.intersectObjects([mesh])
      if (intersects.length > 0) {
        mesh.rotation.x += 01
      }
    }, false)


    // 初始化渲染器
    function initRenderer() {
      renderer = new THREE.WebGLRenderer({ antialias: true })
      renderer.setSize(window.innerWidth, window.innerHeight)
      renderer.setPixelRatio(window.devicePixelRatio)
      document.body.appendChild(renderer.domElement)
    }

    // 初始化場景
    function initScene() {
      scene = new THREE.Scene()
      const axesHelper = new THREE.AxesHelper(100)
      scene.add(axesHelper)
    }

    // 初始化相機
    function initCamera() {
      camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000)
      camera.position.set(0, 0, 15)
      controls = new THREE.TrackballControls(camera, renderer.domElement)
    }

    // 初始化模型
    function initMesh() {

      // const map = new THREE.TextureLoader().load('img/icon.png')
      // const material = new THREE.SpriteMaterial({ map: map, color: 0xffffff })
      // const sprite = new THREE.Sprite(material)
      // scene.add(sprite)

      geometry = new THREE.BoxGeometry(2, 2, 2)
      // material = new THREE.MeshNormalMaterial()
      const texture = new THREE.TextureLoader().load('img/crate.gif')
      material = new THREE.MeshBasicMaterial({
        map: texture,
        side: THREE.DoubleSide
      })
      mesh = new THREE.Mesh(geometry, material)
      scene.add(mesh)
    }

    // 初始化動畫
    function animate() {
      requestAnimationFrame(animate)
      controls.update()
      renderer.render(scene, camera)
    }

    // 定義初始化方法
    function init() {
      initRenderer()
      initScene()
      initCamera()
      initMesh()
      animate()
    }

    init()

  </script>
</body>

</html>
           

五、VR全景拼裝

threejs
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>軌迹球控制與材質</title>
  <style>
    * {
      margin: 0;
      padding: 0;
    }

    canvas {
      display: block;
      height: 100%;
      width: 100%;
    }
  </style>
  <script src="./three.min.js"></script>
  <script src="./TrackballControls.js"></script>
</head>

<body>
  <script>
    // 定義全局變量
    let scene, camera, geometry, mesh, renderer, controls

    // 初始化渲染器
    function initRenderer() {
      renderer = new THREE.WebGLRenderer({ antialias: true })
      renderer.setSize(window.innerWidth, window.innerHeight)
      renderer.setPixelRatio(window.devicePixelRatio)
      document.body.appendChild(renderer.domElement)
    }

    // 初始化場景
    function initScene() {
      scene = new THREE.Scene()
      const axesHelper = new THREE.AxesHelper(100)
      scene.add(axesHelper)
    }

    // 初始化相機
    function initCamera() {
      camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000)
      camera.position.set(0, 0, 15)
      controls = new THREE.TrackballControls(camera, renderer.domElement)
    }

    // 初始化模型
    function initMesh() {

      // 前面
      const geometryF = new THREE.PlaneGeometry(4, 4)
      const materialF = new THREE.MeshBasicMaterial({
        map: new THREE.TextureLoader().load('img/0_f.jpg'),
        side: THREE.DoubleSide
      })
      const meshF = new THREE.Mesh(geometryF, materialF)
      meshF.rotation.y = 180 * Math.PI / 180
      meshF.position.z = 2
      scene.add(meshF)

      // 後面
      const geometryB = new THREE.PlaneGeometry(4, 4)
      const materialB = new THREE.MeshBasicMaterial({
        map: new THREE.TextureLoader().load('img/0_b.jpg'),
        side: THREE.DoubleSide
      })
      const meshB = new THREE.Mesh(geometryB, materialB)
      // meshB.rotation.y = 180 * Math.PI / 180
      meshB.position.z = -2
      scene.add(meshB)

      // 左側 
      const geometryL = new THREE.PlaneGeometry(4, 4)
      const materialL = new THREE.MeshBasicMaterial({
        map: new THREE.TextureLoader().load('img/0_l.jpg'),
        side: THREE.DoubleSide
      })
      const meshL = new THREE.Mesh(geometryL, materialL)
      meshL.rotation.y = (-90) * Math.PI / 180
      meshL.position.x = 2
      scene.add(meshL)

      // 右側 
      const geometryR = new THREE.PlaneGeometry(4, 4)
      const materialR = new THREE.MeshBasicMaterial({
        map: new THREE.TextureLoader().load('img/0_r.jpg'),
        side: THREE.DoubleSide
      })
      const meshR = new THREE.Mesh(geometryR, materialR)
      meshR.rotation.y = (90) * Math.PI / 180
      meshR.position.x = -2
      scene.add(meshR)

      // 上面
      const geometryU = new THREE.PlaneGeometry(4, 4)
      const materialU = new THREE.MeshBasicMaterial({
        map: new THREE.TextureLoader().load('img/0_u.jpg'),
        side: THREE.DoubleSide
      })
      const meshU = new THREE.Mesh(geometryU, materialU)
      meshU.rotation.x = (90) * Math.PI / 180
      meshU.rotation.z = (180) * Math.PI / 180
      meshU.position.y = 2
      scene.add(meshU)

      // 下面
      const geometryD = new THREE.PlaneGeometry(4, 4)
      const materialD = new THREE.MeshBasicMaterial({
        map: new THREE.TextureLoader().load('img/0_d.jpg'),
        side: THREE.DoubleSide
      })
      const meshD = new THREE.Mesh(geometryD, materialD)
      meshD.rotation.x = (-90) * Math.PI / 180
      meshD.rotation.z = (180) * Math.PI / 180
      meshD.position.y = -2
      scene.add(meshD)
    }

    // 初始化動畫
    function animate() {
      requestAnimationFrame(animate)
      controls.update()
      renderer.render(scene, camera)
    }

    // 定義初始化方法
    function init() {
      initRenderer()
      initScene()
      initCamera()
      initMesh()
      animate()
    }

    init()

  </script>
</body>

</html>
           

六、全景看房地标添加

threejs
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>軌迹球控制與材質</title>
  <style>
    * {
      margin: 0;
      padding: 0;
    }

    canvas {
      display: block;
      height: 100%;
      width: 100%;
    }
  </style>
  <script src="./three.min.js"></script>
  <script src="./TrackballControls.js"></script>
</head>

<body>
  <script>
    // 定義全局變量
    let scene, camera, geometry, mesh, renderer, controls
    let sixPlane = []
    let spriteArrow = ""
    let raycaster = new THREE.Raycaster()
    const mouse = new THREE.Vector2()

    function onMouseMove(event) {
      // 将滑鼠位置歸一化為裝置坐标。x 和 y 方向的取值範圍是 (-1 to +1)
      mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
      mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;

    }
    window.addEventListener('mousemove', onMouseMove, false)

    // 滑鼠點選 
    function mouseClickEvent(ev) {
      ev.preventDefault();
      // 射線捕獲
      raycaster.setFromCamera(mouse, camera)

      const intersects = raycaster.intersectObjects([spriteArrow])

      if (intersects.length > 0) {
        changeScene()
      }
    }

    window.addEventListener('click', mouseClickEvent, false)

    // 初始化渲染器
    function initRenderer() {
      renderer = new THREE.WebGLRenderer({ antialias: true })
      renderer.setSize(window.innerWidth, window.innerHeight)
      renderer.setPixelRatio(window.devicePixelRatio)
      document.body.appendChild(renderer.domElement)
    }

    // 初始化場景
    function initScene() {
      scene = new THREE.Scene()
      const axesHelper = new THREE.AxesHelper(100)
      scene.add(axesHelper)
    }

    // 初始化相機
    function initCamera() {
      camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000)
      camera.position.set(0, 0, 15)
      controls = new THREE.TrackballControls(camera, renderer.domElement)
      // 控制視覺位置
      controls.maxDistance = 2  // 最大距離
      controls.minDistance = 0  // 最小距離
    }

    // 初始化模型
    function initMesh() {

      // 利用精靈材質引入地面标記 
      new THREE.TextureLoader().load('img/icon.png', (texture) => {
        const spriteMaterial = new THREE.SpriteMaterial({
          map: texture
        })
        spriteArrow = new THREE.Sprite(spriteMaterial) //會固定位置
        spriteArrow.scale.set(0.1, 0.1, 0.1)
        spriteArrow.position.set(0.5, -1, -1.5)
        scene.add(spriteArrow)
      })

      sixPlane = createPlane(0)
      for (let i = 0; i < 6; i++) {
        scene.add(sixPlane[i])
      }
    }

    // 初始化動畫
    function animate() {
      requestAnimationFrame(animate)
      controls.update()
      renderer.render(scene, camera)
    }

    // 定義初始化方法
    function init() {
      initRenderer()
      initScene()
      initCamera()
      initMesh()
      animate()
    }

    init()

    function createPlane(num) {
      const BoxGeometry = []
      // 前面
      const geometryF = new THREE.PlaneGeometry(4, 4)
      const materialF = new THREE.MeshBasicMaterial({
        map: new THREE.TextureLoader().load('img/' + num + '_f.jpg'),
        side: THREE.DoubleSide
      })
      const meshF = new THREE.Mesh(geometryF, materialF)
      meshF.rotation.y = 180 * Math.PI / 180
      meshF.position.z = 2
      BoxGeometry.push(meshF)

      // 後面
      const geometryB = new THREE.PlaneGeometry(4, 4)
      const materialB = new THREE.MeshBasicMaterial({
        map: new THREE.TextureLoader().load('img/' + num + '_b.jpg'),
        side: THREE.DoubleSide
      })
      const meshB = new THREE.Mesh(geometryB, materialB)
      // meshB.rotation.y = 180 * Math.PI / 180
      meshB.position.z = -2
      BoxGeometry.push(meshB)

      // 左側 
      const geometryL = new THREE.PlaneGeometry(4, 4)
      const materialL = new THREE.MeshBasicMaterial({
        map: new THREE.TextureLoader().load('img/' + num + '_l.jpg'),
        side: THREE.DoubleSide
      })
      const meshL = new THREE.Mesh(geometryL, materialL)
      meshL.rotation.y = (-90) * Math.PI / 180
      meshL.position.x = 2
      BoxGeometry.push(meshL)

      // 右側 
      const geometryR = new THREE.PlaneGeometry(4, 4)
      const materialR = new THREE.MeshBasicMaterial({
        map: new THREE.TextureLoader().load('img/' + num + '_r.jpg'),
        side: THREE.DoubleSide
      })
      const meshR = new THREE.Mesh(geometryR, materialR)
      meshR.rotation.y = (90) * Math.PI / 180
      meshR.position.x = -2
      BoxGeometry.push(meshR)

      // 上面
      const geometryU = new THREE.PlaneGeometry(4, 4)
      const materialU = new THREE.MeshBasicMaterial({
        map: new THREE.TextureLoader().load('img/' + num + '_u.jpg'),
        side: THREE.DoubleSide
      })
      const meshU = new THREE.Mesh(geometryU, materialU)
      meshU.rotation.x = (90) * Math.PI / 180
      meshU.rotation.z = (180) * Math.PI / 180
      meshU.position.y = 2
      BoxGeometry.push(meshU)

      // 下面
      const geometryD = new THREE.PlaneGeometry(4, 4)
      const materialD = new THREE.MeshBasicMaterial({
        map: new THREE.TextureLoader().load('img/' + num + '_d.jpg'),
        side: THREE.DoubleSide
      })
      const meshD = new THREE.Mesh(geometryD, materialD)
      meshD.rotation.x = (-90) * Math.PI / 180
      meshD.rotation.z = (180) * Math.PI / 180
      meshD.position.y = -2
      BoxGeometry.push(meshD)

      return BoxGeometry
    }

    function changeScene() {
      // 建立六個面 
      const sixBox = createPlane(2)
      const timer = setInterval(() => {
        camera.fov -= 1
        camera.updateProjectionMatrix()
        if (camera.fov == 20) {
          clearInterval(timer)
          camera.fov = 45
          camera.updateProjectionMatrix()
          for (let i = 0; i < 6; i++) {
            scene.remove(sixPlane[i])
          }
          sixPlane = sixBox
          for (let i = 0; i < 6; i++) {
            scene.add(sixPlane[i])
          }
          spriteArrow.visible = false
        }
      }, 50)
    }

  </script>
</body>

</html>
           

七、全景看房實作