1. 程式人生 > >WebGL的3D家居創意設計總結

WebGL的3D家居創意設計總結

回顧整個比賽,直到到現在還是覺得做的很水。不過大學期間有機會團隊去開發自己的專案,對於技術或者是溝通的能力還有會有很大的提高。最開始選擇這個命題的時候真的覺得自己玩大了,3D什麼的完全沒有接觸過,畢竟是學Java的,但是真正開始接觸WebGL或者說是three.js的時候,感覺迷上這個技術了,場景、相機、渲染器、光線選擇、碰撞檢測、粒子效果等等。不過這個技術的學習成本確實比較大,首先是就是被普遍吐槽的官方文件,確實寫的不細緻;再者就是英文材料,對於英語不好的童鞋並不友好;最後就是Three.js的坑實在是太多了,既然是坑就慢慢的填。我購買過一本Three.js開發指南,當時對於這個技術的學習也就停留在可以實現出我們設計的需求就夠了(所以不會使用3D建模工具),整個專案是在2017年8月和9月這段時間完成的,9月底是截止日期了,其實很慌臨提交的前一天還有bug。不過好在這個組最後進決賽的人數也不多,競爭壓力比隔壁那個幾個組小太多了,好在最後成績不錯。

最開始在設計專案的時候,按照規定的要求是模仿酷家樂製作一個線上的3D家裝設計平臺,我們將整個業務邏輯拆成兩個主要的模組:第一個是戶型製作模組,就是設計一個線上的建模工具,專門用於製作房屋模型的;另外一個就是3D家裝模組,用來對製作好的戶型進行裝修。基本上核心的業務邏輯都是在前端完成的,前後端分離後臺提供介面用來對資料進行儲存互動,當時我是負責戶型製作模組,不過在後續重構把整個前端都包了。在第一次編碼的時候真的是js掌握的不好,ECMAScript、BOM、DOM就草草的瞭解,所以寫出來的程式碼太醜了~已經不想吐槽了。下面幾張是戶型設計工具的截圖,有些圖示是copy酷家樂的,由於這個模組不能在手機端低解析度的裝置

    

    

上使用,所以就不需要過多的考慮響應式的問題,戶型設計工具主要包括的有繪製牆壁、繪製門窗、自動生成地板,這幾步是主要操作。這幾個功能需要的額外功能很多,接下來做必要的說明:

  • 座標轉換:瀏覽器座標是一個二維座標,而我們想要點選的3D場景是一個三維座標,我們在螢幕上點選一個位置如何對映到3D場景,這裡用到的是光線選擇器,簡單來說就是在相機與我們點選的點構成了一條射線, 射線經過的所有物體都被儲存在一個數組中,陣列第一個元素就是點選的物體,我們通過這種方式去與3D場景中的元素進行互動。這裡再多說兩句,var selected  = intersects[0]獲取的是點選的物件object,這個object還包括這幾個屬性 [ { distance, point, face, faceIndex, object }, … ],distance - 射線的起點到相交點的距離,point - 在世界座標中的相交點座標,face -相交的面,faceIndex - 相交的面的索引,另外現光線選擇器選不到子物件。是因為intersectObject 這個方法引數如下( object, recursive : Boolean, optionalTarget : Array ) 第二個引數為true則檢查後代物件,預設值為false。
event.preventDefault(); 

//以螢幕中心為原點,值的範圍為-1到1.

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

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

raycaster.setFromCamera( mouse, camera ); 

var intersects = raycaster.intersectObjects( scene.children );

var selected = intersects[0];
  • 平面圖繪製:最開始考慮使用canvas,後來偷了個懶three.js的相機種類很多,THREE.OrthographicCamera相機就是一個平面相機,自頂向下看3D場景就是一個平面圖,當需要切換到3D效果時,將相機的型別換為THREE.PerspectiveCamera並調整為合適的位置即可,所以整個繪製區域是一個3D場景,使用正交相機使其看起來是一個平面圖。
  • 新增牆壁:這個功能類似於一個畫板程式,具體的實現就不做說明了,通過renderer.render(scene, camera)去渲染畫面,考慮到效能不需要一直迴圈渲染,每次需要重新整理頁面時呼叫即可。
  • 繪製矯正:滑鼠點選是不精確的,尤其是在點選某一面牆壁時,可以將這個牆壁拆分為兩面牆,但是點選處很難是精確的中心線的位置,這裡需要做一下矯正,通過簡單的計算將座標偏移到正確的位置。另外比較重要的就是讓直線保持水平垂直,單憑肉眼很難做到,這裡需要一些偏移處理,這個模組設計滑鼠移動過程進行檢測,如果偏移距離小於15則認為使用者在畫水平或者垂直線,將對應的座標賦值為確定值。
  • 地板生成:地板是以房間為單位,所以上面圖片中的地板有兩個(整個戶型的大輪廓不會生成對應地板)。地板自動生成演算法有兩個步驟,一是遍歷找出所有的地板迴路,記錄下所有的頂點,第二步是使用THREE.Shape生成地板圖案,並附上指定的材質。並不是使用者每次操作都會進行遍歷,首先會根據連通圖的邊數 - 頂點數 + 1 =閉合區域個數來判定圖中有沒有閉合迴路,如果有則dfs通過回溯的方式找到迴路,每次找到迴路進行兩個判定,一是這個迴路是否包含子迴路,同樣是利用連通圖的邊數 - 頂點數 + 1 =閉合區域個數進行判定,第二個判定是否已經所搜到相同的迴路,將該回路與儲存下來的其他迴路進行比較,只有滿足不包含子迴路而且沒有搜到過這兩個條件才會將回路儲存。生成地板就很簡單了,根據頂點座標使用THREE.Shape的moveTo和lineTo方法可以生成對應的mesh。
  • 3D展示:通過滑鼠自由的拖動視角是利用了three.js自帶的THREE.OrbitControls控制元件使用十分方便,但是一旦開始這個控制元件,即使當我們離開3D展示這個模組THREE.OrbitControls一直會不停的渲染,update開啟控制元件,requestAnimationFrame一直進行渲染。使用window.cancelAnimationFrame(OrbitControlsAnmo)可以結束動畫。
//開啟控制元件
OrbitControls.update();
OrbitControlsAnmo = requestAnimationFrame(render);

//關閉動畫
if (OrbitControlsAnmo)
        window.cancelAnimationFrame(OrbitControlsAnmo);
  • 儲存戶型:這個模組儲存已經設計好的戶型模型,以及一張戶型平面圖。戶型模型通過JSON.stringify(scene.toJSON())方法匯出場景的json格式的模型,提交後臺通過I/O操作寫入檔案儲存到伺服器上,renderer.domElement.toDataURL('image/png')方法獲取使用者繪製的平面圖的,這個方法拿到的是base64位編碼,建議轉為png格式的圖片。