深入理解Three.js(WebGL)贴图(纹理映射)和UV映射
本文将详细描述如何使用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();
}
|
点击菜单栏中的[运行]菜单(),或者按快捷键: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上。
转载于:https://www.cnblogs.com/yanan-boke/p/7815018.html
深入理解Three.js(WebGL)贴图(纹理映射)和UV映射相关推荐
- 6个网页背景特效源码 canvas+three.js科技贴图 webgl源码
隧道穿梭特效 粒子矩阵特效 几何随机变换特效 下载地址: 6个网页背景特效源码 canvas+three.js科技贴图 webgl源码-素材美
- dva.js 知识导图
dva.js 知识导图 JavaScript 语言 变量声明 const 和 let 模板字符串 默认参数 箭头函数 模块的 Import 和 Export ES6 对象和数组 析构赋值 对象字面量改 ...
- Echart.js的趋势图入门与实例
在平时开发项目时,免不了需要对数据进行图表显示的需求,比如:趋势图.饼状图.柱形图等.自己身为一个PHPer,除了PHP本身的功能,不得不需要借助js来实现显示.在了解众多趋势图插件,国内外开源的项目 ...
- Three.js光照贴图添加阴影(·lightMap)
Three.js光照贴图添加阴影(·lightMap) 本文是Three.js电子书的8.7节 在三维场景中有时候需要设置模型的阴影,也就是阴影贴图或者说光照贴图·lightMap,一般Threejs ...
- d3 svg path添加文本_D3.js 力导向图的显示优化
D3.js 作为一个前端,说到可视化除了听过 D3.js 的大名,常见的可视化库还有 ECharts.Chart.js,这两个库功能也很强大,但是有一个共同特点是封装层次高,留给开发者可设计和控制的部 ...
- 惊艳!可视化的 js:动态图演示 - 事件循环 Event Loop
原文地址:https://dev.to/lydiahallie/javascript-visualized-event-loop-3dif 原文作者:Lydia Hallie 译者:夜尽天明 译者博客 ...
- 深入理解 Node.js 中的 Worker 线程
多年以来,Node.js 都不是实现高 CPU 密集型应用的最佳选择,这主要就是因为 JavaScript 的单线程.作为对此问题的解决方案,Node.js v10.5.0 通过 worker_thr ...
- 新手理解的JS原型链
一直以来对于JavaScript 的原型链的概念,始终有些东西有一种模糊感,最近刚好有时间就塌下心认真的把<JavaScript高级程序设计>中相关内容认真读了一遍,也查看了很多网上很多资 ...
- JavaScript的案例(数据校验,js轮播图,页面定时弹窗)
1.数据校验 步骤 1.确定事件(onsubmit)并绑定一个函数 2.书写这个函数,获取数据,并绑定id ...
最新文章
- distinct吃亏记
- FAIR发布两大更新:PyTorch1.8和一个10亿参数自监督模型,自监督也学GPT-3套路?
- [NIKON D80]实习纪实 苏州工业之美
- 微信小程序01【目录结构详解、视图与渲染、事件、input、scroll-view】
- Java基础教程——Set
- C# WPF MVVM开发框架Caliburn.Micro 关于Conventions⑧
- C语言过时了吗?不,我们需要的是一份个人成长
- 苹果新Mac Pro生产线将从美国转至中国 会更便宜吗?
- 下面哪个选项不是oracle用户,作业三(有答案)
- 使用系统视图发现SQL Server实例信息
- 和php结合实现分页js代码,无JS,完全php面向过程数据分页实现代码
- Java基础面试题:常见的异常类有哪些?
- java 方法(函数)详解
- RFID定位技术下的资产管理与应急仓储物流--RFID资产管理--新导智能
- PUG转HTML格式
- Unity3D脚本中文系列教程(八)
- 什么是核心文件,它们什么时候有用
- NodeRed基础1--循环结构
- 有感而发:中国十大最黑心的职业排行榜
- Windows卡死问题分析
热门文章
- 界面设计语言_使用任何语言设计界面的提示
- 荒径 弗罗斯特_弗罗斯特庞克,颠覆性城市建设者
- quartus FIR仿真笔记
- Ubuntu 16.04使用timedatectl进行管理时间(UTC/CST)(服务器/桌面)
- [.net 面向对象程序设计深入](4)MVC 6 —— 谈谈MVC的版本变迁及新版本6.0发展方向...
- [转]ORACLE 异常错误处理
- 分享10个2012年最新发布的jQuery插件
- SharePoint 2010 - 如何导入\导出WebPart
- 你的网页加载太慢了怎么办?
- Simple TCP Server Client Socket C