學習Threejs的那些事兒

Threejs---Web端的三維可視化引擎庫

寫在最開始的話

    (關心技術內容的小夥伴這一段就跳過吧,是我自己瞎扯了)終於終於,我開始了threejs的學習。回憶過往,這是我自畢業後再次重新開始學習三維可視化方面的相關內容,並且是依託於web進行學習。畢業已將近一年,也近一年沒有再碰過三維方面的的內容,心中一直對其十分惦念。由於本人在學校裏從事的是計算機視覺中的三維重建方向,因此對於影像,點雲等方面的內容進行了深入研究,編程語言學習的是C++以及Winform,.Net那一套C/S端的開發模式。然而畢業後因各種原因,從事了web端的0基礎學習與開發。現在自己web端的技術水平不能說達到大牛,但是對web端的開發流程與模式都有了較爲深入的瞭解,能夠自己獨立的進行web應用的開發。但我心中對於三維開發一直充滿着熱情。因此,今天在CSDN上開始記錄自己threejs學習的點滴路程,一是爲了記錄自己在學習過程中所遇到的坑,爲同樣對三維感興趣的小夥伴提供些參考,更重要的是是爲了督促自己學習,爲自己提供學習的動力(你們對我的關注就是我最大的動力啦~~,我想看着自己的博客閱讀量一天天增加,心裏肯定會特別高興),避免自己懶惰而拖更,所以我就暫定每週更新一篇吧。
    在本專欄中,我將記錄自己的學習的過程,學習資料主要是從根據未雨綢繆,暮志未晚進行,這裏面涵蓋了大量的threejs案例,我準備挑選自己喜歡的案例進行學習並總結其中遇到的問題,記錄自己感興趣的部分。同時本人在公司從事了較多的vue前端開發,因此我還會記錄vue+threejs的開發過程。
在這裏插入圖片描述

從首頁例子說起

1 搭建threejs環境

    這裏我默認大家都已經對web開發十分了解了,因此只記錄我覺得值得記錄下的內容。如果有小夥伴有哪些地方不理解,或者覺得我講的不太清楚,請及時聯繫我交流解決問題。
    其實threejs的搭建環境很簡單,有了代碼編輯器之後,那就可以立刻去Github上找到其源碼,然後把例子運行起來,跟着例子學習就好了。這裏提供上述網址對應的github地址。源代碼的結構目錄如下所示:
在這裏插入圖片描述
在這裏插入圖片描述
在Build中,以及存在編譯好的三個文件,分別爲three.js,three.min.js以及three.module.js。剛開始我並不理解這三個文件的具體意義以及使用方法,後來經過摸索,發現了規律,總結如下:

  • three.js 該文件主要是應用在一般的html文件中,使用< script >標籤進行引入。經過實現發現,使用本地形式的引入,在旋轉場景時更快;但在vue項目中使用後,旋轉場景時明顯感覺到不如前者順暢。
  • three.min.js 打開該文件,與three.js進行對比,可以發現前者更像後者的壓縮版,可能正因如此,才命名爲“min”吧。回想其他js庫,也都可以發現這樣的命名方式。所以在html中引入時,使用這兩者是都可以的。
  • three.module.js 這個文件顧名思義是模塊化的庫文件,適用於es6的語法。從例子中你可以看到這個文件的使用方法。

可氣的是,後來找到了threejs中文網,上面對源碼master的目錄結構有非常詳細的描述,這就是不好好看說明文檔的下場,切記~~順便把官網上的介紹圖放在這裏。仔細讀下來,就發現學習threejs只要好好研究master源碼和其中的例子就完全ok了。
在這裏插入圖片描述
    接下來進入正題,講述一下我復現上述網站的背景案例。其實代碼作者已經提供了,我所做的主要工作有兩個方面。其一爲使用html,直接復現案例;其二是在vue項目中復現案例。
在這裏插入圖片描述
    對於如何在html中復現,相信大家都會覺得很簡單。我的文件目錄如下:
在這裏插入圖片描述
大家可以看到,我把編譯好的three.js文件直接拿了過來,還有使用npm命令下載的threejs庫中的OrbitControls.js和TrackballControls.js,這兩個文件主要是實現鼠標控制場景的,具體不同之處我還沒有深入研究。其他的文件比如dat庫,其他教程上可能還有stats庫,都是可有可無,並不影響threejs的關鍵功能。然後把例子中的代碼拷貝進去,就ok了。
    對於如何在vue項目中復現案例,由於我查閱了其他教程,描述的並不詳細,因爲我在復現過程中就遇到了許多問題,因此我着重講解我在復現過程中遇到的問題。首先,在前端框架上,爲了方便,我直接使用了vue-element-admin,直接添加一個三維模塊的路由,然後在添加自己的三維模塊組件就ok了。效果如圖所示
在這裏插入圖片描述
在vue中使用threejs,第一步當然是使用npm install threejs進行依賴庫安裝,這一點本文和其他教程大同小異。本文這裏並沒有全局引入threejs,只是在自己新添加的ThreeDiimension組件中引入了,全局引入的方法和其他庫全局引入的方法相同。先爲大家呈上我的ThreeDiimension組件代碼:

<template>
  <div class="app-container">
    <div id="secen-container" ref="secenContainer" />
  </div>
</template>

<script>
import * as THREE from 'three'
import * as dat from 'dat.gui'
// import { TrackballControls } from 'three/examples/jsm/controls/TrackballControls.js'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
// const OrbitControls = require('three-orbit-controls')(THREE)
export default {
  name: 'ThreeDimension',
  data() {
    return {
      container: null,
      render: null,
      camera: null,
      scene: null,
      raycaster: new THREE.Raycaster(),
      mouse: new THREE.Vector2(),
      dat: null,
      stats: null,
      controls: null
    }
  },
  created() {
  },
  mounted() {
    this.initRender()
    this.initScene()
    this.initCamera()
    this.initLight()
    this.initModel()
    // this.initGui()
    this.initControls()
    this.initStats()

    this.animate()
    window.onresize = this.onWindowResize
    window.addEventListener('click', this.onMouseClick, false)
  },
  methods: {
    initRender: function() {
      this.render = new THREE.WebGLRenderer({ antialias: true, alpha: true })
      this.render.setSize(this.$refs.secenContainer.offsetWidth, this.$refs.secenContainer.offsetHeight)

      this.render.shadowMap.enabled = true
      this.render.shadowMap.type = THREE.PCFSoftShadowMap
      this.render.setClearColor(0xffffff)

      this.container = document.getElementById('secen-container')
      this.container.appendChild(this.render.domElement)
    },
    initCamera: function() {
      this.camera = new THREE.PerspectiveCamera(75, this.$refs.secenContainer.offsetWidth / this.$refs.secenContainer.offsetHeight, 0.1, 1000)
      this.camera.position.set(0, 40, 100)
      this.camera.lookAt(new THREE.Vector3(0, 0, 0))
    },
    initScene: function() {
      this.scene = new THREE.Scene()
    },
    initLight: function() {

    },
    initModel: function() {
      var helper = new THREE.AxesHelper(10)
      this.scene.add(helper)

      var s = 25

      var cube = new THREE.CubeGeometry(s, s, s)

      for (var i = 0; i < 3000; i++) {
        var material = new THREE.MeshBasicMaterial({ color: this.randomColor() })

        var mesh = new THREE.Mesh(cube, material)

        mesh.position.x = 800 * (2.0 * Math.random() - 1.0)
        mesh.position.y = 800 * (2.0 * Math.random() - 1.0)
        mesh.position.z = 800 * (2.0 * Math.random() - 1.0)

        mesh.rotation.x = Math.random() * Math.PI
        mesh.rotation.y = Math.random() * Math.PI
        mesh.rotation.z = Math.random() * Math.PI

        mesh.updateMatrix()

        this.scene.add(mesh)
      }
    },
    initGui: function() {
      this.gui = new dat.GUI()
    },
    initStats: function() {
      /* this.stats = new Stats()
      this.container.appendChild(this.stats) */
    },
    initControls: function() {
      this.controls = new TrackballControls(this.camera, this.render.domElement)
      this.controls.enableDamping = true
      this.controls.enableZoom = true
      this.controls.autoRotate = false
      this.controls.minDistance = 50
      this.controls.maxDistance = 200
      this.controls.enablePan = true
    },
    animate: function() {
      this.threeRender()
      // this.stats.update()
      this.controls.update()
      requestAnimationFrame(this.animate)
    },
    randomColor: function() {
      var arrHex = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f']
      var strHex = '#'
      var index = ''
      for (var i = 0; i < 6; i++) {
        index = Math.round(Math.random() * 15)
        strHex += arrHex[index]
      }
      return strHex
    },
    onMouseClick: function(event) {
      this.mouse.x = ((event.clientX - 210) / this.$refs.secenContainer.offsetWidth) * 2 - 1
      this.mouse.y = -((event.clientY - 84) / this.$refs.secenContainer.offsetHeight) * 2 + 1

      // 通過鼠標點的位置和當前相機的矩陣計算出raycaster
      this.raycaster.setFromCamera(this.mouse, this.camera)

      // 獲取raycaster直線和所有模型相交的數組集合
      var intersects = this.raycaster.intersectObjects(this.scene.children)

      // 將所有的相交的模型的顏色設置爲紅色,如果只需要將第一個觸發事件,那就數組的第一個模型改變顏色即可
      if (intersects.length > 0) {
        intersects[0].object.material.color.set(0xff0000)
      }
    },
    onWindowResize: function() {
      this.camera.aspect = this.$refs.secenContainer.offsetWidth / this.$refs.secenContainer.offsetHeight
      this.camera.updateProjectionMatrix()
      this.threeRender()
      this.render.setSize(this.$refs.secenContainer.offsetWidth, this.$refs.secenContainer.offsetHeight)
    },
    threeRender: function() {
      this.render.render(this.scene, this.camera)
    }
  }
}
</script>

