做有态度的前端团队

网易FEG前端团队

这片云加了特效,Duang Duang Duang

首先,看下效果页面,感受一下。点我

是不是有点炫咧,这就是运用WebGL简单实现的效果,下面我们一起来看看代码吧。

Three.js

一个优秀的WebGL开源框架,官网地址https://github.com/mrdoob/three.js。点进去你会发现有好多酷炫的效果,而且基于这个库可以快速地写出3d程序。上面的镇魔曲手游官网的云海效果就是基于three.js实现的。

刚开始接触到Three.js,好多东西都是一头雾水的,然后官网的文档说明又是英文,阅读起来实在困难。在此强烈推荐一个中文基础教程,个人觉得对各种术语以及图形的构造和渲染理解起来相对简单些。英语厉害的同学可以忽略。

渲染的三大要素

在Three.js中,要渲染物体到网页中,我们需要3个组建:场景(scene)、相机(camera)和渲染器(renderer)。有了这三样东西,才能将物体渲染到网页中去。

所以,先初始化这三个东东。

1
2
3
scene = new THREE.Scene(); //场景,一个物体的容器
camera = new THREE.Camera( 30, w / h, 1, 3000 ); //相机,在场景中取一个合适的景,把它拍下来,可以理解为视角。
renderer = new THREE.WebGLRenderer( { antialias: false } );//渲染器

画云

实现步骤:

  1. 加载纹理
  2. 画一个平面
  3. 为平面赋予纹理坐标
  4. 将纹理应用于材质
加载纹理

在three.js中,纹理即图片。首先加载一张云的图片,方便后面将此纹理以一定的规则映射到平面上。这里要注意一下,采取相对路径,而且图片资源不能跨域,所以要配置一下fis,不然上线后会报cross-origin的错。

1
var texture = THREE.ImageUtils.loadTexture( './ignore/cloud10.png' );
画一个平面以及云的对应坐标

随机画出几百个无规律重叠的云,形成一片云海的即视感。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var geometry = new THREE.Geometry();
var plane = new THREE.Mesh( new THREE.Plane( 64, 64 ) );  //一个可在在三维3D空间无限延伸的二维平面。
var cloud_num = 400;
 
for ( i = 0; i < cloud_num; i++ ) {
 
     plane.position.x = Math.random() * 1000 - 600;
     plane.position.y = - Math.random() * Math.random() * 200 - 15;
     plane.position.z = 1;
     plane.rotation.z = Math.random() * Math.PI;
     plane.scale.x = plane.scale.y = Math.random() * Math.random() * 1.5 + 0.5;
 
     GeometryUtils.merge( geometry, plane );
 }
将纹理应用于材质

这里采取一种特殊的材质。

A ShaderMaterial will only be rendered properly by WebGLRenderer, since the GLSL code in the vertexShader and fragmentShader properties must be compiled and run on the GPU using WebGL.

意思大概是vertexShader和fragmentShader两个属性需要特殊配置一下,具体可以查看官方文档。在html下作了以下处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<script id="vs" type="x-shader/x-vertex">
     varying vec2 vUv;
     void main() {
         vUv = uv;
         gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
     }
 </script>
 
 <script id="fs" type="x-shader/x-fragment">
     uniform sampler2D map;
 
     uniform vec3 fogColor;
     uniform float fogNear;
     uniform float fogFar;
 
     varying vec2 vUv;
 
     void main() {
         float depth = gl_FragCoord.z / gl_FragCoord.w;
         float fogFactor = smoothstep( fogNear, fogFar, depth );
 
         gl_FragColor = texture2D( map, vUv );
         gl_FragColor.w *= pow( gl_FragCoord.z, 20.0 );
         gl_FragColor = mix( gl_FragColor, vec4( fogColor, gl_FragColor.w ), fogFactor );
     }
 </script>

初始化材质后,将纹理应用于材质,这样就可以看到一片云海了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var fog = new THREE.Fog( 0x4584b4, - 100, 3000 );
 
//材质
material = new THREE.MeshShaderMaterial( {
 
     uniforms: {
 
         "map": { type: "t", value:2, texture: texture },
         "fogColor" : { type: "c", value: fog.color },
         "fogNear" : { type: "f", value: fog.near },
         "fogFar" : { type: "f", value: fog.far }
 
     },
     vertexShader: document.getElementById( 'vs' ).textContent,
     fragmentShader: document.getElementById( 'fs' ).textContent,
     depthTest: false
 
 } );
  
 mesh = new THREE.Mesh( geometry, material );
 
 scene.addObject( mesh ); //加入场景
 
 renderer.setSize( window.innerWidth, window.innerHeight );
 document.body.appendChild( renderer.domElement );
 renderer.render( scene, camera );//渲染出来

让画面动起来

有两种实现方式,要么让相机(camera)改变位置,云一直不动;要么就是云动相机不动。然后requestAnimationFrame循环调用渲染器的render函数一直重绘场景,实现动画效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function animate() {
    requestAnimationFrame( animate );
    customRender();
}
 
function render() {
var v = 0.13;
var position = ( ( new Date().getTime() - start_time ) * v ) % cloud_num;
 
//相机移动
camera.position.x += ( -40 - camera.target.position.x ) * 0.02;
camera.position.y += ( -60 - camera.target.position.y ) * 0.02;
camera.position.z = - position + cloud_num;
 
camera.target.position.x = camera.position.x;
camera.target.position.y = camera.position.y;
camera.target.position.z = camera.position.z - 1000;
 
renderer.render( scene, camera );
}

总结

第一次接触WebGL,某些地方理解还不是很透彻,希望各位大神好好指导一下。某些地方可能说错了也求多多包容。

QQ图片20160601114726.gif

手机阅读请扫描下方二维码:

分享到: