本文来自网易云社区

作者:唐钊

Three.js 是一款运行在浏览器中的 3D 引擎,你可以用它在 web 中创建各种三维场景,包括了摄影机、光影、材质等各种对象。使用它可以让我们更加直观的了解 webgl 的世界。

3D 场景前置知识

1.场景(Scene):是物体、光源等元素的容器,可以配合 chrome 插件使用,抛出 window.scene即可实时调整 obj 的信息和材质信息。2.相机(Camera):场景中的相机,代替人眼去观察,场景中只能添加一个,一般常用的是透视相机(PerspectiveCamera)3.物体对象(Mesh):包括二维物体(点、线、面)、三维物体,模型等等4.光源(Light):场景中的光照,如果不添加光照场景将会是一片漆黑,包括全局光、平行光、点光源等5.渲染器(Renderer):场景的渲染方式,如webGL\canvas2D\Css3D。6.控制器(Control): 可通过键盘、鼠标控制相机的移动

下面我们依次详细学习以上的细分知识点。

相机

Three.js中我们常用的有两种类型的相机:正交(orthographic)相机、透视(perspective)相机。一般情况下为了模拟人眼我们都是使用透视相机; 正交镜头的特点是,物品的渲染尺寸与它距离镜头的远近无关。也就是说在场景中移动一个物体,其大小不会变化。正交镜头适合2D游戏。 透视镜头则是模拟人眼的视觉特点,距离远的物体显得更小。透视镜头通常更适合3D渲染。

THREE.PerspectiveCamera(fov,aspect,near,far)

参数 描述
fov 视野角度,从镜头可以看到的场景的部分。通常3D游戏的FOV取值在60-90度之间较好的默认值为60
aspect 渲染区域的纵横比。较好的默认值为window.innerWidth/window.innerHeight
near 最近离镜头的距离
far 远离镜头的距离

透视相机示意图:

创建摄像机以后还要对其进行移动、然后对准物体积聚的场景中心位置,分别是设置其 position和调用 lookAt 方法,参数均是一个 xyz向量(new THREE.Vector3(x,y,z))

camera.position:控制相机在整个3D环境中的位置(取值为3维坐标对象-THREE.Vector3(x,y,z))
camera.lookAt:控制相机的焦点位置,决定相机的朝向(取值为3维坐标对象-THREE.Vector3(x,y,z))

灯光

在Three.js中光源是必须的,如果一个场景你不设置灯光那么世界将会是一片漆黑。Three.js内置了多种光源以满足特定场景的需要。大家可以根据自己的项目需要来选择何种灯光

光源分类

光源 说明
AmbientLight 环境光,其颜色均匀的应用到场景及其所有对象上,这种光源为场景添加全局的环境光。
这种光没有特定的方向,不会产生阴影。通常不会把AmbientLight作为唯一的光源,
而是和SpotLight、DirectionalLight等光源结合使用,从而达到柔化阴影、添加全局色调的效果。
指定颜色时要相对保守,例如#0c0c0c。设置太亮的颜色会导致整个画面过度饱和,什么都看不清:
PointLight 3D空间中的一个点光源,向所有方向发出光线
SpotLight 产生圆锥形光柱的聚光灯,台灯、天花板射灯通常都属于这类光源,这种光源的使用场景最多
,特别是在你需要阴影效果的时候。
DirectionalLight 也就无限光,光线是平行的。典型的例子是日光,用于模拟遥远的,类似太阳那样的光源。
该光源与SpotLight的主要区别是,它不会随着距离而变暗,所有被照耀的地方获得相同的光照强度。
HemisphereLight 特殊光源,用于创建户外自然的光线效果,
此光源模拟物体表面反光效果、微弱发光的天空,模拟穹顶(半球)的微弱发光效果,
让户外场景更加逼真。使用DirectionalLight + AmbientLight可以在某种程度上来模拟户外光线,
但是不够真实,因为无法体现大气层的散射效果、地面或物体的反射效果
AreaLight 面光源,指定一个发光的区域
LensFlare 不是光源,用于给光源添加镜头光晕效果

关于光源的详细 API 大家可以参考 threejs 官网,很详细,demo 也很完整 传送门

Mesh

在计算机的世界里,一条弧线是由有限个点构成的有限条线段连接得到的。当线段数量越多,长度就越短,当达到你无法察觉这是线段时,一条平滑的弧线就出现了。 计算机的三维模型也是类似的。只不过线段变成了平面,普遍用三角形组成的网格来描述。我们把这种模型称之为 Mesh 模型。 在 threeJs 的世界中,材质(Material)+几何体(Geometry)就是一个 mesh。设置其name属性可以通过scene.getObjectByName(name)获取该物体对象;Geometry就好像是骨架,材质则类似于皮肤,对于材质和几何体的分类见下表格

