最终成果
注:上面链接用 vpn 访问, 另外,写这篇文章是为了高德地图3D标注做准备。
js添加动画效果:
模型下载并查看
首先,这个模型是网上寻找的, 并且格式是 GLB 格式。 下载了之后, 怎么预览呢?
查看 3D GLB 格式的文件, 可以将文件拖拽到下面这个网站进行预览:
预览结果:
可以看到,这个文件似乎是白色的, 我们可以重新设置下材质,改变其颜色。
一、创建 Scene、Camera、 Renderer、 OrbitControls
为了看上面这个模型,首先搭建 3D 场景, 最典型的 3D 场景, 包括:
- Scene 创建 3D 世界
- Camera 照相机, 拍摄每一帧的页面
- Renderer 渲染器,将相机拍摄的画面,渲染在网页上
- OrbitControls 轨道控制器, 控制鼠标拖拽, 相机位置
典型代码如下:
import * as THREE from 'three';
import {
OrbitControls
} from "three/examples/jsm/controls/OrbitControls";
export class Basic {
public dom: HTMLElement;
public scene: THREE.Scene;
public camera: THREE.PerspectiveCamera;
public renderer: THREE.WebGLRenderer;
public controls: OrbitControls;
constructor(dom: HTMLElement) {
this.dom = dom;
this.initScenes();
this.setControls();
}
initScenes() {
//第1步,Scene,初始化场景
this.scene = new THREE.Scene();
//第2步,Camera,初始化照相机,并摆好照相机的位置
this.camera = new THREE.PerspectiveCamera(30, window.innerWidth / window.innerHeight, 1, 10000);
this.camera.position.set(-450, 180, -600);
//第3步,设置好渲染器
this.renderer = new THREE.WebGLRenderer({
//透明,设置整个canvas是否透明,true的话,会显示大背景颜色,false的话,会覆盖大背景颜色
alpha: true,
//抗锯齿,true的话,放大缩小后,线条更加圆润
antialias: true,
});
//设置屏幕像素比
this.renderer.setPixelRatio(window.devicePixelRatio);
this.renderer.setSize(window.innerWidth, window.innerHeight);
this.dom.appendChild(this.renderer.domElement);
}
//设置轨道控制器,主要目的是实现放大缩小、拖拽、点击, 原理是控制照相机的运行轨迹
setControls() {
//初始化轨道控制器
this.controls = new OrbitControls(this.camera, this.renderer.domElement);
//这个是用来干什么的,暂时不是很清楚
this.controls.autoRotateSpeed = 3
//使动画循环使用时阻尼或自转,意思是否有惯性,设置为true,拖拽有惯性,更加丝滑
this.controls.enableDamping = true;
//动态阻尼系数 就是鼠标拖拽旋转灵敏度(设置为0.05就可以,具体效果也不是很清楚)
this.controls.dampingFactor = 0.05;
//是否可以缩放
this.controls.enableZoom = true;
//设置相机距离原点的最近距离(如果想要放大,这个值可以缩小)
this.controls.minDistance = 100;
//设置相机距离原点的最远距离(如果想要缩小,这个值可以放大)
this.controls.maxDistance = 400;
//是否开启右键拖拽(设置为true时,动画效果不好控制,所以还是不要右键操作了)
this.controls.enablePan = false;
}
}
二、加载四棱锥模型
首先,加载上面的四棱锥模型, 然后,重新创建一个材质, 将颜色改成需要的颜色,这时候,由于材质是感光的, 最好还是加上光线。
加载模型的方法如下:
loadOneModel(sourceUrl) {
const loader = new GLTFLoader();
return new Promise(resolve => {
loader.load(sourceUrl, (gltf) => {
const mesh = gltf.scene.children[0];
resolve(mesh);
},
function (xhr) {
console.log(xhr);
},
function (error) {
console.log('loader model fail' + error);
})
})
}
模型加载完毕, 需要添加光线,改变其材质, 并加入到 3D 世界中, 代码如下:
async createMainMesh() {
const hemiLight = new HemisphereLight(0xffffff, 0x8d8d8d, 2);
hemiLight.position.set(100, 0, 0);
this.scene.add(hemiLight);
const dirLight = new DirectionalLight(0xffffff, 1.5);
dirLight.position.set(100, 10, 10);
this.scene.add(dirLight);
//加载模型
const model: any = await this.loadOneModel('../../../static/models/taper2.glb');
//给模型换一种材质
const material = new MeshStandardMaterial({
//自身颜色
color: 0x1171ee,
//透明度
transparent: true,
opacity: 1,
//金属性
metalness: 0.0,
//粗糙度
roughness: 0.5,
//发光颜色
emissive: new Color(0xff0000),
emissiveIntensity: 0.2,
//blending: THREE.AdditiveBlending
});
//model.material = material;
model.traverse((child: any) => {
if (child.isMesh) {
child.material = material;
}
});
model.scale.set(1, 1, 1);
model.position.set(0, 0, 1);
model.rotateZ(Math.PI / 4);
this.mainModel = model;
this.scene.add(model);
}
三、加载底座
底座的贴图如下:
看到前面展示的效果, 底座应该是水波纹的圈圈, 为什么贴图是这个样子呢?
其实就是为了控制每一帧的样式, 画了个长帧图, 每一帧切换一副, 从而达到水波纹涟漪的效果。
第一步,加载水波纹平面
平面是类似一个正方形, 材质贴图用到上面的贴图, 代码如下:
async createTrayMesh() {
const model: any = await this.loadOneModel('../../../static/models/taper1-p.glb');
const loader = new TextureLoader();
const texture = await loader.loadAsync('../../../static/images/wave.png');
const { width, height } = texture.image;
this.frameX = width / height;
texture.wrapS = texture.wrapT = RepeatWrapping;
//设置xy方向重复次数,x轴有frameX帧,仅取一帧
texture.repeat.set(1 / this.frameX, 1);
const material = new MeshStandardMaterial({
color: 0x1171ee,
map: texture,
transparent: true,
opacity: 0.8,
metalness: 0.0,
roughness: 0.6,
depthTest: true,
depthWrite: false
});
model.material = material;
this.waveTexture = texture;
this.trayModel = model;
this.scene.add(model);
}
第二步,记录贴图帧数
上面代码中,记录了宽高比 frameX, 这样贴图 y 轴重复为 1, 即 y 轴全部由贴图的高度填充; x 轴的重复为 1/frameX , 即 x 轴由贴图长度的 1/frameX 来填充。 后面的关键帧动画会根据 frameX 这个参数,不断推进 offset, 达到一帧一帧移动的效果。
this.frameX = width / height;
texture.wrapS = texture.wrapT = RepeatWrapping;
//设置xy方向重复次数,x轴有frameX帧,仅取一帧
texture.repeat.set(1 / this.frameX, 1);
四、帧动画
动画目标如下
- 四棱柱不断旋转
- 底座实现水波纹效果
代码入下:
render() {
requestAnimationFrame(this.render.bind(this));
this.renderer.render(this.scene, this.camera);
this.controls && this.controls.update();
if(this.mainModel){
this.mainModel.rotation.z += 0.02;
}
if(this.trayModel && this.waveTexture){
this.offset += 0.6;
this.waveTexture.offset.x = Math.floor(this.offset) / this.frameX;
}
}
彩蛋
为什么写的这么简单? 其实这只是做个小铺垫, 后面会在高德地图的基础上, 加上 three.js 图层, 并将上面这个四棱柱的模型, 应用在高德地图上面,后续会更新博文。
gif 效果如下, 3D 效果:
详细可以看下一篇: