第6章 纹理,不一样的皮肤(一)

1、 你能学习到从图片到纹理的过程

2、 多重纹理的使用等

3、 为炫酷的中级教程做好准备

1 纹理概述

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

纹理之于3D世界,就像皮肤之于动物世界一样。如果没有皮肤,那么人就会非常的丑陋,没有纹理,那么3D世界也就不会那么吸引人了。

2 纹理由图片组成

我们来想一想,3D世界的纹理,由什么组成呢?3D世界的纹理由图片组成。

是的,就这么简单,如果下次谁问你什么是纹理,那么你告诉它是图片,或者贴图就完了。将纹理以一定的规则映射到几何体上,一般是三角形上,那么这个几何体就有纹理皮肤了。

那么在threejs中,或者任何3D引擎中,纹理应该怎么来实现呢?首先应该有一个纹理类,其次是有一个加载图片的方法,将这张图片和这个纹理类捆绑起来。

在threejs中,纹理类由THREE.Texture表示,其构造函数如下所示:

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

各个参数的意义是:

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

// url 是一个http://xxxx/aaa.jpg 的类似地址,javascript没有从本地加载数据的能力,
// 所以没有办法从您电脑的C盘加载数据。
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,各个参数介绍完了。我们接下来看纹理坐标。

3 纹理坐标

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

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

4 实例

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

你可以在6下找到这份代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <title></title>
    <meta charset="utf-8">
    <style>
        body {
            margin: 0px;
            background-color: #000000;
            overflow: hidden;
        }
    </style>
</head>
<body>

<script src="../js/three.js"></script>

<script>

    var camera, scene, renderer;
    var mesh;

    init();
    animate();

    function init() {

        renderer = new THREE.WebGLRenderer();
        renderer.setSize( window.innerWidth, window.innerHeight );
        document.body.appendChild( renderer.domElement );
        //
        camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 1, 1000 );
        camera.position.z = 400;
        scene = new THREE.Scene();


        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 = THREE.ImageUtils.loadTexture("textures/a.jpg",null,function(t)
        {
        });
        var material = new THREE.MeshBasicMaterial({map:texture});
        var mesh = new THREE.Mesh( geometry,material );
        scene.add( mesh );

        //
        window.addEventListener( 'resize', onWindowResize, false );

    }

    function onWindowResize() {
        camera.aspect = window.innerWidth / window.innerHeight;
        camera.updateProjectionMatrix();
        renderer.setSize( window.innerWidth, window.innerHeight );
    }

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

</script>

</body>
</html>

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

a:画一个平面

b:为平面赋予纹理坐标

c:加载纹理

d:将纹理应用于材质

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

4.1 画一个平面

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

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

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

4.2为平面赋予纹理坐标

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

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是分不清楚的。

4.3 加载纹理

纹理作为一张图片,可以来源于互联网,或者本地服务器,但是就是不能来源于类似C:\pic\a.jpg这样的本地路径。这是因为javascript没有加载本地路径文件的权限。如果你尝试这么做,会报如下的错误:

所以务必在你的电脑上搭建一个tomcat,apache,iis等服务器中的一种,并把图片资源放到服务器中,并通过http://localhost:8080/img/a.jpg这样的方式去访问它。这样就能解决上面的交叉域问题。我们接着往下讲。

这里加载纹理使用了上面介绍的loadTexture函数,代码如下:

var texture = THREE.ImageUtils.loadTexture("textures/a.jpg",null,function(t)
{

});

这个函数的第一个参数是一个相对路径,表示与您的网页之间的相对路径。相对路径对应了一个纹理图片textures/a.jpg。

第二个参数为null,表示时候要传入一个纹理坐标参数,来覆盖前面在geometry中的参数。

第三个表示一个回调函数,表示成功加载纹理后需要执行的函数,参数t是传入的texture。

最后,这个函数的返回值是加载的纹理。

4.4 将纹理应用于材质

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

var material = new THREE.MeshBasicMaterial({map:texture});

ok,接下来的事情就简单了,直接将纹理甩给Mesh,同时也别忘了Mesh也需要geometry,他们暧昧的关系如下:

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

最后的最后,将这个mesh加入场景中:

scene.add( mesh );

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

5、总结

这只是纹理的一个简单入门,也许你还想知道怎么加载立体纹理、为非光滑的平面贴纹理、甚至为人脸贴纹理,那么就关注后面的课程吧。我们无法在一课把这些知识讲完,但后面的课程会陆续告诉你这些知识的,敬请期待,非常感谢您的阅读。

请疯狂转载基础教程吧,为我们宣传一下,这样我们给你提供高质量的文章,你给我们增加访问量,我们扯平了。玩笑,玩笑,谢谢大家的支持。

6、补充视频

大家除了在首页菜单中下载本课的视频外,还可以通过如下的地址看补充课程“纹理,你必须了解的知识”,这节视频对理解纹理坐标非常有意义。点击这里观看

给WebGL中文网团队的女程序员"小果妹妹"发一个鸡腿吧,微信扫一扫赞赏,感谢。

亲爱的读者,如果你觉得WebGL中文网的课程不错,您可以购买《WebGL中文网视频课程》 课程支持我们哦,购买后记得给我们好评哦!我们强烈建议您不要在iphone上的网易云课堂软件中购买,这样苹果会收取31%左右的服务费,虽然这是明码标价,我们也表示认可和理解,具体选择权在您自己了。

感谢大家的支持,下面是课程的截图之一

[1楼] fion** 2016-07-19 14:23

老师你好。。有没有办法可以加载gif的动图的。。。

WebGL中文网老师回答:

当然可以,当做纹理,视频都是可以加载的哦。

[2楼] like** 2016-07-19 17:34

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);

这四个坐标是象征层面的 显示出来的大小 是与图片真实大小决定的吗

[3楼] 1611** 2016-11-23 12:56

首先感谢作者的辛勤付出,写了这篇通俗易懂的3D教程。

但是,看到这一章我真的忍不住想说一句。

不知道这里有多少女读者,本人作为一个女的,看这篇教程的不少地方都觉得不太舒服。

很多地方有拿女性在开玩笑我也忍了,应该是作者团队全部是男性导致的趣味吧。

但这一章的纹理贴图,让我觉得是在浏览一个不正经的h色网站一样……

作者可以抱着欣赏的态度,放一些美女图片,很好。但是请不要让人感觉像是抱着一种猥琐的态度,放一些衣着暴露的女性图片,很不好。

忍不住吐槽一二,见谅。

[4楼] anjo** 2016-11-30 11:35

赞同楼上~

[5楼] woni** 2016-12-05 13:10

老师你好。我想问一下为什么src="js/three.js" 这个路径不需要部署就可以访问的到,而图片的相对路径却需要部署后才能加载?

[6楼] woni** 2016-12-05 13:38

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); 

老师您好。请问这四行 “为平面赋予纹理坐标” 的代码具体是什么意思?为什么我把其中的1改成10000最后运行结果还是不变?

[7楼] lj96** 2017-03-01 17:10

赞同3楼

[8楼] laoq** 2017-04-12 19:14

这里animate有什么用?

[9楼] laoq** 2017-04-12 19:22

另外这里将物体加入场景应该放在纹理加载的回调函数里吧

[10楼] 天上的白** 2017-05-05 11:15

特意注册了一个账号,就是为了说本节的示例图用得太不好了,用这么暴露的女性照片,看起来特别刺眼!考虑过女性用户的感受吗??有种进错网站的感觉!!赞同3楼!!

[11楼] thre** 2017-07-18 15:30

回答3楼和10楼,你们是玩开发的吗,会F12吗。不想看,把响应的代码删除不就可以了。还打那么多字。。。。。。

回答5楼,最终代码都是放到服务器上的。纠结于本地程序为什么不能加载本地图片有什么意义吗

[12楼] Davi** 2017-08-25 14:38

教程非常好,我先进一步学习,请问老师 :透明的贴图我怎么样才能加上呢?

[13楼] loly** 2017-09-26 11:50

回复11楼,你没听懂人家意思。为什么要手动隐藏下?换个张爱玲或者温美玲或者林志玲或者熊黛林,不是挺好的吗?何必弄个貌似岛国的AV明星?三维材质要用uv,不用av。

[14楼] gosa** 2017-11-24 16:34

赞同13楼。。。同回复11楼。。。。 身为女性。。看到这样的图片真的会感觉不舒服。就算手动隐藏,隐藏前也看到了图片不是吗?。。虽然我非常非常感谢能提供那么棒的文章内容。。。。

