ThreeJs 基础

166 阅读4分钟

Three.js 是一款运行在浏览器中的 3D 引擎,你可以用它在 web 中创建各种三维场景,包括了摄影机、光影、材质等各种对象。

3D 场景知识点

7ff3e8267d11bcb2b7387da5f5c5f174.png

  • 场景Scene:是物体、光源等元素的容器。
  • 相机Camera:场景中的相机,代替人眼去观察,场景中只能添加一个,常用的是透视相机PerspectiveCamera
  • 物体对象Mesh:包括二维物体(点、线、面)、三维物体,模型等。
  • 光源Light:场景中的光照,如果不添加光照场景将会是一片漆黑,包括全局光、平行光、点光源等。
  • 渲染器Renderer:场景的渲染方式,如webGL\canvas2D\Css3D。
  • 控制器Control:可通过键盘、鼠标控制相机的移动。

场景(Scene)

  const scene = new THREE.Scene();
  scene.background = new THREE.Color(0x808080);
  scene.fog = new THREE.Fog('#ffffff', 20, 500);

场景能够让你在什么地方、摆放什么东西来交给three.js来渲染,这是你放置物体、灯光和摄像机的地方。

属性

  • background: Object

若不为空,在渲染场景的时候将设置背景,且背景总是首先被渲染的。 可以设置一个用于的“clear”的Color(颜色)、一个覆盖canvas的Texture(纹理), 或是 a cubemap as a CubeTexture or an equirectangular as a Texture。默认值为null。

  • environment: Texture

若该值不为null,则该纹理贴图将会被设为场景中所有物理材质的环境贴图。 然而,该属性不能够覆盖已存在的、已分配给 MeshStandardMaterial.envMap 的贴图。默认为null。

  • fog : Fog

一个fog实例定义了影响场景中的每个物体的雾的类型。默认值为null。

  • isScene : Boolean

只读标志,用于检查给定对象是否属于场景类型。

  • overrideMaterial : Material

如果不为空,它将强制场景中的每个物体使用这里的材质来渲染。默认值为null。

方法

  • toJSON : Object

meta -- 包含有元数据的对象,例如场景中的的纹理或图片。 将scene对象转换为 three.js JSON Object/Scene format(three.js JSON 物体/场景格式)。

相机

Three.js中我们常用的有两种类型的相机:

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

透视 perspective 相机

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内置了多种光源以满足特定场景的需要。大家可以根据自己的项目需要来选择何种灯光。

光源分类

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在针对单独的点进行渲染时用到

场景交互

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

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();
}