侧边栏壁纸
  • 累计撰写 225 篇文章
  • 累计创建 275 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

给新手的three.js入门讲解(一)

DGF
DGF
2022-09-13 / 0 评论 / 0 点赞 / 37 阅读 / 0 字

three.js是用javascript来编写的3D渲染库。在浏览器端,WebGL是一个底层的标准,在这些标准被定义之后,Chrome、Firefox之类的浏览器实现了这些标准。然后就能通过JavaScript代码,在网页上实现三维图形的渲染了。Three.js则是封装了底层的图形接口,使得在网页上实现3D效果更加便捷。

post193-1.png
核心

WebGL的渲染需要用到HTML5的Canvas元素。一个典型的Three.js程序至少要包括渲染器(Renderer)、场景(Scene)、照相机(Camera)、灯光(Light),以及你在场景中创建的物体。

渲染器

渲染器将和Canvas元素进行绑定,如果在HTML中手动定义了Canvas元素,那么Renderer可以这样写:

var renderer = new THREE.WebGLRenderer({canvas: document.getElementById('#mainCanvas')});

如果想要Three.js生成Canvas元素,在HTML中就不需要定义Canvas元素,在JavaScript代码中可以这样写:

var renderer = new THREE.WebGLRenderer();
renderer.setSize(400, 300); // 设置渲染器的大小为窗口的内宽度,也就是内容区的宽度
document.getElementsByTagName('body')[0].appendChild(renderer.domElement);

场景

在Three.js中添加的物体都是添加到场景中的,因此它相当于一个大容器。在程序最开始的时候进行实例化,然后将物体添加到场景中即可。

var scene = new THREE.Scene();

也就是说,场景是光源、相机和所有物体的父容器,通过scene.children访问到这些子物体。这些物体在创建的时候是没有名字的,可以通过name属性指定名字,这样就可以通过name来访问具体的子物体。

//访问子物体

scene.children
scene.getChildByMName(name)

//遍历该父场景中的所有子物体来执行回调函数

scene.traverse(function)

相机

WebGL和Three.js使用的坐标系是右手坐标系,即右手伸开,拇指为X,四指为Y,手心为Z。

post193-2.png

相机就像人的眼睛一样,人站在不同位置,抬头或者低头都能够看到不同的景色。在Threejs中有多种相机,透视相机(THREE.PerspectiveCamera)用的最多。定义透视投影的照相机:

var camera = new THREE.PerspectiveCamera(45, 4 / 3, 1, 1000);
camera.position.set(0, 0, 5);
scene.add(camera);

注意,照相机也需要被添加到场景中。

总结

Three.js中的场景是一个物体的容器,开发者将需要的物体放入场景中。

相机的作用就是指向场景,在场景中取一个合适的景,把它拍下来。

渲染器的作用就是将相机拍摄下来的图片,放到浏览器中去显示。

在定义了场景中的物体,设置好的照相机之后,渲染器就知道如何渲染出二维的结果了。这时候,只需要调用渲染器的渲染函数,就能使其渲染一次了。

renderer.render(scene, camera);

照相机

根据投影方式的不同,照相机又分为正交投影照相机与透视投影照相机。使用透视投影照相机获得的结果是类似人眼看到的有“近大远小”的效果;而使用正交投影照相机获得的结果就像平面画3D的效果,在三维空间内平行的线,投影到二维空间中也一定是平行的。

正交投影照相机

正交投影照相机的构造函数是:

new THREE.OrthographicCamera(left, right, top, bottom, near, far)

这六个参数分别代表正交投影照相机拍摄到的空间的六个面的位置,其为视景体(Frustum)。只有在视景体内部的物体才可能显示在屏幕上,而视景体外的物体会在显示之前被裁减掉。

为了保持照相机的横竖比例,需要保证 (right - left) 与 (top - bottom) 的比例与Canvas宽度与高度的比例一致。

照相机默认都是沿z轴负方向观察的,可以通过lookAt函数指定它的朝向:

var camera = new THREE.OrthographicCamera(-2, 2, 1.5, -1.5, 1, 100);
//设定照相机的位置
camera.position.set(0, 0, 5);
scene.add(camera);
//设置照相机的朝向为原点
camera.lookAt(new THREE.Vector3(0, 0, 0));

这样就改变照相机观察方向由当前位置指向原点。

透视投影相机

透视投影是更符合人眼视觉的投影,构造函数是:

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

fov:为视角的大小,如果设置为0,相当你闭上眼睛了,所以什么也看不到,如果为180,那么可以认为你的视界很广阔,但是在180度的时候,往往物体很小,因为他在你的整个可视区域中的比例变小了。

aspect:为实际窗口的纵横比,即宽度除以高度。通常设为Canvas的横纵比例。