[15楼] btsh** 2017-12-08 17:10

讨论图片的同学,有必要吗?穿比基尼的图片,连国家都没有说这违法了。有必要上纲上线吗?难道出现比基尼的导演,都没有考虑过女性用户的感受,难道比基尼T台就赞助商,组织者,观看者,都应该拉出去突突了?老师免费分享,提供学习机会,还上纲上线,哎

[16楼] time** 2018-02-11 14:37

使用新版three.js按照示例代码运行后不能正常出现图片,要把下面这句做下修改

var texture = THREE.ImageUtils.loadTexture("textures/a.jpg",null,function(t)
{
});
改为:

var texture = new THREE.TextureLoader().load("textures/a.jpg");

即可

[17楼] wzw_** 2018-03-01 11:36

看到这张贴图我就笑了, 直接转到发贴区抢沙发了.

还好, 大家都是来学习的, 所以这里才只到了十几楼. 也说明女性码农不多.

我是男性码农, 对于作者的娱乐精神不对反. 但作者明显是政治不正确啊.

可以躲着看H片, 但大庭广众下谈论还是道德禁忌啊. 哈哈哈

感谢作者的辛苦劳动, 也赞赏女性的主动反对态度!

WebGL中文网老师回答:

我们非常尊重女性,非常尊重女性,女性。

[18楼] 烽烟起浪** 2018-03-07 17:55

库改了

var texture = new THREE.TextureLoader();
texture.load("img/cq.jpg",function(texture){
var material = new THREE.MeshBasicMaterial({map:texture});
var mesh = new THREE.Mesh( geometry,material );
scene.add( mesh );
});

这一块

var texture = THREE.ImageUtils.loadTexture("http://www.hewebgl.com/attached/image/20130601/20130601103132_996.png");

自己替换一下。上面是异步加载(emmmmmmm)

[19楼] Jad2** 2018-04-16 18:04

你好,请问在给一个球体加载纹理图片的时候,如何添加UV映射?

现在想把一张正方形的地图贴到一个球体上形成一个地球。

[20楼] xmlh** 2018-04-20 15:49

@[6楼]你的问题搞清楚了吗?分享一下

[21楼] aiai** 2018-05-03 17:32

作为女码农,3楼因为个图片上纲上线也真是够了。。。。

[22楼] duow** 2018-06-15 11:04

纹理图一直没加载出来



//3.加载纹理
var texture = THREE.TextureLoader("textures/a.jpg", null, function (t) {
});
//4.将纹理应用于材质
var material = new THREE.MeshBasicMaterial({ map: texture });
var mesh = new THREE.Mesh(geometry, material);
mesh.position.set(0, 0, 0);
scene.add(mesh);
window.addEventListener('resize', onWindowResize, false);
}

[23楼] zhun** 2018-06-15 20:44

23楼,翻聊天记录:

19楼的

var texture = THREE.ImageUtils.loadTexture("http://www.hewebgl.com/attached/image/20130601/20130601103132_996.png");

17楼的:

var texture = new THREE.TextureLoader().load("textures/a.jpg");

[24楼] 上帝就如** 2018-06-19 16:45

3楼一看就是没男人的。

[25楼] jerr** 2018-08-02 14:37

女码农一枚,个人感觉啊,不要喷我啊,感觉贴上这种图也没啥,讲课之余增加一些乐趣嘛,可能是之前上学的时候老师就是这种污污的讲课方式然后习惯了吧~~

WebGL中文网老师回答:

哈哈,年龄,阅历不一样就会感觉不一样,只要心是正的,什么都不怕。一些表面的老实人,反而可能是好色之徒呢。

[26楼] yuex** 2018-08-04 22:40

虽然我是男同胞,视频中和文字中很多比喻总用男女或者两性来比方,我觉得不妥。美女我也很喜欢啊,但是出现在这种学习资料中,有点倒胃口,还是分一下场合吧!

WebGL中文网老师回答:

好的,改正。

[27楼] WebG** 2019-02-01 18:21

18楼正解,感谢18楼。另外我也想说说图片的事,两个字“挺好”,比基尼不犯法。

