教你如何利用threejs對3D模型皮膚進行DIY
- 2020 年 3 月 30 日
- 筆記
一步一步教你如何利用threejs加載gltf模型來實現DIY換膚功能。
模型準備
-
模型製作
模型可以通過網上下載,也可以自己通過c4d、maya、blender等模型製作軟件得到。這裡就不敘述有關模型製作的問題,本文中會在blender進行模型的有關設置。
-
模型導出
1、導出前設定
為了在頁面中方便後續的操作,在導出模型前,將模型的各個部件拆分好進行命名約定(本文以小車模型為例)具體如下圖所示:
圖1
2、導出模型格式選取threejs可以加載的模型有很多中,之前.ojb、.json、.FBX等格式都有講過參我之前的文章從Maya中把模型搬運至網頁的過程、首個threejs-3D項目,所以我這裡選取官方推薦現在使用的格式.gltf、.glb。
gltf與glb的區別: gltf文件類似與json格式而glb是以二進制流進行存儲。
3、模型導出
在blender中直接有gltf格式導出的選項,如果沒有特別的要求,按照默認配置導出就可以了。導出界面如下圖所示:
圖2
場景建立
- 使用threejs建立一個場景
首先將需要的東西從threejs (r110) 中引入,然後進行建立場景四部曲:
import {Scene, WebGLRenderer, PerspectiveCamera, Color} from 'three';
1、Scene
let scene = new Scene(); scene.background = new Color(0xB3CEFB); scene.fog = new Fog(scene.background, 1, 100);
2、Camera
let camera = new PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000); camera.position.z = 15;
3、Render
let renderer = new WebGLRenderer({ alpha: true, antialias: true }); renderer.setPixelRatio(window.devicePixelRatio); renderer.setSize(window.innerWidth, window.innerHeight);
4、Animate
const render = function () { requestAnimationFrame(render); renderer.render(scene, camera); }
然後就會看到一個藍藍的場景(因為設置了背景顏色)
加載模型
- GLTFLoader加載模型至場景
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'; const loader = function () { let gltfLoader = new GLTFLoader(); gltfLoader.load( 'toycar.glb', // your .glb & .gltf gltf => { scene.add(gltf.scene); // 添加至建立好的場景中 // gltf.animations; // Array<THREE.AnimationClip> // gltf.scene; // THREE.Group // gltf.scenes; // Array<THREE.Group> // gltf.cameras; // Array<THREE.Camera> // gltf.asset; // Object }, () => { // .. }, (error) => { console.log(error); } ) }
GLTFLoader加載成功後會返回一個對象,其中scene.children中會包含所導出的所有部件。具體返回以及參數介紹可以查看參考文檔。
添加至場景後可以看到一個漆黑的汽車,如下圖所示:
圖3
這是因為場景中沒有光源的照射所以導致漆黑一片,只能看一個輪廓,所以我么需要在建立的場景中增加燈關。
- 添加燈光
threejs中燈光有很多中,這裡我們添加DirectionalLight(平行光)HemisphereLight(半球光)
import { DirectionalLight, DirectionalLightHelper, HemisphereLight, HemisphereLightHelper } from 'three'; // Helper為燈關的輔助線方便調試 let directionalLight = new DirectionalLight(0xffffff, 0.5); directionalLight.position.set(-4, 8, 4); let dhelper = new DirectionalLightHelper(directionalLight, 5, 0xff0000); let hemisphereLight = new HemisphereLight(0xffffff, 0xffffff, 0.4); hemisphereLight.position.set(0, 8, 0); let hHelper = new HemisphereLightHelper(hemisphereLight, 5); scene.add(directionalLight); scene.add(hemisphereLight);
添加燈光後效果如圖:
圖4
- 陰影渲染
接着我們將設置模型的陰影顯示,陰影顯示需要光源和投射地(陰影顯示的地方),現在我們已經有了光源差投射地,所以我們創建一個地板,讓陰影投射至地板上,來達到想要的效果:
// 製作一個地板 import {PlaneGeometry, MeshPhongMaterial, Mesh} from 'three'; let floorGeometry = new PlaneGeometry(5000, 5000, 1); let floorMaterial = new MeshPhongMaterial({ color: 0x77F28F, shininess: 0, // wireframe: true }); let floor = new Mesh(floorGeometry, floorMaterial); floor.rotation.x = -0.5 * Math.PI; floor.position.y = -2.1; scene.add(floor);
現在我們可以看到一個綠色的地板出現在場景中,但是還是不見車子的陰影,這是因為我們還要對光源、渲染器、模型、地板進行設置才能顯示陰影:
// 首先渲染器開啟陰影 renderer.shadowMap.enabled = true; // 光源開啟陰影 directionalLight.castShadow = true; directionalLight.shadow.mapSize = new Vector2(1024, 1024); // 地板接受陰影開啟 floor.receiveShadow = true; // 模型Mesh開啟陰影 gltf.scene.traverse(obj => { if(obj.isMesh) { obj.castShadow = true; obj.receiveShadow = true; } })
這時候就可以看到小車的陰影渲染到繪製的地板上了,然後你可能會看到車身上有很多條紋狀的黑線,這是因為在渲染陰影中產生了偽影,然後我們可以調節light.shadow.bias來解決
directionalLight.shadow.bias = -0.001; // value 自行調節
陰影渲染對比如下圖所示:
圖5
換膚功能
想要實現各個部件換膚功能,我們需要選中部件,修改選中部件材質來達到我們換膚的功能。
- 部件選中
選取部件其實比較簡單,在場景中加入射線檢測就可以了,實現如下:
import {Vector2, Vector3, Raycaster} from 'three'; let raycaster = new Raycaster(); let mouse = new Vector2(); document.body.addEventListener('click', selectHandler, false); const selectHandler = function (ev) { mouse.x = (ev.clientX / window.innerWidth) * 2 - 1; mouse.y = -(ev.clientY / window.innerHeight) * 2 + 1; raycaster.setFromCamera(mouse, camera); // 這裡我們只檢測模型的選中情況 let intersects = raycaster.intersectObjects(gltf.scene.children, true); if (intersects.length > 0) { let selectedObjects = intersects[0].object; } }
- 設置顏色或者紋理
現在我們得到部件,現在只需要修改材質顏色即可。
核心code如下:
let newMaterial = selectedObjects.material.clone(); newMaterial.color = new Color('#D3C542'); //重新修改顏色 selectedObjects.material = newMaterial;
如果需要用圖片,那麼更改方式更修改圖片類似,先用TextureLoader()
加載紋理圖,然後設置material.map,最後更新material就可以了。
- 添加選中效果
這裡想增加一個選中部件後,那個部件進行外發光的效果,讓用戶覺得自己是選中了這個部件。這個外發光的效果可以自己用ShaderMaterial()
去實現,我這邊用的threejs在postprocessing提供的效果,具體實現如下所示:
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer'; import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass'; import { OutlinePass } from 'three/examples/jsm/postprocessing/OutlinePass'; let composer = new EffectComposer(renderer); let renderPass = new RenderPass(scene, camera); composer.addPass(renderPass); let outlinePass = new OutlinePass(new Vector2(window.innerWidth, window.innerHeight), scene, camera); composer.addPass(outlinePass); outlinePass.visibleEdgeColor.set('#130AF2'); // 選中顏色 outlinePass.edgeStrength = 5; outlinePass.edgeGlow = 1.5; const render = function () { requestAnimationFrame(render); composer.render(); }
功能展示(gif圖的效果可能沒那麼好):
圖6
至此使用threejs給3d模型的基本操作就是這樣的了,剩下的發揮靠自己想像吧。
其它功能效果
-
camera-controls
模型的旋轉控制,這裡直接使用threejs提供的控件
OrbitControls()
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'; let controls = new OrbitControls(camera, renderer.domElement); controls.enablePan = false; controls.maxPolarAngle = Math.PI / 2; controls.minPolarAngle = Math.PI / 3; controls.enableZoom = false; controls.update();
-
部件分離動畫
分離動畫:簡單點解釋就是給各個部件的位置設置一個開始的點,一個結束點,然後利用TweenLite、TweenMax進行補間動畫, 下面以車聲為例子:
// 車身上移動畫 let component = gltf.scene.getObjectByName('car_body'); TweenLite.to(component.position, 1.5, { y: 5, ease: Power4.easeOut });
-
重影動畫
利用threejs的提供的postprocessing來實現部件移動的時候會產生重影,實現如下:
import { AfterimagePass } from 'three/examples/jsm/postprocessing/AfterimagePass.js'; let afterimagePass = new AfterimagePass(); composer.addPass(afterimagePass);
功能展示(gif圖的效果可能沒那麼好):
圖7
最後的最後
如果以上有啥錯誤或者有啥要交流的歡迎騷擾:(吃口飯不容易!)
wechat: flowers1225
gmail: [email protected]
github: https://github.com/flowers1225