最近公司有虚拟仿真方面的想法,所以做了一些网页3D方面的研究,以及代码,这里记录一下
框架选择
之前用过threejs,所以一开始考虑用threejs做,但是之前用threejs的时候碰到过一个问题:物体位置坐标以及角度的调整太麻烦。每次调整后要刷新网页看下调整效果,再根据效果修改数据,反复修改。一个模型有时候就能弄一下午,实在是浪费了太多生命在上面。
后来在github上看到了babylonjs,看到官网上炫酷的例子效果,就被吸引住了。更吸引我的是babylonEditor,网页上的三维建模可以在这个编辑器里面做,简单的点击拖拽就可以完成大部分工作。后面再对物体写一写脚本。一个简单的模型就被开发出来了。
那就没什么好说的了,初步定,用babylonjs开始做。
开发过程
babylonjs和babylonEdtior都可以在github上找到,我这里是实现了一个简单的demo,效果如图:
说一下这里面包含的功能点:
前面五点都是可以通过babylonEdtior做的,后面四点需要写脚本,脚本语言是typescript
-
简单模型的创建,摆放
中间是预览区域,可以拖动鼠标旋转,或者进行一些操作。点击菜单栏的add,addmesh,可以往预览中添加形状,相机,灯光,天空等,选择对应的对象,可以点击preview下面的那三个按钮,进行旋转,拖拽,缩放。或者在左侧边框直接键入对应的值。仅仅这一个功能我就感觉太好用了,至少不用来回调试了。 -
模型的皮肤贴图
选择对象后,可以在左侧的操作框中选择皮肤material,然后给material贴图texture,贴图一般用图片就行,比如我截图中的方块就是这么做的。很人性化,开发起来真爽。 -
导入外部三维模型
这个就更简单了,把三维文件拖到下面这个位置,再拖到页面中,结束。注意下面这个assets是静态文件,从上到下:网格、皮肤、贴图、音频,脚本。 -
天空盒的搭建,可以搭建任何场景
天空盒的搭建有一点不一样,贴图不能使用简单的图片,点击Texture→add→pur cube Textures,看到下面这个框,分别选择六边形的六个面的图片,这时将天空或者场景沙盒的六个面的贴图选择进去,点击create就ok了。
然后新建一个皮肤,空的就可以,再将这个皮肤的Reflection贴图选择刚刚新建的贴图,就可以得到一个沙盒的场景。
到此为止,我们还没有写一行代码,点击play,已经在网页端运行起了一个三维的场景,场景中包含了一个物体。 -
物理引擎,碰撞检测
为了使相机和物体有碰撞检测和重力,可以模拟现实中的情况,我们开始使用物理引擎。非常简单,给场景添加重力,并且允许检测碰撞。再给相机,物体都勾上物理属性,简单的物理引擎就实现了。注意,地面的重力设为0.
-
脚本创建
新建脚本,并且指定给某个对象,双击可以打开脚本,编写具体内容。
脚本中包含,onInitialize()创建的钩子函数,这个函数里还没有this,onStart()开始的钩子函数,开始时调用,onUpdate()每次浏览器页面刷新时调用,就是浏览器的每一帧都会调用这个函数。比使用定时器效率高,更稳定。
我们可以在onStart中写一写初始化的操作,比如,创建GUI、创建Mesh,事件注册等。可以在update中写一写物体的位移,旋转等。
比如,物体在每次网页页面更新时旋转一度。
public onUpdate(): void {
this.rotate(Axis.Y, Math.PI/180)
}
- babylon GUI
babylon本身有一套GUI比如按钮,文本,slider,checkbox等。
下面这个是官方的实例。
js
var createScene = function () {
var scene = new BABYLON.Scene(engine);
var camera = new BABYLON.ArcRotateCamera("Camera", -Math.PI / 2, 1, 110, BABYLON.Vector3.Zero(), scene);
camera.attachControl(canvas, true);
var hemi = new BABYLON.HemisphericLight("toto");
var sphereMaterial = new BABYLON.StandardMaterial;
var sphere1 = BABYLON.Mesh.CreateSphere("Sphere1gdtdyf", 1, 9, scene);
var sphere2 = BABYLON.Mesh.CreateSphere("Sphere2", 2, 9, scene);
var sphere3 = BABYLON.Mesh.CreateSphere("Sphere3", 3, 9, scene);
var sphere4 = BABYLON.Mesh.CreateSphere("Sphere4", 10, .5, scene);
var sphere5 = BABYLON.Mesh.CreateSphere("Sphere5", 4, 9, scene);
var sphere6 = BABYLON.Mesh.CreateSphere("Sphere6", 10, 9, scene);
var sphere7 = BABYLON.Mesh.CreateSphere("Sphere7", 100, 9, scene);
sphere1.position.x = -30;
sphere2.position.x = -20;
sphere3.position.x = -10;
sphere4.position.x = 0;
sphere5.position.x = 10;
sphere6.position.x = 20;
sphere7.position.x = 30;
sphere1.material = sphereMaterial;
sphere2.material = sphereMaterial;
sphere3.material = sphereMaterial;
sphere4.material = sphereMaterial;
sphere5.material = sphereMaterial;
sphere6.material = sphereMaterial;
sphere7.material = sphereMaterial;
var advancedTexture = BABYLON.GUI.AdvancedDynamicTexture.CreateFullscreenUI("ui1");
var panel = new BABYLON.GUI.StackPanel;
panel.width = .25;
panel.rotation = .2;
panel.horizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_LEFT;
advancedTexture.addControl(panel);
var button1 = BABYLON.GUI.Button.CreateSimpleButton("but1", "Click Me");
button1.width = .2;
button1.height = "40px";
button1.color = "white";
button1.cornerRadius = 20;
button1.background = "green";
button1.onPointerUpObservable.add(function () {
circle.scaleX += .1
});
panel.addControl(button1);
var circle = new BABYLON.GUI.Ellipse;
circle.width = "50px";
circle.color = "white";
circle.thickness = 5;
circle.height = "50px";
circle.paddingTop = "2px";
circle.paddingBottom = "2px";
panel.addControl(circle);
var button2 = BABYLON.GUI.Button.CreateSimpleButton("but2", "Click Me 2");
button2.width = .2;
button2.height = "40px";
button2.color = "white";
button2.background = "green";
button2.onPointerUpObservable.add(function () {
circle.scaleX -= .1
});
panel.addControl(button2);
var createLabel = function (mesh) {
var label = new BABYLON.GUI.Rectangle("label for " + mesh.name);
label.background = "black";
label.height = "30px";
label.alpha = .5;
label.width = "100px";
label.cornerRadius = 20;
label.thickness = 1;
label.linkOffsetY = 30;
advancedTexture.addControl(label);
label.linkWithMesh(mesh);
var text1 = new BABYLON.GUI.TextBlock;
text1.text = mesh.name;
text1.color = "white";
label.addControl(text1)
};
createLabel(sphere1);
createLabel(sphere2);
createLabel(sphere3);
createLabel(sphere4);
createLabel(sphere5);
createLabel(sphere6);
var label = new BABYLON.GUI.Rectangle("label for " + sphere7.name);
label.background = "black";
label.height = "30px";
label.alpha = .5;
label.width = "100px";
label.cornerRadius = 20;
label.thickness = 1;
label.linkOffsetY = 30;
label.top = "10%";
label.zIndex = 5;
label.verticalAlignment = BABYLON.GUI.Control.VERTICAL_ALIGNMENT_TOP;
advancedTexture.addControl(label);
var text1 = new BABYLON.GUI.TextBlock;
text1.text = sphere7.name;
text1.color = "white";
label.addControl(text1);
var line = new BABYLON.GUI.Line;
line.alpha = .5;
line.lineWidth = 5;
line.dash = [5, 10];
advancedTexture.addControl(line);
line.linkWithMesh(sphere7);
line.connectedControl = label;
var endRound = new BABYLON.GUI.Ellipse;
endRound.width = "10px";
endRound.background = "black";
endRound.height = "10px";
endRound.color = "white";
advancedTexture.addControl(endRound);
endRound.linkWithMesh(sphere7);
var plane = BABYLON.Mesh.CreatePlane("plane", 20);
plane.parent = sphere4;
plane.position.y = -10;
var advancedTexture2 = BABYLON.GUI.AdvancedDynamicTexture.CreateForMesh(plane);
var panel2 = new BABYLON.GUI.StackPanel;
panel2.top = "100px";
advancedTexture2.addControl(panel2);
var button1 = BABYLON.GUI.Button.CreateSimpleButton("but1", "Click Me");
button1.width = 1;
button1.height = "100px";
button1.color = "white";
button1.fontSize = 50;
button1.background = "green";
panel2.addControl(button1);
var textblock = new BABYLON.GUI.TextBlock;
textblock.height = "150px";
textblock.fontSize = 100;
textblock.text = "please pick an option:";
panel2.addControl(textblock);
var addRadio = function (text, parent) {
var button = new BABYLON.GUI.RadioButton;
button.width = "40px";
button.height = "40px";
button.color = "white";
button.background = "green";
button.onIsCheckedChangedObservable.add(function (state) {
if (state) {
textblock.text = "You selected " + text
}
});
var header = BABYLON.GUI.Control.AddHeader(button, text, "400px", {isHorizontal: true, controlFirst: true});
header.height = "100px";
header.children[1].fontSize = 80;
header.children[1].onPointerDownObservable.add(function () {
button.isChecked = !button.isChecked
});
parent.addControl(header)
};
addRadio("option 1", panel2);
addRadio("option 2", panel2);
addRadio("option 3", panel2);
addRadio("option 4", panel2);
addRadio("option 5", panel2);
scene.registerBeforeRender(function () {
panel.rotation += .01
});
var advancedTexture = BABYLON.GUI.AdvancedDynamicTexture.CreateFullscreenUI("UI");
advancedTexture.layer.layerMask = 2;
var panel3 = new BABYLON.GUI.StackPanel;
panel3.width = "220px";
panel3.fontSize = "14px";
panel3.horizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_RIGHT;
panel3.verticalAlignment = BABYLON.GUI.Control.VERTICAL_ALIGNMENT_CENTER;
advancedTexture.addControl(panel3);
var checkbox = new BABYLON.GUI.Checkbox;
checkbox.width = "20px";
checkbox.height = "20px";
checkbox.isChecked = true;
checkbox.color = "green";
var panelForCheckbox = BABYLON.GUI.Control.AddHeader(checkbox, "checkbox", "180px", {
isHorizontal: true,
controlFirst: true
});
panelForCheckbox.color = "white";
panelForCheckbox.height = "20px";
panelForCheckbox.horizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_LEFT;
panel3.addControl(panelForCheckbox);
var header = new BABYLON.GUI.TextBlock;
header.text = "Slider:";
header.height = "40px";
header.color = "white";
header.textHorizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_LEFT;
header.paddingTop = "10px";
panel3.addControl(header);
var slider = new BABYLON.GUI.Slider;
slider.horizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_LEFT;
slider.minimum = 0;
slider.maximum = 2 * Math.PI;
slider.color = "green";
slider.value = 0;
slider.height = "20px";
slider.width = "200px";
slider.onValueChangedObservable.add(function(val){
console.log(val)
})
panel3.addControl(slider);
header = new BABYLON.GUI.TextBlock;
header.text = "Sphere diffuse:";
header.height = "40px";
header.color = "white";
header.textHorizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_LEFT;
header.paddingTop = "10px";
panel3.addControl(header);
var picker = new BABYLON.GUI.ColorPicker;
picker.horizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_LEFT;
picker.value = sphereMaterial.diffuseColor;
picker.height = "150px";
picker.width = "150px";
picker.onValueChangedObservable.add(function (value) {
sphereMaterial.diffuseColor = value
});
panel3.addControl(picker);
return scene
};
var demo = {
constructor: createScene, onload: function () {
}
};
html
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width">
<title>Babylon.js - GUI demo</title>
<!-- <script src="//cdn.webglstats.com/stat.js" defer="defer" async="async"></script> -->
<!-- <script>
(function (i, s, o, g, r, a, m) {
i['GoogleAnalyticsObject'] = r; i[r] = i[r] || function () {
(i[r].q = i[r].q || []).push(arguments)
}, i[r].l = 1 * new Date(); a = s.createElement(o),
m = s.getElementsByTagName(o)[0]; a.async = 1; a.src = g; m.parentNode.insertBefore(a, m)
})(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga');
ga('create', 'UA-41767310-1', 'babylonjs.com');
ga('send', 'pageview');
</script> -->
<link rel='stylesheet' href='https://d33wubrfki0l68.cloudfront.net/css/00b7b31c511ce943616fd1c5512819b8fcf13ab6/css/index.css'/>
<!-- <script src="https://code.jquery.com/pep/0.4.0/pep.min.js"></script> -->
<script src="babylon.js"></script>
<script src="babylon.gui.min.js"></script>
<script src='a123.js'></script>
</head>
<body>
<canvas id="renderCanvas" touch-action="none"></canvas>
<div id="fps"></div>
<div id="stats"></div>
<div id="status"></div>
<div id="controlPanel">
<div id="controlsZone">
<p>
<button id="enableDebug">Debug layer</button>
</p>
<p>
<button id="fullscreen">Fullscreen</button>
</p>
</div>
<div class="tag">Control panel</div>
<div class="tag" id="clickableTag"></div>
</div>
<div id="cameraPanel">
<div id="cameraControlsZone">
Active camera:<br>
<select id="camerasList"></select>
<p>
Change control method:
<button class="buttonControlPanel" id="touchCamera">
<img src="https://d33wubrfki0l68.cloudfront.net/dcd183b9dc6674b3acb29895c9be9c241e933435/21aa7/assets/camtouchoff.png" class="buttonImg" />Touch camera
</button>
<button class="buttonControlPanel" id="deviceOrientationCamera">
<img src="https://d33wubrfki0l68.cloudfront.net/b79ca59bdd1cb71dd762d9a703fd40cf1ab0cb2e/e70d2/assets/camdeviceoff.png" class="buttonImg" />VR Device orientation camera
</button>
<button class="buttonControlPanel" id="gamepadCamera">
<img src="https://d33wubrfki0l68.cloudfront.net/39496e30a5783bade6a71f853eda7c4e11fff147/09f04/assets/camgamepadjoy.png" class="buttonImg" />Gamepad camera
</button>
<button class="buttonControlPanel" id="virtualJoysticksCamera">
<img src="https://d33wubrfki0l68.cloudfront.net/91e4f8a450087a60aec5c3cc9bb8b3cbdb52d341/a5f13/assets/camvirtualjoy.png" class="buttonImg" />Virtual joysticks camera
</button>
<button class="buttonControlPanel" id="anaglyphCamera">
<img src="https://d33wubrfki0l68.cloudfront.net/3a29c33554bd3b3723badb1507c9de8e1859064c/b0895/assets/camanagly.png" class="buttonImg" />Anaglyph camera
</button>
</p>
<p>
Post-processes:
<button class="smallButtonControlPanel" id="toggleFxaa">Toggle FXAA (antialiasing)</button>
<button class="smallButtonControlPanel" id="toggleFsaa4">Toggle FSAA 4X (antialiasing)</button>
<button class="smallButtonControlPanel" id="toggleBandW">Toggle Black and white</button>
<button class="smallButtonControlPanel" id="toggleSepia">Toggle Sepia</button>
</p>
</div>
<div class="cameraTag"><img src="https://d33wubrfki0l68.cloudfront.net/4be4d643f634ae7cb26ddff105477a067c434512/11f81/assets/camera.png" /></div>
<div class="cameraTag" id="cameraClickableTag"></div>
</div>
<div id="notSupported" class="hidden">Sorry but your browser does not support WebGL...</div>
<script src='loadercustoms.js'></script>
</body>
</html>
- 点击事件
var cubeMesh = this.getScene().getMeshByName("Cube1")
cubeMesh.actionManager = new ActionManager(cubeMesh.getScene())
cubeMesh.actionManager.registerAction(
new ExecuteCodeAction({
trigger: ActionManager.OnLeftPickTrigger,
},
function (e) {
window["clickCube1"]()
// console.log(12313)
// location.href = "https://www.baidu.com/?tn=62095104_31_oem_dg"
})
)
cubeMesh.actionManager.registerAction(
new ExecuteCodeAction({
trigger: ActionManager.OnLongPressTrigger,
},
function (e) {
console.log(3333)
// location.href = "https://www.baidu.com/?tn=62095104_31_oem_dg"
})
)
- 动画
var addAnimation = function(mesh) {
var animationBox = new Animation(
"myAnimation",
"rotation.x",
30,
Animation.ANIMATIONTYPE_FLOAT,
Animation.ANIMATIONLOOPMODE_CYCLE
);
var keys = []
keys.push({
frame: 0,
value: 0
})
keys.push({
frame: 100,
value: Math.PI
})
animationBox.setKeys(keys)
mesh.animations = []
mesh.animations.push(animationBox)
return scene.beginAnimation(mesh, 0, 100, true)
}