var camera = new THREE.PerspectiveCamera(45, 400 / 300, 1, 100);
camera.position.set(0, 0, 5);
scene.add(camera);

形状

three.js封装了一些常见的几何形状,在使用时,就只需要定义three.js设定好需要的值即可,如果想要自定义形状,就需要手动创造顶点和面。

立方体

new THREE.CubeGeometry(width, height, depth, widthSegments, heightSegments, depthSegments)

这里,width是x方向上的长度;height是y方向上的长度;depth是z方向上的长度;后三个参数分别是在三个方向上的分段数,如widthSegments为3的话,代表x方向上水平分为三份。一般情况下不需要分段的话,可以不设置后三个参数,后三个参数的默认值为1。

如:new THREE.CubeGeometry(1, 2, 3);可以创建一个x方向长度为1,y方向长度为2,z方向长度为3的立方体。

物体的几何中心默认在原点的位置。若设置了分段,会对六个面进行分段,而不是对立方体分段,因此在立方体的中间是不分段的,只有六个侧面被分段。

平面

new THREE.PlaneGeometry(width, height, widthSegments, heightSegments)

width是x方向上的长度;height是y方向上的长度;后两个参数同样表示分段。

球体

new THREE.SphereGeometry(radius, segmentsWidth, segmentsHeight, phiStart, phiLength, thetaStart, thetaLength)

其中,radius是半径;segmentsWidth表示经度上的切片数;segmentsHeight表示纬度上的切片数;phiStart表示经度开始的弧度;phiLength表示经度跨过的弧度;thetaStart表示纬度开始的弧度;thetaLength表示纬度跨过的弧度。

使用var sphere = new THREE.SphereGeometry(3, 8, 6)可以创建一个半径为3,经度划分成8份,纬度划分成6份的球体。

segmentsWidth相当于经度被切成了几瓣,而segmentsHeight相当于纬度被切成了几层。对于球体而言,当这两个值较大的时候,形成的多面体就可以近似看做是球体了。

圆形

参数和球体的类似

THREE.CircleGeometry(radius, segments, thetaStart, thetaLength)

柱体

THREE.CylinderGeometry(radiusTop, radiusBottom, height, radiusSegments, heightSegments, openEnded)

其中,radiusTop与radiusBottom分别是顶面和底面的半径,由此可知,当这两个参数设置为不同的值时,实际上创建的是一个圆台;height是圆柱体的高度;radiusSegments与heightSegments可类比球体中的分段;openEnded是一个布尔值,表示是否没有顶面和底面,默认为false,表示有顶面和底面。

圆环

THREE.TorusGeometry(radius, tube, radialSegments, tubularSegments, arc)

其中,radius是圆环半径;tube是管道半径;radialSegments与tubularSegments分别是两个分段数,arc是圆环面的弧度,默认为Math.PI * 2。

材质

材质(Material)是与渲染效果相关的属性。通过设置材质可以改变物体的颜色、纹理贴图、光照模式等。材质的共有属性包括:

基础属性

id:用来标识材质。

name:赋予材质名称。

opacity:定义物体透明度,取值范围0~1

side:设定在几何体的哪个面应用材质,默认值为THREE.FrontSide,即外面。也可以设置为THREE.BackSide(内面)或THREE.DoubleSide(双面)。

基本材质

用基本材质(BasicMaterial)的物体渲染后的颜色始终为该材质的颜色,不会由于光照产生明暗、阴影效果。如果没有指定材质的颜色,则颜色是随机的。

THREE.MeshBasicMaterial(opt)

其中,opt为包含各属性的值。如新建一个不透明度为0.75的黄色材质:

new THREE.MeshBasicMaterial({color: 0xffff00,opacity: 0.75});

常用的属性包括:

visible:是否可见,默认为true

side:渲染面片正面或是反面,默认为正面THREE.FrontSide,可设置为反面THREE.BackSide,或双面THREE.DoubleSide

wireframe:是否渲染线而非面,默认为false
color:十六进制RGB颜色,如红色表示为0xff0000

map:使用纹理贴图

Lambert 材质

Lambert光照模型的主要特点是只考虑漫反射而不考虑镜面反射的效果,因而对于金属、镜子等需要镜面反射效果的物体就不适应,对于其他大部分物体的漫反射效果都是适用的。

new THREE.MeshLambertMaterial(opt);

color是用来表现材质对散射光的反射能力,也是最常用来设置材质颜色的属性。除此之外,还可以用ambient和emissive控制材质的颜色。

ambient表示对环境光的反射能力,只有当设置了AmbientLight后,该值才是有效的,材质对环境光的反射能力与环境光强相乘后得到材质实际表现的颜色。

emissive是材质的自发光颜色,可以用来表现光源的颜色。

