第6章 将canvas作为纹理,将动画作为纹理(二)

1、 学会用canvas作为纹理

2、 学会纹理的更新

1、白话纹理原理

从本质上来说,纹理只是图片而已,它是由像素点组成。无论在内存还是显存中,它都是由4个分量组成,这四个分量是R、G、B和A。唯一的不同的,在显存中,会比内存中更快的渲染到显示器上。这是毋庸置疑的,因为显存中的帧缓冲本来就是和显示器上 的像素一一对应的。

从上面的概念中,我们就能够引申出一些重要的理解了,就是只要是图像数据,准确的说是内存或者显存中的图像数据,都可以作为纹理,显示在屏幕中。

上面的课程,我们讲了通过loadtexture函数在服务器上加载一张图片作为纹理,这一节课,我们来讲通过html中的canvas来作为纹理。它非常重要,以后的课程中我们会经常使用这个技巧。

它们两者之间有很多差别,这个差别就是图片和canvas的差别,图片是通过图像处理软件,如photoshop来处理的。而canvas是通过浏览器的绘图API来绘制的。显示canvas能够给程序员更多的想象空间,从而做出更有意思的效果出来。

2、本节效果展示

本节的效果,是一个可以旋转的三维时钟。如下图所示,你可以在【初级教程\chapter6\6-6.html】找到本课的代码。也可以在线预览,请打开代码,然后对照下面的解释来阅读。

发现了它与普通时钟的区别了吗?首页它在正方体的6个面都有时钟的效果,而且时钟是每秒钟都更新一次的,能够准确的反应当前的时间。下面我们来介绍一下,这一关于canvas作为纹理的知识。

3、时钟纹理生成过程

实现这个效果的步骤可以用下面的框图来表示:

1、在canvas上画时钟

由于时钟的秒针会每秒滴答一次,那么canvas中的内容,其本身就会被更新一次。我们将在canvas中绘制时钟的代码放到了【初级教程\chapter6\clock.js】中。打开代码,你会发现它非常熟悉,这里的函数都是html5绘制canvas的基本函数,只不过其逻辑需要对照代码好好来理解一下,不过这都不是我们的重点了在,重点是它能画出如下的图像来,这段代码在【初级教程\chapter6\6-7.html】中能够找到。

2、将canvas传递给THREE.Texture纹理

Canvas可以作为纹理传递给THREE.Texture函数,纹理的构造函数是:

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

这个函数的第一个参数image,接收一个image类型的图像,或者canvas,后面的参数可以暂时不理会,它会以默认值去填充texture后面的参数。

这里我们只需要将canvas传递给Texture就ok了,代码如下:

texture = new THREE.Texture( canvas);

那么纹理怎么知道其每一个像素怎么映射到形状状的表面呢,默认情况下,纹理被均匀分配到四边形的各个顶点上。

3、将纹理传递给THREE.MeshBasicMaterial材质

将texture传递给材质就更简单了,材质本身可以接受一个属性名为map的参数,代码如下:

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

这样就将纹理赋给了材质。

4、构造THREE.Mesh

在我们的中级教程中,详细的讲解过Mesh,从它的概念,组成,怎么将几何体和材质融合一体,又怎么知道几何体和材质是否发生变化,怎么更新Mesh动画,几乎都讲了一遍,可以说是目前最为完整的3DMesh教程。不过这里篇幅有限,而Mesh又确实需要太多的笔墨去讲解,所以,这里就一笔带过了。

Mesh就是一个网格表面,它代表着我们渲染到3D世界中的各种模型。其构造函数如下:

THREE.Mesh = function ( geometry, material )

它接受2个参数,一个是几何体,一个是材质。

Mesh就是一个网格表面,它代表着我们渲染到3D世界中的各种模型。其构造函数如下:

THREE.Mesh = function ( geometry, material )

它接受2个参数,一个是几何体,一个是材质。

这里我们这样来构造它:

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

其中geometry是一个THREE.CubeGeometry表示的正方体。

ok,经过这4步,我们的代码就可以运行了。

4、总结

好了,最后,我们来看看,我们的所有代码,代码中"<后的-",请忽略掉,是因为html排版的原因被加上。

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

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

<script>

    var camera, scene, renderer;
    var mesh;
	var texture;
	
	function start()
	{
		clock();
		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.CubeGeometry(150, 150, 150);
		texture = new THREE.Texture( canvas);
        var material = new THREE.MeshBasicMaterial({map:texture});
		texture.needsUpdate = true;
        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() {
		texture.needsUpdate = true;
		mesh.rotation.y -= 0.01;
		mesh.rotation.x -= 0.01;
        requestAnimationFrame( animate );
        renderer.render( scene, camera );
    }

</script>

</body>
</html>

代码中clock.js就是绘制时钟的代码,里面有一个全局变量canvas,表示canvas本身。另外,需要注意的是在定义了纹理之后,我们将texture.needsUpdate设置为了true,如果不设置为true,那么纹理就不会更新,很可能你看到的是一个黑色的正方体,原因是纹理没有被载入之前,就开始渲染了,而渲染使用了默认的材质颜色。

这是什么原因呢?是这样的,纹理的绘制是需要一段时间的,javascript是可以异步运行的,在canvas绘制出时钟之前,可能three.js就开始根据纹理渲染图形了。如果纹理不更新,那么正方体一直会是以前没有绘制完成的纹理,很可能是材质本身的颜色。

另一个方面,canvas由于绘制的是时钟,其每一秒都会重新绘制一次,所以为了让正方体上的纹理可以及时反映canvas上的时钟,也需要不断的更新纹理,所以需要将needUpdate设置为true,不过缺点是其效率会低一些,不过这种效率的降低,是完全可以接受的。

好了,那么这一节,我们就讲到这里,WebGL中文网的全体同仁都将为您服务。我们不仅提供优秀的教程,而且提供专业的在线答疑。希望大家支持,您的付出,将绝对不会后悔。

我们有信心,在您学完WebGL中文网的课程后,做一个漂亮的3D游戏或者3D网站,一点没有问题。

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

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

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

[1楼] kyo7** 2016-07-05 18:06

有这么一个问题 我用cocos引擎写的canvas 然后用文中的方法调用canvas作为纹理 可以加载出来但是里面的内容不动 看输出是正常执行了已经 但是正方体上的画面并没有改变 texture.needsUpdate = true;设置了

WebGL中文网老师回答:

texture.needsUpdate为true,并且render函数要一直调用

[2楼] Mang** 2016-07-12 15:51

代码中的data-ke-onload里的data-ke是什么意思

WebGL中文网老师回答:

请到第一课下载代码来学习,这个在网页中被转义了。

[3楼] xiao** 2016-08-13 21:32

为什么我 注释了 texture.needsUpdate = true,还是一样的效果呢

[4楼] 推理全是** 2016-10-12 17:10

circle.update() 这个update()方法能解释一下吗  网上也没找到比较好的资料

[5楼] 推理全是** 2016-10-12 17:10

circle.update() 这个update()方法能解释一下吗  网上也没找到比较好的资料

[6楼] leey** 2017-03-06 23:39

您好:

        我在6-6.html文件的body中增加了<canvas id="canvas" width=200 height=200></canvas>标签,然后在clock.js中将 canvas = document.createElement('canvas')改成了canvas = document.getElementById('canvas'),为何浏览器打开6-6.html文件页面除了展现旋转的时钟外,左上角还有静态的时钟?

        谢谢。

[7楼] 人在江湖** 2017-03-08 14:29

老师您好,看了您这篇教程,我这边有一个想法,怎么给Canvas贴图加上事件?比如在Canvas上绘制了一个矩形,将其显示到立方体上,我想在鼠标经过矩形时,改变矩形的颜色,这个该怎么实现,望老师指教

WebGL中文网老师回答:

您的想法很好,也很普遍。虽然已经超过了WebGL的范畴,但是我们还是打算讲一讲。首先,我们需要理解点击事件的原理,在操作系统底层是怎么处理点击事件的呢?是不是告诉操作系统,当我点击一个按钮的时候,你就告诉我点击了呢?不是的,因为操作系统根本没有这么智能,它并不能以问答的方式来告诉你,你现在点击的是什么,是按钮区域,还是canvas区域,是一个美女的胸部,还是臀部。操作系统实现点击的原理,过去到现在,都只有一个,就是用数学运算中的包围的概念,例如,它判断一个矩形是否被点击,那么它计算鼠标的位置是否在矩形中,并且鼠标是否被点了一下。同理,判断canvas是否被点击,也是通过位置,你可要自己记录canvas的位置,并判断鼠标是否在canvas中点击。在webgl中有一个函数raycast,专门用来处理点击,准确的说:是处理当前鼠标是否在目标区域内,从而判断是否点击到目标区域。好了,讲了,这么多,不知道,大家明白没?没明白,再看一遍吧。

[8楼] thre** 2017-07-18 16:53

发现一篇一模一样的文章 http://www.haomou.net/2016/05/28/2016_threejs7/。文章的作者说是自己写的。最不喜欢这些抄的。

[9楼] thre** 2017-07-18 17:21

发现一篇一模一样的文章 http://www.haomou.net/2016/05/28/2016_threejs7/。文章的作者说是自己写的。最不喜欢这些抄的。

[10楼] clin** 2017-08-31 14:00

您的文章读来感觉挺有乐趣,通俗易懂,收获颇多。非常感谢!

[11楼] fang** 2017-11-14 13:27

6-6.html引入不同的three.js,效果不一样。

当引入自己的three.js,控制台输出THREE.WebGLRenderer 73,页面没效果,时钟未出来,也不报错。

当引入教程压缩包中的three.js,控制台输出THREE.WebGLRenderer 52,时钟才出来,为什么那?

[12楼] bzy2** 2017-12-05 14:09

老师你好:

         我是一名三维模型开发人员,我想把每帧视频的canvas转化为纹理,然后将每帧纹理保存为array,最后每秒30帧顺序播放纹理,形成带文丽三维模型,我按照本文中所述方法已经调通了单帧播放,但是先做缓存,后用setinterval方法顺序播放时纹理更新不对,该怎么解决?

[13楼] btsh** 2017-12-08 17:59

老师,你好。我用的是r88.代码运行后,出现“Texture marked for update but image is undefined” 。这可能是什么导致的呢?

[14楼] jack** 2018-03-18 14:17

【问题同11楼】

老师,使用73版本效果不出来也不报错,是什么原因呢

[15楼] fuju** 2018-04-10 17:41

image图像为GIF格式,且含有透明通道,不能播放呀~只能显示图片第一帧;设置了needsUpdate没用~~

[16楼] jerr** 2018-08-02 14:49

【初级教程\chapter6\clock.js】老师,这个代码是从哪里找呢?

WebGL中文网老师回答:

看菜单中的代码下载

[17楼] curi** 2018-08-14 17:26

var geometry = new THREE.CubeGeometry(300, 300, 300);
var texture = new THREE.Texture(c);
texture.needsUpdate = true;
var material = new THREE.MeshBasicMaterial({map: texture});
var mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);

