翻开月熊志的第一本书,首先映入眼帘的就是一只可爱的月熊——Jasper。它是贯穿月熊志的主角,在这页中可以看到月熊及场景是3D的并且是无插件的,那么它是如何被活灵活现的搬到网页中的呢?这篇文章来解答这个疑惑。

Blender导入模型_渲染器

在这个页面,为了更好的把月熊的生长环境和氛围更好的渲染出来,我们通过WebGL把Jasper搬到浏览器上,让大家可以更好的了解它。WebGL是一种3D绘图标准,允许把JS和OpenGL ES 2.0结合在一起,无需插件并且可以利用GPU来进行图形渲染,在网页上流畅的展示3D场景和模型,接下来我们就来了解这些是如何实现的:

导出模型

Blender导入模型_构造函数_02

首先当然是在3D建模软件中制作模型,这部分不多介绍,以Blender

为例,在输出时选择Three.js并勾选Morph animation(变形动画)即可导出带动画的JS文件。

Blender导入模型_Blender导入模型_03

选择Export > Three.js

Blender导入模型_渲染器_04

勾选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);

这个时候运行页面,就能看到模型了,不过是全黑的,因为现在还没有打上灯光。

Blender导入模型_Blender导入模型_05

添加光源

我们需要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后,你可以看到光的框架,方便调试。

打上合适的光源后,就能看到久违的地面了:

Blender导入模型_构造函数_06

模型默认的材质是平滑的,要显示为最终的棱角分明的风格需要设置下材质,注意新加的循环:

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); 
 
 
 
});

Blender导入模型_Blender导入模型_07

导入月熊

操作和导入地面一样,因为导出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();