Vue+Three.js+Rhino三維建模初探

最近在研究三維可視化
發(fā)現(xiàn)了一堆奇怪的問題
我好菜啊我怎么這么菜
記錄一下過程和一些坑

Three.js官網(wǎng):https://threejs.org/
學(xué)犀牛中文論壇:http://www.xuexiniu.com/

最近在研究三維可視化期犬,發(fā)現(xiàn)是個(gè)水很深的領(lǐng)域旭蠕。Three.js是一個(gè)基于WebGL的三維可視化庫,功能很強(qiáng)大冰垄,官網(wǎng)上有很多炫酷的例子砸狞。Rhino是一個(gè)類似3Dmax的建模軟件捻勉,為什么不用3Dmax……是因?yàn)镸ac電腦裝不了3Dmax ╮(╯_╰)╭
官網(wǎng)的Documentation中對(duì)Three.js的基礎(chǔ)用法介紹的很詳細(xì),這里就不般教程了刀森,只是稍微記錄下這兩天學(xué)習(xí)(爬坑)的過程踱启。
警告??:本篇不是個(gè)詳細(xì)的Three.js教程,事實(shí)上研底,Vue埠偿,Three.js,Rhino單拎出來都不是很困難榜晦,遇到的大部分問題都是因?yàn)樽髡咝难獊沓币瑫r(shí)用這仨冠蒋。

一. 環(huán)境搭建

Three.js的官網(wǎng)教程是基于html+js的,集成到Vue里直接npm或者yarn裝就行了乾胶。

npm install three

Vue是我們的老朋友了這里就不講相關(guān)知識(shí)啦抖剿,在我們的Vue工程里朽寞,引入Three,并創(chuàng)建一個(gè)簡單的場景斩郎。
Three.js三要素場景Scene脑融、相機(jī)Camera、渲染器Renderer孽拷,場景Scene定義了整體空間吨掌,用于保存、追蹤所渲染物體脓恕;相機(jī)Camera定義了觀察場景的角度膜宋;渲染器Renderer最終在瀏覽器中渲染出畫面。所以我們?cè)赩ue的data里炼幔,先定義好camera, scene, renderer秋茫,初值給null即可。

<template>
  <div class="area">
        <!-- Three.js主體展示 -->
      <div id="container"></div>
  </div>
</template>
<script>
import * as THREE from 'three'

export default {
  name: 'ThreeTest',
  data() {
    return {
      camera: null,
      scene: null,
      renderer: null,
    }
  }
  methods: {
  }
}
</script>

接下來在method里面創(chuàng)建場景乃秀,基本是跟在html里寫沒什么差別肛著。關(guān)于相機(jī),渲染器的基本參數(shù)請(qǐng)參照官網(wǎng)api跺讯。

methods: {
    init() {
        let container = document.getElementById('container');
        
        this.scene = new THREE.Scene();

        this.camera = new THREE.PerspectiveCamera(45, container.clientWidth/container.clientHeight, 0.1, 1000);
        this.camera.position.x = 40;
        this.camera.position.y = 40;
        this.camera.position.z = 40;
        this.camera.lookAt(this.scene.position);

        this.renderer = new THREE.WebGLRenderer();
        this.renderer.setClearColor(0xEEEEEE); //背景色
        this.renderer.setSize(container.clientWidth, container.clientHeight); //場景大小
        this.renderer.shadowMap.enabled = true; //啟用陰影

        console.log(scene);
        console.log(camera);
        console.log(renderer);

        container.appendChild(this.renderer.domElement);
        this.renderer.render(this.scene, this.camera);
    }
  },
  mounted() {
      this.init();
  }

添加完畢后枢贿,雖然屏幕上一片灰啥都沒有,但我們可以在控制臺(tái)輸出場景刀脏、相機(jī)局荚、渲染器參數(shù)查看。自此愈污,證明我們已經(jīng)在Vue里成功創(chuàng)建了一個(gè)three.js的三維場景耀态。

Three.js三要素

如果控制臺(tái)遇到問題
"ReferenceError: THREE is not defined"
請(qǐng)確定導(dǎo)入的時(shí)候是不是寫對(duì)了

import * as THREE from 'three'

二. Three.js基礎(chǔ)元素

Step1: 坐標(biāo)系建立
為了便于查看,先在場景中建立一個(gè)直角坐標(biāo)系暂雹,包括坐標(biāo)軸和平面首装。

        //添加坐標(biāo)軸
        let axes = new THREE.AxesHelper(20);
        this.scene.add(axes);

        //平面
        let planeGeometry = new THREE.PlaneGeometry(60,20)
        let planeMaterial = new THREE.MeshLambertMaterial({color: 0xcccccc});
        let plane = new THREE.Mesh(planeGeometry, planeMaterial);
        plane.receiveShadow = true;
        plane.rotation.x = -0.5* Math.PI;
        this.scene.add(plane);