材质分类

材质 说明
MeshBasicMaterial 基本的材质,显示为简单的颜色或者显示为线框。不考虑光线的影响
MeshDepthMaterial 使用简单的颜色,但是颜色深度和距离相机的远近有关
MeshNormalMaterial 基于面Geometry的法线(normals)数组来给面着色
MeshFacematerial 容器,允许为Geometry的每一个面指定一个材质
MeshLambertMaterial 考虑光线的影响,哑光材质
MeshPhongMaterial 考虑光线的影响,光泽材质
ShaderMaterial 允许使用自己的着色器来控制顶点如何被放置、像素如何被着色
LineBasicMaterial 用于THREE.Line对象,创建彩色线条
LineDashMaterial 用于THREE.Line对象,创建虚线条
RawShaderMaterial 仅和THREE.BufferedGeometry联用,优化静态Geometry(顶点、面不变)的渲染
SpriteCanvasMaterial 在针对单独的点进行渲染时用到
SpriteMaterial 在针对单独的点进行渲染时用到
PointCloudMaterial 在针对单独的点进行渲染时用到

几何图形

2D

图形 说明
矩形 THREE.PlaneGeometry 外观上是一个矩形
new THREE.PlaneGeometry(width, height, widthSegments, heightSegments);
THREE.CircleGeometry
传送门
外观上是一个圆形或者扇形
// 半径为3的圆
new THREE.CircleGeometry(3, 12);
// 半径为3的半圆
new THREE.CircleGeometry(3, 12, 0, Math.PI);
第三个参数和第四个分别是起始角度和结束角度,默认0-2*PI
THREE.RingGeometry
传送门
外观上是一个圆环或者扇环
new THREE.RingGeometry(innerRadius, outerRadius, thetaSegments, phiSegments, thetaStart, thetaLength)
THREE.ShapeGeometry
传送门
该形状允许你创建自定义的二维图形,其操作方式类似于SVG/Canvas中的画布

3D

图形 说明
THREE.BoxGeometry
传送门
这是一个具有长宽高的盒子
BoxGeometry(width, height, depth, widthSegments, heightSegments, depthSegments)
THREE.SphereGeometry
传送门
这是一个三维球体/不完整球体
SphereGeometry(radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength)
THREE. CylinderGeometry
传送门
可以绘制圆柱、圆筒、圆锥或者截锥
new THREE.CylinderGeometry(radiusTop,radiusBottom,height,radialSegments,heightSegments,openEnded)

加载外部模型

一般来讲我们的场景中不可能都是一些奇奇怪怪的形状,或多或少项目中都会用到一些外部的模型资源,不如动物啊,装饰物啊什么的,再加上一些动画,这样整个场景更加显得生动,那么 threejs 中我们可以通过哪些方式来加载外部的模型资源呢?

加载外部模型,是通过Three.js加载器(Loader)实现的。加载器把文本/二进制的模型文件转化为Three.js对象结构。 每个加载器理解某种特定的文件格式。

需要注意的是,由于贴图的尺寸必须是(2的幂数)X (2的幂数),如:1024X512,所以为了防止贴图变形,平面的宽度比例需要与贴图的比例一致。

支持的格式

格式 说明
JSON Three.js自定义的、基于JSON的格式。可以声明式的定义一个Geometry或者Scene.利用该格式,你可以方便的重用复杂的Geometry或Scene
OBJ / MTL OBJ是Wavefront开发的一种简单3D格式,此格式被广泛的支持,用于定义Geometry,MTL用于配合OBJ,它指定OBJ使用的材质
Collada(dae) 基于XML的格式,被大量3D应用程序、渲染引擎支持
STL STereoLithography的简写,在快速原型领域被广泛使用。3D打印模型通常使用该格式定义Three.js提供了STLExporter.js,使用它可以把Three.js模型导出为STL格式
CTM openCTM定义的格式,以紧凑的格式存储基于三角形的Mesh
VTK Visualization Toolkit定义的格式,用于声明顶点和面。此格式有二进制/ASCII两种变体,Three.js仅支持ASCII变体
AWD 3D场景的二进制格式,主要被away3d引擎使用,Three.js不支持AWD压缩格式
Assimp 开放资产导入库(Open asset import library)是导入多种3D模型的标准方式。使用该Loader你可以导入多种多样的3D模型格式
VRML 虚拟现实建模语言(Virtual Reality Modeling Language)是一种基于文本的格式,现已经被X3D格式取代尽管Three.js不直接支持X3D,但是后者很容易被转换为其它格式
Babylon 游戏引擎Babylon的私有格式
PLY 常用于存储来自3D扫描仪的信息

在项目一开始尝试是使用 dae 文件,后面发现 json 文件更加方便一点,所以最终使用的是 jsonloader 导入 json 文件。json文件可以通过 blender 或者3DsMax 导出,他们都有各自的 export json的插件。在软件中处理好模型贴图和动画以后,导出 json 文件和相应的贴图文件给到前端即可。

var jsonLoader = new THREE.JSONLoader();
 jsonLoader.load('model.json', function (geometry, materials) {
    materials.forEach(function (mat) {         //这里面可以设置材质的各种信息
        mat.skinning = true;
        mat.color = new THREE.Color("rgb(233,203,113)"); //模型颜色
        mat.emissive = new THREE.Color("rgb(110,110,110)");//自发光颜色
    });    var model = new THREE.SkinnedMesh(geometry, new THREE.MeshFaceMaterial(materials));
    model.name = “model name”;
    scene.add(model);    //下面是播放模型中的动画内容
    var sceneAnimationClip = model.geometry.animations[0]    var mixer = new THREE.AnimationMixer(model);
    mixers.push(mixer);    var sceneAnimation = mixer.clipAction(sceneAnimationClip);
    sceneAnimation.play(); });

同理其他类型的文件也可以使用相应的 loader 导入文件,控制其材质信息和动画播放,具体的可以查看官网的 demo。

粒子

THREE.Sprite

在WebGlRenderer渲染器中使用THREE.Sprite创建的粒子可以直接添加到scene中。创建出来的精灵总是面向镜头的。即不会有倾斜变形之类透视变化,只有近大远小的变化。

比如一个纹理为花瓣的粒子示例:

//花瓣的贴图var textureList = [
        __uri("../../img/flower-1.png"),
        __uri("../../img/flower-2.png"),
        __uri("../../img/flower-3.png"),
        __uri("../../img/flower-4.png"),
        __uri("../../img/flower-5.png"),
        __uri("../../img/flower-6.png"),
        __uri("../../img/flower-7.png"),
        __uri("../../img/flower-8.png"),
        __uri("../../img/flower-9.png"),
        __uri("../../img/flower-10.png")]var particles = []; //存储生成的粒子//粒子从Z轴产生区间在-20到20for (var zpos = -20; zpos < 20; zpos += 0.5) {    var texturerain = textLoader.load(textureList[Math.floor(Math.random() * 10)])    var material = new THREE.SpriteMaterial({
            transparent: true,
            opacity: util.getRandomInt(0.7, 1),
            map: texturerain
        }
    );    //生成粒子
    particle = new THREE.Sprite(material);
    particle.name = "particle"
    //随即产生x轴,y轴
    particle.position.x = Math.random() * 100 
    particle.position.y = Math.random() * 100;    //设置z轴
    particle.position.z = zpos;    //将产生的粒子添加到场景
    scene.add(particle);    //将粒子位置的值保存到数组
    particles.push(particle);
}//移动粒子的函数function updateParticles() {    //遍历每个粒子
    for (var i = 0; i < particles.length; i++) {
        particle = particles[i];        //设置粒子向前移动的速度依赖于鼠标在平面Y轴上的距离
        particle.position.y -= i / particles.length / 50;
        particle.position.x -= i / particles.length / 80;        if (particle.position.y < -7) { //溢出视野以后设置回原位置
            particle.position.x = Math.random() * 100 - 50;
            particle.position.y = 7;
        }
    }
   
    }

场景交互

Three.js中并没有直接提供“点击”功能,一开始使用的时候我也觉得一脸懵逼,后来才发现我们可以基于THREE.Raycaster来判断鼠标当前对应到哪个物体,用来进行碰撞检测.

