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 安装包瘦身(下篇)

原文地址:https://www.cnblogs.com/zyfd/p/9711902.html

时间: 2024-11-14 21:53:47

ThreeJs 基础入门的相关文章

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

前言 我们前面使用的是自己编写的ts,以及自己手动引入的jquery,由于第三方库采用的是直接引入js,没有d.ts声明文件,开发起来很累,所以一般情况下我们使用npm引入第三方的库,本文记录使用npm,typescript开发threejs3D项目,搭建基础实例,为以后开发具体业务做准备 项目结构   依旧是熟悉的SpringBoot项目,不同以往的是使用了npm管理工具来下载依赖js库,类似maven,同时为了解决typescript编译后引入npm库的路径有问题,导致浏览器报错的问题,我们

Android基础入门教程——10.12 传感器专题(3)——加速度-陀螺仪传感器

Android基础入门教程--10.12 传感器专题(3)--加速度/陀螺仪传感器 标签(空格分隔): Android基础入门教程 本节引言: 本节继续来扣Android中的传感器,本节带来的是加速度传感器(Accelerometer sensor)以及 陀螺仪传感器(Gyroscope sensor),和上一节的方向传感器一样有着x,y,z 三个轴, 还是要说一点:x,y轴的坐标要和绘图那里的x,y轴区分开来!传感器的是以左下角 为原点的!x向右,y向上!好的,带着我们的套路来学本节的传感器吧

Android基础入门教程——8.1.3 Android中的13种Drawable小结 Part 3

Android基础入门教程--8.1.3 Android中的13种Drawable小结 Part 3 标签(空格分隔): Android基础入门教程 本节引言: 本节我们来把剩下的四种Drawable也学完,他们分别是: LayerDrawable,TransitionDrawable,LevelListDrawable和StateListDrawable, 依旧贴下13种Drawable的导图: 1.LayerDrawable 层图形对象,包含一个Drawable数组,然后按照数组对应的顺序来

Android基础入门教程——8.1.2 Android中的13种Drawable小结 Part 2

Android基础入门教程--8.1.2 Android中的13种Drawable小结 Part 2 标签(空格分隔): Android基础入门教程 本节引言: 本节我们继续来学习Android中的Drawable资源,上一节我们学习了: ColorDrawable:NinePatchDrawable: ShapeDrawable:GradientDrawable!这四个Drawable~ 而本节我们继续来学习接下来的五个Drawable,他们分别是: BitmapDrawable:Insert

Android基础入门教程——2.3.12 Date &amp; Time组件(下)

Android基础入门教程--2.3.12 Date & Time组件(下) 标签(空格分隔): Android基础入门教程 本节引言: 本节我们来继续学习Android系统给我们提供的几个原生的Date & Time组件,他们分别是: DatePicker(日期选择器),TimePicker(时间选择器),CalendarView(日期视图),好吧, 其实一开始让我扣这几个玩意我是拒绝的,因为在我的印象里,他们是这样的: 简直把我丑哭了,有木有,终于知道为什么那么多人喜欢自定义这种类型的

Android基础入门教程——2.1 View与ViewGroup的概念

Android基础入门教程--2.1 View与ViewGroup的概念 标签(空格分隔): Android基础入门教程 本节引言: 告别了第一章,迎来第二章--Android中的UI(User Interface)组件的详解, 而本节我们要学习的是所有控件的父类View和ViewGroup类!突发奇想,直接翻译官方文档对 这两个东西的介绍吧,对了,天朝原因,google上不去,Android developer上不去,我们可以 改hosts或者用vpn代理,当然也可以像笔者一样使用国内的API

DAX基础入门 – 30分钟从SQL到DAX — PowerBI 利器

看到漂漂亮亮的PowerBI报表,手痒痒怎么办?! 有没有面对着稀奇古怪的DAX而感到有点丈八金刚摸不着头脑或者干瞪眼?! 有没有想得到某个值想不出来DAX怎么写而直跳脚!? 看完这篇文章,你会恍然大悟,捂脸偷笑.呼呼呼~ 前言: 这篇文章对于具有一点SQL查询基础人会十分容易理解,譬如:掌握SELECT,SUM,GROUP BY等. 注:此文不涉及到Filter Context(筛选上下文)的介绍. 正文: 对于对SQL有一定了解的人来说,咋看DAX,怎么都不习惯. 但是,如果理解以下几个后,

Linux 基础入门----推荐课程

Linux 基础入门课程:https://www.shiyanlou.com/courses/1 很好的一门Linux基础课,精炼.简洁!推荐! 课程内容: 第1节 Linux 系统简介 https://www.shiyanlou.com/courses/1/labs/1/document 第2节 基本概念及操作 https://www.shiyanlou.com/courses/1/labs/2/document 第3节 用户及文件权限管理 https://www.shiyanlou.com/

从零基础入门JavaScript(1)

从零基础入门JavaScript(1) 1.1  Javascript的简史 1995年的时候   由网景公司开发的,当时的名字叫livescript    为了推广自己的livescript,搭了java顺风车,改名为javascript 与此同时,     微软因此在自身的浏览器里,也推出了自己的脚本语言 jscript 1997年时候,  由ECMA(欧洲计算机制造商协会)出面,推出了一套javascript的规范,Ecmascript ,规范提出js由三部分组成 JS的组成: ECMAS