為了渲染,還需要加個(gè)光源杭跪,不然所有元素都會(huì)是一片黑色仙逻。

        let spotLight = new THREE.SpotLight(0xffffff);
        spotLight.position.set(40, 80, -20);
        spotLight.castShadow = true;
        this.scene.add(spotLight);

在Three.js的坐標(biāo)系里,紅色是x軸涧尿,綠色是y軸桨醋,藍(lán)色是z軸。注意這時(shí)候根據(jù)我們的設(shè)定现斋,相機(jī)位置在(40喜最,40,40)庄蹋,所以看到的是如下效果瞬内∶匝可以適當(dāng)調(diào)整相機(jī)位置感受一下坐標(biāo)變換。

坐標(biāo)系建立

Step2: 添加幾何體
下面給這個(gè)場景加兩個(gè)幾何體虫蝶。加一個(gè)球和一個(gè)方塊章咧。
首先在data里加上cube和sphere用來存儲(chǔ)球和方塊。

data() {
    return {
      ……
      sphere:null,
      cube:null,
    }
  }

然后init方法里定義一下位置

        //方體
        let cubeGeometry = new THREE.BoxGeometry(5, 5, 5);
        let cubeMaterial = new THREE.MeshLambertMaterial({color: 0xffee6b});
        this.cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
        this.cube.castShadow = true;
        this.cube.position.x = -10;
        this.cube.position.y = 10;
        this.scene.add(this.cube);

        //球體
        let sphereGeometry = new THREE.SphereGeometry(2, 20, 20);
        let sphereMaterial = new THREE.MeshLambertMaterial({color: 0x7777ff});
        this.sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
        this.sphere.castShadow = true;
        this.sphere.position.x = 20;
        this.sphere.position.y = 5;
        this.sphere.position.z = 0;
        this.scene.add(this.sphere);

立體幾何學(xué)的好能真,坐標(biāo)設(shè)置無煩惱赁严。
MeshLambertMaterial是Three.js中一種不太亮的純色材質(zhì),Three.js中的材質(zhì)還有很多粉铐,可以去官網(wǎng)文檔的Materials那欄多嘗試幾個(gè)疼约。

立方體和球

推薦試一下MeshNormalMaterial,效果非常炫酷蝙泼。

MeshNormalMaterial

Step3: 添加動(dòng)畫
現(xiàn)在我們想讓這兩個(gè)元素動(dòng)一動(dòng)程剥,所以新建一個(gè)animate方法。(記得先在data里定義好step汤踏,用來作為球移動(dòng)的變量织鲸。)

    animate() {  
        requestAnimationFrame(this.animate);
        //旋轉(zhuǎn)方體
        this.cube.rotation.x += 0.02;
        this.cube.rotation.y += 0.02;

        //彈跳球
        this.step += 0.03;
        this.sphere.position.x = 10 + ( 10 * (Math.cos(this.step)));
        this.sphere.position.y = 2 + ( 10 * Math.abs(Math.sin(this.step)));

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

并且在mounted里把a(bǔ)nimate加上。

  mounted() {
      this.init();
      this.animate();
  }

為了光效更好一些我們可以在init中剛剛定義光源的地方加個(gè)環(huán)境光源溪胶。

        let ambiColor = "#523318";
        let ambientLight = new THREE.AmbientLight(ambiColor);
        this.scene.add(ambientLight);

這樣元素就動(dòng)起來了搂擦,不會(huì)上傳動(dòng)圖請(qǐng)假裝下面是個(gè)動(dòng)圖。

我想起那天夕陽下的彈球哗脖,那是我逝去的……

Step4: 添加紋理貼圖
現(xiàn)在方塊和球的顏色都是我們自己定的rgb顏色瀑踢,但是三維模型一般都是需要貼圖的,(我在給外部Obj貼圖的時(shí)候遇到了個(gè)很大的坑懒熙!這個(gè)第四部分再述丘损。)Three.js中普办,提供TextureLoader可以直接加載外部紋理圖片工扎。
比如我們有一張伊麗莎白癡呆圖。在Vue中衔蹲,需要把它放在public靜態(tài)資源文件夾下肢娘。(Vue3.0之前的工程應(yīng)該是static文件夾。)這里靜態(tài)資源不建議跟代碼放在同一目錄舆驶,因?yàn)閂ue工程會(huì)自動(dòng)變動(dòng)目錄地址橱健,可能會(huì)匹配不到。

不是假發(fā)是伊麗莎白
Vue的靜態(tài)資源文件夾

之后沙廉,用TextureLoader加載圖片拘荡。注意引用靜態(tài)資源直接加個(gè)/就行。

        //加載紋理
        new THREE.TextureLoader().load('/Elizabeth.jpg', texture => {
            this.cube.material = new THREE.MeshLambertMaterial({map: texture});
        });

這時(shí)撬陵,場景中的方塊就貼上了圖珊皿。


我想起那天夕陽下的伊麗莎白……

三. 添加Control組件

Three.js里一般用gui.dat作為輔助的控制組件网缝,Vue添加gui.dat基本上沒遇到什么坑。Html里怎么寫的蟋定,除了把控制變量都放Data里定義粉臊,其他寫法照搬過來就行。

npm install dat.gui
import dat from 'dat.gui'

之后使用方式基本跟在html中一樣了驶兜,這里就不逐行介紹了扼仲。比如希望控制立方體旋轉(zhuǎn)速度和小球彈跳速度,Data里先定義好兩個(gè)速度變量抄淑。

  data() {
    return {
      ……
      step:0,
      controls: {
        rotationSpeed: 0.02,
        bouncingSpeed: 0.03
      }
    }
  },

method里增加方法初始化gui屠凶,并在init中調(diào)用一下。

    initgui() {
        const gui = new dat.GUI(); // gui監(jiān)測器
        gui.add(this.controls, 'rotationSpeed', 0, 0.2);
        gui.add(this.controls, 'bouncingSpeed', 0, 0.2);
        this.init();
    },

之后改一下animate蝇狼,將動(dòng)畫綁定到controls中阅畴。

        let { controls } = this
        requestAnimationFrame(this.animate);
        //旋轉(zhuǎn)方體
        this.cube.rotation.x += controls.rotationSpeed;
        this.cube.rotation.y += controls.rotationSpeed;

        //彈跳球
        this.step += controls.bouncingSpeed;
        this.sphere.position.x = 10 + ( 10 * (Math.cos(this.step)));
        this.sphere.position.y = 2 + ( 10 * Math.abs(Math.sin(this.step)));

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

這樣,就能通過控制組件改變動(dòng)畫速度迅耘。


控制組件

四. 引入外部三維模型

現(xiàn)在贱枣,終于要輪到我們Rhino出場了ヾ(≧▽≦*)o。這一步非常不順利會(huì)有很多坑颤专,我用加粗注明了纽哥。
我們首先在Rhino中建個(gè)模型。怎么建模的就不講了栖秕,總之我們有了一條草魚(草紋理的魚春塌,簡稱草魚)。

草魚建模

然后簇捍,把這個(gè)模型導(dǎo)出成obj文件只壳。會(huì)看到包括一個(gè)obj,一個(gè)mtl和一個(gè)png文件暑塑。obj是模型的基本輪廓吼句,沒有顏色。mtl是相關(guān)聯(lián)的色彩配置文件事格。png就是mtl里用到的紋理圖片惕艳。把這三個(gè)文件都放到public文件夾下。

草魚模型文件

這里的第一個(gè)坑驹愚,是如果不放到public里面远搪,可能會(huì)報(bào)以下錯(cuò)誤,因?yàn)関ue會(huì)編譯時(shí)會(huì)更改資源路徑逢捺。
Uncaught Error: THREE.OBJLoader: Unexpected line: “”

Three.js可以加載多種3D模型谁鳍,需要單獨(dú)引入js文件。但放到Vue里,畢竟不是引個(gè)js就能搞定的事倘潜,首先要保證npm上有這些加載模型的工具包余佛。好在常見的Obj,Stl加載工具都是有的窍荧。

npm install three-obj-mtl-loader

裝完后引入辉巡。

import { MTLLoader, OBJLoader } from 'three-obj-mtl-loader'

我們?nèi)サ魟倓偟钠聊唬⒎襟w和彈球蕊退,或者新建另一個(gè)組件郊楣,加載這條魚。
首先data里定義一個(gè)數(shù)據(jù)存放魚瓤荔。

data() {
    return {
      camera: null,
      scene: null,
      renderer: null,
      fish: null,
    }
  }

然后method里用MTLLoader和OBJLoader加載魚净蚤。

init() {
        ……
        //加載模型
        let mtlLoader = new MTLLoader();
        let objLoader = new OBJLoader();
        mtlLoader.load('/fish.mtl', (materials) => {
          materials.preload();
          objLoader.setMaterials(materials);
          objLoader.load('/fish.obj', (object) => {
            object.scale.set(1, 1, 1);
            this.fish = object;
            this.scene.add(object);
          })
        })
        ……
    },
    animate() {  
      requestAnimationFrame(this.animate);
      if(this.fish){
        this.fish.rotation.y += 0.006;
      }
      this.renderer.render(this.scene, this.camera);
    }

