一、前言
近期有个小程序 +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);
}
浏览器最终效果:
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于