drawClock(10, 5, 10);

window.setInterval(() => {
time = new Date();
time.h = time.getHours();
time.m = time.getMinutes();
time.s = time.getSeconds();
drawClock(time.h, time.m, time.s);
document.getElementById('cc').getContext('2d').drawImage(c, 0, 0);
// console.log(h,m,s);
}, 1000) function update() { mesh.rotation.y += 0.01; } function render() { renderer.clear(); renderer.render(scene, camera); update(); requestAnimationFrame(render); }


老师,我是用定时器更新钟表图案画面的;

只有第一帧的画面,没有更新;

[18楼] favo** 2019-04-29 10:36

 Texture marked for update but image is undefined


我也遇到这样的错误

[19楼] favo** 2019-04-29 10:51

 Texture marked for update but image is undefined

知道原因了,因为没有调用clock()方法, 然后renderer方法调用texture.needsUpdate = true也需要调用。

[20楼] 浪漫杀手** 2019-06-03 18:28

为什么我的画出来的正方体是黑色的,但是有动态始终纹理;设置了texture.needsUpdate = true

[21楼] 浪漫杀手** 2019-06-03 18:29

为什么我的画出来的正方体是黑色的,但是有动态时钟纹理;设置了texture.needsUpdate = true

[22楼] 1513** 2019-11-08 11:30

three.js 的版本不对,案例中用的是52的,之前用过73的,也就是教程用的three版本都不太一致,写的代码有很大的出入,为什么不说明一下版本案例使用的版本,自己手敲的代码查了N变也没发现问题在哪里,换了three的包就好用了,这太坑了,

[23楼] huch** 2021-05-06 10:32

一个程序员必须要有写bug,改bug的过程,要不然一遇见问题自己无从下手,到那时候不就拉了吗, 我只看看老师说的文字,不去实现相应的代码,看完老师的思路之后,直接把three.js 下载下来本地运行,一个例子一个例子的去看,怎样学习感觉也不错的,官方的api确实太多,懒得看!

提问或评论

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