做有态度的前端团队

网易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)。有了这三样东西,才能将物体渲染到网页中去。

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


    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的错。

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

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

   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下作了以下处理:

   <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>

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

   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函数一直重绘场景,实现动画效果。


    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

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