使用WebGL + Three.js制做动画场景

使用WebGL + Three.js制做动画场景

3D图像,技术,打造产品,还有互联网:这些只是我爱好的一小部分。php

如今,感谢WebGL的出现-一个新的JavaScriptAPI,它能够在不依赖任何插件的状况下渲染浏览器中的3D图像-这让3D渲染操做变得异常简单。ios

随着虚拟现实和加强现实应用的发展,大型厂商们开始转向数字化触觉体验,这是使人动心的一项技术。git

或者,至少那些已经投资的人这一年还抱有但愿-11亿美金流入VR和AR领域.github

Abbey Road Studios的谷歌交互之旅拍摄Deadliest Catch用到的舰队,经过在非真实世界给观众沉浸式的体验,全部的产品,服务和环境得以实现更好的配合。web

因为人们能接触到更多体验性的技术,2D开始变得有些单调。这是事实。chrome

让咱们现实点。目前看来,不少致力于创造体验的应用仍处在技术探索阶段,对大多数商业领域而言前景不算明朗。canvas

或者说他们真的创造了使人激动的体验吗?后端

走进WebGL:一项实用与灵活的技术,能够创造更强沉浸式3D内容。不管是Porsche展现一辆新911的细节,仍是NASA重点介绍的火星是什么样子,或者是J Dlla备受喜好的甜甜圈专辑庆典,WebGL能应用于不少领域来表现各类类型的故事。浏览器

为让你熟悉这项强大的技术,我打算作一个关于它是如何工做的简要归纳,还有使用Three.js(一个基于WebGL API的JavaScript库)一步步创造简单3D环境的快速教程。app

首先,什么是WebGL?

WebGL是一项在浏览器中展现基于硬件加速的3D图像的web技术,不须要安装额外插件或者下载多余的软件。

所以,不少受众能够更方便地接触到WebGL。浏览器支持程度也很不错(目前应用普遍),Chrome,Firefox,IE,Opera和Safari等主流移动端和桌面浏览器都提供了很好的支持。

许多计算机和智能手机有先进的图像渲染单元(GPUS),可直到最近,大多数网页和移动网站都不能使用GPUS。这致使设备的加载速度缓慢,图像质量低,而且对3D内容的支持程度也很低。

为了解决这个问题WebGL花了很多时间。基于著名的OpenGL 3D 图像标准,WebGL赋予Javascript插件式的自由接入方式,经过HTML5 元素链接一个设备的图像硬件,并在浏览器中直接应用3D技术。结果是360度的3D内容变得更容易建立—排除了使用独立应用或插件的干扰——同时用户能更容易地在网上拥有高清体验。

什么是Three.js?

OpenGL和WebGL的复杂度相差不大。

Three.js是一个开源语法库,简化了WebGL工具和环境的建立工做。它支持大部分基于GPU加速的低代码量3D动画。

聊得差很少了,让咱们编写代码吧

示例用Three.js库展现了更复杂的效果。为了练习须要,我会尽可能写的简单,用低复杂度的环境来展现仅靠理解的基础知识能实现什么效果。

我打算构建一个咱们已使用过的例子
Christmas-Closure_Header

让咱们开始用了解的基础知识作点东西吧。

一个渲染器,一个场景,还有一个相机

代码连接第一步

贡献者 Matt Agar(@agar)

代码发布于CodePen.

点击并拖动这个例子,作点尝试

CodePen上的例子至关于入门,如今咱们开始使用Three.js。

Firstly we need a Scene — a group or stage containing all the objects we want to render. Scenes allow you to set up what and where is going to be rendered by Three.js. This is where you place objects, lights, and cameras.
首先咱们须要一个场景 — 一个包含了咱们要渲染的全部对象的群组。场景容许你设置Three.js要渲染的对象和渲染位置,以及如何进行渲染。这个场景指的就是你放置对象,光线和相机的地方。

`var scene = new THREE.Scene();`

-用一个好方法建立场景

接下来咱们在这个例子中添加一个相机。我添加的是透视相机,但也有其余可用的选项。头两个参数分别指明了相机的视野区域和宽高比。后两个参数表明相机渲染对象的截止距离。

