1 Star 5 Fork 1

邓海兵/threeJs基础

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README
MulanPSL-1.0

使用ThreeJs实现三维场景

三维场景在我们的工作和生活中非常常见,比如百度地图,电影,游戏等等。

我们最熟悉的就是我们的定位地图,在我们的定位系统中,可以使用三维场景作为地图显示使用,同时能通过鼠标操作视角,观看场景中的不同位置,以及我们可以把定位卡坐标通过三维模型(行走的小人)显示到场景中。

ThreeJs与webGL

目前我们实现三维场景使用的是threeJs,这是一个基于webGL的3D库。

先说一下webGL,webGL是一种3D绘图协议,为html5 canvas提供硬件3D加速渲染, 让开发人员就可以借助显卡在浏览器里更流畅地展示3D场景和模型(摘抄自百度) 。

webGL的技术规范继承自免费开源的openGL标准,后者在计算机图形学,电子游戏,设计等领域广泛使用。可以说webGL就是web版的openGL。

threeJS在webGL的基础上定义了一系列常用的API,让开发者可以非常简单方便的搭建三维场景。

使用threeJs搭建三维场景

import * as THREE from './lib/three.module.js';
import Stats from './lib/stats.module.js';
import {OrbitControls} from './lib/OrbitControls.js';
let container, renderer, camera, scene, stats ;
init();
animate();
function init () {
    // 获取dom,<div id="container"></div
    container = document.getElementById('container');
   
    // 新建渲染器,并加入到dom中
    renderer = new THREE.WebGLRenderer({antialias: true});
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(window.innerWidth, window.innerHeight);
    container.appendChild(renderer.domElement);
    
    // 新建透视摄像机,参数为(视角,宽高比,近剪裁平面,远剪裁平面)
    camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 1, 5000);
    camera.position.y = 100;
    camera.position.z = 20;
    
    // 新建场景,场景的作用是一个容器,模型,灯光等等都会放在场景中
   	scene = new THREE.Scene();
    scene.background = new THREE.Color(0xbfd1e5);
       	
 	// 新建一个用于显示实时帧率的组件
    stats = new Stats();
    container.appendChild(stats.dom);
    
    // 摄像机控制组件,用来操作摄像机
    const controls = new OrbitControls(camera, renderer.domElement);
}

function animate () {
    requestAnimationFrame(animate);
    render();
    stats.update();
}

function render () {
    renderer.render(scene, camera);
}

以上代码,搭建了一个基础的三维场景,其中包含了一些必要的元素,渲染器、像机、场景、以及代码中没有提到的模型、材质、贴图等,这些元素不仅仅存在threeJs的世界里,其他三维引擎(unity3D,虚幻4)中也是通过这些元素搭建三维世界的。

下面就详细说说这些元素的作用。

实现一个三维场景需要的元素

渲染器(render)

渲染从字面意思上理解,就是把数据转化成图像的过程,比如前端把html代码渲染成为网页。

上面的代码代码中,我新建了一个渲染器加入到场景中。

renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth,window.innerHeight);
container.appendChild(renderer.domElement);

渲染器的作用是把三维场景中的模型渲染成为二维画面展示给用户。如果不经过渲染,我们看到的三维世界就是一大堆代码。

"geometries": [
		{
			"uuid": "A046F00F-06A1-4E33-B28C-0F25D7FEEB7A",
			"type": "BufferGeometry",
			"data": {
				"attributes": {
					"position": {
						"itemSize": 3,
						"type": "Float32Array",
						"array": [-0.5,-0.5,0.5,0.5,-0.5,0.5,0.5,0.5,0.5,-0.5,-0.5,0.5,0.5,0.5,0.5,-0.5,0.5,0.5,-0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,-0.5,-0.5,0.5,0.5,0.5,0.5,-0.5,-0.5,0.5,-0.5,-0.5,0.5,-0.5,0.5,0.5,-0.5,0.5,-0.5,-0.5,-0.5,0.5,-0.5,0.5,-0.5,-0.5,-0.5,-0.5,-0.5,-0.5,-0.5,-0.5,0.5,-0.5,-0.5,0.5,-0.5,0.5,-0.5,-0.5,-0.5,0.5,-0.5,0.5,-0.5,-0.5,0.5,0.5,-0.5,0.5,0.5,-0.5,-0.5,0.5,0.5,-0.5,0.5,-0.5,0.5,0.5,0.5,-0.5,0.5,0.5,0.5,-0.5,-0.5,-0.5,-0.5,-0.5,0.5,-0.5,0.5,0.5,-0.5,-0.5,-0.5,-0.5,0.5,0.5,-0.5,0.5,-0.5],
						"normalized": false
					},
					"normal": {
						"itemSize": 3,
						"type": "Float32Array",
						"array": [-1,0,0,-1,0,0,-1,0,0,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,-1,0,0,-1,0,0,-1,0,0],
						"normalized": false
					},
					"uv": {
						"itemSize": 2,
						"type": "Float32Array",
						"array": [0.375,0,0.625,0,0.625,0.25,0.375,0,0.625,0.25,0.375,0.25,0.375,0.25,0.625,0.25,0.625,0.5,0.375,0.25,0.625,0.5,0.375,0.5,0.375,0.5,0.625,0.5,0.625,0.75,0.375,0.5,0.625,0.75,0.375,0.75,0.375,0.75,0.625,0.75,0.625,1,0.375,0.75,0.625,1,0.375,1,0.625,0,0.875,0,0.875,0.25,0.625,0,0.875,0.25,0.625,0.25,0.125,0,0.375,0,0.375,0.25,0.125,0,0.375,0.25,0.125,0.25],
						"normalized": false
					}
				},
				"boundingSphere": {
					"center": [0,0,0],
					"radius": 0.866025
				}
			}
		}],