如果同时使用红色的自发光与黄色的散射光:

new THREE.MeshLambertMaterial({color: 0xffff00,emissive: 0xff0000})

Phong 材质

Phong模型考虑了镜面反射的效果,因此对于金属、镜面的表现尤为适合。

new THREE.MeshPhongMaterial(opt);

可以通过shininess属性控制光照模型中的n值,当shininess值越大时,高光的光斑越小,默认值为30。

使用黄色的镜面光,红色的散射光:

material = new THREE.MeshPhongMaterial({color: 0xff0000,specular: 0xffff00,shininess: 100});

post193-3.png
Depth材质

这种材质的特点在于,不控制物体的渲染效果,外观根据物体到相机的距离变化。一般与其他材质结合形成远处逐渐消失的效果。

联合材质

var depthMaterial=new new THREE.MeshDepthMaterial;
var basicMaterial=new new THREE.MeshBasicMaterial(opt);
var cube=new THREE.SceneUtils.createMaterialObject(cubeGeometry,[depthMaterial,basicMaterial]);
cube.children[1].scale.set(0.99,0.99,0.99);

对于要进行融合的材质,需要添加属性transparent:true开启融合模式。

createMaterialObject()创建网格时,几何体·会被复制,返回一个网格组,内部的网格完全一样。渲染时画面会闪烁。所以需要最后一行代码来缩小带有depth材质的网格,避免出现闪烁。

物体

使用几何形状和材质就能创建物体了。最常用的一种物体就是网格(Mesh),网格是由顶点、边、面等组成的物体;其他物体包括线段(Line)、骨骼(Bone)、粒子系统(ParticleSystem)等。

创建物体需要指定几何形状和材质,其中,几何形状决定了物体的顶点位置等信息,材质决定了物体的颜色、纹理等信息。

创建网格

创建网格要把几何形状与材质传入其构造函数。

Mesh(geometry, material)
var material = new THREE.MeshLambertMaterial({color: 0xffff00});
var geometry = new THREE.CubeGeometry(1, 2, 3);
var mesh = new THREE.Mesh(geometry, material);scene.add(mesh);

若不设置属性material,则每次会随机分配一种wireframe为true的材质,每次刷新页面后的颜色是不同的。除了在构造函数中指定材质,在网格被创建后,也能对材质进行修改。

mesh.material = new THREE.MeshLambertMaterial({color: 0xff0000});

几何变换

平移、缩放、旋转是物体三个常用属性。即为translate、scale、rotate三个属性。

平移

mesh.translateX(100);//沿着x轴正方向平移距离100

沿着向量(0,1,0)方向平移

var axis = new THREE.Vector3(0,1,0);//向量axis
mesh.translateOnAxis(axis,100);//沿着向量axis方向平移100

旋转

mesh.rotateX(Math.PI/4);//绕x轴旋转π/4//绕(0,1,0)向量轴旋转π/8
var axis = new THREE.Vector3(0,1,0);//向量axis
mesh.rotateOnAxis(axis,Math.PI/8);//绕axis向量轴旋转π/8

缩放

mesh.scale.x = 2.0;//x轴方向放大2倍
mesh.scale.set(0.5,0.5,0.5);//缩小为原来0.5倍

加载几何模型

Three.js有一系列导入外部文件的辅助函数,是在three.js之外的,使用前需要额外下载。.obj是最常用的模型格式,导入.obj文件需要OBJLoader.js;导入带.mtl材质的.obj文件需要MTLLoader.js以及OBJMTLLoader.js。

无材质模型

创建loader变量,用于导入模型:

var loader = new THREE.OBJLoader();

接受两个参数,第一个表示模型路径,第二个表示完成导入后的回调函数,一般我们需要在这个回调函数中将导入的模型添加到场景中。