<style lang="scss" scoped>
.app-container{
  width: 100%;
  min-height: 100%;
  position: absolute;
  padding: unset;
  #secen-container{
    width: 100%;
    min-height: 100%;
    position: absolute;
  }
}
</style>

這個例子實現了threejs中的基本功能,包括初始化場景,相機,添加模型,以及我最感興趣的鼠標控制場景功能。在vue中,我把所有用到的場景元素放在了data()中,例如render,camera,scene等。大多數教程中都是把canvas的容器鋪滿整個屏幕,但通過我上面放的效果圖,大家可以看到我並沒有把容器鋪滿全屏,這個形式帶來的最大的影響就是選取場景中模型不能使用案例中原始的代碼。

onMouseClick: function(event) {
      this.mouse.x = ((event.clientX - 210) / this.$refs.secenContainer.offsetWidth) * 2 - 1
      this.mouse.y = -((event.clientY - 84) / this.$refs.secenContainer.offsetHeight) * 2 + 1

      // 通過鼠標點的位置和當前相機的矩陣計算出raycaster
      this.raycaster.setFromCamera(this.mouse, this.camera)

      // 獲取raycaster直線和所有模型相交的數組集合
      var intersects = this.raycaster.intersectObjects(this.scene.children)

      // 將所有的相交的模型的顏色設置爲紅色,如果只需要將第一個觸發事件,那就數組的第一個模型改變顏色即可
      if (intersects.length > 0) {
        intersects[0].object.material.color.set(0xff0000)
      }
    },

最主要的問題就是出在event.clientX 與event.clientY ,我在這裏分別減去了210與84個像素,分別是我左側邊欄的寬度與容器上方導航欄與麪包屑的高度。我本來是想動態獲取這兩個值。學習過vue-element-admin的同學應該知道這個問題就是獲取兄弟組件的寬高,但是我不想利用vuex獲取,所以暫時還沒找到方法。有好方法的同學讓我學習學習,不勝感激。現在特別不喜歡用vuex,因爲這個東西每次F5刷新頁面後,store裏的變量都會不存在了,所以這個“全局變量”不好用。當然也有可能是我還沒有學到精髓,哈哈~~
另外,在選用OrbitControls和TrackballControls時,我還遇到了不少小坑。其實這兩個控制器都可以控制場景旋轉等功能。我在查閱資料時,看到許多博客都說使用three-orbit-controls或者three-orbitcontrols這兩個庫。的確,我嘗試過後是可以使用無誤的,但是有強迫症的我並不想引入那麼多的庫,所以查看node_modules裏threejs的源碼,發現有js和jsm兩個文件夾,而且裏面都有OrbitControls.js和TrackballControls.js,所以我分別進行了嘗試。使用js裏的控制器會出現問題,但jsm裏的控制器是可以的,應該是由於我的項目裏使用es6模塊化的語法吧,所以纔要採用threejs的模塊化庫文件。當使用js文件夾裏的時候,會報如下錯誤
在這裏插入圖片描述
所以我在OrbitControls.js的最上方加入了‘import * as THREE from ‘three’’,但又出現了下面的錯誤,
在這裏插入圖片描述
所以,js文件夾裏的控制器在vue裏就用不了,換成jsm裏的控制器問題就解決了!
這篇文章就到這裏了,期待大家的回覆哦!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章