上面的代码无法一眼就看出所描述的是一个正方体,通过渲染我们才可以直观的看到它得样子。

提到渲染器,从事三维工作的人员,一般从事三维工作的人员都会想到vray(主要用于室内设计),menterRay,anorld(主要用于影视动画)等,这些渲染器都能渲染出难辨真伪的画面。

这些渲染器为了追求真实的画面效果,会进行非常多的的运算,比如光线的反射,折射,阴影的处理,材质等等,运算量非常大,一般完成一副画面的渲染都需要很长时间。

除去这些渲染器,一般用于交互的三维场景(三维软件的主界面,三维地图,游戏,虚拟城市等),使用的都是实时渲染器,实时渲染器优化(简化)渲染的过程,把没有经过太多运算的画面展示给用户,方便用户操作。

(以上图片不仅仅来源于网络)

虽然现在很多游戏画面效果非常真实,不过在渲染的开销远远没有影视渲染器那么多,一般采用次世代技术(使用法线贴图,光照贴图等)达到真实的效果。

我们的定位地图场景中使用了灯光贴图,让光线看起来比较自然舒服,实际上场景中的物体不受灯光影响,完全是贴图在发挥作用。

摄像机(camera)

摄像机的责任是告诉渲染器,需要渲染场景中那些物体,更准确的说法是规定渲染的范围,进入摄像机范围的模型才会被渲染。

比如这个场景,并没有把场景中的所有物体都渲染出来,仅仅渲染了进入摄像机范围的部分。

以上场景使用的是透视摄像机,这种摄像机模拟现实中的摄像机设计,摄像机的范围看起是一个四棱锥,越接近摄像机,范围也就越小,物体也就会被渲染得越大,也就实现了近大远小。

在之前的threeJs代码中,加入摄像机的代码中有4个参数,视角,宽高比,近剪裁平面和远剪裁平面。视角就是视线的夹角,宽高比就是屏幕的宽高比(设置错误会造成画面拉伸),近剪裁平面以及远剪裁平面用于规定摄像机可视范围。

// 新建透视摄像机,参数为(视角,宽高比,近剪裁平面,远剪裁平面)
camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 1, 5000);
camera.position.y = 100;
camera.position.z = 20;

实际上在threeJs中,透视摄像机的可视范围是一个梯形,如下图,处在可视范围外的物体或者部分都会被剪裁掉(不渲染)。

下图展示了设置近剪裁平面的效果,图中得立方体一个角被剪裁掉了。

除了透视摄像机,还有一种摄像机叫正交摄像机,现实中基本不存在这种摄像机,按道理说,如果现实摄像机非常遥远,就可以非常接近正交相机的效果,比如卫星图。

卫星离地面非常遥远,拍摄的图片看起来就没有什么透视,图中(百度卫星地图)大楼的顶部和底部都是差不多的大小,非常接近正交视图的画面。

正交相机的范围从形状上看,是一个长方体,无论物体距离摄像机多远多近,渲染出来的大小都不会改变,正交相机视图会让人觉得非常奇怪(我们看到的世界都是带有透视的),一般会用在场景制作过程中,以及一些上帝视角的二维游戏中(比如传奇)。

坐标系

这里先说一下三维坐标系的问题,threeJs采用的笛卡尔坐标系,这是一种非常通用的坐标系。与我们熟悉的坐标系有一些不同,实际上我们通过简单的算法,转变了笛卡尔坐标系,以方便与二维地图对应。

下图中可以看出,我们的Z轴就是笛卡尔的Y轴,而我们的Y轴是笛卡尔的-Z轴。

这里我们不关心二维地图对应的问题,使用通用的笛卡尔坐标系完成接下来的事情。

几何结构(geometry)

之前我们已经设置好了渲染器,摄像机已经场景,这是一个空的场景,场景中什么都没有,我们只能看到背景色,下面我们需要把模型加入到场景中。

实际使用中,我们一般会使用三维制作软件(maya,max,blender等)制作模型,然后导入到我们的场景中,比如我们的定位场景模型,定位卡标签模型等。有时候我们还需要通过数据直接生成模型,比如我们场景中显示的电子围栏。

要在三维场景中绘制电子围栏,需要理解模型的几何结构,几何结构描述模型的形状和位置,构成结构最基本的元素是顶点( vector )。

几何结构描述模型的形状和位置,构成结构最基本的元素是顶点( vector )。

每个顶点都是由一组三维坐标构成(x,y,z),这个坐标描绘了顶点在三维空间中的位置,三个顶点可以构成一个三角面,三维世界中的一切形体都可以用三角面还塑造,不管建筑还是生物。

Position

我们先看看一个正方体在三维空间中,每个顶点的位置正方体上面有8个顶点,每一个顶点都有一组三维坐标。正方体有6个面,每个面都是由两个三边面构成。

我们从最简单的三角形开始,threeJs提供了bufferGeometry和geometry两种方式绘制三角形,geometry的方式是bufferGeometry的一种友好替代方式,提供了很API方便开发者更加简单方便的创建几何结构,不过效率上比bufferGrometry要差。

下面分别用bufferGeometry和geometry的方式往场景中加入一个三角形。

bufferGeometry:

let geom = new THREE.BufferGeometry();
geom.setAttribute('position',new THREE.BufferAttribute(new Float32Array([
   0.0,0.0,0.0,
   1.0,0.0,1.0,
   1.0,0.0,0.0
]),3));
let material = new THREE.MeshBasicMaterial({color: 0xffdd99});
let triangle = new THREE.Mesh(geom, material);
scene.add(triangle);

geometry:

let geom = new THREE.Geometry();
geom.vertices.push(
    new THREE.Vector3(0,0,0),
    new THREE.Vector3(1,0,1),
    new THREE.Vector3(1,0,0)
);
geom.faces.push(new THREE.Face3(0,1,2));
let material = new THREE.MeshBasicMaterial({color: 0xffdd99});
let triangle = new THREE.Mesh(geom, material);
scene.add(triangle);

创建的图形如下图,从顶点1开始到顶点3结束。仔细思考一下,其实不管从哪个顶点开始,哪个顶点结束,顺序怎么样,都能构成一个三角形才对。

实际上从哪个顶点开始不是关键的,threeJs遵循右手法则,就是下面的图片这样,顶点循序按照逆时针方向加入的话,三角形的面就是向上的,反之则是向下,threeJs默认是单面显示,只会显示正面,不会显示背面。

所以我们加入点的循序可以是123,231,312都可以,要是加入循序为321,那么画面中就不会看到我们创建的三角型。

img

我们系统中的电子围栏,就可以用这种方式去实现,下图是在我们的系统中绘制的一个电子围栏。

我的想法大致如下图,按照顺序逐一加入顶点。

代码实现如下,电子围栏的顶点坐标放在一个二维数组里面,循环这个数组,按照思路逐一把顶点加入就行了。

const h = 0.5; // 围栏高度
const area = [[0.0,2.0],[0.5,1.0],[2.0,1.0],[1.0,0.0],
			[2.0,-1.0],[1.0,-1.0], [0.0,0.0],[-1.0,-1.0],
			[-2.0,-1.0],[-1.0,0.0],[-2.0,1.0],[-0.5,1.0]];
const len = area.length;
let position = [];
for(let i = 0; i < len ; i++){
	if(i == 0){
		position = [...position,area[i][0],0.0,-area[i][1]];
         position = [...position,area[i][0],h,-area[i][1]];
	}else{  
         position = [...position,area[i][0],0.0,-area[i][1]];
         position = [...position,area[i][0],0.0,-area[i][1]];
         position = [...position,area[i-1][0],h,-area[i-1][1]];
         position = [...position,area[i][0],h,-area[i][1]];
         position = [...position,area[i][0],0.0,-area[i][1]];
         position = [...position,area[i][0],h,-area[i][1]];
	}
	if(i == (len -1 )){
         position = [...position,area[0][0],0.0,-area[0][1]];
         position = [...position,area[0][0],0.0,-area[0][1]];
         position = [...position,area[i][0],h,-area[i][1]];
         position = [...position,area[0][0],h,-area[0][1]];
	}
}
let geometry = new THREE.BufferGeometry();
geometry.setAttribute('position',new THREE.BufferAttribute(new Float32Array(position),3));
const material = new THREE.MeshBasicMaterial({color: 0x8844ff,side:2,transparent:true,opacity:0.5});
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);

然后就可以在三维场景中看到我们的电子围栏了,这里把围栏的显示设置成了双面,避免有些面我们看不到。

围栏方便演示,上面的代码后方加入再加入一段代码,给这个几何结构指定另一个显示线框的材质球并加入场景中。

const wire = new THREE.MeshBasicMaterial({color: 0x000000,wireframe:true});
const wire_mesh = new THREE.Mesh(geometry, wire);
scene.add(wire_mesh);

如下图,可以看到围栏的构成。

上面的例子中可以看到,在创建了bufferGeometry后又创建一个meshBasicMaterial,这是一个threeJs提供的基本材质。在三维中仅有几何结构(顶点和面的信息)是没办法渲染出三维图形的,还需要着色器才能让渲染器知道应该怎么去描绘图形。

normal

着色器是渲染三维图形的关键,用GLSL ES语言编写,threeJs为开发者封装好了一系列常用的着色器,上面的meshBasicMaterial是其中的一种,threeJs里叫材质,这个材质仅仅不受光照的影响,显示模型的固有颜色和贴图信息。常用的材质还有meshLambertMaterial,meshPhoneMaterial等。

下面我使用可以接收灯光的meshLambertMaterial材质创建一个立方体,并且往场景中加入灯光。

let geom = new THREE.BufferGeometry();
geom.setAttribute('position',new THREE.BufferAttribute(new Float32Array([
    0.0,0.0,0.0, 1.0,0.0,0.0, 1.0,0.0,1.0,
    0.0,0.0,0.0, 1.0,0.0,1.0, 0.0,0.0,1.0,
    0.0,0.0,0.0, 0.0,1.0,0.0, 1.0,0.0,0.0,
    0.0,1.0,0.0, 1.0,1.0,0.0, 1.0,0.0,0.0,
    0.0,0.0,0.0, 0.0,1.0,1.0, 0.0,1.0,0.0,
    0.0,0.0,0.0, 0.0,0.0,1.0, 0.0,1.0,1.0,
    0.0,1.0,1.0, 0.0,0.0,1.0, 1.0,1.0,1.0,
    0.0,0.0,1.0, 1.0,0.0,1.0, 1.0,1.0,1.0,
    1.0,1.0,1.0, 1.0,0.0,1.0, 1.0,1.0,0.0,
    1.0,0.0,1.0, 1.0,0.0,0.0, 1.0,1.0,0.0,
    0.0,1.0,0.0, 0.0,1.0,1.0, 1.0,1.0,0.0,
    0.0,1.0,1.0, 1.0,1.0,1.0, 1.0,1.0,0.0,
]),3));
let material = new THREE.MeshLambertMaterial({color: 0xffdd99});
let box = new THREE.Mesh(geom, material);
scene.add(box);

下面的脚本中加入了三个灯光,两个平行光和一个环境光。

let ambient  = new THREE.AmbientLight(0xcccccc);
scene.add(ambient);
let directional = new THREE.DirectionalLight(0xffffff,0.6);
directional.position.x = 3;
directional.position.z = 5;
directional.position.y = 10;
scene.add(directional);
let directional_back = new THREE.DirectionalLight(0xffffff,0.1);
directional_back.position.x = -5;
directional_back.position.z = -3;
directional_back.position.y = 0;
scene.add(directional_back);

渲染结果是下面这样,立方体完全没有受到灯光的影响,一点立体感都没有,看起来就是一个二维得六边形。

为什么会这样呢,原因是我们创建的几何结构缺少normal元素,也就是法线。

法线用来表述表面的方向,一般情况下,法线是垂直与平面的一根线(面法线),三维中面法线依靠组成面的顶点法线来描述(顶点法线)。

法线为面的时候,面上的三个顶点法线都是垂直与面的一根线。

法线是由一组三维坐标组成的一个向量,每个顶点的法线都可以单独设置。

法线决定模型在灯光照耀下的颜色,我们知道现实中的物体都是因为反射光线才能被我们看到的,现实中的物体大多数表面都是粗糙的,反射光以不同的方向射出,我们在不同角度都能看到物体,这种被成为漫反射。如果物体的表面向镜子一样光滑,那么光线就会以特定的方向反射出去。我们通过镜子看到反射的物体,就需要在特定的位置才行。

在三维世界中,漫反射通过材质(material)表现出来。物体的颜色取决与光线颜色,物体的固有色以及光线与面法线的夹角,计算公式为:光的颜色 * 固有色 * cos(夹角)。

使用threeJs的编辑器验证了一下,结果跟我们算出来的一样,可喜可贺。

注意,新版官方的编辑器默认开启伽马矫正,颜色会比实际的亮,使用我们改动后的编辑器可以设置伽马

image-20191122153252068

了解法线概念后,我们继续完成之前的立方体,加入法线。每个面上三个顶点的法线都是一样的,也就是把法线设置成了面法线。

let geom = new THREE.BufferGeometry();
geom.setAttribute('position',new THREE.BufferAttribute(new Float32Array([
        0.0,0.0,0.0, 1.0,0.0,0.0, 1.0,0.0,1.0,
        0.0,0.0,0.0, 1.0,0.0,1.0, 0.0,0.0,1.0,
        0.0,0.0,0.0, 0.0,1.0,0.0, 1.0,0.0,0.0,
        0.0,1.0,0.0, 1.0,1.0,0.0, 1.0,0.0,0.0,
        0.0,0.0,0.0, 0.0,1.0,1.0, 0.0,1.0,0.0,
        0.0,0.0,0.0, 0.0,0.0,1.0, 0.0,1.0,1.0,
        0.0,1.0,1.0, 0.0,0.0,1.0, 1.0,1.0,1.0,
        0.0,0.0,1.0, 1.0,0.0,1.0, 1.0,1.0,1.0,
        1.0,1.0,1.0, 1.0,0.0,1.0, 1.0,1.0,0.0,
        1.0,0.0,1.0, 1.0,0.0,0.0, 1.0,1.0,0.0,
        0.0,1.0,0.0, 0.0,1.0,1.0, 1.0,1.0,0.0,
        0.0,1.0,1.0, 1.0,1.0,1.0, 1.0,1.0,0.0,
]),3));
geom.setAttribute('normal',new THREE.BufferAttribute(new Float32Array([
        0.0,-1.0,0.0, 0.0,-1.0,0.0, 0.0,-1.0,0.0,
        0.0,-1.0,0.0, 0.0,-1.0,0.0, 0.0,-1.0,0.0,
        0.0,0.0,-1.0, 0.0,0.0,-1.0, 0.0,0.0,-1.0,
        0.0,0.0,-1.0, 0.0,0.0,-1.0, 0.0,0.0,-1.0,
        -1.0,0.0,0.0, -1.0,0.0,0.0, -1.0,0.0,0.0,
        -1.0,0.0,0.0, -1.0,0.0,0.0, -1.0,0.0,0.0,
        0.0,0.0,1.0, 0.0,0.0,1.0, 0.0,0.0,1.0,
        0.0,0.0,1.0, 0.0,0.0,1.0, 0.0,0.0,1.0,
        1.0,0.0,0.0, 1.0,0.0,0.0, 1.0,0.0,0.0,
        1.0,0.0,0.0, 1.0,0.0,0.0, 1.0,0.0,0.0,
        0.0,1.0,0.0, 0.0,1.0,0.0, 0.0,1.0,0.0,
        0.0,1.0,0.0, 0.0,1.0,0.0, 0.0,1.0,0.0,
]),3));
let material = new THREE.MeshLambertMaterial({color: 0xffdd99});
let box = new THREE.Mesh(geom, material);
scene.add(box);

结果就是下面的这样,立方体拥有了体感。

image-20191122162011341

上面有说,每个顶点的法线都是可以单独设置的,我们完成的立方体法线是这个样子的,每个面上的三个顶点的法线都垂直于面。

image-20191122162543697

下图三个平面,第一个法线全部朝着一个方向,且与面垂直(面法线);

第二个法线方向一致,不过并不垂直与平面,平面的颜色变暗了,不过整个平面都是一个颜色;

第三个法线方向不一致,平面呈现出平滑过度的两个颜色;

image-20191122163143971

思考一下,为什么要这样设置法线以及为什么三维引擎允许我们可以设置每个顶点的法线,这样做有什么意义呢。

看一下下面两个物体,左边的菱角分明,而右边的却很光滑,两个物体的几何结构是一样的,为何右边的看起来非常光滑呢。

image-20191122163916029

显示两个物体的法线,可以看到左边物体为面法线,也就是单独每个点的法线都是一个方向并且垂直与面。而右边物体的法线并不垂直于平面,并且不是同一个方向,仔细观察可以法线每个顶点的法线于邻面上顶点的法线重合。

image-20191122164622644