//核心代码var clickObjects = []; //存储哪些 obj 需要交互var _raycaster = new THREE.Raycaster();//射线拾取器var raycAsix = new THREE.Vector2();//屏幕点击点二维坐标var container = null;function onMouseMove(event) {
    event.preventDefault();
    container = document.getElementById("Canvas1");
    raycAsix.x = ( (event.pageX - $(container).offset().left) / container.offsetWidth ) * 2 - 1;
    raycAsix.y = -( (event.pageY - $(container).offset().top) / container.offsetHeight ) * 2 + 1;
    _raycaster.setFromCamera(raycAsix, Camera);    var intersects = _raycaster.intersectObjects(clickObjects);//获取射线上与存储的可被点击物体的集合的交集,集合的第一个物体为距离相机最近的物体,最后一个则为离相机最远的。
    if (intersects.length > 0) {        document.body.style.cursor = 'pointer';        console.log(intersects[0].object.name) //打印导入模型时设置的model name
    } else {        document.body.style.cursor = 'default';
    }
}

其他的交互比如点击事件都是基于此。

动画

场景中如果我们添加了各种 mesh 和模型并给他加入了一些 tweend动画会发现他并不会运动,因为你的场景并没有实时渲染,所以要让场景真的动起来,我们需要用到requestAnimationFrame;关于它的详细使用请大家自行 google,核心代码如下

var requestAnimationFrame = window.requestAnimationFrame
        || window.mozRequestAnimationFrame
        || window.webkitRequestAnimationFrame
        || window.msRequestAnimationFrame; function animate() {    var delta = clock.getDelta();    if (mixers.length > 0) {        for (var i = 0; i < mixers.length; i++) {
            mixers[i].update(delta);
        }
    }    //Renderer即我们实例化的 webglRender 对象;
    updateParticles()
    Renderer.clear();
    Renderer.render(scene, Camera);
    requestAnimationFrame(animate);    //如果有使用 Tween做一些补间动画,也需要在此调用 TWEEN.update();
    TWEEN.update();
}

另外我们如果想自己 K 动画也是可以的,不过我觉得应该没有人这么无聊

jsonLoader.load('../resource/hudie.json', function ( geometry, materials ) {
    materials.forEach(function (mat){
        mat.skinning = true;
        mat.color =  new THREE.Color("rgb(0,255, 0)");
        mat.emissive =new THREE.Color("rgb(255, 0, 255)");
    });    var tracks = [];    //NumberKeyframeTrack(name,times,values),依次去K模型的位置,缩放和旋转
    tracks.push( new THREE.NumberKeyframeTrack( '.position', [ 0, 1, 2 ], [ -15,-5,-7,  0, 0, 0, 5,0.3,-9 ] ) );
    tracks.push( new THREE.NumberKeyframeTrack( '.scale', [ 0, 1, 2 ], [  0.04, 0.04, 0.04,  0.04, 0.04, 0.04,     0.04, 0.04, 0.04] ) );
    tracks.push( new THREE.NumberKeyframeTrack( '.rotation', [ 0, 1, 2 ], [  0, 0.2, 0,  0, 0, 0, 0, 0.2, 0.2 ] ) );    var  model = new THREE.SkinnedMesh(geometry, new THREE.MeshFaceMaterial(materials));
    model.name="蝴蝶";     var clip = new THREE.AnimationClip( 'Action', -1, tracks );     var mixer = new THREE.AnimationMixer( model );
     mixers.push(mixer);
     mixer.clipAction( clip ).play();
     scene.add( model );
} );

网易云免费体验馆,0成本体验20+款云产品!

更多网易研发、产品、运营经验分享请访问网易云社区

相关文章:
【推荐】 iOS 安装包瘦身(下篇)