[28楼] 随```** 2019-03-27 16:11

那个我想给正方体每个面贴上不同的图片,一定要搭建一个服务器么,上面的没太看懂

[29楼] 落入蛋挞** 2019-04-03 00:05

我用的是WAMPSERVER(集成PHP,MYSQL,APACHE),在localhost中放了一个图片,结果还是提示JS没有权限,这可怎么办啊。

同样,引用网络图片,也是没有权限(估计是被网站方封锁了)。

[30楼] 落入蛋挞** 2019-04-03 00:24

var texture = THREE.TextureLoader("https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=4250112166,2387168490&fm=26&gp=0.jpg",null,function(t)

        {

        });

var material = new THREE.MeshBasicMaterial({map:texture});

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

    scene.add( mesh );

报错 :

three.js:12848 THREE.Material: 'map' parameter is undefined.

[31楼] tian** 2019-05-16 18:14

用谷歌浏览器的时候引用的图片会有跨域的问题,临时先用ie或者edge就行了。

但是这个例子就像教程里的效果一样。只是看到了一张图片,感受不到什么效果。继续看后面的教程吧。

但是教程里的那张美女图片真的很提神啊,赞一个!(对这张图片的态度完全取决于看这张图片的人自己的想法)

[32楼] 七月七月** 2019-05-17 15:02

按18楼的修改,是对的

[33楼] alex** 2019-05-31 10:41

老师你好,关于4.2为平面赋予纹理坐标这块不太理解,我把这四行代码注释掉似乎也没啥问题,threejs版本是83

[34楼] x595** 2019-06-10 10:19

我们尊重写这篇教程的老师,也希望你们尊重下我们

在看到评论之前就已经对这个图片很不爽,没想到下面也有人指出来,如果老师们也觉得这样不对,并表示你们尊重女性的话,可不可以把这个图片改了呢?

这是一点我为贵网站能更进一步提出的小建议

顺便 24楼那位 我还真难以想象这是2018年发的评论 莫非是清朝穿越过来的?

[35楼] Silv** 2019-06-27 07:01

同女码农,觉得两性举例并没有增加教材的趣味性,都是成年人什么意思心里都有逼数,所以别扯什么心正则正。并且这种举例某种意义上更加固化了码农的某种形象。

另外,教材知识内容写的很清楚,感谢大佬的贡献。

[36楼] chen** 2019-06-27 13:04

大佬4.1,PlaneGemotry,写错了

[37楼] 哪吒** 2019-07-18 15:12

这不就是一张正常的泳装照片吗?没有露点也没有性暗示动作,怎么有些人就看出色情和不尊重女性的含义了?(想想高中生物书上的那张金刚芭比...)

[38楼] SanT** 2019-07-19 16:54

说到金刚芭比  。怎么一下子就有画面了呢。。。。。

[39楼] luom** 2019-08-14 13:56

为什么我的按照18楼改了  没有报错了 但还是加载不出图片呢

[40楼] liyu** 2019-08-21 23:13

老师你好,关于纹理坐标的不是很理解,

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);

改变这几个坐标,调换顺序,注释掉都没有用,请问这几个函数到底怎么起作用的

[41楼] fish** 2020-01-26 05:33

老师好 感谢教程! 我想请问一下将视频作为纹理应该怎样操作呢?


是在代码上直接替换成视频的url吗

我想在立方体的不同面上播放不同的视频,谢谢老师

[42楼] wils** 2020-02-29 13:44

Date: 2020.02.29

THREE:  113, from: https://threejs.org/build/three.js

Problem: could not load image

Tricks: 

THREE.ImageUtils.crossOrigin = '';
var texture = THREE.ImageUtils.loadTexture('http://i.imgur.com/3tU4Vig.jpg');

learn from: https://stackoverflow.com/a/37325781

[43楼] wils** 2020-02-29 13:45

Date: 2020.02.29

THREE:  113, from: https://threejs.org/build/three.js

Problem: could not load image

Tricks: 

THREE.ImageUtils.crossOrigin = '';
var texture = THREE.ImageUtils.loadTexture('http://i.imgur.com/3tU4Vig.jpg');

learn from: https://stackoverflow.com/a/37325781

[44楼] rrrr** 2021-01-20 17:33

抱歉,可否不要再用女人来举例,观感很不好,也是有很多女学员的

提问或评论

登陆后才可留言或提问哦:) 登陆 | 注册 登陆后请返回本课提问
用户名
密   码
验证码