光线照射在顶点上的时候根据之前说的算法,可以得到这个顶点的颜色,若是平面上所有顶点的颜色都是一致的(法线方向一致),那么平面也就是这个颜色,两个相邻平面的颜色不一致,我们看到的图像就有了菱角(左图的效果)。

若是平面上顶点的法线方向不一样,也是说每个点的颜色也就不相同,片面就会呈现渐变的多个颜色,当相邻平面的交接处(临边)颜色一致的时候,我们就不会看到一条边了。

image-20191122165231787

这样设置法线的方式在三维世界中非常常见,如果我只是用面法线,那么看到三维世界就会向纸折出来的一样,一点都不真实。

img

我们在电影游戏中看到的很多武铁表面都是光滑的,正是因为运用了法线的特点,特别在以前的游戏中,电脑性能不能支持一个场景太多面的情况下,一个柱形物体往往只有很少的面数(八边柱),而我们看起来也挺圆的。

UV

几何结构(geometry)除了顶点的位置(position),法线方向(normal)外还有一个属性叫UV,UV描述的是面在二维上的映射位置。

为啥需要把三维的面映射到二维呢?

现实中纸盒子可以帮助我们非常好的去理解UV,下图是一个化妆品的包装盒,图案和文字都被印刷在一张平面的纸上,通过折叠和黏贴,可以得到一个漂亮的纸盒子。

下图是用maya制作的盒子,使用纸盒子的示意图做贴图。通过UV,把二维的图片映射到三维的模型上。

在three.js中,UV由一组二维坐标构成,对应position中的每个三角面,描述模型每个面在UV中的位置。

let geom = new THREE.BufferGeometry();
geom.setAttribute('position',new THREE.BufferAttribute(new Float32Array([
        0.0,0.0,0.0, 1.0,0.0,0.0, 1.0,0.0,1.0,
        0.0,0.0,0.0, 1.0,0.0,1.0, 0.0,0.0,1.0,
        0.0,0.0,0.0, 0.0,1.0,0.0, 1.0,0.0,0.0,
        0.0,1.0,0.0, 1.0,1.0,0.0, 1.0,0.0,0.0,
        0.0,0.0,0.0, 0.0,1.0,1.0, 0.0,1.0,0.0,
        0.0,0.0,0.0, 0.0,0.0,1.0, 0.0,1.0,1.0,
        0.0,1.0,1.0, 0.0,0.0,1.0, 1.0,1.0,1.0,
        0.0,0.0,1.0, 1.0,0.0,1.0, 1.0,1.0,1.0,
        1.0,1.0,1.0, 1.0,0.0,1.0, 1.0,1.0,0.0,
        1.0,0.0,1.0, 1.0,0.0,0.0, 1.0,1.0,0.0,
        0.0,1.0,0.0, 0.0,1.0,1.0, 1.0,1.0,0.0,
        0.0,1.0,1.0, 1.0,1.0,1.0, 1.0,1.0,0.0,
]),3));
geom.setAttribute('normal',new THREE.BufferAttribute(new Float32Array([
        0.0,-1.0,0.0, 0.0,-1.0,0.0, 0.0,-1.0,0.0,
        0.0,-1.0,0.0, 0.0,-1.0,0.0, 0.0,-1.0,0.0,
        0.0,0.0,-1.0, 0.0,0.0,-1.0, 0.0,0.0,-1.0,
        0.0,0.0,-1.0, 0.0,0.0,-1.0, 0.0,0.0,-1.0,
        -1.0,0.0,0.0, -1.0,0.0,0.0, -1.0,0.0,0.0,
        -1.0,0.0,0.0, -1.0,0.0,0.0, -1.0,0.0,0.0,
        0.0,0.0,1.0, 0.0,0.0,1.0, 0.0,0.0,1.0,
        0.0,0.0,1.0, 0.0,0.0,1.0, 0.0,0.0,1.0,
        1.0,0.0,0.0, 1.0,0.0,0.0, 1.0,0.0,0.0,
        1.0,0.0,0.0, 1.0,0.0,0.0, 1.0,0.0,0.0,
        0.0,1.0,0.0, 0.0,1.0,0.0, 0.0,1.0,0.0,
        0.0,1.0,0.0, 0.0,1.0,0.0, 0.0,1.0,0.0,
]),3));
geom.setAttribute('uv', new THREE.BufferAttribute(new Float32Array([
        0.0, 0.0, 1.0, 0.0, 1.0, 1.0,
        0.0, 0.0, 1.0, 1.0, 0.0, 1.0,
        0.0, 0.0, 0.0, 1.0, 1.0, 0.0,
        0.0, 1.0, 1.0, 1.0, 1.0, 0.0,
        0.0, 0.0, 1.0, 1.0, 1.0, 0.0,
        0.0, 0.0, 0.0, 1.0, 1.0, 1.0,
        1.0, 1.0, 1.0, 0.0, 0.0, 1.0,
        1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
        1.0, 1.0, 0.0, 1.0, 1.0, 0.0,
        0.0, 1.0, 0.0, 0.0, 1.0, 0.0,
        0.0, 0.0, 0.0, 1.0, 1.0, 0.0,
        0.0, 1.0, 1.0, 1.0, 1.0, 0.0,
]), 2));
new THREE.TextureLoader().load('./gold.png', function (texture) {
      texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
      let material = new THREE.MeshLambertMaterial({ map: texture });
      let box = new THREE.Mesh(geom, material);
      scene.add(box);
});

