教你如何利用threejs對(duì)3D模型皮膚進(jìn)行DIY
發(fā)布日期:2022/11/18 10:12:07 瀏覽量:
一步一步教你如何利用threejs加載gltf模型來(lái)實(shí)現(xiàn)DIY換膚功能。
模型準(zhǔn)備
-
模型制作
模型可以通過(guò)網(wǎng)上下載,也可以自己通過(guò)c4d、maya、blender等模型制作軟件得到。這里就不敘述有關(guān)模型制作的問(wèn)題,本文中會(huì)在blender進(jìn)行模型的有關(guān)設(shè)置。
-
模型導(dǎo)出
1、導(dǎo)出前設(shè)定
為了在頁(yè)面中方便后續(xù)的操作,在導(dǎo)出模型前,將模型的各個(gè)部件拆分好進(jìn)行命名約定(本文以小車(chē)模型為例)具體如下圖所示:

圖12、導(dǎo)出模型格式選取threejs可以加載的模型有很多中,之前.ojb、.json、.FBX等格式都有講過(guò)參我之前的文章從Maya中把模型搬運(yùn)至網(wǎng)頁(yè)的過(guò)程、首個(gè)threejs-3D項(xiàng)目,所以我這里選取官方推薦現(xiàn)在使用的格式.gltf、.glb。
gltf與glb的區(qū)別: gltf文件類(lèi)似與json格式而glb是以二進(jìn)制流進(jìn)行存儲(chǔ)。
3、模型導(dǎo)出
在blender中直接有g(shù)ltf格式導(dǎo)出的選項(xiàng),如果沒(méi)有特別的要求,按照默認(rèn)配置導(dǎo)出就可以了。導(dǎo)出界面如下圖所示:

圖2
場(chǎng)景建立
- 使用threejs建立一個(gè)場(chǎng)景
首先將需要的東西從threejs (r110) 中引入,然后進(jìn)行建立場(chǎng)景四部曲:
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);
}
然后就會(huì)看到一個(gè)藍(lán)藍(lán)的場(chǎng)景(因?yàn)樵O(shè)置了背景顏色)
加載模型
- GLTFLoader加載模型至場(chǎng)景
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); // 添加至建立好的場(chǎng)景中
// gltf.animations; // Array
// gltf.scene; // THREE.Group
// gltf.scenes; // Array
// gltf.cameras; // Array
// gltf.asset; // Object
},
() => {
// ..
},
(error) => {
console.log(error);
}
)
}
GLTFLoader加載成功后會(huì)返回一個(gè)對(duì)象,其中scene.children中會(huì)包含所導(dǎo)出的所有部件。具體返回以及參數(shù)介紹可以查看參考文檔。
添加至場(chǎng)景后可以看到一個(gè)漆黑的汽車(chē),如下圖所示:
這是因?yàn)閳?chǎng)景中沒(méi)有光源的照射所以導(dǎo)致漆黑一片,只能看一個(gè)輪廓,所以我么需要在建立的場(chǎng)景中增加燈關(guān)。
- 添加燈光
threejs中燈光有很多中,這里我們添加DirectionalLight(平行光)HemisphereLight(半球光)
import {
DirectionalLight,
DirectionalLightHelper,
HemisphereLight,
HemisphereLightHelper
} from ’three’;
// Helper為燈關(guān)的輔助線方便調(diào)試
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);
添加燈光后效果如圖:
- 陰影渲染
接著我們將設(shè)置模型的陰影顯示,陰影顯示需要光源和投射地(陰影顯示的地方),現(xiàn)在我們已經(jīng)有了光源差投射地,所以我們創(chuàng)建一個(gè)地板,讓陰影投射至地板上,來(lái)達(dá)到想要的效果:
// 制作一個(gè)地板
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);
現(xiàn)在我們可以看到一個(gè)綠色的地板出現(xiàn)在場(chǎng)景中,但是還是不見(jiàn)車(chē)子的陰影,這是因?yàn)槲覀冞€要對(duì)光源、渲染器、模型、地板進(jìn)行設(shè)置才能顯示陰影:
// 首先渲染器開(kāi)啟陰影
renderer.shadowMap.enabled = true;
// 光源開(kāi)啟陰影
directionalLight.castShadow = true;
directionalLight.shadow.mapSize = new Vector2(1024, 1024);
// 地板接受陰影開(kāi)啟
floor.receiveShadow = true;
// 模型Mesh開(kāi)啟陰影
gltf.scene.traverse(obj => {
if(obj.isMesh) {
obj.castShadow = true;
obj.receiveShadow = true;
}
})
這時(shí)候就可以看到小車(chē)的陰影渲染到繪制的地板上了,然后你可能會(huì)看到車(chē)身上有很多條紋狀的黑線,這是因?yàn)樵阡秩娟幱爸挟a(chǎn)生了偽影,然后我們可以調(diào)節(jié)light.shadow.bias來(lái)解決
directionalLight.shadow.bias = -0.001; // value 自行調(diào)節(jié)
陰影渲染對(duì)比如下圖所示:
換膚功能
想要實(shí)現(xiàn)各個(gè)部件換膚功能,我們需要選中部件,修改選中部件材質(zhì)來(lái)達(dá)到我們換膚的功能。
- 部件選中
選取部件其實(shí)比較簡(jiǎn)單,在場(chǎng)景中加入射線檢測(cè)就可以了,實(shí)現(xiàn)如下:
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);
// 這里我們只檢測(cè)模型的選中情況
let intersects = raycaster.intersectObjects(gltf.scene.children, true);
if (intersects.length > 0) {
let selectedObjects = intersects[0].object;
}
}
- 設(shè)置顏色或者紋理
現(xiàn)在我們得到部件,現(xiàn)在只需要修改材質(zhì)顏色即可。
核心code如下:
let newMaterial = selectedObjects.material.clone(); newMaterial.color = new Color(’#D3C542’); //重新修改顏色 selectedObjects.material = newMaterial;
如果需要用圖片,那么更改方式更修改圖片類(lèi)似,先用TextureLoader()加載紋理圖,然后設(shè)置material.map,最后更新material就可以了。
- 添加選中效果
這里想增加一個(gè)選中部件后,那個(gè)部件進(jìn)行外發(fā)光的效果,讓用戶(hù)覺(jué)得自己是選中了這個(gè)部件。這個(gè)外發(fā)光的效果可以自己用ShaderMaterial()去實(shí)現(xiàn),我這邊用的threejs在postprocessing提供的效果,具體實(shí)現(xiàn)如下所示:
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圖的效果可能沒(méi)那么好):
圖6
至此使用threejs給3d模型的基本操作就是這樣的了,剩下的發(fā)揮靠自己想象吧。
其它功能效果
-
camera-controls
模型的旋轉(zhuǎn)控制,這里直接使用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(); -
部件分離動(dòng)畫(huà)
分離動(dòng)畫(huà):簡(jiǎn)單點(diǎn)解釋就是給各個(gè)部件的位置設(shè)置一個(gè)開(kāi)始的點(diǎn),一個(gè)結(jié)束點(diǎn),然后利用TweenLite、TweenMax進(jìn)行補(bǔ)間動(dòng)畫(huà), 下面以車(chē)聲為例子:
// 車(chē)身上移動(dòng)畫(huà) let component = gltf.scene.getObjectByName(’car_body’); TweenLite.to(component.position, 1.5, { y: 5, ease: Power4.easeOut }); -
重影動(dòng)畫(huà)
利用threejs的提供的postprocessing來(lái)實(shí)現(xiàn)部件移動(dòng)的時(shí)候會(huì)產(chǎn)生重影,實(shí)現(xiàn)如下:
import { AfterimagePass } from ’three/examples/jsm/postprocessing/AfterimagePass.js’; let afterimagePass = new AfterimagePass(); composer.addPass(afterimagePass);功能展示(gif圖的效果可能沒(méi)那么好):
圖7
最后的最后
如果以上有啥錯(cuò)誤或者有啥要交流的歡迎騷擾:(吃口飯不容易?。?
wechat: flowers1225
gmail: [email protected]
github: https://github.com/flowers1225
個(gè)人網(wǎng)站:https://flowers1225.com/
馬上咨詢(xún): 如果您有業(yè)務(wù)方面的問(wèn)題或者需求,歡迎您咨詢(xún)!我們帶來(lái)的不僅僅是技術(shù),還有行業(yè)經(jīng)驗(yàn)積累。
QQ: 39764417/308460098 Phone: 13 9800 1 9844 / 135 6887 9550 聯(lián)系人:石先生/雷先生