翻开月熊志的第一本书,首先映入眼帘的就是一只可爱的月熊——Jasper。它是贯穿月熊志的主角,在这页中可以看到月熊及场景是3D的并且是无插件的,那么它是如何被活灵活现的搬到网页中的呢?这篇文章来解答这个疑惑。
在这个页面,为了更好的把月熊的生长环境和氛围更好的渲染出来,我们通过WebGL把Jasper搬到浏览器上,让大家可以更好的了解它。WebGL是一种3D绘图标准,允许把JS和OpenGL ES 2.0结合在一起,无需插件并且可以利用GPU来进行图形渲染,在网页上流畅的展示3D场景和模型,接下来我们就来了解这些是如何实现的:
导出模型
首先当然是在3D建模软件中制作模型,这部分不多介绍,以Blender
为例,在输出时选择Three.js并勾选Morph animation(变形动画)即可导出带动画的JS文件。
选择Export > Three.js
勾选Morph animation
建立场景
在Three.js的3D世界,我们至少需要创建以下对象:
1.一个场景(scene)
2.一个渲染器(renderer)
3.一个摄像机(camera)
//创建场景
var scene = new THREE.Scene();
//创建摄像机
var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 10000);
//创建渲染器,并设置透明背景和抗锯齿为true
Var renderer = new THREE.WebGLRenderer({
alpha: true,
antialias: true
});
//设置视口和窗口大小一致
renderer.setSize(window.innerWidth, window.innerHeight);
导入地面
我们使用的是.js格式的模型文件,所以要用Three.js的JSONLoader对象。
var loader = new THREE.JSONLoader();
JSONLoader上的load方法类似jQuery的get方法,第一个参数传入模型文件的地址,第二个参数是回调函数,回调函数同样有2个参数,第一个是模型(geometry),第二个是材质(materials)。
loader.load("models/ground.js", function (geometry, materials) {
//创建地面模型
var ground = new THREE.Mesh(geometry, new THREE.MeshFaceMaterial(materials));
//缩放到合适大小
groundground.scale.x = ground.scale.y = ground.scale.z = 50;
//旋转模型角度
ground.rotation.y = Math.PI;
//设置模型位置
ground.position.y = -150;
ground.position.z = 200;
//添加模型到地面
scene.add(ground);
});
//添加 renderer 元素到文档中
document.body.appendChild(renderer.domElement);
//让摄像机对准场景
camera.lookAt(scene.position);
//渲染画面
renderer.render(scene, camera);
这个时候运行页面,就能看到模型了,不过是全黑的,因为现在还没有打上灯光。
添加光源
我们需要2个光源,一个半球光和一个平行光。
半球光可以用来模拟环境光,他是在场景上方的一个光源。半球光的构造函数HemisphereLight有两个参数:颜色和光线强度,这里分别设置为白色和 0.7。
平行光是一组具有方向的没有衰减的平行光线,可以用它来模拟太阳光:虽然遥远但打在物体上的光都来自同一个方向。
环境光的构造函数THREE.DirectionalLight也是两个参数——16进制的颜色值和光线的强度(默认为1)。
var directionalLight = new THREE.DirectionalLight(0xffffff, .6);
directionalLight.position.set(600, 1000, 700);
directionalLight.castShadow = true;
directionalLight.shadowCameraNear = 50;
directionalLight.shadowCameraFar = 3000;
directionalLight.shadowCameraLeft = -1000;
directionalLight.shadowCameraRight = 1000;
directionalLight.shadowCameraTop = 1000;
directionalLight.shadowCameraBottom = -1000;
directionalLight.shadowDarkness = .2;
//directionalLight.shadowCameraVisible = true;
scene.add(directionalLight);
平行光有很多属性,可以调整光的范围、方向等,这里就不一一介绍。
shadowCameraVisible是一个比较特殊的属性,设置为true后,你可以看到光的框架,方便调试。
打上合适的光源后,就能看到久违的地面了:
模型默认的材质是平滑的,要显示为最终的棱角分明的风格需要设置下材质,注意新加的循环:
loader.load("models/ground.js", function (geometry, materials) {
for (var i = 0; i < materials.length; i++) {
materials[i].shading = THREE.NoShading;
};
var ground = new THREE.Mesh(geometry, new THREE.MeshFaceMaterial(materials));
groundground.scale.x = ground.scale.y = ground.scale.z = 50;
ground.rotation.y = Math.PI;
ground.position.y = -150;
ground.position.z = 200;
scene.add(ground);
});
导入月熊
操作和导入地面一样,因为导出JS时选的是Morph animation,所以在创建模型是相应的应该选择MorphAnimMesh。
loader.load("models/bear.js", function (geometry, materials) {
var material = materials[0];
material.shading = THREE.NoShading;
bear = new THREE.MorphAnimMesh(geometry, new THREE.MeshFaceMaterial(materials));
bear.position.z = 200;
bear.position.y = -150;
bearbear.scale.x = bear.scale.y = bear.scale.z = 50;
scene.add(bear);
});
但是这个时候熊还不会动,需要将材质的morphTargets设置为true,并且设置动画的播放时长等,最后循环渲染画面。
loader.load("models/bear.js", function (geometry, materials) {
var material = materials[0];
material.morphTargets = true;
material.shading = THREE.NoShading;
bear = new THREE.MorphAnimMesh(geometry, new THREE.MeshFaceMaterial(materials));
bear.setFrameRange(0, 290); //设置起始帧和结束帧
bear.duration = 290 * 24 / 1000; //设置动画播放时长
…
}
var clock = new THREE.Clock();//计时器,用于更新动画
function animate() {
requestAnimationFrame(animate);
bear.updateAnimation(clock.getDelta());
render();
};
function render() {
camera.lookAt(scene.position);
renderer.render(scene, camera);
};
animate();
设置投影
Three.js的光源默认不会导致物体间的投影,打开投影需要执行以下几步:
打开渲染器的地图阴影: renderer.shadowMapEnabled = true;
启用光线的投影:light.castShadow = true;
把模型设置为生成投影:mesh.castShadow = true;
把模型设置为接收阴影:mesh.receiveShadow= true;
跟随鼠标旋转摄像头
简单的说就是根据鼠标的位置计算摄像机的x坐标和y坐标。
var mouseX = 0, mouseY = 0;
function render() {
camera.position.x += (mouseX - camera.position.x) * .05;
camera.position.y += (-mouseY - camera.position.y) * .05;
camera.lookAt(scene.position);
renderer.render(scene, camera);
};
function onDocumentMouseMove(event) {
mouseX = (event.clientX - windowHalfX) * 2;
mouseY = (event.clientY - windowHalfY) * 2;
};
document.addEventListener("mousemove", onDocumentMouseMove, false);
animate();