Three.js 学习笔记
概述
说到 Three.js,很可能我们也会想到 WebGL、OpenGL,这几个东西的关系首先要屡清楚。
OpenGL(Open Graphics Library),开放图形库,属于计算机图形学的内容,是用于渲染 2D、3D 矢量图的跨语言、跨平台的 API。这些 API 只处理图形渲染,并不提供动画、定时器、文件IO、图像文件格式处理、GUI 等功能。OpenGL 所提供的只是接口,应用程序会调用这些接口,显卡驱动需要把 OpenGL 接口进行具体实现来驱动显卡进行图形渲染。
OpenGL ES (OpenGL for Embedded System),是 OpenGL 的子集,针对手机、PDA和游戏主机等嵌入式设备而设计,是对 OpenGL裁剪定制而来。
WebGL(Web Graphic Library),是一种 3D 绘图协议,这种绘图标准允许把 JavaScript 和 OpenGL ES 2.0 结合在一起,通过增加 OpenGL ES 2.0 的一个 JavaScript 绑定,WebGL 可以为 HTML5 Canvas 提供硬件 3D加速渲染,这样 Web 开发人员就可以借助系统显卡来在浏览器里更流畅地展示 3D 场景和模型了。WebGL 是由浏览器来支持和实现的。
在 JavaScript 里直接使用 webGL 编程,创建三维场景并生成动画,这个过程非常复杂,而且容易出错。Three.js 库可以简化这个过程。如果没有 Three.js 那么我们只能在 canvas 上去绘制 3D 图形,那工作量就会非常大。实时上 WebGL 只能画点、线和三角形,无数的三角形组成了各种各样形状的物体,我们把这种网格模型叫做 Mesh 模型,给模型贴上皮肤(纹理),就组成了 3D 世界。
基础
第一个 3D 场景
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>three</title>
<style>
canvas {
width: 100%;
height: 100%;
}
</style>
<script src="./js/three.js"></script>
</head>
<body>
<script>
// 创建一个场景
const scene = new THREE.Scene();
// 创建一个相机,透视相机
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
// 渲染器
const renderer = new THREE.WebGLRenderer({
antialias: true, // 抗锯齿
});
// 设置渲染器的大小为窗口的内宽度,也就是内容区的宽度
renderer.setSize(window.innerWidth, window.innerHeight);
// 设置之后界面不容易模糊
renderer.setPixelRatio(window.devicePixelRatio);
// 添加到 body 上
document.body.appendChild(renderer.domElement);
// 创建一个立体矩形
const geometry = new THREE.BoxGeometry(1, 1, 1);
// 创建材质
const material = new THREE.MeshBasicMaterial({
color: 0x00ff00
});
// 创建 cube
const cube = new THREE.Mesh(geometry, material);
// 添加到场景中
scene.add(cube);
camera.position.z = 5;
// 循环渲染
function render() {
requestAnimationFrame(render);
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
renderer.render(scene, camera);
}
render();
</script>
</body>
</html>
场景
在 Three.js 中,场景只有一种,场景是所有物体的容器,如果要显示一个苹果,就需要将苹果对象加入场景中。
const scene = new THREE.Scene();
相机
相机决定了场景中哪个角度的景色会显示出来,相机就像人的眼睛一样,人站在不同的位置,抬头或者低头都能够看到不同的景色。
相机可以有很多种,和现实中一样,不同的相机确定了呈相的各个方向。比如有的相机适合人像,有的相机适合风景,专业摄影师根据实际用途不一样,选择不同的相机。对于程序员来说,只要设置不同的相机参数,就能够让相机产生不一样的效果。
透视相机 PerspectiveCamera
类似人眼观察物体的视角,远处的物体比近处的物体看着要小。
/**
* fov 视野,视场角度 默认50,通常设置 60~90
* aspect 横尺寸和纵尺寸的比值
* near 从距离摄像机多近的距离开始渲染,默认0.1
* far 摄像机从它所处的位置开始能看到多少距离,默认1000,过小远处的场景渲染不出来,过大会影响性能
*/
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
正投影相机 OrthographicCamera
远近高低比例相同。工程建筑领域正投影的例子很多。
/**
* left 左平面距离相机中心点的垂直距离
* right 右平面距离相机中心点的垂直距离
* top 顶平面距离相机中心点的垂直距离
* bottom 底平面距离相机中心点的垂直距离
* near 近平面距离相机中心点的垂直距离
* far 远平面距离相机中心点的垂直距离
*/
const camera = new THREE.OrthographicCamera(width / -2, width / 2, height / 2, height / -2, 1, 1000 );
渲染器
渲染器决定了渲染的结果应该画在页面的什么元素上面,并且以怎样的方式来绘制。
const renderer = new THREE.WebGLRenderer({
antialias: true, // 抗锯齿
});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
渲染器结合相机和场景来渲染画面
renderer.render(scene, camera);
渲染的方式有两种:
-
实时渲染-需要不停的对画面进行渲染,性能开销较大,需要”渲染循环“来对画面进行重新渲染
-
离线渲染-事先渲染后每一帧的图
// 循环渲染
function render() {
requestAnimationFrame(render);
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
renderer.render(scene, camera);
}
render();
点
计算机世界里,3D世界是由点组成的,两个点能够组成一条直线,三个不在一条线上的点就能够组成一个三角形面,无数三角形面就能够组成各种形状的物体。这种有三角形面组成的网格模型叫做 Mesh 模型。给模型上贴上皮肤(也就是纹理)物体就活灵活现了。
在 Three.js 中定义一个点只需要 x, y, z 三个坐标:
const point1 = new THREE.Vector3(4, 8, 9);
// 或者
const point1 = new THREE.Vector3();
point1.set(4, 8, 9);
线
两个点就可以绘制出一条线。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>three</title>
<style>
canvas {
width: 100%;
height: 100%;
}
</style>
<script src="./js/three.js"></script>
</head>
<body>
<script>
// 创建一个场景
const scene = new THREE.Scene();
// 创建一个相机,透视相机
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
// 设置相机位置
camera.position.set(0, 0, 100);
// 设置观察点
camera.lookAt(new THREE.Vector3(0, 0, 0));
// 创建一个几何体容器
const geometry = new THREE.BufferGeometry();
// 创建材质,通过线段基础材质,可以设置线段的颜色、宽度、断点及连接点等属性
const material = new THREE.LineBasicMaterial( { vertexColors: true, linewidth: 2, morphTargets: true } );
// 设置每个点的位置
const positions = [
0, 0, 0,
10, 10, 10,
];
// 设置每个顶点的颜色
const colors = [
255, 0, 0,
0, 255, 0,
];
// 设置位置信息
geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( positions, 3 ) );
// 设置颜色信息
geometry.setAttribute( 'color', new THREE.Float32BufferAttribute( colors, 3 ) );
// 计算边界球体
geometry.computeBoundingSphere();
line = new THREE.Line( geometry, material );
scene.add( line );
// 渲染器
const renderer = new THREE.WebGLRenderer({
antialias: true,
});
// 设置渲染器的大小为窗口的内宽度,也就是内容区的宽度
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
// 添加到 body 上
document.body.appendChild(renderer.domElement);
renderer.render( scene, camera );
</script>
</body>
</html>
坐标系
Three.js 使用的是右手坐标系,右手拇指从屏幕指向屏幕外是 Z 轴正方向,手向右指是 X 周正方向, 向上则是 Y 轴正方向。
光源
宇宙中的很多物体都是不发光的,所以如果没有光,我们的世界可能将是一片黑暗,Three.js 中也是这样,我们需要在我们的场景中加入光源很多物体才能被看到。
环境光
环境光是经过多次反射而来的,无法确定其最初的方向。环境光是一种无处不在的光。环境光源放出的光线被认为来自任何方向。因此,当你仅为场景指定环境光时,所有物体无论法向量如何,都将表现为同样的明暗度。
const light = new THREE.AmbientLight( 0x404040 ); // soft white light
scene.add( light );
点光源
由这种光源放出的光线来自同一点,且方向辐射四面八方。例如蜡烛放出的光。
/**
* color 光的颜色
* intensity 光的强度,默认1.0,就是100%的强度
* distance 光的距离,从光源所在的位置,经过这段距离后光的强度将衰减为0,默认为0,不衰减
*/
const light = new THREE.PointLight( 0xff0000, 1, 100 );
light.position.set( 50, 50, 50 );
scene.add( light );
聚光灯
这种光源的光线从一个椎体中射出,在被照射的物体上产生聚光灯的效果。使用这种光源需要指定光的射出方向以及椎体的顶角 α。
// white spotlight shining from the side, casting a shadow
/**
* hex 聚光灯发出的颜色
* intensity 光源的强度,默认1
* distance 光线的距离
* angle 聚光灯的角度
* penumbra 周围的阴影
* decay 光源模型中衰减的一个参数,越大衰减越快
*/
const spotLight = new THREE.SpotLight( 0xffffff );
spotLight.position.set( 100, 1000, 100 );
spotLight.castShadow = true;
spotLight.shadow.mapSize.width = 1024;
spotLight.shadow.mapSize.height = 1024;
spotLight.shadow.camera.near = 500;
spotLight.shadow.camera.far = 4000;
spotLight.shadow.camera.fov = 30;
scene.add( spotLight );
平行光
平行光又称方向光,是一组没有衰减的平行的光线,类似太阳光的效果。
// White directional light at half intensity shining from the top.
/**
* hex 聚光灯发出的颜色
* intensity 光源的强度,默认1
*/
const directionalLight = new THREE.DirectionalLight( 0xffffff, 0.5 );
scene.add( directionalLight );
材质与光源的关系
材质 Material 就是材料和质感。在渲染程序中,它是表面各可视属性的结合,这些可视属性是指表面的色彩、纹理、光滑度、透明度、反射率、折射率、发光度等,有了这些属性,才能让我们识别三维中的模型是什么做成的,也正是有了这些属性,我们计算机三维虚拟世界才会和真实世界一样缤纷多彩。但是材质离开了光,将什么都体现不出来,不同的材质在光的作用下会呈现出不同的效果,这就是物体的材质与光的微妙关系。
纹理
3D 世界的纹理是有图片组成的,就是在网格模型上进行贴图。
// load a texture, set wrap mode to repeat
const texture = new THREE.TextureLoader().load( "textures/water.jpg" );
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set( 4, 4 );
除了图片,canvas 也可以作为纹理。