加载模型
3d模型的文件格式有很多,但threejs
里常用的基本是
- OBJ格式
老牌通用3d模型文件,不包含贴图,材质,动画等信息。
- GLTF格式(图形语言传输格式)
由OpenGL官方维护团队推出的现代3d模型通用格式,可以包含几何体、材质、动画及场景、摄影机等信息,并且文件量还小。有3D模型界的JPEG之称。
原项目中我使用的是OBJ格式,本文里我们使用GLTF格式。利用threejs提供的editor,我们可以将模型的格式进行转换并导出。
通过GLTFLoader,我们可以加载一个.gltf
格式的3d模型文件。需要注意的是,这些Loader都以插件的形式存在,需要引入相应的XXXLoader.js
才能使用
//<script src="js/GLTFLoader.js"></script> //放到之前添加立方体的代码处 const loader = new THREE.GLTFLoader(); loader.load( 'images/model.gltf', function ( gltf ) { scene.add( gltf.scene ); }, function ( xhr ) { //侦听模型加载进度 console.log( ( xhr.loaded / xhr.total * 100 ) + '% loaded' ); }, function ( error ) { //加载出错时的回调 console.log( 'An error happened' ); } );
通过这个代码可以遍历查看模型里的几何体列表
console.log(gltf.scene.children); //可以用for,也可以用traverse api //gltf.scene.children.traverse((child){});
贴图和材质
现在我们来给几何体添加贴图,贴图怎么做是设计师的专业。这里不过多的说,我们只需要知道,这些贴图如何使用即可。
- 普通贴图(
_col
)
material.map,替代颜色
- 法线贴图(
_nor
)
material.normalMap,让细节程度较低的表面生成高细节程度的精确光照方向和反射效果
- 环境光遮蔽贴图(
_occ
)
material.aoMap,用来描绘物体和物体相交或靠近的时候遮挡周围漫反射光线的效果
- 环境反射贴图
material.envMap,用于模拟材质反射周围环境的效果
我们现在先把这些贴图文件统一加载到内存里
var allTexture; function loadAllTexture(cb){ allTexture = {}; var loadIndex = 0; var textures = [ "skymap", "shache_occ", "shache_nor", "shache_col", "neishi_occ", "neishi_nor", "mennei_col", "luntai_nor", "luntai_col", "lungu_occ", "lungu_nor", "lungu_col", "linjian_occ", "linjian_nor", "linjian_col", "floor", "deng_occ", "deng_nor", "deng_col", "cheshen_occ", "cheshen_nor", "chejia_occ", "chejia_nor", "chedengzhao_nor" ]; function loadNextTexture(){ var textureName = textures[loadIndex]; loadTexture("images/textures/"+textureName+".jpg",function(texture){ if(loadIndex<textures.length-1){ allTexture[textureName] = { texture:texture }; loadIndex++; loadNextTexture(); }else{ if(cb)cb(); } }); } loadNextTexture(); } function loadTexture(filepath,cb){ const textureLoader = new THREE.TextureLoader(); textureLoader.load(filepath,cb); }
然后根据名称手动一一对应,比如我们先把车轮毂的贴图给加上
for(var i=0;i<gltf.scene.children[0].children.length;i++){ var modelObj = gltf.scene.children[0].children[i]; if(modelObj.name=="smart_lungu0"||modelObj.name=="smart_lungu1"||modelObj.name=="smart_lungu2"||modelObj.name=="smart_lungu3"){ modelObj.material = new THREE.MeshStandardMaterial(); modelObj.material.map = allTexture["lungu_col"].texture; modelObj.material.normalMap = allTexture["lungu_nor"].texture; modelObj.material.aoMap = allTexture["lungu_occ"].texture; } }
我们继续把车轮的贴图给加上
else if(modelObj.name=="smart_chelun0"||modelObj.name=="smart_chelun1"||modelObj.name=="smart_chelun2"||modelObj.name=="smart_chelun3"){ modelObj.material = new THREE.MeshStandardMaterial(); modelObj.material.map = allTexture["luntai_col"].texture; modelObj.material.normalMap = allTexture["luntai_nor"].texture; }
其余的材质贴图都如此添加上,后续当然还有很多材质的细节是可以去调整的,但这是个细活儿,这里主要重点分享下玻璃的反射和透明
,金属漆的反光
- 透明的玻璃
天窗和前挡风玻璃的透明度以及基底颜色是不同的
else if(child.name=="smart_boli"){ child.material=new THREE.MeshPhongMaterial(); child.material.color = new THREE.Color( 0x333333 ); child.material.transparent=true; child.material.opacity=.2; }else if(child.name=="smart_tianchuang"){ child.material=new THREE.MeshPhongMaterial(); child.material.color = new THREE.Color( 0x000 ); child.material.transparent=true; child.material.opacity=.5; }
仔细看看动图里前挡风和天窗透明度的差异
- 玻璃的反射
想真的去反射真实的环境?你别想多了,用envMap做个假的看起来就很可以了...
child.material.envMap=allTexture["skymap"].texture; //环境反射贴图envMap的映射方式,这里用的是一个叫等量矩形投影的映射方法 child.material.envMap.mapping = THREE.EquirectangularReflectionMapping; //环境反射贴图的强度 child.material.envMapIntensity=1;
仔细看动图里的前挡风玻璃,是不是反射了什么东西?看过《三种前端实现VR全景看房的方案!说不定哪天就用得上!》的小伙伴们,记得这张图么?
- 车身漆面质感
使用
MeshStandardMaterial
材质,通过调节metalness
,roughness
的值来调节金属的质感
child.material = new THREE.MeshStandardMaterial(); child.material.color=new THREE.Color(0x70631B); child.material.metalness = 0.44; child.material.roughness = 0;
信息点
毕竟是个在线展厅,在车身周围得呈现一些信息点,点击后可以弹窗显示更多信息对吧。实现方式同样在VR全景的文章中提到过了,就是
Sprite
+Raycast
//frame只是一个标记,叫什么都行 var poiPosArray=[ {x:-1.47,y:0.87,z:-0.36,frame:1}, {x:-1.46,y:0.49,z:-0.69,frame:2}, {x:1.5,y:.7,z:0,frame:8}, {x:0.33,y:1.79,z:0,frame:3}, {x:0,y:0.23,z:0.96,frame:4}, {x:0.73,y:1.38,z:-0.8,frame:5}, {x:-.1,y:1.17,z:0.88,frame:6}, {x:-1.16,y:0.16,z:0.89,frame:7} ],poiObjects=[]; function setupInfoPoint(){ const pointTexture = new THREE.TextureLoader().load("images/point.png"); var group = new THREE.Group(); var materialC = new THREE.SpriteMaterial( { map: pointTexture, color: 0xffffff, fog: false } ); for ( var a = 0; a < poiPosArray.length; a ++ ) { var x = poiPosArray[a].x; var y = poiPosArray[a].y-.5; var z = poiPosArray[a].z; var sprite = new THREE.Sprite( materialC ); sprite.scale.set( .15, .15, 1 ); sprite.position.set( x, y, z ); sprite.idstr="popup_"+poiPosArray[a].frame; group.add( sprite ); poiObjects.push(sprite); } scene.add( group ); document.body.addEventListener("click",function (event) { event.preventDefault(); var raycaster = new THREE.Raycaster(); var mouse = new THREE.Vector2(); mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1; mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1; raycaster.setFromCamera( mouse, camera ); var intersects = raycaster.intersectObjects( poiObjects ); if(intersects.length>0){ var popIndex=parseInt(intersects[ 0 ].object.idstr.substr(6,1)); console.log(popIndex); } }); }
UI怎么做
既然我们用了threejs
,所以我们就要在threejs
里把UI做出来吗?这么想的话,会把自己累死。要知道在3d场景里做2d的UI可不算是一件容易的事,还要实现UI的一些用户行为(点击,拖动等)的话就更麻烦了...所以我们直接用html
来做UI就好啦~
到这里,这个3D汽车展厅的核心部分你已经学会(fei)了吧!
结语
以上只是对threejs
一个非常粗浅的使用,threejs
能实现的酷炫效果远远不止于此,希望本文能让你开始对Web3D开发产生兴趣,如果觉得本文还不错,请点赞收藏关注吧~