最近因为项目需求,需要构建一个3D仓库,能够显示整个仓库的布局以及库内货物的情况。于是刚入职两年的小白我毅然决然接下了这个沉重的任务(手动狗头),但是我并没有任何关于3D开发的经验,于是乎就各种翻书查百度,经过几个月孤独的开发与实践,已经能够初步的完成预期的设想了。
先来给大家看一下仓库的整体面貌
2019.11.26 更新:我最近建立了个人网站,大家可以访问下面的链接查看演示
3D仓库演示
2019.11.28 更新:代码和图片资源等已上传至GitHub
https://github.com/xiao149/ThreeJsDemo
再来一些细节的,鼠标单击一个物体,可以在物体周围高亮一圈白光突出显示,可以显示一个标签做描述
每一个库位都是独立可选中的
更有趣的是双击画面上的箱子(也就是货物啦),可以弹出详细的货物信息,比如下图托盘上就叠了好几层箱子,选中某一个箱子还可以显示货物的详细详细,比如数量批号之类的
当然我还很闲地给门做了个动画,可以实现开门和关门的动作,kono下图哒(虽然只是张静态的)
终于到了最关键的时候呢,既然展示完了,就当然地要给大家如何完成这一切的步骤啦。这是从零开始构建3D仓库的第一篇,所以我想从一个从未接触过ThreeJs的小白的角度来开始介绍。
ThreeJs是一个面向网页端的3D设计框架,借用这个Js你可以将精致的3D建模、特效、交互、物理系统统统搬到网页上,理论上,你可以在浏览器上开发一整个3D游戏,当然我只是个初学者,向来也只是自学的,能够教给大家的很有限,这是ThreeJs官网的地址,点此进入,上面有很多有用的示例和开发文档,不过英语水平可能得好点,国内也有中文的网站不过更新的比较慢我这就不给了,大家可以自行百度。
无论怎样的高楼大厦都是从一砖一瓦一步步搭建的,我们的3D仓库也一样,虽然这是最简单的功能(如下图),但却包含了最基本的要素:场景,相机,光照,物体,渲染器,控制器。接下来我将一一为大家介绍。
我们在本地开发的时候经常遇到跨域的问题,即在html中无法读取到本地存放的图片等等,下图展示了跨域问题出现的时候
可见门,窗户,地板等有贴图的地方全部变成了空白,F12调试模式下可以看到跨域错误的具体报错信息,解决方法很简单,就是在谷歌浏览器快捷方式下右键属性,在快捷方式的目标下,加入 --allow-file-access-from-files,注意最前面有一个空格,如下图所示
完成后重新打开浏览器,就可以看到跨域问题已经解决啦,门窗地板全部显示出来啦!
这部分代码比较简单,而且不同的项目基本都类似,所以我就直接放出代码了,不再做过多介绍。
完成之后能够显示背景为蓝色的一个场景,当然里面什么都没有,因为我们还没有添加物体。
var stats = initStats();
var scene, camera, renderer, light;
// 初始化场景
function initScene() {
scene = new THREE.Scene();
}
// 初始化相机
function initCamera() {
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 10000);
camera.position.set(0, 800, 1500);
camera.lookAt(new THREE.Vector3(0, 0, 0));
}
// 初始化灯光
function initLight() {
var directionalLight = new THREE.DirectionalLight( 0xffffff, 0.3 );//模拟远处类似太阳的光源
directionalLight.color.setHSL( 0.1, 1, 0.95 );
directionalLight.position.set( 0, 200, 0).normalize();
scene.add( directionalLight );
var ambient = new THREE.AmbientLight( 0xffffff, 1 ); //AmbientLight,影响整个场景的光源
ambient.position.set(0,0,0);
scene.add( ambient );
}
// 初始化性能插件
function initStats() {
var stats = new Stats();
stats.domElement.style.position = 'absolute';
stats.domElement.style.left = '0px';
stats.domElement.style.top = '0px';
document.body.appendChild(stats.domElement);
return stats;
}
// 初始化渲染器
function initRenderer() {
renderer = new THREE.WebGLRenderer({
antialias: true});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0x4682B4,1.0);
document.body.appendChild(renderer.domElement);
}
同样废话不多说,直接上代码!
//创建地板
function createFloor(){
var loader = new THREE.TextureLoader();
loader.load("./ThreeJs/images/floor.jpg",function(texture){
texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set( 10, 10 );
var floorGeometry = new THREE.BoxGeometry(2600, 1400, 1);
var floorMaterial = new THREE.MeshBasicMaterial( {
map: texture, side: THREE.DoubleSide } );
var floor = new THREE.Mesh(floorGeometry, floorMaterial);
floor.position.y = -0.5;
floor.rotation.x = Math.PI / 2;
floor.name = "地面";
scene.add(floor);
});
}
这里使用了本地的图片作为地板的贴图素材,大家可以自定义自己需要的图片。完成之后大概是这个样子
光秃秃的背景上出现了一个光秃秃的地面。(我变秃了也变强了)
完成上面的步骤,我们发现场景还无法旋转、平移、放大缩小等。这些就要依赖我们的控制器了,ThreeJs官网上有很多类型的控制器,我们这里选用比较常用的轨道球控制器,代码很简单。
// 初始化轨迹球控件
function initControls() {
controls = new THREE.OrbitControls( camera, renderer.domElement );
controls.enableDamping = true;
controls.dampingFactor = 0.5;
// 视角最小距离
controls.minDistance = 100;
// 视角最远距离
controls.maxDistance = 5000;
// 最大角度
controls.maxPolarAngle = Math.PI/2.2;
}
这里设置了最大的旋转角度,防止某些绅士看到人家下面呢!
之前我们分模块或者功能讲解了每一部分的代码,这一节里我贴出全部的代码,是可以直接运行的哦,相关引入的JS在ThreeJS官网上都有。
<!DOCTYPE html>
<html>
<head includeDefault="true">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<title>3D库图显示</title>
<style>
body {
margin: 0;
overflow: hidden;
}
</style>
<script src="./ThreeJs/three.js"></script>
<script src="./ThreeJs/stats.min.js"></script>
<script src="./ThreeJs/OBJLoader.js"></script>
<script src="./ThreeJs/OrbitControls.js"></script>
</head>
<body>
<div id="container"></div>
<script>
var stats = initStats();
var scene, camera, renderer, controls, light;
// 初始化场景
function initScene() {
scene = new THREE.Scene();
scene.fog = new THREE.Fog( scene.background, 3000, 5000 );
}
// 初始化相机
function initCamera() {
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 10000);
camera.position.set(0, 800, 1500);
camera.lookAt(new THREE.Vector3(0, 0, 0));
}
// 初始化灯光
function initLight() {
var directionalLight = new THREE.DirectionalLight( 0xffffff, 0.3 );//模拟远处类似太阳的光源
directionalLight.color.setHSL( 0.1, 1, 0.95 );
directionalLight.position.set( 0, 200, 0).normalize();
scene.add( directionalLight );
var ambient = new THREE.AmbientLight( 0xffffff, 1 ); //AmbientLight,影响整个场景的光源
ambient.position.set(0,0,0);
scene.add( ambient );
}
// 初始化性能插件
function initStats() {
var stats = new Stats();
stats.domElement.style.position = 'absolute';
stats.domElement.style.left = '0px';
stats.domElement.style.top = '0px';
document.body.appendChild(stats.domElement);
return stats;
}
// 初始化渲染器
function initRenderer() {
renderer = new THREE.WebGLRenderer({
antialias: true});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0x4682B4,1.0);
document.body.appendChild(renderer.domElement);
}
//创建地板
function createFloor(){
var loader = new THREE.TextureLoader();
loader.load("./ThreeJs/images/floor.jpg",function(texture){
texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set( 10, 10 );
var floorGeometry = new THREE.BoxGeometry(2600, 1400, 1);
var floorMaterial = new THREE.MeshBasicMaterial( {
map: texture, side: THREE.DoubleSide } );
var floor = new THREE.Mesh(floorGeometry, floorMaterial);
floor.position.y = -0.5;
floor.rotation.x = Math.PI / 2;
floor.name = "地面";
scene.add(floor);
});
}
// 初始化模型
function initContent() {
createFloor();
}
// 初始化轨迹球控件
function initControls() {
controls = new THREE.OrbitControls( camera, renderer.domElement );
controls.enableDamping = true;
controls.dampingFactor = 0.5;
// 视角最小距离
controls.minDistance = 100;
// 视角最远距离
controls.maxDistance = 5000;
// 最大角度
controls.maxPolarAngle = Math.PI/2.2;
}
// 更新控件
function update() {
stats.update();
controls.update();
}
// 初始化
function init() {
initScene();
initCamera();
initRenderer();
initContent();
initLight();
initControls();
document.addEventListener('resize', onWindowResize, false);
}
// 窗口变动触发的方法
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
update();
}
init();
animate();
</script>
</body>
</html>
完成之后就可以看到一个可以随意转动移动缩放的地面啦!
第一章我们涉及的内容都比较简单,但也是最为基础关键的,致此我们创建好了一个可以控制的地面,下一章我们将创建墙面,门,窗户,以及选中物体的交互事件。
我跟广大学习ThreeJs的初学者一样,仍带着懵懂的心去探索这片新大陆,CSDN上的许多前辈都给了我很多关键的灵感和技术方法,如果大家有兴趣,也可以互相交流成长,欢迎大家指导咨询。PS:大家有兴趣可以点进去我的头像,陆陆续续也写了十来篇了。
链接:使用ThreeJs从零开始构建3D智能仓库——第一章: 点我跳转.
链接:使用ThreeJs从零开始构建3D智能仓库——第二章: 点我跳转.
链接:使用ThreeJs从零开始构建3D智能仓库——第三章: 点我跳转.
链接:使用ThreeJs从零开始构建3D智能仓库——第四章: 点我跳转.
链接:使用ThreeJs从零开始构建3D智能仓库——第五章: 点我跳转.