继续之前的立方体,加入UV属性,并且导入一张贴图后的效果就是这样的。

实际模型使用的时候,UV和贴图都要复杂很多,而且贴图也不仅仅只是显示模型的表面纹理,three.js提供了多种贴图用于提升场景效果,比如反射贴图、高光贴图、透明贴图、自发光贴图等等,依赖贴图,可以让并不复杂的模型呈现出复杂的效果,这个技术也就是常说的次世代技术。

下图是仅仅有漫反射贴图的效果,艺术家(浦贵)通过一笔一画绘制的头像,一般被成为手绘贴图,大名鼎鼎的魔兽世界技术就是采用这种制作方式搭建了整个艾泽拉斯。

我们的地图,使用的也是漫反射贴图的方式表现,地图上的光照,颜色阴影都是通过软件计算出来的。这种方式叫做烘培,先渲染出模型每个面的光影和颜色保存到贴图走个,再把贴图贴到模型上,使用这种技术可以弥补实时渲染光照能力不足的缺陷,游戏行业中广泛使用的一种技术。

最后说一下法线贴图,法线用于模型每个面在光照下的颜色显示,同时可以通过设置法线方向改变模型的软硬边。实际上通过贴图也能改变模型表面的法线方向,让单调的平面波澜起伏,并且会随着灯光位置的改变而改变。法线贴图是次世代技术非常重要的组成部分,在不增加模型面数的情况下,让模型的细节更加丰富,更趋近于真实。