var camera = new THREE.PerspectiveCamera(
    75,                                   // Field of view
    window.innerWidth/window.innerHeight, // Aspect ratio
    0.1,                                  // Near clipping pane
    1000                                  // Far clipping pane
);

// Reposition the camera
camera.position.set(5,5,0);

// Point the camera at a given coordinate
camera.lookAt(new THREE.Vector3(0,0,0));

-添加相机,视场,宽高比和截止距离

最后相当重要的部分是渲染器自己,它掌握着一个来自给定相机视角场景的渲染。Three.js提供了不少种渲染器以供选择,但我决定在这个练习中使用标准的WebGL渲染器。

var renderer = new THREE.WebGLRenderer({ antialias: true });

// Size should be the same as the window
renderer.setSize( window.innerWidth, window.innerHeight );

// Set a near white clear color (default is black)
renderer.setClearColor( 0xeeeeee );

// Append to the document
document.body.appendChild( renderer.domElement );

// Render the scene/camera combination
renderer.render(scene, camera);

-添加渲染器

这个例子也包括了一些基础的几何结构— 在这里是一个扁平的平面 — 咱们能够看到一些特征以深度形式被渲染出来。若是没有它,咱们只能看到空空的屏幕。我接下来会简短介绍关于Geometry(几何结构),Materials(材质)和Meshes(网格)。

// A mesh is created from the geometry and material, then added to the scene
var plane = new THREE.Mesh(
  new THREE.PlaneGeometry( 5, 5, 5, 5 ),
  new THREE.MeshBasicMaterial( { color: 0x222222, wireframe: true } )
);
plane.rotateX(Math.PI/2);
scene.add( plane );

-添加一个扁平的平面

一个关于控制相机的小贴士

你可能已经意识到我在这个例子里使用了外部模块。这个是Three.js 的Github repo里能找到的众多可用模块的一个。

在这个例子里是轨道控制
它容许咱们捕获canvas元素上的鼠标事件以从新定位围绕着场景的相机。

var controls = new THREE.OrbitControls( camera, renderer.domElement );
controls.addEventListener( 'change', function() { renderer.render(scene, camera); } );

-实现轨道控制

在CodePen例子中从动做,点击和拖放或者滚动鼠标轮等方面检验轨道控制。在这个示例中,因为咱们没有设置动做循环(一旦我开始装饰个人圣诞树,我就会介绍动做循环),当控制发生更新时咱们一样须要从新渲染场景。

准备渲染

好吧,以前的例子如今可能看着有点蠢,但你在没有硬件基础的状况下没法构建一个更好的屋子或圣诞树。

是时候给咱们的场景添些东西了,如今有三件事须要咱们去探索:Geometries,Materials,还有Meshes。

代码连接第二步

贡献者Matt Agar(@agar)

来自Codepen

-铃儿叮当响。是,它们必定会响的

不管是点击仍是拖拽,快尝试一下吧。

使用平面阴影来添加一些简单的多边形

首先咱们须要一些Geometry。它能够是包含点和线的任何立方形状。

Three.js简化了一系列可实现的构建基础多边形操做。这里有不少种适合3D格式的文件加载器。你也能够选择经过指定顶点和表面建立你本身的几何结构。

如今,咱们将以一个基础的八面体做为开始。

`var geometry = new THREE.OctahedronGeometry(10,1);`

-添加Geometry

Materials描绘了对象的外观。它们的定义不受渲染器影响(大部分状况下),因此当你决定使用不一样的渲染器时没必要重写它。

这里是可实现的各类Materials,全部的Materials都使用一个包含各类属性的对象,属性会被应用于这些Materials。

下面的例子实现了一个扁平带阴影的Material,这展现了咱们的多边形对象,而不是打算对它们进行平滑处理。

var material = new THREE.MeshStandardMaterial( {
    color: 0xff0051,
    shading: THREE.FlatShading, // default is THREE.SmoothShading
    metalness: 0,
    roughness: 1
} );

-用Materials肯定对象的纹理

第三个咱们须要的是Mesh(网格)。一个Mesh就是一个对象,它获得一个多面体并给它应用Material,咱们能够把网格插入咱们的场景中并自由移动它。

下面是如何来合并Geometry和Material并放入一个Mesh,而后添加到场景中。须要指明的是,将Mesh添加进场景后,咱们能够自由地从新定位或者旋转它。

var shapeOne = new THREE.Mesh(geometry, material);
shapeOne.position.y += 10;
scene.add(shapeOne);

-将Geometry和Material合并进一个Mesh中,并将Mesh加入场景

添加光线

一旦咱们在场景中拥有了对象,咱们须要照亮它们。为了实现这种效果,咱们会添加两类不一样的光线:环境光和点状光。

环境光的色彩会全局应用到场景中全部的对象。

var ambientLight = new THREE.AmbientLight( 0xffffff, 0.2 );
scene.add( ambientLight );

-给场景添加环境光

点状光在场景中某特定位置建立光。光在任何方向都会闪烁,大概和灯泡的效果相似。

var pointLight = new THREE.PointLight( 0xffffff, 1 );
pointLight.position.set( 25, 50, 25 );
scene.add( pointLight );

-为场景添加点状光

若是这些不能知足你的需求,还有其余种类的光能够选择,包括定向光和斑点光。查看Three.js 光线手册来得到更多信息。

制造并接收阴影

阴影默认是不能使用的,但对建立视觉上的深度颇有帮助 — 因此咱们须要在渲染器上启用它们。

renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;

-在渲染器中启用阴影

下一步是指定哪些光线能够造成阴影,还有要渲染的阴影范围有多大。

pointLight.castShadow = true;
pointLight.shadow.mapSize.width = 1024;
pointLight.shadow.mapSize.height = 1024;

-启用光线。相应地,阴影也会出现

最终咱们指定哪些网格应该接收阴影。须要指明的是任何网格在不依赖场景的状况下都能制造和接收阴影。

shapeOne.castShadow = true;
shapeOne.receiveShadow = true;

-利用阴影来突出Mesh

在这个场景里,咱们使用了一个特殊的阴影Material。它容许一个Mesh仅展现阴影,而非对象自己。

var shadowMaterial = new THREE.ShadowMaterial( { color: 0xeeeeee } );
shadowMaterial.opacity = 0.5;

-实现阴影效果

利用简单要素构建复杂物体

目前为止咱们作的一些简单的例子还不错,可若是咱们能实现元素复用的话事情会更简单。

代码连接 第三步

代码贡献者 (@agar)

来自 CodePen.

-能肯定的是,这些拼合的多边形变得更小巧了

在codepen中点击并拖拽以得到更清晰的效果。

经过对多边形对象进行合并和分层操做,咱们能够开始建立更多的复杂形状。

下面的操做是扩展Three.Group对象以求在构造器中建立复杂形状。

var Decoration = function() {

    // Run the Group constructor with the given arguments
    THREE.Group.apply(this, arguments);

    // A random color assignment
    var colors = ['#ff0051', '#f56762','#a53c6c','#f19fa0','#72bdbf','#47689b'];

    // The main bauble is an Octahedron
    var bauble = new THREE.Mesh(
        addNoise(new THREE.OctahedronGeometry(12,1), 2),
        new THREE.MeshStandardMaterial( {
            color: colors[Math.floor(Math.random()*colors.length)],
            shading: THREE.FlatShading ,
            metalness: 0,
            roughness: 1
    } )
    );
    bauble.castShadow = true;
    bauble.receiveShadow = true;
    bauble.rotateZ(Math.random()*Math.PI*2);
    bauble.rotateY(Math.random()*Math.PI*2);
    this.add(bauble);

    // A cylinder to represent the top attachment
    var shapeOne = new THREE.Mesh(
        addNoise(new THREE.CylinderGeometry(4, 6, 10, 6, 1), 0.5),
        new THREE.MeshStandardMaterial( {
            color: 0xf8db08,
            shading: THREE.FlatShading ,
            metalness: 0,
            roughness: 1
        } )
    );
    shapeOne.position.y += 8;
    shapeOne.castShadow = true;
    shapeOne.receiveShadow = true;
    this.add(shapeOne);
};
Decoration.prototype = Object.create(THREE.Group.prototype);
Decoration.prototype.constructor = Decoration;

