three.js 06-06 之 Binary Operations 幾何體
本篇將要介紹的是在 three.js 中如何使用二元操作來自由組合物體。為此,我們需要引入一個 three.js 的擴充套件 ThreeBSP.js 庫,你可以從網上找到這個庫,譬如從 http://download.csdn.net/download/zhulx_sz/10202730 中下載,這個資源裡面提供的這個 ThreeBSP.js 庫,已經針對 three.js r8x 進行了小小的修復,包括一些警告資訊都消除了,建議從這裡下載。當然,你可以從官方下載,但必須自己來編譯,這樣才能引入到你的工程中使用,網址為 https://github.com/skalnik/threebsp。
所謂的二元操作是指,將各種標準的幾何體(如 THREE.BoxGeometry、THREE.SphereGeometry 等)組合在一起,並通過 subtract(相減)、intersect(相交)、union(聯合)等運算操作後創建出一個新的幾何體。下面我們給出這幾種運算操作的大致定義:
操作 | 描述 |
---|---|
intersect(相交) | 此運算操作可以在兩個幾何體的交集上創建出新的幾何體。兩個幾何體相互交疊的部分就是新的幾何體 |
subtract(相減) | 此運算操作可以在第一個幾何體中減去兩個幾何體交疊的部分,剩下的部分就是新的幾何體 |
union(聯合) | 此運算操作可以將兩個幾何體聯合在一起,從而創建出新的幾何體 |
在這個示例中,我們首先添加了三個物體:一個立方體和兩個球體。初始場景裡中間那個 sphere1 球體,所有操作都會在這個物件上進行。它的右邊是 sphere2 球體,左邊是 cube 立方體。讀者可以在 sphere2 及 cube 上指定四種操作中的一種,即:subtract、intersect、union 和 none(無操作)。這些操作都是基於 sphere1 的。<!DOCTYPE html> <html> <head> <title>示例 06.08 - Binary Operations Geometry</title> <script src="../build/three.js"></script> <script src="../build/js/controls/OrbitControls.js"></script> <script src="../build/js/libs/stats.min.js"></script> <script src="../build/js/libs/dat.gui.min.js"></script> <script src="../jquery/jquery-3.2.1.min.js"></script> <script src="../build/js/ThreeBSP.js"></script> <script src="../build/js/libs/spin.js"></script> <style> body { /* 設定 margin 為 0,並且 overflow 為 hidden,來完成頁面樣式 */ margin: 0; overflow: hidden; } /* 統計物件的樣式 */ #Stats-output { position: absolute; left: 0px; top: 0px; } </style> </head> <body> <!-- 用於 WebGL 輸出的 Div --> <div id="webgl-output"></div> <!-- 用於統計 FPS 輸出的 Div --> <div id="stats-output"></div> <!-- 執行 Three.js 示例的 Javascript 程式碼 --> <script type="text/javascript"> var scene; var camera; var render; var webglRender; //var canvasRender; var controls; var stats; var guiParams; var ground; var sphere1; var sphere2; var cube; var result; var spinner; var ambientLight; var spotLight; var axesHelper; //var cameraHelper; $(function() { stats = initStats(); scene = new THREE.Scene(); webglRender = new THREE.WebGLRenderer( {antialias: true, alpha: true} ); // antialias 抗鋸齒 webglRender.setSize(window.innerWidth, window.innerHeight); webglRender.setClearColor(0xeeeeee, 1.0); webglRender.shadowMap.enabled = true; // 允許陰影投射 render = webglRender; camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.01, 2147483647); // 2147483647 camera.position.set(-45.5, 68.2, 90.9); var target = new THREE.Vector3(10, 0 , 0); controls = new THREE.OrbitControls(camera, render.domElement); controls.target = target; camera.lookAt(target); $('#webgl-output')[0].appendChild(render.domElement); window.addEventListener('resize', onWindowResize, false); // 加入一個座標軸:X(橙色)、Y(綠色)、Z(藍色) axesHelper = new THREE.AxesHelper(60); scene.add(axesHelper); ambientLight = new THREE.AmbientLight(0x0c0c0c); scene.add(ambientLight); spotLight = new THREE.SpotLight(0xffffff); spotLight.position.set(0, 260, 230); spotLight.shadow.mapSize.width = 5120; // 必須是 2的冪,預設值為 512 spotLight.shadow.mapSize.height = 5120; // 必須是 2的冪,預設值為 512 spotLight.castShadow = true; scene.add(spotLight); //cameraHelper = new THREE.CameraHelper(spotLight.shadow.camera); //scene.add(cameraHelper); // sphere1 sphere1 = createMesh(new THREE.SphereGeometry(5, 20, 30)); sphere1.position.x = -2; // sphere1 sphere2 = createMesh(new THREE.SphereGeometry(5, 20, 30)); sphere2.position.set(3, 0, 0); // cube cube = createMesh(new THREE.BoxGeometry(5, 5, 5)); cube.position.x = -7; // 加入到場景中 scene.add(sphere1); scene.add(sphere2); scene.add(cube); /** 用來儲存那些需要修改的變數 */ guiParams = new function() { this.rotationSpeed = 0.02; //this.actionSphere = 'none'; //this.actionCube = 'none'; this.showResult = function() { redrawResult(); }; this.rotateResult = false; this.hideWireframes = false; this.sphere1 = { posx: 3, posy: 0, posz: 0, scale: 1 } this.sphere2 = { posx: 3, posy: 0, posz: 0, scale: 1, actionSphere: 'subtract' } this.cube = { posx: 3, posy: 0, posz: 0, scalex: 1, scaley: 1, scalez: 1, actionCube: 'none' } } /** 定義 dat.GUI 物件,並繫結 guiParams 的幾個屬性 */ var gui = new dat.GUI(); var folder = gui.addFolder('sphere1'); //folder.open(); folder.add(guiParams.sphere1, "posx", -15, 15, 0.1).onChange(function(e){ sphere1.position.set(guiParams.sphere1.posx, guiParams.sphere1.posy, guiParams.sphere1.posz); }); folder.add(guiParams.sphere1, "posy", -15, 15, 0.1).onChange(function(e){ sphere1.position.set(guiParams.sphere1.posx, guiParams.sphere1.posy, guiParams.sphere1.posz); }); folder.add(guiParams.sphere1, "posz", -15, 15, 0.1).onChange(function(e){ sphere1.position.set(guiParams.sphere1.posx, guiParams.sphere1.posy, guiParams.sphere1.posz); }); folder.add(guiParams.sphere1, "scale", -15, 15, 0.1).onChange(function(e){ sphere1.scale.set(guiParams.sphere1.scale, guiParams.sphere1.scale, guiParams.sphere1.scale); }); folder = gui.addFolder('sphere2'); folder.open(); folder.add(guiParams.sphere2, "posx", -15, 15, 0.1).onChange(function(e){ sphere2.position.set(guiParams.sphere2.posx, guiParams.sphere2.posy, guiParams.sphere2.posz); }); folder.add(guiParams.sphere2, "posy", -15, 15, 0.1).onChange(function(e){ sphere2.position.set(guiParams.sphere2.posx, guiParams.sphere2.posy, guiParams.sphere2.posz); }); folder.add(guiParams.sphere2, "posz", -15, 15, 0.1).onChange(function(e){ sphere2.position.set(guiParams.sphere2.posx, guiParams.sphere2.posy, guiParams.sphere2.posz); }); folder.add(guiParams.sphere2, "scale", -15, 15, 0.1).onChange(function(e){ sphere2.scale.set(guiParams.sphere2.scale, guiParams.sphere2.scale, guiParams.sphere2.scale); }); folder.add(guiParams.sphere2, "actionSphere", ['subtract', 'intersect', 'union', 'none']); folder = gui.addFolder('cube'); folder.open(); folder.add(guiParams.cube, "posx", -15, 15, 0.1).onChange(function(e){ cube.position.set(guiParams.cube.posx, guiParams.cube.posy, guiParams.cube.posz); }); folder.add(guiParams.cube, "posy", -15, 15, 0.1).onChange(function(e){ cube.position.set(guiParams.cube.posx, guiParams.cube.posy, guiParams.cube.posz); }); folder.add(guiParams.cube, "posz", -15, 15, 0.1).onChange(function(e){ cube.position.set(guiParams.cube.posx, guiParams.cube.posy, guiParams.cube.posz); }); folder.add(guiParams.cube, "scalex", -15, 15, 0.1).onChange(function(e){ cube.scale.set(guiParams.cube.scalex, guiParams.cube.scaley, guiParams.cube.scalez); }); folder.add(guiParams.cube, "scaley", -15, 15, 0.1).onChange(function(e){ cube.scale.set(guiParams.cube.scalex, guiParams.cube.scaley, guiParams.cube.scalez); }); folder.add(guiParams.cube, "scalez", -15, 15, 0.1).onChange(function(e){ cube.scale.set(guiParams.cube.scalex, guiParams.cube.scaley, guiParams.cube.scalez); }); folder.add(guiParams.cube, "actionCube", ['subtract', 'intersect', 'union', 'none']); gui.add(guiParams, "showResult"); gui.add(guiParams, "rotateResult"); gui.add(guiParams, "hideWireframes").onChange(function(e){ if (e) { sphere1.visible = false; sphere2.visible = false; cube.visible = false; } else { sphere1.visible = true; sphere2.visible = true; cube.visible = true; } }); renderScene(); }); /** 渲染場景 */ function renderScene() { stats.update(); rotateMesh(); // 旋轉物體 requestAnimationFrame(renderScene); render.render(scene, camera); } /** 初始化 stats 統計物件 */ function initStats() { stats = new Stats(); stats.setMode(0); // 0 為監測 FPS;1 為監測渲染時間 $('#stats-output').append(stats.domElement); return stats; } /** 當瀏覽器視窗大小變化時觸發 */ function onWindowResize() { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); render.setSize(window.innerWidth, window.innerHeight); } /** 旋轉物體 */ var step = 0; function rotateMesh() { step += guiParams.rotationSpeed; scene.traverse(function(mesh) { if (mesh === result && guiParams.rotateResult) { //mesh.rotation.x = step; mesh.rotation.y = step; //mesh.rotation.z = step; } }); } function createMesh(geom) { var wireFrameMat = new THREE.MeshBasicMaterial({ //transparency: true, opacity: 0.5, wireframe: true, wireframeLinewidth: 0.5 }); return new THREE.Mesh(geom, wireFrameMat); } function redrawResult() { showSpinner(); setTimeout(function() { scene.remove(result); var sphere1BSP = new ThreeBSP(sphere1); var sphere2BSP = new ThreeBSP(sphere2); var cubeBSP = new ThreeBSP(cube); var resultBSP; // 第一步:球體 switch(guiParams.sphere2.actionSphere) { case 'subtract': resultBSP = sphere1BSP.subtract(sphere2BSP); break; case 'intersect': resultBSP = sphere1BSP.intersect(sphere2BSP); break; case 'union': resultBSP = sphere1BSP.union(sphere2BSP); break; case 'none': // 無操作 break; } // 第二步:方塊 if (!resultBSP) resultBSP = sphere1BSP; switch(guiParams.cube.actionCube) { case 'subtract': resultBSP = resultBSP.subtract(cubeBSP); break; case 'intersect': resultBSP = resultBSP.intersect(cubeBSP); break; case 'union': resultBSP = resultBSP.union(cubeBSP); break; case 'none': // noop break; } if (guiParams.sphere2.actionSphere === 'none' && guiParams.cube.actionCube === 'none') { // 無操作 } else { result = resultBSP.toMesh(); result.geometry.computeFaceNormals(); result.geometry.computeVertexNormals(); scene.add(result); } hideSpinner(spinner); }, 100); } /** 顯示等待畫面 */ function showSpinner() { var opts = { lines: 13, // The number of lines to draw length: 20, // The length of each line width: 10, // The line thickness radius: 30, // The radius of the inner circle corners: 1, // Corner roundness (0..1) rotate: 0, // The rotation offset direction: 1, // 1: clockwise, -1: counterclockwise color: '#000', // #rgb or #rrggbb or array of colors speed: 1, // Rounds per second trail: 60, // Afterglow percentage shadow: false, // Whether to render a shadow hwaccel: false, // Whether to use hardware acceleration className: 'spinner', // The CSS class to assign to the spinner zIndex: 2e9, // The z-index (defaults to 2000000000) top: 'auto', // Top position relative to parent in px left: 'auto' // Left position relative to parent in px }; var target = $('#webgl-output')[0]; //var target = document.getElementById('webgl-output'); spinner = new Spinner(opts).spin(target); return spinner; } /** 隱藏等待畫面 */ function hideSpinner(spinner) { spinner.stop(); } </script> </body> </html>
其中最核心的部分是 showResult() 函式,在這個函式中,我們首先將各種待運算操作的網格物件包裝成 ThreeBSP 物件,只有這樣才能進行 subtract、intersect 和 union 操作。隨後我們在操作結果物件 resultBSP 上呼叫 toMesh() 函式,並通過呼叫 computeFaceNormals() 和 computeVertexNormals() 函式以確保所有的法向量可以正確的計算出來。之所以要呼叫這兩個函式,是因為在執行二元操作後,幾何體中頂點和麵的法向量可能會被改變,而 three.js 在著色時會用到面法向量和頂點法向量。所以顯式呼叫這兩個函式可以保證新生成的物件著色光滑、渲染正確,並能加入到場景 scene 中。
特別地,這種 union 操作的方法並不是很好的,我們後面將會給出一種更好的 three.js 的合併方法。因為此處這種方法,你在旋轉時會發現,中心還是在原來 sphere1 那個球上,而不在 union 後的新幾何體的中心。其實另外兩種操作也存在類似的問題。
未完待續···
相關推薦
three.js 06-06 之 Binary Operations 幾何體
本篇將要介紹的是在 three.js 中如何使用二元操作來自由組合物體。為此,我們需要引入一個 three.js 的擴充套件 ThreeBSP.js 庫,你可以從網上找到這個庫,譬如從 http://download.csdn.net/download/zhulx_
three.js 05-02 之 CircleGeometry 幾何體
本篇我們來介紹 CircleGeometry 幾何體,其常用屬性如下表所示: 屬性 描述 radius (半徑) 必選。此屬性指定圓的半徑 segments (分段) 可選。此屬性定義建立圓所用的面數量。最少3個,預設為8個。值越大,圓就越光滑 thetaStart
three.js入門系列之視角和輔助線
假設你已經建立好了three.js的開發環境(我是寫在vue專案中的),那麼接下來,從頭開始演示是如何用three.js來構建3D圖形的。(筆記本寫的程式碼,螢幕小,所以為了能夠整屏看到完整程式碼,就將字型縮小了,如果覺得看不清的,可以另行放大) 一、頁面DOM結構 正如你所見,這就是一
three.js入門系列之旋轉的圓臺、球體、正方體
先來張圖: 一、調整機位和輔助線 由上述程式碼可知,現在的機位是三維座標軸上的點(2,2,2),方框的那一句很重要,有了這一句,你將獲得上帝視角!!! 接下來新增輔助線(立體空間三軸): 這樣就添加了一個軸輔助線,由於我們是站在(2,2,2),所以看到的輔助線是這樣的: 這是一個標
three.js入門系列之光和陰影
初中物理教過我們鏡面反射和漫反射,這是由於物體的材質直接導致的。 在three.js中,由於物體的材料不同,對於光源的反應也是不一樣的,下面就讓我們一探究竟。 一、材料 據Three.js中描述,有兩種材料能對光源有所反應: 就是圖中箭頭標識的兩種材料。 二、檢驗 編輯前例,設定光源位置:
three.js入門系列之光源
首先建立場景來試驗各種光源帶來的不同效果: 一、錐形光源(聚光燈) SpotLight 接下來縮小範圍(π/7): 二、基礎光源(環境光) AmbientLight 上例中沒有新增環境光,使得周圍黑漆漆的,下面就新增環境光: 效果: 三、點光源(照射所有方向) P
three.js入門系列之材質
一、基礎網孔材料 MeshBasicMaterial 圖示(光源是(0,1,0)處的點光源): 二、深度網孔材料 MeshDepthMaterial (由於只是改了材料名,程式碼將不重複貼出) 在這裡,有必要提一下遠景相機的屬性了: 大概就是這麼個意思,下面,我們把上述兩個引
three.js入門系列之粒子系統
其實程式碼很簡單,也很容易懂(我用的是r99版本的three.js,目前網上大多數demo是60或者80的版本,其中的一些api已經廢棄,如下是r99版本支援的寫法): 注:渲染器是WebGl渲染器 如上的程式碼,你將看到如下畫面: 但是這麼多“粒子”都是正方形的啊,哪來的雪花呢,不急
three.js入門系列之匯入拓展類
先來看一下three.js包的目錄結構: 我們使用的時候,可以一次性import所有的功能,也可以按需引入,全依賴three.module.js這個檔案對three.js的功能作了模組化處理; 但是,該模組化處理的功能僅僅是引入了src下面的所有功能類,實際開發中,我們還需要拓展包(examples)
three.js 02-02 之使用幾何與網格物件
上一篇我們對場景 Scene 物件進行了單獨講解。本篇我們將來看看 Three.js 中的幾個標準的幾何體的用法。我們先上一段完整的程式碼,如下所示: <!DOCTYPE html> <html> <head> <
three.js 04-07 之 MeshPhongMaterial 材質
上篇我們已經介紹了 three.js 中高階材質中的 MeshLambertMaterial 材質。本篇將要介紹的是另一種與之對應的高階材質 MeshPhongMaterial 材質。通過它可以建立一種光亮表面的材質效果。這種材質的屬性基本跟 MeshLambert
three.js 09-02 之 選擇物件
我們在討論相機和動畫之前,先來看看物件的選擇實現,儘管這個跟動畫沒有直接的關係,但瞭解這個實現之後將會是一個很有益的補充。 在給出示例程式碼之前,我們先來看一下核心程式碼:function onDocumentMouseDown(event) { event.
three.js 07-04 之 Points 粒子系統
前面都是在介紹 如何通過 THREE.Sprite 來構建粒子系統。本篇我們來介紹一下如何通過 THREE.Points 及 THREE.PointsMaterial 來構建粒子系統。我們先來看一個完整的示例,程式碼如下所示: <!DOCTYPE html&g
three.js 02-04 之網格物件函式及屬性
上一篇中,我們對自定義幾何體的相關知識做了一個簡單介紹,並講解了如何克隆(複製)一個幾何體。本篇我們要介紹的是關於 Mesh 網格物件常用的一些相關函式和屬性。照例,我們先上一個完整的示例,程式碼如下: <!DOCTYPE html> <html
three.js 08-02 之 網格合併
上一篇文中,我們介紹到利用 THREE.Group 物件可以很容易操縱和管理大量的網格。但是當網格物件的數量非常多時,效能就會成為一個瓶頸。實際上在組裡的每個物件還是獨立的,需要對它們分別進行處理和渲染。 在舊版 three.js 中,有個叫 THREE.G
three.js 04-09 之 LineBasicMaterial 材質
關於 three.js 中的材質部分介紹就快講完了。接下來要介紹關於線段幾何的兩種材質:LineBasicMaterial 和 LineDashedMatertial。如下所示: LineBasicMaterial:通過線段基礎材質,可以設定線段的顏色、寬度、斷點
three.js 數學方法之Box3
從今天開始郭先生就會說一下three.js 的一些數學方法了,像Box3、Plane、Vector3、Matrix3、Matrix4當然還有尤拉角和四元數。今天說一說three.js的Box3方法(Box2是Box3的二維版本,可以參考Box3)。線上案例點選部落格原文。 Box3在3D空間中表示一個包圍盒。
three.js 數學方法之Plane
今天郭先生就來繼續說一說three.js數學方法中的plane(平面)。在三維空間中無限延伸的二維平面,平面方程用單位長度的法向量和常數表示。構造器為Plane( normal : Vector3, constant : Float )。第一個引數為平面的法向量,既然是法向量也就預示著這個平面是有方向之分的,
three.js 數學方法之Vector3
今天郭先生來說一說three.js的Vector3,該類表示的是一個三維向量(3D vector)。 一個三維向量表示的是一個有順序的、三個為一組的數字組合(標記為x、y和z),可被用來表示很多事物,它的建構函式為Vector3( x : Float, y : Float, z : Float )x - 向量
three.js 數學方法之Matrix3
今天郭先生來說一說three.js的三維矩陣,這塊知識需要結合線性代數的一些知識,畢業時間有點長,線性代數的知識大部分都還給了老師。於是一起簡單的複習了一下。所有的計算都是使用列優先順序進行的。然而,由於實際的排序在數學上沒有什麼不同, 而且大多數人習慣於以行優先順序考慮矩陣,所以three.js文件以行為主