這里的第二個(gè)坑是如果模型沒有顯示,并且控制臺(tái)也不報(bào)錯(cuò)输硝,請(qǐng)先在控制臺(tái)輸出scene今瀑,確認(rèn)模型是不是被加載上了。一般來講都是因?yàn)橄鄼C(jī)視角不對(duì)点把,把相機(jī)位置和模型的縮放比例調(diào)一調(diào)橘荠,理論上就可以看到。
object.scale.set(1, 1, 1); 對(duì)于有些模型郎逃,可能要放大到100倍才能看到哥童,也有些模型可能要縮小到0.1.

調(diào)整完畢后,OK褒翰,有一條魚在動(dòng)了贮懈!

魚模型

但是為什么是條黑魚不是草魚(⊙?⊙)?紋理沒有加載上优训,整個(gè)模型呈黑色朵你。

這是第三個(gè)坑,經(jīng)測試揣非,如果用的是html+js抡医,其實(shí)是沒有任何問題的可以看到草魚,但是在Vue里妆兑,大概又是路徑的配置問題魂拦,其實(shí)mtl并沒有獲取到對(duì)應(yīng)的png毛仪。需要我們打開mtl文件做一些更改搁嗓。

mtl文件詳情

可以看到最后一行,是匹配到Grass.png中的箱靴,我們將它改成

map_Kd /Grass.png

對(duì)腺逛,就加上一個(gè)/路徑(°ー°〃)。再回到瀏覽器里衡怀,發(fā)現(xiàn)紋理已經(jīng)加上了棍矛。

草魚

當(dāng)然安疗,很多情況下,加載模型的時(shí)候會(huì)出現(xiàn)各種紋理加載不上的狀況够委,所以這里介紹另一種曲線救國的萬能解決方法荐类。
就是只加載obj,然后手動(dòng)用前文所說的TextureLoader自己加載紋理茁帽。

        //曲線救國加載紋理
        new OBJLoader().load('/fish.obj' , obj => {
          let texture = new THREE.TextureLoader().load('/Grass.png');
          let material = new THREE.MeshLambertMaterial( { map: texture } );
          obj.children.forEach(child => {
              child.material = material;
          });
          this.fish = obj;
          this.scene.add(obj);
        })

效果也是一樣的玉罐!改個(gè)背景色,于是:

水中草魚

五. 總結(jié)

Three.js很強(qiáng)大潘拨,三維可視化還有很大的發(fā)展空間吊输,篇幅所限,以后再對(duì)材質(zhì)铁追,紋理季蚂,動(dòng)畫做更深入研究。本文中記錄的坑希望大家不要遇到琅束。尤其是mtl找不到png這個(gè)坑 →_→
就到這里吧扭屁,差不多是只咸魚了<。)#)))≦

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末涩禀,一起剝皮案震驚了整個(gè)濱河市疯搅,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌埋泵,老刑警劉巖幔欧,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異丽声,居然都是意外死亡礁蔗,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門雁社,熙熙樓的掌柜王于貴愁眉苦臉地迎上來浴井,“玉大人,你說我怎么就攤上這事霉撵』钦悖” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵徒坡,是天一觀的道長撕氧。 經(jīng)常有香客問我,道長喇完,這世上最難降的妖魔是什么伦泥? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上不脯,老公的妹妹穿的比我還像新娘府怯。我一直安慰自己,他們只是感情好防楷,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布牺丙。 她就那樣靜靜地躺著,像睡著了一般复局。 火紅的嫁衣襯著肌膚如雪赘被。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天肖揣,我揣著相機(jī)與錄音民假,去河邊找鬼。 笑死龙优,一個(gè)胖子當(dāng)著我的面吹牛羊异,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播彤断,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼野舶,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了宰衙?” 一聲冷哼從身側(cè)響起平道,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎供炼,沒想到半個(gè)月后一屋,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡袋哼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年冀墨,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片涛贯。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡诽嘉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出弟翘,到底是詐尸還是另有隱情虫腋,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布稀余,位于F島的核電站悦冀,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏滚躯。R本人自食惡果不足惜雏门,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望掸掏。 院中可真熱鬧茁影,春花似錦、人聲如沸丧凤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽愿待。三九已至浩螺,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間仍侥,已是汗流浹背要出。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留农渊,地道東北人患蹂。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像砸紊,于是被迫代替她去往敵國和親传于。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容