`

深入理解Three.js(WebGL)贴图(纹理映射)和UV映射

阅读更多

深入理解Three.js(WebGL)贴图(纹理映射)和UV映射

iefreer 发表于 2016-08-12 09:14:18

 

标签: webgltexturemapuvcube

+

本文将详细描述如何使用Three.js给3D对象添加贴图(Texture Map,也译作纹理映射,“贴图”的翻译要更直观,而“纹理映射”更准确。)。为了能够查看在线演示效果,你需要有一个兼容WebGL的现代浏览器(最好是Chrome/FireFox/Safari/Edge/IE11+)。

本文的在线演示结果和代码请点击这里:Three.js贴图实例

什么是贴图(Texture Mapping)

贴图是通过将图像应用到对象的一个或多个面,来为3D对象添加细节的一种方法。

这使我们能够添加表面细节,而无需将这些细节建模到我们的3D对象中,从而大大精简3D模型的多边形边数,提高模型渲染性能。

开始吧

这里方便起见,我们使用踏得网在线开发工具来一步步边学边操作。

请点击新建作品,在第三方库中选择Three.js 80版本,这将自动加载对应版本的Three.js开发库(注:你也可以直接把<script src="http://wow.techbrood.com/libs/three.r73.js"></script>拷贝到HTML代码面板中去)。

首先我们创建一个立方体,在JavaScript面板中编写代码如下:

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
var camera;
var scene;
var renderer;
var mesh;
  
init();
animate();
  
function init() {
  
    scene = new THREE.Scene();
    camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 1, 1000);
  
    var light = new THREE.DirectionalLight( 0xffffff );
    light.position.set( 0, 1, 1 ).normalize();
    scene.add(light);
  
    var geometry = new THREE.CubeGeometry( 10, 10, 10);
    var material = new THREE.MeshPhongMaterial( { ambient: 0x050505, color: 0x0033ff, specular: 0x555555, shininess: 30 } );
  
    mesh = new THREE.Mesh(geometry, material );
    mesh.position.z = -50;
    scene.add( mesh );
  
    renderer = new THREE.WebGLRenderer();
    renderer.setSize( window.innerWidth, window.innerHeight );
    document.body.appendChild( renderer.domElement );
  
    window.addEventListener( 'resize', onWindowResize, false );
  
    render();
}
  
function animate() {
    mesh.rotation.x += .04;
    mesh.rotation.y += .02;
  
    render();
    requestAnimationFrame( animate );
}
  
function render() {
    renderer.render( scene, camera );
}
  
function onWindowResize() {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize( window.innerWidth, window.innerHeight );
    render();
}

点击菜单栏中的[运行]菜单(blob.png),或者按快捷键:CTRL+R,来运行该代码,你将看到一个旋转的蓝色立方体:

我们接下来要做的就是把这个立方体变成一个游戏里常见的木箱子,如下图所示:

为此我们需要一张箱子表面的图像,并用这张图像映射到立方体对象的材料中去,

这里我们直接使用在线图片http://wow.techbrood.com/uploads/1702/crate.jpg.

JS代码中修改之前的材料(material)创建代码:

1
var material = new THREE.MeshPhongMaterial( { ambient: 0x050505, color: 0x0033ff, specular: 0x555555, shininess: 30 } );

为使用贴图:

1
var material = new THREE.MeshPhongMaterial( { map: THREE.ImageUtils.loadTexture('http://wow.techbrood.com/uploads/1702/crate.jpg') } );

再运行下(按[运行]菜单或CTRL+R快捷键),你会看到一个旋转的板条箱,而不是一个普通的蓝色立方体。

在构造我们的材质时,我们指定了texture属性并将其值设置为木箱图像,Three.js然后会加载纹理图像并映射到立方体各个面上。

那么,问题是如果我们想给不同的面添加不同的纹理贴图,该怎么办呢?

一种方法是使用材料数组,我们创建6个新材料,每一个使用不同的纹理贴图:bricks.jpg,clouds.jpg,stone-wall.jpg,water.jpg,wood-floor.jpg以及上面的crate.jpg。

相应的,我们把材料构造代码修改为:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var material1 = new THREE.MeshPhongMaterial( { 
map: THREE.ImageUtils.loadTexture('/uploads/1702/crate.jpg') } );
var material2 = new THREE.MeshPhongMaterial( { 
map: THREE.ImageUtils.loadTexture('/uploads/1702/bricks.jpg') } );
var material3 = new THREE.MeshPhongMaterial( { 
map: THREE.ImageUtils.loadTexture('/uploads/1702/clouds.jpg') } );
var material4 = new THREE.MeshPhongMaterial( { 
map: THREE.ImageUtils.loadTexture('/uploads/1702/stone-wall.jpg') } );
var material5 = new THREE.MeshPhongMaterial( { 
map: THREE.ImageUtils.loadTexture('/uploads/1702/water.jpg') } );
var material6 = new THREE.MeshPhongMaterial( { 
map: THREE.ImageUtils.loadTexture('/uploads/1702/wood-floor.jpg') } );
 
var materials = [material1, material2, material3, material4, material5, material6];
 
var meshFaceMaterial = new THREE.MeshFaceMaterial( materials );

 

上述代码,我们先分别创建了6个材料,组成了一个材料数组,并使用这个数组创建一个MeshFaceMaterial对象。

最后,我们需要告诉我们的3D模型来使用这个新的组合“面材料”,修改下面的代码:

1
mesh = new THREE.Mesh(geometry, material );

为:

1
mesh = new THREE.Mesh(geometry,  meshFaceMaterial);

再运行下(按[运行]菜单或CTRL+R快捷键),你就将看到立方体的各个表面使用了不同的贴图。

这很酷,Three.js会自动把数组中的这些材料应用到不同的面上去。

但问题又来了,随着3D模型的面的增长,为每个面创建贴图是不现实的。

这就是为什么我们需要另外一种更为普遍的解决方法:UV映射的原因。

UV映射(UV Mapping)

UV映射最典型的例子就是把一张地图映射到3D球体的地球仪上去。其本质上就是把平面图像的不同区块映射到3D模型的不同面上去。我们把之前的6张图拼装成如下的一张图:http://wow.techbrood.com/uploads/160801/texture-atlas.jpg.

修改如下代码:

1
2
3
4
5
6
var material1 = new THREE.MeshPhongMaterial( { map: THREE.ImageUtils.loadTexture('images/crate.jpg') } );
var material2 = new THREE.MeshPhongMaterial( { map: THREE.ImageUtils.loadTexture('images/bricks.jpg') } );
var material3 = new THREE.MeshPhongMaterial( { map: THREE.ImageUtils.loadTexture('images/clouds.jpg') } );
var material4 = new THREE.MeshPhongMaterial( { map: THREE.ImageUtils.loadTexture('images/stone-wall.jpg') } );
var material5 = new THREE.MeshPhongMaterial( { map: THREE.ImageUtils.loadTexture('images/water.jpg') } );
var material6 = new THREE.MeshPhongMaterial( { map: THREE.ImageUtils.loadTexture('images/wood-floor.jpg') } );

为:

1
var material = new THREE.MeshPhongMaterial( { map: THREE.ImageUtils.loadTexture('images/texture-atlas.jpg') } );

我们又把代码给改回来使用一张贴图了,接下来我们需要把贴图的不同位置映射到立方体不同的面上去。

首先我们创建贴图的6个子图,在创建完材料的代码后面添加如下几行:

1
2
3
4
5
6
var bricks = [new THREE.Vector2(0, .666), new THREE.Vector2(.5, .666), new THREE.Vector2(.5, 1), new THREE.Vector2(0, 1)];
var clouds = [new THREE.Vector2(.5, .666), new THREE.Vector2(1, .666), new THREE.Vector2(1, 1), new THREE.Vector2(.5, 1)];
var crate = [new THREE.Vector2(0, .333), new THREE.Vector2(.5, .333), new THREE.Vector2(.5, .666), new THREE.Vector2(0, .666)];
var stone = [new THREE.Vector2(.5, .333), new THREE.Vector2(1, .333), new THREE.Vector2(1, .666), new THREE.Vector2(.5, .666)];
var water = [new THREE.Vector2(0, 0), new THREE.Vector2(.5, 0), new THREE.Vector2(.5, .333), new THREE.Vector2(0, .333)];
var wood = [new THREE.Vector2(.5, 0), new THREE.Vector2(1, 0), new THREE.Vector2(1, .333), new THREE.Vector2(.5, .333)];

上面的代码创建了六个数组,每一个对应于纹理贴图中的每个子图像。每个数组包含4个点,定义子图像的边界。坐标的范围值是0到1,(0,0)表示左下角,(1,1)表示右上角。

子图像的坐标是根据贴图中百分比来定义。比如下面这个砖头子图像:

1
2
3
4
5
6
var bricks = [
new THREE.Vector2(0, .666),
new THREE.Vector2(.5, .666),
new THREE.Vector2(.5, 1),
new THREE.Vector2(0, 1)
];

在贴图中的位置在左上角(占据横向1/2,竖向1/3的位置),以逆时针方向来定义顶点坐标,从该子图像较低的左下角开始。

左下角:
0 - 最左边
.666 - 底部向上2/3处

右下角:
.5 - 中间线
.666 - 底部向上2/3处

右上角:
.5 - 中间线
1 - 顶边

右上角:
0 - 最左边
1 - 顶边

定义好子图像后,我们现在需要把它们映射到立方体的各个面上去。首先添加如下代码:

1
geometry.faceVertexUvs[0] = [];

上述代码清除现有的UV映射,接着我们添加如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
geometry.faceVertexUvs[0][0] = [ bricks[0], bricks[1], bricks[3] ];
geometry.faceVertexUvs[0][1] = [ bricks[1], bricks[2], bricks[3] ];
  
geometry.faceVertexUvs[0][2] = [ clouds[0], clouds[1], clouds[3] ];
geometry.faceVertexUvs[0][3] = [ clouds[1], clouds[2], clouds[3] ];
  
geometry.faceVertexUvs[0][4] = [ crate[0], crate[1], crate[3] ];
geometry.faceVertexUvs[0][5] = [ crate[1], crate[2], crate[3] ];
  
geometry.faceVertexUvs[0][6] = [ stone[0], stone[1], stone[3] ];
geometry.faceVertexUvs[0][7] = [ stone[1], stone[2], stone[3] ];
  
geometry.faceVertexUvs[0][8] = [ water[0], water[1], water[3] ];
geometry.faceVertexUvs[0][9] = [ water[1], water[2], water[3] ];
  
geometry.faceVertexUvs[0][10] = [ wood[0], wood[1], wood[3] ];
geometry.faceVertexUvs[0][11] = [ wood[1], wood[2], wood[3] ];

geometry对象的faceVertexUvs属性包含该geometry各个面的坐标映射。既然我们映射到一个多维数据集,你可能会疑惑为什么数组中有12个面。原因是在ThreeJS模型中,立方体的每个面实际上是由2个三角形组成的。所以我们必须单独映射每个三角形。上述场景中,ThreeJS将为我们加载单一材料贴图,自动分拆成三角形并映射到每个面。

这里要注意每个面的顶点坐标的定义顺序必须遵循逆时针方向。为了映射底部三角形,我们需要使用的顶点指数0,1和3,而要映射顶部三角形,我们需要使用索引1,2,和顶点的3。

最后,我们替换如下代码:

1
2
var meshFaceMaterial = new THREE.MeshFaceMaterial( materials );
mesh = new THREE.Mesh(geometry,  meshFaceMaterial);

为:

1
mesh = new THREE.Mesh(geometry,  material);

我们再运行下代码(按[运行]菜单或CTRL+R快捷键),将看到各个面使用不同贴图的旋转立方体。

当然对于复杂的对象,我们还可以在建模的时候建立好模型贴图,并导出为ThreeJS所支持的模型格式,然后在场景中直接加载。

这个超出本文范围,请自行搜索本站Three.js在线实例。

 

参考: http://solutiondesign.com/blog/-/blogs/webgl-and-three-js-texture-mappi-1/

编注:原文在线演示和源代码链接不可用,已重新建立在WOW上。

分享到:
评论

相关推荐

    Three.js开发指南源码-3

    这个"Three.js 开发指南源码-3"很可能是针对 Three.js 的一系列教程或示例代码的第三部分,旨在帮助开发者更深入地理解和掌握 Three.js 的使用。在这里,我们将深入探讨 Three.js 的基础知识,特别是与 "textures" ...

    html5+three.js科幻地球仪3D旋转动画特效.zip

    Three.js支持多种图像格式,并允许开发者通过UV映射将它们应用到3D几何体上。 在实现这个特效时,开发者可能使用了以下关键技术点: 1. **WebGL**:WebGL是一种在浏览器中进行硬件加速3D图形渲染的API,它是OpenGL...

    three.js迷宫小游戏.zip

    three.js支持加载各种图像格式的纹理,并且支持UV映射,使贴图在3D模型上平滑展开。 9. **碰撞检测(Collision Detection)** 为了确保游戏逻辑正确,我们需要实现简单的碰撞检测。这可能涉及到空间划分技术如包围...

    Three.js Cookbook.pdf

    2. UV映射:理解UV坐标的概念,以及如何进行纹理映射以精确控制几何体表面的贴图。 四、动画与交互 1. 动画框架:探索Three.js中的动画机制,如使用请求动画帧(requestAnimationFrame)实现平滑的动画效果。 2. ...

    threejs 源码和demo示例

    4. **纹理与映射**:如何加载和应用纹理,包括 UV 映射、立方体贴图等。 5. **相机与视口**:展示了不同类型的相机(正交、透视)以及视口控制的实现。 6. **加载3D模型**:如何使用 Three.js 的加载器导入外部3D...

    3DMinecraftSkinViewer-使用Three.JsHTML53D引擎_HTML_JavaScript_下载.zip

    1. **Three.js**:Three.js是基于WebGL的JavaScript 3D库,它为开发者提供了在浏览器中创建和展示3D图形的简便途径。它封装了WebGL接口,使得开发者无需深入了解底层图形编程,也能轻松构建复杂的3D场景。 2. **...

    使用three.js制作的人物源码.zip

    2. **UV映射与纹理**:为了使人物模型具有逼真的外观,通常需要使用UV映射,即将2D纹理贴图到3D模型的表面。Three.js支持纹理加载,可以使用`TextureLoader`加载图片作为材质的纹理。 3. **骨骼动画**:人物模型...

    Three.js-3D互动城市.zip

    - **贴图(Maping)**:控制纹理如何在几何体表面上展开,如UV映射、立方体贴图等。 6. **光照和阴影(Lighting and Shadows)**: - **光照模型(Light Models)**:如点光源、平行光和聚光灯,以及它们对物体的影响。...

    threejs-video-cube:一个简单的three.js演示,该演示使用gUM从用户的网络摄像头获取媒体流,然后创建一个多维数据集并将视频作为纹理应用于多维数据集的面Kong

    本篇文章将深入探讨一个基于Three.js的项目——"threejs-video-cube",它巧妙地结合了WebGL技术和用户的网络摄像头,实时渲染出一个视频立方体,为用户提供独特的视觉体验。 首先,我们要理解Three.js的核心——...

    3MFLoader:3.js的3MF加载程序

    Three.js是一个基于WebGL的开源JavaScript库,它使开发者能够在网页上创建和展示交互式的3D图形。 在3MFLoader中,有几个关键的概念和技术点值得深入探讨: 1. **3MF文件结构**:3MF文件采用XML格式,包含了模型的...

    Unity资源导出成json文件供网页加载

    5. 处理材质和纹理信息,包括颜色、纹理映射和透明度。 6. 如果存在光照和摄像机,也要进行相应的转换。 7. 将所有数据写入JSON文件,并确保格式正确,易于ThreeJS或AFrame解析。 最后,Web端的ThreeJS或AFrame应用...

    madcaps:玩Matcaps和Threejs

    3. **纹理映射**:在Three.js中,我们需要将Matcaps纹理正确地映射到3D对象上。这涉及到理解UV坐标和纹理坐标系的概念。 4. **光照模型**:尽管Matcaps技术自身处理了光照效果,但理解基本的光照模型(如点光源、...

    效果超棒的Webgl模型-手表

    开发者可能使用了诸如Three.js或Babylon.js这样的库,它们简化了WebGL编程的复杂性,提供了便利的API来处理模型加载、光照、纹理映射和动画等功能。 1. **模型加载**:3D模型通常以OBJ、FBX或GLTF等格式存储,WebGL...

    效果超棒的Webgl模型-海豚2

    纹理是指给3D模型添加颜色和细节,这可能包括贴图(如颜色、法线、位移和镜面贴图)和UV映射。最后,动画部分涉及设置关键帧或使用骨骼系统让模型动起来。 在WebGL中,3D模型通常被导出为轻量级格式,如.gltf或.glb...

    blend模型源文件,城区城市街道楼群,单层商场购物中心内部,城市城镇群

    为了创建这样一个城市环境,建模师会使用Blender中的各种工具,如网格建模、雕刻工具、UV展开和映射、纹理绘制以及光照设置等。同时,为了优化性能和加载速度,还需要考虑模型的简化、LOD(Level of Detail)层次...

    效果超棒的Webgl模型-驱逐舰

    这通常涉及UV映射,即将2D纹理映射到3D模型表面的过程。 3. **导出与优化**:模型完成后,将其导出为WebGL支持的格式,如 glTF 或 OBJ。导出时需要进行优化,减少多边形数量,压缩纹理,以降低加载时间和内存占用。...

    hypr-ThreeJsTerrain:使用开放地形数据对三个js进行地形测试

    3. 纹理映射:为了增强视觉效果,地形可能会被覆盖各种纹理,如草地、水体、岩石等,这些可以通过贴图和UV坐标实现。 4. 光照处理:通过设置光源,模拟太阳、天空或其他环境光,使地形显得更加立体和真实。 5. 摄像...

    效果超棒的Webgl模型-单层小公寓4

    5. **导出和编码**:将3D模型导出为WebGL支持的格式,如 glTF 或 COLLADA,然后使用工具(如Three.js库)将模型转换成JavaScript和JSON格式,便于在浏览器中加载和解析。 6. **交互性**:编写JavaScript代码处理...

    效果超棒的Webgl模型-飞机

    模型的表面处理、纹理贴图和UV映射都需要精细处理,以确保在Web环境中呈现出最佳的视觉体验。 在Web上展示3D模型时,加载速度和性能优化是关键。模型可能经过优化,减少了多边形数量,使用了LOD(Level of Detail)...

Global site tag (gtag.js) - Google Analytics