loader.load('../lib/port.obj', function(obj) {mesh = obj; //储存到全局变量中scene.add(obj);});

默认的情况下,只有正面的面片被绘制,模型中部分可能穿模。而如果需要双面绘制,需要这样设置:

var loader = new THREE.OBJLoader();
loader.load('../lib/port.obj', function(obj) {
    obj.traverse(function(child) {
        if (child instanceof THREE.Mesh) {
            child.material.side = THREE.DoubleSide; 
        }
    });
    mesh = obj;
    scene.add(obj);
});

有材质模型

代码中设置材质

在回调函数中设置模型的材质:

var loader = new THREE.OBJLoader();
loader.load('../lib/port.obj', function(obj) {
    obj.traverse(function(child) {
        if (child instanceof THREE.Mesh) {
            child.material = new THREE.MeshLambertMaterial({
            color: 0xffff00,
            side: THREE.DoubleSide
        });
    }});
    mesh = obj;scene.add(obj);
});

建模软件导出材质

在建模软件导出port.obj模型文件以及port.mtl材质文件,就需要使用MTLLoader.js与OBJMTLLoader.js,并且要按改顺序引用:

<script type="text/javascript" src="MTLLoader.js"></script>
<script type="text/javascript" src="OBJMTLLoader.js"></script>

调用方法:

var loader = new THREE.OBJMTLLoader();
loader.addEventListener('load', function(event) {
    var obj = event.content;mesh = obj;
    scene.add(obj);
});
loader.load('../lib/port.obj', '../lib/port.mtl');

光线

环境光

环境光是指场景整体的光照效果,环境光没有明确的光源位置,在各处形成的亮度也是一致的。

THREE.AmbientLight(hex)

在设置环境光时,只需要指定光的颜色。使用环境光渲染时,环境光并不在乎物体材质的color属性,而是ambient属性。ambient属性的默认值是0xffffff。

当环境光使用的颜色比较明亮,渲染的颜色往往会过饱和。因此,环境光通常使用白色或者灰色,作为整体光照的基础。

点光源

点光源是一种单点发光,照射所有方向的光源。点光源照到不同物体表面的亮度是线性递减的。

THREE.PointLight(hex, intensity, distance)

其中,hex是光源十六进制的颜色值;intensity是亮度,默认值为1,表示100%亮度;distance是光源最远照射到的距离,默认值为0。

var light = new THREE.PointLight(0xffffff, 2, 100);
light.position.set(0, 1.5, 2);scene.add(light);

聚光灯

聚光灯是一种特殊的点光源,它能够朝着一个方向投射光线。聚光灯投射出的是类似圆锥形的光线,与现实中看到的聚光灯是一致的。

THREE.SpotLight(hex, intensity, distance, angle, exponent)

angle是聚光灯的张角,默认值是Math.PI / 3,最大值是Math.PI / 2;exponent是光强在偏离target(target需要在之后定义,默认值为(0, 0, 0))的衰减指数,默认值是10。

在调用构造函数之后,除了设置光源本身的位置,一般还需要设置目标点target:

light.position.set(x1, y1, z1);
light.target.position.set(x2, y2, z2);

除了设置light.target.position的方法外,如果想让聚光灯跟着某一物体移动(真正的聚光灯),可以target指定为该物体:

var cube = new THREE.Mesh(new THREE.CubeGeometry(1, 1, 1),new THREE.MeshLambertMaterial({color: 0x00ff00}));
var light = new THREE.SpotLight(0xffff00, 1, 100, Math.PI / 6, 25);
light.target = cube;

平行光

对于任意平行的平面,平行光照射的亮度都是相同的,而与平面所在位置无关。

THREE.DirectionalLight(hex, intensity)

对于平行光而言,设置光源位置尤为重要。

var light = new THREE.DirectionalLight();
light.position.set(2, 5, 3);
scene.add(light);

注意,这里设置光源位置并不意味着所有光从(2, 5, 3)点射出(如果是的话,就成了点光源),而是意味着,平行光将以矢量(-2, -5, -3)的方向照射到所有平面。因此,平面亮度与平面的位置无关,而只与平面的法向量相关。只要平面是平行的,那么得到的光照也一定是相同的。

阴影

阴影的形成也就是因为比周围获得的光照更少。因此,要形成阴影,光源必不可少。

在Three.js中,能形成阴影的光源只有平行光THREE.DirectionalLight与聚光灯THREE.SpotLight;而相对地,能表现阴影效果的材质只有THREE.LambertMaterial与THREE.PhongMaterial。

初始化

首先告诉渲染器渲染阴影:

renderer.shadowMapEnabled = true;

然后,对于光源以及所有要产生阴影的物体调用:

xxx.castShadow = true;

对于接收阴影的物体调用:

xxx.receiveShadow = true;

这就是产生阴影效果的前置条件。

创建阴影

为了看到阴影照相机的位置,通常可以在调试时开启light.shadowCameraVisible

对于聚光灯,需要设置shadowCameraNear、shadowCameraFar、shadowCameraFov三个值,类比我们在第二章学到的透视投影照相机,只有介于shadowCameraNear与shadowCameraFar之间的物体将产生阴影,shadowCameraFov表示张角。

对于平行光,需要设置shadowCameraNear、shadowCameraFar、shadowCameraLeft、shadowCameraRight、shadowCameraTop以及shadowCameraBottom六个值,相当于正交投影照相机的六个面。同样,只有在这六个面围成的长方体内的物体才会产生阴影效果。

light.castShadow = true;
light.shadow.camera.top = 180;
light.shadow.camera.bottom = -100;
light.shadow.camera.left = -120;
light.shadow.camera.right = 120;
0

评论区