最近公司有虚拟仿真方面的想法,所以做了一些网页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)
}
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
Babylon.js - GUI demo
Control panel
Active camera:
Change control method:
Post-processes:
Sorry but your browser does not support WebGL...
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)
}