-在构造器中建立复杂形状

咱们如今能屡次复用拼合获得的多边形来给咱们的场景添加多重距离,用比单首创建每个元素更少的工做量让树木更真实。

var decoration = new Decoration();
decoration.position.y += 10;
scene.add(decoration);

-装饰树干

另外一个建议是给建立的对象添加一个随机的元素。

在对象的Geometry内移动顶点,以添加一个随机组织的元素来下降形状复杂度。若没有这些小缺陷,作出来的物体会有点合成的感受。我使用了一个辅助函数来给Geometry的顶点随机添加噪点。

function addNoise(geometry, noiseX, noiseY, noiseZ) {
    var noiseX = noiseX || 2;
    var noiseY = noiseY || noiseX;
    var noiseZ = noiseZ || noiseY;
    for(var i = 0; i < geometry.vertices.length; i++){
        var v = geometry.vertices[i];
        v.x += -noiseX / 2 + Math.random() * noiseX;
        v.y += -noiseY / 2 + Math.random() * noiseY;
        v.z += -noiseZ / 2 + Math.random() * noiseZ;
    }
    return geometry;
}

-添加噪点可使对象更真实

实现动做

目前为止咱们只为WebGLRender实现了一个单独的渲染调用。为了向咱们的场景中添加一些动做,咱们须要作出一些更新。

代码连接 第四步

代码贡献者 (@agar)

来自 CodePen.

-观察下多面体催眠式的缓慢旋转

渲染循环

为了使浏览器适应咱们的更新速度,咱们正在使用浏览器动做请求框架API来调用一个新的渲染函数。

requestAnimationFrame(render);
function render() {
    // Update camera position based on the controls
    controls.update();

    // Re-render the scene
    renderer.render(scene, camera);

    // Loop
    requestAnimationFrame(render);
}

-利用动做请求框架建立一个渲染循环

超时更新元素

如今,我会对复杂对象作出一些改变,每次建立距离时给装饰物初始化一个随机旋转速度。

this.rotationSpeed = Math.random() * 0.02 + 0.005;
this.rotationPosition = Math.random();

-进入旋转

咱们一样设置了一个能够被调用来基于当前值值绕Y轴旋转的新函数。须要指出的是旋转速度基于浏览器取得的帧率,但对这个简单的例子来讲还好。对处理这个过程而言,你必定会用到数学函数。

Decoration.prototype.updatePosition = function() {
    this.rotationPosition += this.rotationSpeed;
    this.rotation.y = (Math.sin(this.rotationPosition));
};

-观察装饰物旋转状况

随着一个更新函数的定义,每运行一次咱们就能经过更新渲染循环来从新计算每一个元素每次被建立的位置。

function render() {
    // Update camera position based on the controls
    controls.update();

    // Loop through items in the scene and update their position
    for(var d = 0; d < decorations.length; d++) {
        decorations[d].updatePosition();
    }

    // Re-render the scene
    renderer.render(scene, camera);

    // Loop
    requestAnimationFrame(render);
}

-从新计算元素位置

把以上的几个例子结合在一块儿

代码连接 第五步

代码贡献者 (@agar)

来自 CodePen.

-3D圣诞树:彻底成型,装饰完美

最终的产品总算出来了。仅仅使用了基础功能,咱们已经构建出一个交互式的3D圣诞树,而且创建了一个平面的二维场景。

但这只是使用WebGL的开始。当这项技术快速发展的时候,会出现许多可供选择的资源,还有能正确指导你的教程。如下是资源连接:

你还在等什么?尝试下WebGL和Three.js吧,开始建立你本身的3D效果。若是你作了一些有趣的玩意,请告诉我。我很乐意欣赏一下。

分享

Matt Agar

关于做者

拥有超过15年的工程经验,Matt是August的创始成员之一,并团结了世界上一批很优秀的先后端开发者。做为能适应任何情景的问题解决者,Matt从实用角度和大方向上审视项目的技术问题。当他不在解决问题时,他必定在搭建一个虚构的动物王国并和年轻的家人一块儿探索户外。

联系方式

相关文章
相关标签/搜索