文章目录
前言
通常我们在Web端三维场景中看到的动态模型一般都为GLTF或者GLB模型(GLB是GLTF的二级制心态)。而GLTF模型有分为带骨骼动画和不带骨骼动画两种形态。而上面或到的动态效果就是只带有骨骼动画的GLTF。
对于ArcGIS For JavaScript而言,目前可以通过符号化三维对象的方式去加载GLTF,但是在符号化的过程中无法去设置开启GLTF的动画参数,所以不管是骨骼模型还是普通模型,加载出来都只能看到静态的效果。在文章《03-ArcGIS For JavaScript结合ThreeJS功能》的时候,讲过RenderNode可以将ThreeJS的效果集中到ArcGIS的场景中,所以这里就介绍一下ArcGIS中通过ThreeJS去加载带有骨骼动画的GLTF模型。
一、ArcGIS如何加载GLTF
1.JavaScript 代码
首先看一下ArcGIS For JavaScript中如何加载GLTF。
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
<title>Import glTF 3D Models | Sample | ArcGIS Maps SDK for JavaScript 4.32</title>
<link rel="stylesheet" href="https://js.arcgis.com/4.32/esri/themes/light/main.css" />
<script src="https://js.arcgis.com/4.32/"></script>
<style>
html,
body,
#viewDiv {
padding: 0;
margin: 0;
height: 100%;
width: 100%;
}
#paneDiv {
padding: 10px;
max-width: 200px;
background-color: rgba(255, 255, 255, 0.8);
font-size: 1.1em;
}
#credits {
font-size: 0.7em;
line-height: 1.1em;
}
</style>
<script>
require([
"esri/views/SceneView",
"esri/Map",
"esri/layers/GraphicsLayer",
"esri/Graphic",
"esri/geometry/Point"
], (SceneView, Map, GraphicsLayer, Graphic, Point) => {
let scene = new Map({
basemap: 'satellite'
})
const view = new SceneView({
container: "viewDiv",
map: scene,
camera: {
heading: 359.20366065537,
fov: 55,
tilt: 1.2342814547871352,
position: {
latitude: 39.90281086554516,
longitude: 116.37414026811818,
z: 1445.7247159378603
}
}
});
const graphicsLayer = new GraphicsLayer({
elevationInfo: { mode: "on-the-ground" }
});
scene.add(graphicsLayer);
view.when(() => {
let symbol = {
type: "point-3d",
symbolLayers: [
{
type: "object",
resource: {
href: "https://developers.arcgis.com/javascript/latest/sample-code/import-gltf/live/tent.glb"
},
width: 100,
hight:100
}
]
};
let point = new Point({
latitude: 39.903718309815375,
longitude: 116.37175217665084,
z: 60
});
let graphic = new Graphic({
geometry: point,
symbol
})
graphicsLayer.add(graphic);
})
});
</script>
</head>
<body>
<div id="viewDiv"></div>
</body>
</html>
2. 结果
二、ThreeJS使用
ThreeJS的使用方式可以参考前面《03-ArcGIS For JavaScript结合ThreeJS功能》
三、ArcGIS场景基于ThreeJS加载GLTF
1. 编写RenderNode引入代码
renderNodeClass.js
let RenderNodeClass = {
constructor(options) {
this.webgl = options.webgl;
this.view = options.view;
this.mixer = null;
this.horseMixer = null;
this.clock = null;
this.horseClock = null;
},
setParams() {
this.isStop = !this.isStop;
},
initialize() {
let THREE = window.THREE;
// this.mixer = new THREE.AnimationMixer();
this.horseMixer = THREE.AnimationMixer;
this.clock = new THREE.Clock();
this.horseClock = new THREE.Clock();
this.mixer = THREE.AnimationMixer;
this.renderer = new THREE.WebGLRenderer({
context: this.gl, // 可用于将渲染器附加到已有的渲染环境(RenderingContext)中
premultipliedAlpha: false, // renderer是否假设颜色有 premultiplied alpha. 默认为true
});
this.renderer.setPixelRatio(window.devicePixelRatio); // 设置设备像素比。通常用于避免HiDPI设备上绘图模糊
this.renderer.setViewport(0, 0, this.view.width, this.view.height); // 视口大小设置
this.renderer.autoClear = false;
this.renderer.autoClearDepth = false;
this.renderer.autoClearColor = false;
// this.renderer.autoClearStencil = false;
let originalSetRenderTarget = this.renderer.setRenderTarget.bind(this.renderer);
let that = this;
this.renderer.setRenderTarget = function (target) {
originalSetRenderTarget(target);
if (target == null) {
that.bindRenderTarget();
}
};
this.scene = new THREE.Scene();
// setup the camera
let cam = this.camera;
this._camera = new THREE.PerspectiveCamera(cam.fovY, cam.aspect, cam.near, cam.far);
// 添加坐标轴辅助工具
const axesHelper = new THREE.AxesHelper(1);
axesHelper.position.copy(1000000, 100000, 100000);
this.scene.add(axesHelper);
let grid = new THREE.GridHelper(30, 10, 0xf0f0f0, 0xffffff);
this.scene.add(grid);
// setup scene lighting
this.ambient = new THREE.AmbientLight(0xffffff, 0.5);
this.scene.add(this.ambient);
this.sun = new THREE.DirectionalLight(0xffffff, 0.5);
this.sun.position.set(-600, 300, 60000);
this.scene.add(this.sun);
window.gltf = null;
const loader = new THREE.GLTFLoader();
loader.load('Horse.glb', function (gltf) {
console.log('gltf', gltf);
gltf.scene.scale.set(10.8, 10.8, 10.8)
that.scene.add(gltf.scene);
gltf.scene.position.set(12956671.47026511,4851275.366457126, 10);
gltf.scene.rotateX(Math.PI / 2);
that.mixer = new THREE.AnimationMixer(gltf.scene);
// obj.animations[0]:获得剪辑对象clip
var AnimationAction = that.mixer.clipAction(gltf.animations[0]);
window.gltf = gltf;
// AnimationAction.timeScale = 1; //默认1,可以调节播放速度
// AnimationAction.loop = THREE.LoopOnce; //不循环播放
// AnimationAction.clampWhenFinished = true;//暂停在最后一帧播放的状态
AnimationAction.play();//播放动画
});
// this.getCoords(context);
this.resetWebGLState();
},
/**
* 渲染器更新渲染
* @memberof BuildingEffect
* @method render
* @param {Object} context 已有渲染器信息,无需传值
*/
render(context) {
let THREE = window.THREE;
let cam = this.camera;
//需要调整相机的视角
this._camera.position.set(cam.eye[0], cam.eye[1], cam.eye[2]);
this._camera.up.set(cam.up[0], cam.up[1], cam.up[2]);
this._camera.lookAt(new THREE.Vector3(cam.center[0], cam.center[1], cam.center[2]));
// Projection matrix can be copied directly
this._camera.projectionMatrix.fromArray(cam.projectionMatrix);
if (this.mixer && this.mixer.update) {
// 更新混合器相关的时间, clock.getDelta()方法获得两帧的时间间隔
this.mixer.update(this.clock.getDelta());
}
this.renderer.state.reset();
this.bindRenderTarget();
this.renderer.render(this.scene, this._camera);
// as we want to smoothly animate the ISS movement, immediately request a re-render
this.requestRender();
// cleanup
this.resetWebGLState();
}
}
2.调用RenderNode
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="./jquery/jquery-1.12.3.min.js"></script>
<script src="./threejs/three.min.js"></script>
<script src="./threejs/GLTFLoader.js"></script>
<script src="./renderNodeClass.js"></script>
<link rel="stylesheet" href="https://js.arcgis.com/4.32/esri/themes/light/main.css" />
<script src="https://js.arcgis.com/4.32/"></script>
<style>
body,
html,
#viewDiv {
padding: 0;
margin: 0;
height: 100%;
width: 100%;
}
</style>
</script>
<script>
require([
'esri/Map',
'esri/views/SceneView',
"esri/geometry/Mesh",
"esri/Graphic",
"esri/geometry/Point",
"esri/geometry/Polyline",
"esri/layers/GraphicsLayer",
"esri/geometry/operators/densifyOperator",
"esri/Camera",
"esri/views/3d/webgl/RenderNode",
"esri/views/3d/webgl"
], (Map, SceneView, Mesh, Graphic, Point, Polyline, GraphicsLayer, densifyOperator, Camera, RenderNode,webgl) => {
let graphicLayer = new GraphicsLayer();
let map = new Map({
layers: [graphicLayer],
basemap: 'satellite'
})
let view = new SceneView({
container: 'viewDiv',
viewingMode:'local',
map,
camera: {
heading: 46.490512697555566,
tilt: 65.03077172871896,
position: {
// longitude: 116.37280650987242,
// latitude: 39.88099012247863,
x: 12956520.797315104,
y: 4851088.587867753,
z: 127.55169651366651,
spatialReference: {
wkid: 102100
}
}
}
})
let subRenderClass = RenderNode.createSubclass(RenderNodeClass);
view.when(function(){
new subRenderClass({
view,
webgl
})
})
})
</script>
</head>
<body>
<div id="viewDiv">
</div>
</body>
</html>
3、结果
这里面主要在使用ThreeJs的时候引入了GLTFLoader.js用于加载gltf文件,并且调用that.mixer = new THREE.AnimationMixer(gltf.scene)去启动动画效果,在render中实时更新就能看到动态的模型。
四 、总结
在ArcGIS场景中使用ThreeJS得时候,只能通过设置viewingMode=‘local’的方式去加载,及只能在平面效果上看,如果设置为global或出现因为坐标轴不一致导致的模型位置和旋转问题。