一、前言
近期有个小程序 +pc 的项目,需要将客户的 VEX-IQ 模型做成 3D 效果
经过一番研究,最终采用 Threejs 实现成功,记录一下...
二、准备工具
2.1 SnapCAD
客户编辑 VEX-IQ 模型所用工具,生成的文件为.ldr
主要用来安装后获取里面的零件库
下载地址:SnapCAD
2.2 Blender 2.80
Blender 用于将.ldr 转换成 Three 目前主流支持的格式为 gftl/glb
下载地址:Blender
2.3 ImportLDRaw
Blender 默认不支持.ldr 导入,ImportLDRaw 为 Blender 识别.ldr 文件插件。
此插件包含两个部分,一个为导入插件,使 Blender 支持 LDRaw 导入。
另一个为零件库,主要负责导入进来的模型颜色和图案显示
操作说明:https://github.com/TobyLobster/ImportLDraw
插件 zip:https://github.com/TobyLobster/ImportLDraw/releases
零件库:https://ldraw.org/parts/latest-parts.html
2.4 ThreeJs
一个非常好用的实现浏览器端 3D 效果的工具,用于渲染 Blender 导出后的模型文件
下载地址:https://threejs.org/
三、安装工具和插件
3.1 安装 SnapCAD 和 Blender
将下载好的安装文件安装,直接下一步到成功,非常简单。
3.2 安装 ImportLDRaw 插件
打开 Blender
默认是不支持导入.ldr 格式文件的
选择菜单 Edit – Preferences 进入设置画面
选择 Add-on -- Install.. 进入添加插件设置画面
选择下载好的 ImportLDRaw 插件 ZIP 包,双击或者点击 Install Add-on … 按钮
勾选 ImportLDRaw,勾选后才会生效
插件验证,选择 File -- Import
已经支持导入.ldr 文件
3.2 设置零件库
若不设置该步骤,.ldr 无法在 Blender 中正常显示
前往 https://ldraw.org/parts/latest-parts.html 下载完整零件库包,下载后随便解压到一个目录下。
解压完成后,将 SnapCAD 下的 p 和 parts 文件夹复制到零件库 ldraw 文件夹下,重复文件选择跳过,该操作用于.ldr 颜色渲染,不执行会导致.ldr 在 Blender 中没有颜色
在 Blender 中选择导入,类型选择 LDRaw,在左下角 Import Options 中 LDRaw path 填入刚刚解压的零件库地址
导入成功后效果如下
四、导出模型且渲染(FBX)
4.1 导出模型
删除灯光 Light 和地平面 LegoGroundPlane(建议)
因为如果灯光位置不好,会导致整个模型太亮而发白,而导入时模型可能会在地平面之下,需要将模型手动移到地平面上面,否则导出的模型会是空白的。所以建议删除这两项(个人操作下来的结果,也可能是我不会用)
右上角选择 Light 右键菜单选择 Delete 删除灯光,LegoGroundPlane 也是同样操作
导出 fbx,选择 File – Export – FBX
threeJS 主要建议使用 gftl 或者 glb 格式,但是本次项目.ldr 文件即使所有零件库都已加载,导出 gftl 或者 glb 还是会纯白色。而 fbx 则是正常显示的。
选择好保存路径和文件名,点击 Export FBX 按钮,等待 Blender 返回到模型展示页面即可
查看导出结果,若是 win10,可以用自带的 3D 查看器查看效果
五、渲染模型
参照 ThreeJS 官方文档 https://threejs.org/docs/index.html#manual/en/introduction/Creating-a-scene
实例代码结构
index.html
<html lang="en"> <head> <title>FBX-3D模型观看</title> <meta charset="utf-8"> </head> <body> <script type="application/javascript"> //定义模型路径 var fbxUrl = 'model/1.fbx' </script> <script src="js/three.min.js?v=1.0.0"></script> <script src="js/inflate.min.js?v=1.0.0"></script> <script src="js/OrbitControls.js?v=1.0.0"></script> <script src="js/FBXLoader.js?v=1.0.0"></script> <script src="js/stats.min.js?v=1.0.0"></script> <script src="js/3dfbx.js?v=1.0.0"></script> </body> </html>
3dfbx.js
//3dfbx.js var container, stats, controls; var camera, scene, renderer, light, bbox; var rotating = true; init(); animate(); function init() { if (!fbxUrl) { return false; } container = document.createElement( 'div' ); //创建div,并加载到html里,这里的document.body可以换成你想让模型加载的地方。 document.body.appendChild( container ); //创建场景 scene = new THREE.Scene(); //3D盒子 bbox = new THREE.Box3(); //场景背景颜色 scene.background = new THREE.Color( 0xeeeeee ); //半球光 light = new THREE.HemisphereLight( 0xbbbbff, 0x444422, 1.5 ); light.position.set( 0, 1, 0 ); scene.add( light ); //fbx加载器 var loader = new THREE.FBXLoader(); loader.load( fbxUrl, function ( obj ) { this.setContent(obj); scene.add( obj ); }, undefined, function ( e ) { console.error( e ); } ); //创建webgl渲染器 renderer = new THREE.WebGLRenderer( { antialias: true } ); renderer.setPixelRatio( window.devicePixelRatio ); renderer.setSize( window.innerWidth, window.innerHeight ); renderer.gammaOutput = true; container.appendChild( renderer.domElement ); window.addEventListener( 'resize', onWindowResize, false ); camera = new THREE.PerspectiveCamera(45,window.innerWidth / window.innerHeight, 0.5,10000); controls = new THREE.OrbitControls(camera); // to disable pan controls.enablePan = false; // to disable zoom controls.enableZoom = false; controls.target.set(0,0,0); controls.update(); } function onWindowResize() { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize( window.innerWidth, window.innerHeight ); } // function animate() { requestAnimationFrame( animate ); if (rotating) { scene.rotation.y += -0.005; } else { scene.rotation.y = scene.rotation.y; } renderer.render( scene, camera ); } function setContent(object) { object.updateMatrixWorld(); const box = new THREE.Box3().setFromObject(object); const size = box.getSize(new THREE.Vector3()).length(); const boxSize = box.getSize(); const center = box.getCenter(new THREE.Vector3()); object.position.x += object.position.x - center.x; object.position.y += object.position.y - center.y; object.position.z += object.position.z - center.z; this.camera.position.copy(center); if (boxSize.x > boxSize.y) { this.camera.position.z = boxSize.x * -2.85 } else { this.camera.position.z = boxSize.y * -2.85 } this.camera.lookAt(0, 0, 0); }
浏览器最终效果:
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于