three.js纹理贴图(一)

Posted by 姚飞亮 on 2019-01-17

three.js 纹理贴图,不一样的皮肤(一)

纹理概述

纹理对于我们来说是多么的重要,以至于大家已经忘记了它的重要性。闭上眼睛想一想,如果你心爱的女人,没有穿衣服,该是多么的令你心动啊。哦,说错了,是她不仅没有穿衣服,而且没有皮肤,就像画皮中的没有皮的周迅一样,你就不会喜欢它了,因为她奇丑无比。
纹理之于3D世界,就像皮肤之于动物世界一样。如果没有皮肤,那么人就会非常的丑陋,没有纹理,那么3D世界也就不会那么吸引人了。

纹理由图片组成

我们来想一想,3D世界的纹理,由什么组成呢?3D世界的纹理由图片组成。
是的,就这么简单,如果下次谁问你什么是纹理,那么你告诉它是图片,或者贴图就完了。将纹理以一定的规则映射到几何体上,一般是三角形上,那么这个几何体就有纹理皮肤了。
那么在threejs中,或者任何3D引擎中,纹理应该怎么来实现呢?首先应该有一个纹理类,其次是有一个加载图片的方法,将这张图片和这个纹理类捆绑起来。
在threejs中,纹理类由THREE.Texture表示,其构造函数如下所示:

1
2
THREE.Texture( image, mapping, wrapS, wrapT, magFilter,
minFilter, format, type, anisotropy )

各个参数的意义是:
Image:这是一个图片类型,基本上它有ImageUtils来加载,如下代码

1
2
3
// url 是一个http://xxxx/aaa.jpg 的类似地址,javascript没有从本地加载数据的能力,
// 所以没有办法从您电脑的盘加载数据。
var image = THREE.ImageUtils.loadTexture(url);

Mapping:是一个THREE.UVMapping()类型,它表示的是纹理坐标。下一节,我们将说说纹理坐标。
wrapS:表示x轴的纹理的回环方式,就是当纹理的宽度小于需要贴图的平面的宽度的时候,平面剩下的部分应该p以何种方式贴图的问题。
wrapT:表示y轴的纹理回环方式。 magFilter和minFilter表示过滤的方式,这是OpenGL的基本概念,我将在下面讲一下,目前你不用担心它的使用。当您不设置的时候,它会取默认值,所以,我们这里暂时不理睬他。
format:表示加载的图片的格式,这个参数可以取值THREE.RGBAFormat,RGBFormat等。THREE.RGBAFormat表示每个像素点要使用四个分量表示,分别是红、绿、蓝、透明来表示。RGBFormat则不使用透明,也就是说纹理不会有透明的效果。
type:表示存储纹理的内存的每一个字节的格式,是有符号,还是没有符号,是整形,还是浮点型。不过这里默认是无符号型(THREE.UnsignedByteType)。
anisotropy:各向异性过滤。使用各向异性过滤能够使纹理的效果更好,但是会消耗更多的内存、CPU、GPU时间,暂时就了解到这里吧。
ok,各个参数介绍完了。我们接下来看纹理坐标。

纹理坐标

在正常的情况下,你在0.0到1.0的范围内指定纹理坐标。我们来简单看一下纹理坐标如下图:

当我们用一幅图来做纹理的时候,那么这幅图就隐示的被赋予了如图一样的纹理坐标,这个纹理坐标将被对应到一个形状上。

实例

ok,我们来看一下例子,这个例子很简单,就是在平面上贴上一张美女的图片。首先,来看看效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Three框架</title>
<script src="/javascripts/three.min.js"></script>
<script src="/javascripts/stats.min.js"></script>
<script src="/javascripts/Tween.min.js"></script>
<style>
body { margin: 0; }
canvas { width: 100%; height: 100% }
</style>
</head>
<body>
<script>
var renderer;
var stats;
function initThree() {
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setClearColor(0xFFFFFF, 1.0);
document.body.appendChild( renderer.domElement );

stats = new Stats();
//stats.setMode(1); // 0: fps, 1: ms
stats.domElement.style.position = 'absolute';
stats.domElement.style.left = '0px';
stats.domElement.style.top = '0px';
document.body.appendChild(stats.domElement);
}
var camera;
function initCamera() {
camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 10000 );
camera.position.x = 600 ;
camera.position.y = 0;
camera.position.z = 600;
camera.lookAt(new THREE.Vector3(0,0,0));
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
var scene;
function initScene() {
scene = new THREE.Scene();
}