ThreeJs 基础入门的更多相关文章

  1. TypeScript进阶开发——ThreeJs基础实例,从入坑到入门

    前言 我们前面使用的是自己编写的ts,以及自己手动引入的jquery,由于第三方库采用的是直接引入js,没有d.ts声明文件,开发起来很累,所以一般情况下我们使用npm引入第三方的库,本文记录使用np ...

  2. js学习笔记:webpack基础入门(一)

    之前听说过webpack,今天想正式的接触一下,先跟着webpack的官方用户指南走: 在这里有: 如何安装webpack 如何使用webpack 如何使用loader 如何使用webpack的开发者 ...

  3. 「译」JUnit 5 系列:基础入门

    原文地址:http://blog.codefx.org/libraries/junit-5-basics/ 原文日期:25, Feb, 2016 译文首发:Linesh 的博客:JUnit 5 系列: ...

  4. .NET正则表达式基础入门

    这是我第一次写的博客,个人觉得十分不容易.以前看别人写的博客文字十分流畅,到自己来写却发现十分困难,还是感谢那些为技术而奉献自己力量的人吧. 本教程编写之前,博主阅读了<正则指引>这本入门 ...

  5. 从零3D基础入门XNA 4.0(2)——模型和BasicEffect

    [题外话] 上一篇文章介绍了3D开发基础与XNA开发程序的整体结构,以及使用Model类的Draw方法将模型绘制到屏幕上.本文接着上一篇文章继续,介绍XNA中模型的结构.BasicEffect的使用以 ...

  6. 从零3D基础入门XNA 4.0(1)——3D开发基础

    [题外话] 最近要做一个3D动画演示的程序,由于比较熟悉C#语言,再加上XNA对模型的支持比较好,故选择了XNA平台.不过从网上找到很多XNA的入门文章,发现大都需要一些3D基础,而我之前并没有接触过 ...

  7. Shell编程菜鸟基础入门笔记

    Shell编程基础入门     1.shell格式:例 shell脚本开发习惯 1.指定解释器 #!/bin/bash 2.脚本开头加版权等信息如:#DATE:时间,#author(作者)#mail: ...

  8. [Spring框架]Spring AOP基础入门总结二:Spring基于AspectJ的AOP的开发.

    前言: 在上一篇中: [Spring框架]Spring AOP基础入门总结一. 中 我们已经知道了一个Spring AOP程序是如何开发的, 在这里呢我们将基于AspectJ来进行AOP 的总结和学习 ...

  9. [Spring框架]Spring AOP基础入门总结一.

    前言:前面已经有两篇文章讲了Spring IOC/DI 以及 使用xml和注解两种方法开发的案例, 下面就来梳理一下Spring的另一核心AOP. 一, 什么是AOP 在软件业,AOP为Aspect ...

随机推荐

  1. SpringBoot 2.x (14):WebFlux响应式编程

    响应式编程生活案例: 传统形式: 一群人去餐厅吃饭,顾客1找服务员点餐,服务员把订单交给后台厨师,然后服务员等待, 当后台厨师做好饭,交给服务员,经过服务员再交给顾客1,依此类推,该服务员再招待顾客2 ...

  2. JavaScprit30-6 学习笔记

    今天学习的是  仿即时搜索诗句效果 第一个问题: fetch() Fetch API  提供了一个 JavaScript接口,用于访问和操纵HTTP管道的部分,例如请求和响应.它还提供了一个全局 fe ...

  3. UVA11478 Halum (差分约束)

    每次操作是独立的,而且顺序并不影响,作用在同一个结点上的d可以叠加,所以令x(u) = sigma(dui). 最后就是要确定所有的x(u). 因为m越大,满足条件的边就越少,二分答案m. 对于一条边 ...

  4. Java中this关键字的用法

    this关键字作用: 1. 如果存在同名成员变量与局部变量时,在方法内部默认是访问局部变量的数据,可以通过this关键字指定访问成员变量的数据. 2. 在一个构造函数中可以调用另外一个构造函数初始化对 ...

  5. java基础—equals方法

    一.equals方法介绍 1.1.通过下面的例子掌握equals的用法 1 package cn.galc.test; 2 3 public class TestEquals { 4 public s ...

  6. 03_6_package和import语句

    03_6_package和import语句 1. package和import语句 为便于管理大型软件系统中数目众多的类,解决类的命名冲突问题,Java引入包(package)机制,提供类的多重命名空 ...

  7. 关于lua 5.3 服务端热更新流程

    脚本的热更新的流程都大同小异, 第一步先保存旧代码的块的数据, 第二部加载新的代码块,第三步将旧代码块的局部和全局数据拷贝到新代码块的对应的 变量中. 在服务器热更新中,主要考虑热更的内容是什么, 一 ...

  8. cocos2d-x的基本动作2

    1.基本动作 Cocos2d提供的基本动作:瞬时动作.延时动作.运作速度. 瞬时动作:就是不需要时间,马上就完成的动作.瞬时动作的共同基类是 InstantAction. Cocos2d提供以下瞬时动 ...

  9. 微信iOS多设备多字体适配方案总结

    一.背景 2014下半年,微信iOS版先后适配iPad, iPhone6/6plus.随着这些大屏设备的登场,部分用户觉得微信的字体太小,但也有很多用户不喜欢太大的字体.为了满足不同用户的需求,我们做 ...

  10. Sql Server 查询今天,昨天,近七天....数据

    今天数据: 昨天数据: 7天内数据: 30天内数据: 本月数据: 本年数据: 查询今天是今年的第几天: select datepart(dayofyear,getDate()) 查询今天是本月的第几天 ...