木兰宽松许可证, 第1版 木兰宽松许可证, 第1版 2019年8月 http://license.coscl.org.cn/MulanPSL 您对“软件”的复制、使用、修改及分发受木兰宽松许可证,第1版(“本许可证”)的如下条款的约束: 0. 定义 “软件”是指由“贡献”构成的许可在“本许可证”下的程序和相关文档的集合。 “贡献者”是指将受版权法保护的作品许可在“本许可证”下的自然人或“法人实体”。 “法人实体”是指提交贡献的机构及其“关联实体”。 “关联实体”是指,对“本许可证”下的一方而言,控制、受控制或与其共同受控制的机构,此处的控制是指有受控方或共同受控方至少50%直接或间接的投票权、资金或其他有价证券。 “贡献”是指由任一“贡献者”许可在“本许可证”下的受版权法保护的作品。 1. 授予版权许可 每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的版权许可,您可以复制、使用、修改、分发其“贡献”,不论修改与否。 2. 授予专利许可 每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的(根据本条规定撤销除外)专利许可,供您制造、委托制造、使用、许诺销售、销售、进口其“贡献”或以其他方式转移其“贡献”。前述专利许可仅限于“贡献者”现在或将来拥有或控制的其“贡献”本身或其“贡献”与许可“贡献”时的“软件”结合而将必然会侵犯的专利权利要求,不包括仅因您或他人修改“贡献”或其他结合而将必然会侵犯到的专利权利要求。如您或您的“关联实体”直接或间接地(包括通过代理、专利被许可人或受让人),就“软件”或其中的“贡献”对任何人发起专利侵权诉讼(包括反诉或交叉诉讼)或其他专利维权行动,指控其侵犯专利权,则“本许可证”授予您对“软件”的专利许可自您提起诉讼或发起维权行动之日终止。 3. 无商标许可 “本许可证”不提供对“贡献者”的商品名称、商标、服务标志或产品名称的商标许可,但您为满足第4条规定的声明义务而必须使用除外。 4. 分发限制 您可以在任何媒介中将“软件”以源程序形式或可执行形式重新分发,不论修改与否,但您必须向接收者提供“本许可证”的副本,并保留“软件”中的版权、商标、专利及免责声明。 5. 免责声明与责任限制 “软件”及其中的“贡献”在提供时不带任何明示或默示的担保。在任何情况下,“贡献者”或版权所有者不对任何人因使用“软件”或其中的“贡献”而引发的任何直接或间接损失承担责任,不论因何种原因导致或者基于何种法律理论,即使其曾被建议有此种损失的可能性。 条款结束。 如何将木兰宽松许可证,第1版,应用到您的软件 如果您希望将木兰宽松许可证,第1版,应用到您的新软件,为了方便接收者查阅,建议您完成如下三步: 1, 请您补充如下声明中的空白,包括软件名、软件的首次发表年份以及您作为版权人的名字; 2, 请您在软件包的一级目录下创建以“LICENSE”为名的文件,将整个许可证文本放入该文件中; 3, 请将如下声明文本放入每个源文件的头部注释中。 Copyright (c) [2019] [name of copyright holder] [Software Name] is licensed under the Mulan PSL v1. You can use this software according to the terms and conditions of the Mulan PSL v1. You may obtain a copy of Mulan PSL v1 at: http://license.coscl.org.cn/MulanPSL THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. See the Mulan PSL v1 for more details. Mulan Permissive Software License,Version 1 Mulan Permissive Software License,Version 1 (Mulan PSL v1) August 2019 http://license.coscl.org.cn/MulanPSL Your reproduction, use, modification and distribution of the Software shall be subject to Mulan PSL v1 (this License) with following terms and conditions: 0. Definition Software means the program and related documents which are comprised of those Contribution and licensed under this License. Contributor means the Individual or Legal Entity who licenses its copyrightable work under this License. Legal Entity means the entity making a Contribution and all its Affiliates. Affiliates means entities that control, or are controlled by, or are under common control with a party to this License, ‘control’ means direct or indirect ownership of at least fifty percent (50%) of the voting power, capital or other securities of controlled or commonly controlled entity. Contribution means the copyrightable work licensed by a particular Contributor under this License. 1. Grant of Copyright License Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable copyright license to reproduce, use, modify, or distribute its Contribution, with modification or not. 2. Grant of Patent License Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable (except for revocation under this Section) patent license to make, have made, use, offer for sale, sell, import or otherwise transfer its Contribution where such patent license is only limited to the patent claims owned or controlled by such Contributor now or in future which will be necessarily infringed by its Contribution alone, or by combination of the Contribution with the Software to which the Contribution was contributed, excluding of any patent claims solely be infringed by your or others’ modification or other combinations. If you or your Affiliates directly or indirectly (including through an agent, patent licensee or assignee), institute patent litigation (including a cross claim or counterclaim in a litigation) or other patent enforcement activities against any individual or entity by alleging that the Software or any Contribution in it infringes patents, then any patent license granted to you under this License for the Software shall terminate as of the date such litigation or activity is filed or taken. 3. No Trademark License No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, except as required to fulfill notice requirements in section 4. 4. Distribution Restriction You may distribute the Software in any medium with or without modification, whether in source or executable forms, provided that you provide recipients with a copy of this License and retain copyright, patent, trademark and disclaimer statements in the Software. 5. Disclaimer of Warranty and Limitation of Liability The Software and Contribution in it are provided without warranties of any kind, either express or implied. In no event shall any Contributor or copyright holder be liable to you for any damages, including, but not limited to any direct, or indirect, special or consequential damages arising from your use or inability to use the Software or the Contribution in it, no matter how it’s caused or based on which legal theory, even if advised of the possibility of such damages. End of the Terms and Conditions How to apply the Mulan Permissive Software License,Version 1 (Mulan PSL v1) to your software To apply the Mulan PSL v1 to your work, for easy identification by recipients, you are suggested to complete following three steps: i. Fill in the blanks in following statement, including insert your software name, the year of the first publication of your software, and your name identified as the copyright owner; ii. Create a file named “LICENSE” which contains the whole context of this License in the first directory of your software package; iii. Attach the statement to the appropriate annotated syntax at the beginning of each source file. Copyright (c) [2019] [name of copyright holder] [Software Name] is licensed under the Mulan PSL v1. You can use this software according to the terms and conditions of the Mulan PSL v1. You may obtain a copy of Mulan PSL v1 at: http://license.coscl.org.cn/MulanPSL THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. See the Mulan PSL v1 for more details.

简介

暂无描述 展开 收起
JavaScript
MulanPSL-1.0
取消

发行版

暂无发行版

贡献者

全部

近期动态

加载更多
不能加载更多了
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
JavaScript
1
https://gitee.com/dseaice/three-js-foundation.git
git@gitee.com:dseaice/three-js-foundation.git
dseaice
three-js-foundation
threeJs基础
master

搜索帮助

344bd9b3 5694891 D2dac590 5694891