var light;
function initLight() {
// 方向光
/* light = new THREE.DirectionalLight(0xFF0000);
light.position.set(0, 0,1);
scene.add(light);
light = new THREE.PointLight(0x00FF00);
light.position.set(0, 0,25);
scene.add(light);*/
}

var cube;
function initObject() {

var geometry = new THREE.PlaneGeometry( 500, 300, 1, 1 );
geometry.vertices[0].uv = new THREE.Vector2(0,0);
geometry.vertices[1].uv = new THREE.Vector2(2,0);
geometry.vertices[2].uv = new THREE.Vector2(2,2);
geometry.vertices[3].uv = new THREE.Vector2(0,2);

//加载纹理
var texture = new THREE.Texture();
//加载图片
var imgLoader = new THREE.ImageLoader();
imgLoader.load('images/timg.png',function(img)
{
//将图片值赋于纹理
texture.image = img;
texture.needsUpdate = true;
});
// 将纹理应用于材质
var material = new THREE.MeshBasicMaterial({map:texture});
var mesh = new THREE.Mesh( geometry,material );
scene.add(mesh);
window.addEventListener( 'resize', onWindowResize, false );
}
function threeStart() {
initThree();
initCamera();
initScene();
initLight();
initObject();
animation();
}


function animation()
{
//camera.position.x =camera.position.x +1;
renderer.render(scene, camera);
requestAnimationFrame(animation);
stats.update();
//TWEEN.update();
}

threeStart();
</script>
</body>
</html>

仔细阅读上面的代码,一共完成了4件事情:

a:画一个平面
b:为平面赋予纹理坐标
c:加载纹理
d:将纹理应用于材质

做了这四件事,我们的工作就可以大功告成了。还是让我们来详细解释一下吧。

画一个平面

通过PlaneGemotry可以画一个平面,代码如下:

1
var geometry = new THREE.PlaneGeometry( 500, 300, 1, 1 );

这个平面的宽度是500,高度是300.

为平面赋予纹理坐标

平面有4个顶点,所以我们只需要指定4个纹理坐标就行了。纹理坐标由顶点的uv成员来表示,uv被定义为一个二维向量THREE.Vector2(),我们可以通过如下代码来为平面定义纹理:

1
2
3
4
geometry.vertices[0].uv = new THREE.Vector2(0,0);
geometry.vertices[1].uv = new THREE.Vector2(1,0);
geometry.vertices[2].uv = new THREE.Vector2(1,1);
geometry.vertices[3].uv = new THREE.Vector2(0,1);

注意,4个顶点分别对应了纹理的4个顶点。还要注意(0,0),(1,0),(1,1),(0,1)他们之间的顺序是逆时针方向。大家在给平面赋纹理坐标的时候也要注意方向,不然three.js是分不清楚的。

加载纹理

纹理作为一张图片,可以来源于互联网,或者本地服务器

1
2
3
4
5
6
7
8
9
10
 //加载纹理
var texture = new THREE.Texture();
//加载图片
var imgLoader = new THREE.ImageLoader();
imgLoader.load('images/timg.png',function(img)
{
//将图片值赋于纹理
texture.image = img;
texture.needsUpdate = true;
});

将纹理应用于材质

加载好纹理,万事俱备了,只需要将纹理映射到材质就可以了。我们这里使用了一个普通的材质THREE.MeshBasicMaterial,材质中有一个map属性,可以直接接受纹理,我们可以这样定义一个带纹理的材质:

1
2
3
4
 // 将纹理应用于材质
var material = new THREE.MeshBasicMaterial({map:texture});
var mesh = new THREE.Mesh( geometry,material );
scene.add(mesh);

行了,打开你的浏览器,输入你服务器的网址吧,结果就在你眼前。



Ω