ThreeJs 基础入门
本文来自网易云社区
作者:唐钊
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 基础入门的更多相关文章
- TypeScript进阶开发——ThreeJs基础实例,从入坑到入门
前言 我们前面使用的是自己编写的ts,以及自己手动引入的jquery,由于第三方库采用的是直接引入js,没有d.ts声明文件,开发起来很累,所以一般情况下我们使用npm引入第三方的库,本文记录使用np ...
- js学习笔记:webpack基础入门(一)
之前听说过webpack,今天想正式的接触一下,先跟着webpack的官方用户指南走: 在这里有: 如何安装webpack 如何使用webpack 如何使用loader 如何使用webpack的开发者 ...
- 「译」JUnit 5 系列:基础入门
原文地址:http://blog.codefx.org/libraries/junit-5-basics/ 原文日期:25, Feb, 2016 译文首发:Linesh 的博客:JUnit 5 系列: ...
- .NET正则表达式基础入门
这是我第一次写的博客,个人觉得十分不容易.以前看别人写的博客文字十分流畅,到自己来写却发现十分困难,还是感谢那些为技术而奉献自己力量的人吧. 本教程编写之前,博主阅读了<正则指引>这本入门 ...
- 从零3D基础入门XNA 4.0(2)——模型和BasicEffect
[题外话] 上一篇文章介绍了3D开发基础与XNA开发程序的整体结构,以及使用Model类的Draw方法将模型绘制到屏幕上.本文接着上一篇文章继续,介绍XNA中模型的结构.BasicEffect的使用以 ...
- 从零3D基础入门XNA 4.0(1)——3D开发基础
[题外话] 最近要做一个3D动画演示的程序,由于比较熟悉C#语言,再加上XNA对模型的支持比较好,故选择了XNA平台.不过从网上找到很多XNA的入门文章,发现大都需要一些3D基础,而我之前并没有接触过 ...
- Shell编程菜鸟基础入门笔记
Shell编程基础入门 1.shell格式:例 shell脚本开发习惯 1.指定解释器 #!/bin/bash 2.脚本开头加版权等信息如:#DATE:时间,#author(作者)#mail: ...
- [Spring框架]Spring AOP基础入门总结二:Spring基于AspectJ的AOP的开发.
前言: 在上一篇中: [Spring框架]Spring AOP基础入门总结一. 中 我们已经知道了一个Spring AOP程序是如何开发的, 在这里呢我们将基于AspectJ来进行AOP 的总结和学习 ...
- [Spring框架]Spring AOP基础入门总结一.
前言:前面已经有两篇文章讲了Spring IOC/DI 以及 使用xml和注解两种方法开发的案例, 下面就来梳理一下Spring的另一核心AOP. 一, 什么是AOP 在软件业,AOP为Aspect ...
随机推荐
- SpringBoot 2.x (14):WebFlux响应式编程
响应式编程生活案例: 传统形式: 一群人去餐厅吃饭,顾客1找服务员点餐,服务员把订单交给后台厨师,然后服务员等待, 当后台厨师做好饭,交给服务员,经过服务员再交给顾客1,依此类推,该服务员再招待顾客2 ...
- JavaScprit30-6 学习笔记
今天学习的是 仿即时搜索诗句效果 第一个问题: fetch() Fetch API 提供了一个 JavaScript接口,用于访问和操纵HTTP管道的部分,例如请求和响应.它还提供了一个全局 fe ...
- UVA11478 Halum (差分约束)
每次操作是独立的,而且顺序并不影响,作用在同一个结点上的d可以叠加,所以令x(u) = sigma(dui). 最后就是要确定所有的x(u). 因为m越大,满足条件的边就越少,二分答案m. 对于一条边 ...
- Java中this关键字的用法
this关键字作用: 1. 如果存在同名成员变量与局部变量时,在方法内部默认是访问局部变量的数据,可以通过this关键字指定访问成员变量的数据. 2. 在一个构造函数中可以调用另外一个构造函数初始化对 ...
- java基础—equals方法
一.equals方法介绍 1.1.通过下面的例子掌握equals的用法 1 package cn.galc.test; 2 3 public class TestEquals { 4 public s ...
- 03_6_package和import语句
03_6_package和import语句 1. package和import语句 为便于管理大型软件系统中数目众多的类,解决类的命名冲突问题,Java引入包(package)机制,提供类的多重命名空 ...
- 关于lua 5.3 服务端热更新流程
脚本的热更新的流程都大同小异, 第一步先保存旧代码的块的数据, 第二部加载新的代码块,第三步将旧代码块的局部和全局数据拷贝到新代码块的对应的 变量中. 在服务器热更新中,主要考虑热更的内容是什么, 一 ...
- cocos2d-x的基本动作2
1.基本动作 Cocos2d提供的基本动作:瞬时动作.延时动作.运作速度. 瞬时动作:就是不需要时间,马上就完成的动作.瞬时动作的共同基类是 InstantAction. Cocos2d提供以下瞬时动 ...
- 微信iOS多设备多字体适配方案总结
一.背景 2014下半年,微信iOS版先后适配iPad, iPhone6/6plus.随着这些大屏设备的登场,部分用户觉得微信的字体太小,但也有很多用户不喜欢太大的字体.为了满足不同用户的需求,我们做 ...
- Sql Server 查询今天,昨天,近七天....数据
今天数据: 昨天数据: 7天内数据: 30天内数据: 本月数据: 本年数据: 查询今天是今年的第几天: select datepart(dayofyear,getDate()) 查询今天是本月的第几天 ...